123#include "llvm/ADT/ArrayRef.h"
124#include "llvm/ADT/DenseMap.h"
125#include "llvm/ADT/STLExtras.h"
126#include "llvm/ADT/StringExtras.h"
127#include "llvm/ADT/Twine.h"
128#include "llvm/Support/Debug.h"
135#define GEN_PASS_DEF_ACCCGTOGPU
136#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
140#define DEBUG_TYPE "acc-cg-to-gpu"
146enum class PrivateMemScope { Thread, Worker, Gang,
None };
149static std::string getDeviceRemarkQualifier(DeviceType deviceType) {
150 switch (deviceType) {
151 case DeviceType::None:
152 case DeviceType::Star:
153 case DeviceType::Default:
157 llvm::StringRef deviceName = stringifyDeviceType(deviceType);
158 name.reserve(deviceName.size());
159 for (
char c : deviceName)
160 name.push_back(llvm::toUpper(c));
161 return name +
" GPU";
168 FunctionOpInterface funcOp = op->
getParentOfType<FunctionOpInterface>();
173static GPUParallelDimAttr
174getAccRoutineParDim(RoutineOp routineOp,
MLIRContext *ctx,
176 if (routineOp.getGangDimValue() ||
177 routineOp.getGangDimValue(DeviceType::Nvidia)) {
178 int64_t gangDimValue = routineOp.getGangDimValue(DeviceType::Nvidia)
179 ? *routineOp.getGangDimValue(DeviceType::Nvidia)
180 : *routineOp.getGangDimValue();
182 return policy.
gangDim(ctx, gangLevel);
184 if (routineOp.hasGang() || routineOp.hasGang(DeviceType::Nvidia))
185 return policy.
gangDim(ctx, ParLevel::gang_dim1);
186 if (routineOp.hasWorker() || routineOp.hasWorker(DeviceType::Nvidia))
188 if (routineOp.hasVector() || routineOp.hasVector(DeviceType::Nvidia))
190 return policy.
seqDim(ctx);
194static RoutineOp getRoutineOpForAccRoutineFunction(FunctionOpInterface funcOp,
197 SpecializedRoutineAttr attr =
198 funcOp->getDiscardableAttrOfType<SpecializedRoutineAttr>(
200 return symTab.
lookup<RoutineOp>(attr.getRoutine().getLeafReference());
202 RoutineInfoAttr routineInfo =
203 funcOp->getDiscardableAttrOfType<RoutineInfoAttr>(
205 if (!routineInfo || routineInfo.getAccRoutines().empty())
207 return symTab.
lookup<RoutineOp>(
208 routineInfo.getAccRoutines().front().getLeafReference());
212static GPUParallelDimAttr
213getSpecializedRoutineDim(FunctionOpInterface funcOp,
215 SpecializedRoutineAttr specAttr =
216 funcOp->getDiscardableAttrOfType<SpecializedRoutineAttr>(
218 assert(specAttr &&
"expected specialized routine attribute");
219 return policy.
map(funcOp->getContext(), specAttr.getLevel().getValue());
223static GPUParallelDimAttr
224getAccRoutineCallParDim(CallOpInterface callOp,
226 std::optional<CallInterfaceCallable> callee = callOp.getCallableForCallee();
229 SymbolRefAttr calleeSymbolRef = dyn_cast<SymbolRefAttr>(*callee);
230 if (!calleeSymbolRef)
232 ModuleOp moduleOp = callOp->getParentOfType<ModuleOp>();
237 FunctionOpInterface funcOp =
238 symTab.
lookup<FunctionOpInterface>(calleeSymbolRef.getLeafReference());
243 return getSpecializedRoutineDim(funcOp, policy);
244 if (RoutineOp routineOp = getRoutineOpForAccRoutineFunction(funcOp, symTab))
245 return getAccRoutineParDim(routineOp, funcOp.getContext(), policy);
252 ComputeRegionOp computeRegion = op->
getParentOfType<ComputeRegionOp>();
253 assert(computeRegion &&
"missing enclosing acc.compute_region");
256 bool isInnermostParallelParent =
true;
258 bool hasNonSeqParDim =
false;
259 if (GPUParallelDimsAttr parDimsAttr =
getParDimsAttr(parentLoop)) {
260 for (GPUParallelDimAttr parDim : parDimsAttr.getArray()) {
263 hasNonSeqParDim =
true;
274 (hasNonSeqParDim || isInnermostParallelParent))
275 for (GPUParallelDimAttr parDim : computeRegion.getLaunchParDims())
277 isInnermostParallelParent =
false;
278 parentLoop = parentLoop->getParentOfType<scf::ParallelOp>();
281 if (GPUParallelDimsAttr parDimsAttr =
getParDimsAttr(computeRegion))
282 for (GPUParallelDimAttr parDim : parDimsAttr.getArray())
288static Value stripIndexCastsFromValue(
Value x) {
292 while (arith::IndexCastOp castOp = dyn_cast<arith::IndexCastOp>(op)) {
301static FailureOr<int64_t> extractIntConst(
Value x,
302 bool stripIndexCasts =
false) {
304 x = stripIndexCastsFromValue(x);
308 assert(constOp.getType().getIntOrFloatBitWidth() <= 64);
309 return constOp.value();
312 return constOp.value();
319 x = stripIndexCastsFromValue(x);
320 FailureOr<int64_t> conX = extractIntConst(x);
327static bool getPassThroughResults(
Operation *userOp,
Value trackedOperand,
329 if (ViewLikeOpInterface viewLikeOp = dyn_cast<ViewLikeOpInterface>(userOp)) {
330 if (viewLikeOp.getViewSource() == trackedOperand) {
331 passThroughResults.push_back(viewLikeOp.getViewDest());
340 if (acc::PartialEntityAccessOpInterface partialAccess =
341 dyn_cast<acc::PartialEntityAccessOpInterface>(userOp)) {
342 if (partialAccess.getBaseEntity() == trackedOperand) {
354 if (ViewLikeOpInterface viewLike = dyn_cast<ViewLikeOpInterface>(op)) {
355 if (isa<MemRefType>(viewLike.getViewSource().getType()) ||
356 isa<MemRefType>(viewLike.getViewDest().getType())) {
357 v = viewLike.getViewSource();
368static acc::PrivateLocalOp getPrivateLocalForMemref(
Value memref);
371static GPUParallelDimsAttr
372getPrivateParDims(acc::PrivateLocalOp privateLocal,
373 acc::ComputeRegionOp computeRegion);
377static bool storageHasThreadX(acc::PrivateLocalOp privateLocal,
378 acc::ComputeRegionOp computeRegion) {
379 GPUParallelDimsAttr dims = getPrivateParDims(privateLocal, computeRegion);
380 return !dims || llvm::any_of(dims.getArray(), [](GPUParallelDimAttr d) {
381 return d.isThreadX();
394 if (GPUParallelDimsAttr parDimsAttr = privatize.getParDimsAttr())
395 return llvm::any_of(parDimsAttr.getArray(),
396 [](GPUParallelDimAttr d) { return d.isThreadX(); });
402 gpu::BarrierOp::create(builder, loc);
407 gpu::BarrierOp::create(builder, loc,
ArrayAttr{},
409 gpu::BarrierScope::Subgroup);
417 assert(rowWidth > 0 && subgroupSize % rowWidth == 0 &&
418 "row must tile the subgroup");
420 int64_t rowsPerSubgroup = subgroupSize / rowWidth;
426 Value threadY32 = arith::IndexCastOp::create(builder, loc, i32Ty, threadY);
429 Value rowIndex = arith::AndIOp::create(builder, loc, threadY32, rowMask);
431 Value laneBase = arith::MulIOp::create(builder, loc, rowIndex, width);
435 Value mask = arith::ShLIOp::create(builder, loc, laneBits, laneBase);
436 NVVM::SyncWarpOp::create(builder, loc, mask);
440class ACCCGToGPULowering {
442 explicit ACCCGToGPULowering(acc::ComputeRegionOp computeRegion,
443 RewriterBase &rewriter,
444 acc::OpenACCSupport &accSupport,
445 const ACCCGToGPUOptions &options)
446 : rewriter(rewriter), computeRegion(computeRegion),
447 accSupport(accSupport), options(options),
449 options.maxWorkgroupSharedMemory,
455 gpu::LaunchOp getLaunch()
const {
return launch; }
457 bool hasFailed =
false;
458 bool insideAccumulateGridStride =
false;
459 Value reductionSharedBuf;
462 llvm::DenseMap<Value, Value> reductionAccumValue;
464 llvm::SmallVector<std::pair<Value, memref::LoadOp>> pendingCombineReloads;
468 void processParallelOp(scf::ParallelOp parallelOp);
470 template <
typename LoopOp>
471 void processSeqLoop(LoopOp loopOp);
473 void processPredicateRegion(acc::PredicateRegionOp interOp);
476 processPrivateLocal(acc::PrivateLocalOp privateLocal,
477 std::optional<int64_t> sharedMemCopies = std::nullopt);
479 Value processPrivatize(acc::PrivatizeOp privatize);
481 Value allocatePrivatizeHeapStorage(acc::PrivatizeOp privatize,
485 void processExecuteRegion(scf::ExecuteRegionOp op);
487 void processAccumulateOp(acc::ReductionAccumulateOp op);
489 void processAccumulateArrayOp(acc::ReductionAccumulateArrayOp op);
491 void processReductionOp(acc::ReductionInitOp op);
493 void processReductionCombineOp(acc::ReductionCombineOp op);
495 void processCombineRegionOp(acc::ReductionCombineRegionOp op);
497 void processGenericOp(Operation *op);
499 void processGenericOpWithRegions(Operation *op);
501 void processOp(Operation *op);
504 void constructAtomicAccumulation(Location loc, Value memref,
506 arith::AtomicRMWKind kind);
509 FailureOr<arith::AtomicRMWKind> getReductionKind(acc::ReductionOperator redOp,
510 Type type, Location loc);
514 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
515 SmallVector<mlir::acc::GPUParallelDimAttr>>
516 computeActiveAndInactiveParDims(Operation *op,
Block *block);
520 emitPredicate(Location loc,
521 SmallVector<mlir::acc::GPUParallelDimAttr> &inactiveParDims);
525 std::optional<int64_t>
526 isEligibleForSharedMemory(acc::PrivateLocalOp privateLocal,
530 bool tryAllocateSharedMemory(int64_t bytes);
536 void recordThreadPrivate(acc::PrivateLocalOp privateLocal);
539 int64_t getElementSizeInBytes(Location loc, Type elementType)
const;
542 bool canUseStackAlloca(MemRefType baseTy, Location loc,
543 int64_t maxThreadPrivateStack)
const;
546 void createBarrier(Location loc, mlir::acc::GPUParallelDimsAttr parDimsAttr);
552 void createPerRowBarrier(Location loc);
556 void createBarrierAfterSeqLoop(Operation *loopOp);
559 void flushDeferredBarriersBefore(Operation *beforeOp);
562 bool mayWriteSharedMemory(Operation *loopOp);
565 PrivateMemScope getPrivateMemScope(acc::PrivatizeOp privatizeOp);
568 PrivateMemScope getPrivateScopeForMemref(Value memref);
571 acc::PrivatizeOp getPrivatizeForMemref(Value memref);
575 PrivateMemScope needsPreStoreReuseBarrier(acc::PredicateRegionOp interOp);
578 void createGPUAllReduceOp(Location loc, Value input, Value memref,
579 arith::AtomicRMWKind kind,
580 mlir::acc::GPUParallelDimsAttr parDimsAttr,
582 bool isPerThreadPrivateTarget =
false);
585 void postprocessAccumulateOp(acc::ReductionAccumulateOp op);
588 void postprocessLoopReduction(scf::ParallelOp parLoop);
594 llvm::DenseMap<gpu::Processor, Value> &ids,
595 llvm::DenseMap<gpu::Processor, Value> &dims) {
596 ids[gpu::Processor::BlockX] = gpu::BlockIdOp::create(
597 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
598 ids[gpu::Processor::BlockY] = gpu::BlockIdOp::create(
599 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
600 ids[gpu::Processor::BlockZ] = gpu::BlockIdOp::create(
601 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
602 ids[gpu::Processor::ThreadX] = gpu::ThreadIdOp::create(
603 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
604 ids[gpu::Processor::ThreadY] = gpu::ThreadIdOp::create(
605 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
606 ids[gpu::Processor::ThreadZ] = gpu::ThreadIdOp::create(
607 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
608 dims[gpu::Processor::BlockX] = gpu::GridDimOp::create(
609 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
610 dims[gpu::Processor::BlockY] = gpu::GridDimOp::create(
611 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
612 dims[gpu::Processor::BlockZ] = gpu::GridDimOp::create(
613 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
614 dims[gpu::Processor::ThreadX] = gpu::BlockDimOp::create(
615 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
616 dims[gpu::Processor::ThreadY] = gpu::BlockDimOp::create(
617 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
618 dims[gpu::Processor::ThreadZ] = gpu::BlockDimOp::create(
619 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
624 BlockArgument getOrAppendInsBlockArg(Value outside) {
625 if (std::optional<BlockArgument> blockArg =
626 computeRegion.getBlockArg(outside)) {
629 return computeRegion.appendInputArg(outside);
633 void preparePrivatizeExtentInsOperands() {
634 computeRegion.walk([&](acc::PrivateLocalOp privateLocal) {
635 acc::PrivatizeOp privatizeOp =
637 if (privatizeOp->getParentOfType<acc::ComputeRegionOp>() == computeRegion)
639 for (Value extent : privatizeOp.getDynamicSizes())
640 getOrAppendInsBlockArg(extent);
646 resolvePrivateLocalDynamicExtents(acc::PrivateLocalOp privateLocal) {
647 acc::PrivatizeOp privatizeOp =
getPrivatizeOp(privateLocal, computeRegion);
648 SmallVector<Value> extents;
649 for (Value extent : privatizeOp.getDynamicSizes()) {
650 if (std::optional<BlockArgument> blockArg =
651 computeRegion.getBlockArg(extent)) {
652 extents.push_back(mapping.lookupOrDefault(*blockArg));
655 extents.push_back(mapping.lookupOrDefault(extent));
660 RewriterBase &rewriter;
661 acc::ComputeRegionOp computeRegion;
663 acc::OpenACCSupport &accSupport;
664 const ACCCGToGPUOptions &options;
665 gpu::LaunchOp launch;
667 llvm::SmallVector<scf::ParallelOp> loopReductions;
668 llvm::DenseMap<gpu::Processor, Value> threadIdMap;
669 llvm::DenseMap<gpu::Processor, Value> dimensionMap;
671 bool hasThreadYReduction =
false;
673 bool hasThreadXReduction =
false;
675 bool hasThreadLevelArrayReduction =
false;
677 bool hasThreadLevelRoutineCall =
false;
679 bool hasThreadYBarrier =
false;
682 llvm::DenseMap<Type, Value> privatizeBroadcastCache;
684 int64_t staticBlockDimX = 1024;
685 int64_t staticBlockDimY = 1024;
686 int64_t staticBlockDimZ = 1024;
691 bool isSingleThreadWorkerLaunch()
const {
692 return staticBlockDimX == 1 && staticBlockDimZ == 1 &&
693 staticBlockDimY > 1 && staticBlockDimY <= options.subgroupSize;
700 bool isWorkerOnlyShuffleLaunch()
const {
701 return hasThreadYReduction && !hasThreadXReduction &&
702 !hasThreadLevelArrayReduction && staticBlockDimZ == 1 &&
703 staticBlockDimY > 1 && staticBlockDimY <= options.subgroupSize &&
704 options.subgroupSize % staticBlockDimX == 0;
707 acc::DefaultACCToGPUMappingPolicy defaultPolicy;
708 SharedMemoryBudget sharedMemBudget;
709 SmallVector<std::string> sharedMemPrivateVarNames;
710 SmallVector<std::string> threadPrivateVarNames;
711 llvm::SmallVector<Operation *, 4> deferredBarrierSeqLoops;
713 Value getThreadId(Location loc, gpu::Dimension dim) {
714 return gpu::ThreadIdOp::create(rewriter, loc, rewriter.getIndexType(), dim);
717 Value getBlockDim(Location loc, gpu::Dimension dim) {
718 return gpu::BlockDimOp::create(rewriter, loc, rewriter.getIndexType(), dim);
722 Value getGPUThreadIdFor(gpu::Processor proc) {
727 Value getGPUSizeFor(gpu::Processor proc) {
728 return getGPUSize(proc, getLaunch(), dimensionMap);
733 Type elementType)
const {
734 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
735 if (std::optional<acc::TypeSizeAndAlignment> sizeAndAlignment =
737 return sizeAndAlignment->first.getFixedValue();
740 llvm::raw_string_ostream os(msg);
741 os <<
"element size computation for unsupported type: " << elementType;
742 (void)accSupport.
emitNYI(loc, os.str());
746bool ACCCGToGPULowering::canUseStackAlloca(
747 MemRefType baseTy, Location loc, int64_t maxThreadPrivateStack)
const {
748 for (int64_t dim : baseTy.getShape())
749 if (dim == ShapedType::kDynamic)
751 int64_t elementSize = getElementSizeInBytes(loc, baseTy.getElementType());
752 int64_t numElements = 1;
753 for (int64_t dim : baseTy.getShape()) {
754 if (numElements > maxThreadPrivateStack / std::max<int64_t>(dim, 1))
758 return elementSize * numElements < maxThreadPrivateStack;
766static bool reductionHasBlockContext(acc::ReductionAccumulateArrayOp accArr) {
767 auto hasBlock = [](mlir::acc::GPUParallelDimsAttr parDims) {
768 return parDims && llvm::any_of(parDims.getArray(),
769 [](
auto pd) { return pd.isAnyBlock(); });
771 if (hasBlock(accArr.getParDimsAttr()))
773 for (scf::ParallelOp loop = accArr->getParentOfType<scf::ParallelOp>(); loop;
774 loop = loop->getParentOfType<scf::ParallelOp>()) {
784static acc::ReductionAccumulateArrayOp perThreadArrayReductionAccum(Value v) {
785 SmallVector<Value> worklist{v};
787 while (!worklist.empty()) {
788 Value cur = worklist.pop_back_val();
789 if (!seen.insert(cur).second)
791 for (Operation *user : cur.
getUsers()) {
792 if (acc::ReductionAccumulateArrayOp accArr =
793 dyn_cast<acc::ReductionAccumulateArrayOp>(user)) {
794 bool hasThread =
false;
795 for (
auto pd : accArr.getParDims().getArray())
796 hasThread |= pd.isAnyThread();
797 if (hasThread && reductionHasBlockContext(accArr))
801 SmallVector<Value> through;
802 if (getPassThroughResults(user, cur, through))
803 worklist.append(through.begin(), through.end());
804 else if (isa<ViewLikeOpInterface>(user))
805 worklist.append(user->result_begin(), user->result_end());
814static void initPerThreadArrayAccum(OpBuilder &
b, Location loc, Value alloca,
816 arith::AtomicRMWKind kind) {
817 assert(baseTy.getRank() > 0 && baseTy.hasStaticShape() &&
818 "per-thread array reduction accumulator must be static ranked");
824 auto buildLoopNest = [&](
auto &&self,
unsigned dim) ->
void {
825 if (dim == baseTy.getRank()) {
826 memref::StoreOp::create(
b, loc, ident, alloca,
indices);
831 auto forOp = scf::ForOp::create(
b, loc, lb, ub, step);
832 OpBuilder::InsertionGuard g(
b);
833 b.setInsertionPoint(forOp.getBody()->getTerminator());
834 indices.push_back(forOp.getInductionVar());
841std::optional<int64_t>
842ACCCGToGPULowering::isEligibleForSharedMemory(acc::PrivateLocalOp privateLocal,
847 if (perThreadArrayReductionAccum(privateLocal.getResult()) &&
848 storageHasThreadX(privateLocal, computeRegion))
850 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
852 privateLocal, computeRegion, module, defaultPolicy, &accSupport);
853 if (
failed(isCandidate)) {
857 if (!isCandidate.value())
859 std::optional<int64_t> upperBound =
861 module, defaultPolicy);
862 assert(upperBound &&
"candidate private_local must have an upper bound");
863 int64_t elementSize =
864 getElementSizeInBytes(privateLocal.getLoc(), baseTy.getElementType());
865 int64_t numElements = 1;
866 for (int64_t dim : baseTy.getShape())
868 return *upperBound / (elementSize * numElements);
871bool ACCCGToGPULowering::tryAllocateSharedMemory(int64_t bytes) {
875void ACCCGToGPULowering::recordThreadPrivate(acc::PrivateLocalOp privateLocal) {
876 std::string varName = accSupport.
getVariableName(privateLocal.getResult());
881 if (!llvm::is_contained(threadPrivateVarNames, varName))
882 threadPrivateVarNames.push_back(varName);
885FailureOr<arith::AtomicRMWKind>
886ACCCGToGPULowering::getReductionKind(acc::ReductionOperator redOp, Type type,
888 if (std::optional<arith::AtomicRMWKind> kind =
893 llvm::raw_string_ostream os(msg);
894 os <<
"reduction operator (" << redOp <<
") for type " << type;
895 (void)accSupport.
emitNYI(loc, os.str());
899LogicalResult ACCCGToGPULowering::rewrite() {
905 computeRegion->walk([&](acc::ReductionAccumulateOp op) {
906 for (
auto parDim : op.getParDimsAttr().getArray()) {
907 hasThreadXReduction |= parDim.isThreadX();
908 hasThreadYReduction |= parDim.isThreadY();
911 computeRegion->walk([&](acc::ReductionAccumulateArrayOp op) {
912 for (
auto parDim : op.getParDimsAttr().getArray()) {
913 if (!parDim.isAnyBlock())
914 hasThreadLevelArrayReduction =
true;
922 computeRegion->walk([&](CallOpInterface callOp) -> WalkResult {
923 if (mlir::acc::GPUParallelDimAttr parDim =
924 getAccRoutineCallParDim(callOp, defaultPolicy)) {
925 if (parDim.isThreadX() || parDim.isThreadY()) {
926 hasThreadLevelRoutineCall =
true;
933 Location loc = computeRegion->getLoc();
936 auto launchArgument = [&](gpu::Processor processor) -> Value {
937 mlir::acc::GPUParallelDimAttr parDim = mlir::acc::GPUParallelDimAttr::get(
938 computeRegion->getContext(), processor);
939 std::optional<Value> maybeLaunchArg =
940 computeRegion.getKnownLaunchArg(parDim);
941 LLVM_DEBUG(llvm::dbgs() <<
"ACCCGToGPU: launch-arg: "
942 <<
" parDim: " << parDim <<
" gpu: " << processor
944 << maybeLaunchArg.value_or(constantOne) <<
"\n");
948 maybeLaunchArg.value_or(constantOne));
950 LLVM_DEBUG(llvm::dbgs() <<
"ACCCGToGPU: creating gpu launch op: \n");
954 auto mapLaunchArguments = [&](gpu::Processor processor, Value launchArg) {
955 mlir::acc::GPUParallelDimAttr parDim = mlir::acc::GPUParallelDimAttr::get(
956 computeRegion->getContext(), processor);
957 std::optional<Value> kernelArg = computeRegion.getLaunchArg(parDim);
959 mapping.
map(computeRegion.gpuParWidth(processor), launchArg);
962 llvm::StringRef blockDimXName =
"blockDim.x";
963 llvm::StringRef blockDimYName =
"blockDim.y";
964 std::string deviceLabel = getDeviceRemarkQualifier(
options.deviceType);
966 if (!computeRegion->getParentOfType<gpu::GPUFuncOp>()) {
967 Value blockDimX = launchArgument(gpu::Processor::ThreadX);
970 staticBlockDimX = bdxVal.getSExtValue();
971 Value blockDimY = launchArgument(gpu::Processor::ThreadY);
974 staticBlockDimY = bdyVal.getSExtValue();
975 Value blockDimZ = launchArgument(gpu::Processor::ThreadZ);
978 staticBlockDimZ = bdzVal.getSExtValue();
979 Value gridDimX = launchArgument(gpu::Processor::BlockX);
980 Value gridDimY = launchArgument(gpu::Processor::BlockY);
981 Value gridDimZ = launchArgument(gpu::Processor::BlockZ);
987 auto getName = [&](Value val) -> std::string {
989 return name.empty() ?
"(*)" : name;
991 bool isEffectivelySerial =
992 sameEffectiveValue(blockDimX, 1) &&
993 sameEffectiveValue(blockDimY, 1) &&
994 sameEffectiveValue(blockDimZ, 1) && sameEffectiveValue(gridDimX, 1) &&
995 sameEffectiveValue(gridDimY, 1) && sameEffectiveValue(gridDimZ, 1);
996 return (llvm::Twine(
"Generating ") +
997 llvm::Twine(isEffectivelySerial ?
"serial " :
"") + deviceLabel +
998 " code with gridDim=" + getName(gridDimX) +
"x" +
999 getName(gridDimY) +
"x" + getName(gridDimZ) +
1000 " blockDim=" + getName(blockDimX) +
"x" + getName(blockDimY) +
1001 "x" + getName(blockDimZ))
1006 if (mlir::Value streamValue = computeRegion.getStream()) {
1007 LLVM_DEBUG(llvm::dbgs()
1008 <<
"\nDEBUG: Creating async gpu.launch with stream: "
1009 << streamValue <<
"\n");
1010 launch = gpu::LaunchOp::create(
1011 rewriter, loc, gridDimX, gridDimY, gridDimZ, blockDimX, blockDimY,
1017 launch.getAsyncDependenciesMutable().append(streamValue);
1019 LLVM_DEBUG(llvm::dbgs()
1020 <<
"\nDEBUG: No stream, creating sync gpu.launch\n");
1021 launch = gpu::LaunchOp::create(rewriter, loc, gridDimX, gridDimY,
1022 gridDimZ, blockDimX, blockDimY, blockDimZ);
1027 if (
auto kernelFuncName = computeRegion.getKernelFuncNameAttr())
1028 launch.setFunctionAttr(kernelFuncName);
1029 if (
auto kernelModuleName = computeRegion.getKernelModuleNameAttr())
1030 launch.setModuleAttr(kernelModuleName);
1033 gpu::TerminatorOp::create(rewriter, loc);
1035 mapLaunchArguments(gpu::Processor::BlockX,
1036 gpu::GridDimOp::create(rewriter, loc,
1038 gpu::Dimension::x));
1039 mapLaunchArguments(gpu::Processor::BlockY,
1040 gpu::GridDimOp::create(rewriter, loc,
1042 gpu::Dimension::y));
1043 mapLaunchArguments(gpu::Processor::BlockZ,
1044 gpu::GridDimOp::create(rewriter, loc,
1046 gpu::Dimension::z));
1047 mapLaunchArguments(gpu::Processor::ThreadX,
1048 gpu::BlockDimOp::create(rewriter, loc,
1050 gpu::Dimension::x));
1051 mapLaunchArguments(gpu::Processor::ThreadY,
1052 gpu::BlockDimOp::create(rewriter, loc,
1054 gpu::Dimension::y));
1055 mapLaunchArguments(gpu::Processor::ThreadZ,
1056 gpu::BlockDimOp::create(rewriter, loc,
1058 gpu::Dimension::z));
1063 OpBuilder::InsertionGuard guard(rewriter);
1066 mapLaunchArguments(gpu::Processor::BlockX,
1067 dimensionMap[gpu::Processor::BlockX]);
1068 mapLaunchArguments(gpu::Processor::BlockY,
1069 dimensionMap[gpu::Processor::BlockY]);
1070 mapLaunchArguments(gpu::Processor::BlockZ,
1071 dimensionMap[gpu::Processor::BlockZ]);
1072 mapLaunchArguments(gpu::Processor::ThreadX,
1073 dimensionMap[gpu::Processor::ThreadX]);
1074 mapLaunchArguments(gpu::Processor::ThreadY,
1075 dimensionMap[gpu::Processor::ThreadY]);
1076 mapLaunchArguments(gpu::Processor::ThreadZ,
1077 dimensionMap[gpu::Processor::ThreadZ]);
1082 preparePrivatizeExtentInsOperands();
1083 Block *body = computeRegion.getBody();
1084 unsigned numLaunchArgs = computeRegion.getLaunchArgs().size();
1085 ValueRange inputArgs = computeRegion.getInputArgs();
1089 assert(computeRegion.getRegion().hasOneBlock() &&
1090 "compute region only supports one block region for now");
1092 for (
auto &op : computeRegion.getRegion().getBlocks().front().getOperations())
1095 for (
auto &parLoop : loopReductions)
1096 postprocessLoopReduction(parLoop);
1100 if (!pendingCombineReloads.empty() && launch) {
1101 DominanceInfo domInfo(launch);
1102 for (
auto &[slot, loadOp] : pendingCombineReloads) {
1103 llvm::DenseMap<Value, Value>::iterator it =
1104 reductionAccumValue.find(slot);
1105 if (it == reductionAccumValue.end())
1107 if (!domInfo.dominates(it->second, loadOp.getOperation()))
1114 const int64_t subgroupSize =
options.subgroupSize;
1115 const int64_t subgroupAlignMask = subgroupSize - 1;
1123 bool needsThreadXAlign =
false;
1124 bool needsThreadYAlign =
false;
1125 bool alignThreadXReduction =
1131 auto classifyAllReduce = [&](gpu::AllReduceOp allReduce) {
1132 bool hasThreadX =
false;
1133 bool hasThreadY =
false;
1135 hasThreadX |= parDim.isThreadX();
1136 hasThreadY |= parDim.isThreadY();
1138 if (hasThreadX && alignThreadXReduction)
1139 needsThreadXAlign =
true;
1140 else if (hasThreadY)
1141 needsThreadYAlign =
true;
1144 launch.walk(classifyAllReduce);
1146 launch.walk([&](func::CallOp callOp) {
1147 if (gpu::GPUFuncOp callee =
1148 callOp->getParentOfType<ModuleOp>().lookupSymbol<gpu::GPUFuncOp>(
1149 callOp.getCallee()))
1150 callee.walk(classifyAllReduce);
1153 bool isShuffleEnabled = needsThreadXAlign || needsThreadYAlign;
1155 std::optional<int64_t> constBlockDimX =
1157 std::optional<int64_t> constBlockDimY =
1159 std::optional<int64_t> constBlockDimZ =
1167 bool skipAlign = constBlockDimX && constBlockDimY && constBlockDimZ &&
1168 *constBlockDimX > 1 && *constBlockDimX < subgroupSize &&
1169 *constBlockDimY == 1 && *constBlockDimZ == 1;
1174 if (isSingleThreadWorkerLaunch())
1176 if (needsThreadYAlign && !needsThreadXAlign && !hasThreadYBarrier &&
1177 isWorkerOnlyShuffleLaunch())
1180 if ((isShuffleEnabled || hasThreadYBarrier) && !skipAlign) {
1183 Value curBlockDimX = launch.getBlockSizeX();
1184 Value curBlockDimY = launch.getBlockSizeY();
1185 Value curBlockDimZ = launch.getBlockSizeZ();
1189 auto getName = [&](Value val) -> std::string {
1191 return name.empty() ?
"(*)" : name;
1193 std::string blockDimXValStr = getName(curBlockDimX);
1194 std::string blockDimYValStr = getName(curBlockDimY);
1195 llvm::StringRef kind =
1196 isShuffleEnabled ?
"Shuffle reduction" :
"ThreadY barrier";
1197 return (llvm::Twine(kind) +
1198 " is generated while adjusting the number of threads into "
1200 llvm::Twine(subgroupSize) +
".\n\t" + blockDimXName +
": `" +
1201 blockDimXValStr +
"` to `((" + blockDimXValStr +
" + " +
1202 llvm::Twine(subgroupAlignMask) +
") / " +
1203 llvm::Twine(subgroupSize) +
") * " + llvm::Twine(subgroupSize) +
1204 "`\n" +
"\t" + blockDimYName +
": `" + blockDimYValStr +
1205 "` to `max(1, (new-" + blockDimXName +
" * " + blockDimYValStr +
1206 ") / new-" + blockDimXName +
")`")
1215 Value newBlockDimX, newBlockDimY, newBlockDimZ;
1216 if (constBlockDimX && constBlockDimY && constBlockDimZ) {
1217 int64_t bdx = *constBlockDimX;
1218 int64_t bdy = *constBlockDimY;
1219 int64_t bdz = *constBlockDimZ;
1220 int64_t alignedBdx =
1221 ((bdx + subgroupAlignMask) / subgroupSize) * subgroupSize;
1222 int64_t numXYThreads = bdx * bdy;
1223 int64_t numThreads = numXYThreads * bdz;
1224 int64_t newBdy = std::max<int64_t>(1, numXYThreads / alignedBdx);
1226 std::max<int64_t>(1, numThreads / (alignedBdx * newBdy));
1233 Value numXYThreads =
1234 arith::MulIOp::create(rewriter, loc, curBlockDimX, curBlockDimY);
1236 arith::MulIOp::create(rewriter, loc, numXYThreads, curBlockDimZ);
1240 Value cstSubgroupSize =
1243 arith::AddIOp::create(rewriter, loc, curBlockDimX, cstMask);
1244 Value subgroupsRequired =
1245 arith::DivUIOp::create(rewriter, loc, padded, cstSubgroupSize);
1246 newBlockDimX = arith::MulIOp::create(rewriter, loc, subgroupsRequired,
1250 arith::DivUIOp::create(rewriter, loc, numXYThreads, newBlockDimX);
1252 newBlockDimY = arith::MaxUIOp::create(rewriter, loc, cst1, quotient);
1254 Value newNumXYThreads =
1255 arith::MulIOp::create(rewriter, loc, newBlockDimX, newBlockDimY);
1257 arith::DivUIOp::create(rewriter, loc, numThreads, newNumXYThreads);
1258 newBlockDimZ = arith::MaxUIOp::create(rewriter, loc, cst1, quotient);
1261 launch.getBlockSizeXMutable().assign(newBlockDimX);
1262 launch.getBlockSizeYMutable().assign(newBlockDimY);
1263 launch.getBlockSizeZMutable().assign(newBlockDimZ);
1270 if (!threadPrivateVarNames.empty()) {
1272 return (llvm::Twine(
"Thread-private storage used for ") +
1273 llvm::join(threadPrivateVarNames,
","))
1278 if (!sharedMemPrivateVarNames.empty()) {
1280 return (llvm::Twine(
"GPU shared memory used for ") +
1281 llvm::join(sharedMemPrivateVarNames,
","))
1286 rewriter.
eraseOp(computeRegion);
1302static bool isRedundantChainAccumulate(acc::ReductionAccumulateOp op) {
1303 Value memref = op.getMemref();
1304 memref::LoadOp loadOp = op.getValue().
getDefiningOp<memref::LoadOp>();
1305 if (!loadOp || loadOp.getMemRef() != memref)
1307 for (Operation *user : memref.
getUsers()) {
1308 acc::ReductionCombineOp combineOp = dyn_cast<acc::ReductionCombineOp>(user);
1309 if (!combineOp || combineOp.getDestMemref() != memref)
1311 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
1313 if (llvm::any_of(parDims, [](mlir::acc::GPUParallelDimAttr d) {
1314 return d.isAnyBlock();
1322static GPUParallelDimsAttr
1323getPrivateParDims(acc::PrivateLocalOp privateLocal,
1324 acc::ComputeRegionOp computeRegion) {
1327 if (acc::PrivatizeOp privatize =
getPrivatizeOp(privateLocal, computeRegion))
1328 return privatize.getParDimsAttr();
1333static bool isThreadYPrivate(acc::PrivateLocalOp privateLocal,
bool allowBlock,
1334 acc::ComputeRegionOp computeRegion) {
1337 GPUParallelDimsAttr parDims = getPrivateParDims(privateLocal, computeRegion);
1340 return llvm::any_of(parDims.getArray(),
1341 [](
auto dim) { return dim.isThreadY(); }) &&
1342 llvm::all_of(parDims.getArray(), [=](
auto dim) {
1343 return dim.isThreadY() || (allowBlock && dim.isAnyBlock());
1347struct ThreadYBroadeningInfo {
1348 bool hasActiveWorkerCombine =
false;
1349 bool hasExplicitInactiveCombine =
false;
1350 bool hasBroadeningConflict =
false;
1351 Operation *diagnosticOp =
nullptr;
1353 void merge(
const ThreadYBroadeningInfo &other) {
1354 hasActiveWorkerCombine |= other.hasActiveWorkerCombine;
1355 hasExplicitInactiveCombine |= other.hasExplicitInactiveCombine;
1356 hasBroadeningConflict |= other.hasBroadeningConflict;
1358 diagnosticOp = other.diagnosticOp;
1363static bool hasUnsafeEffectsWhenBroadening(Operation *op) {
1364 if (
auto effectOp = dyn_cast<MemoryEffectOpInterface>(op)) {
1365 SmallVector<MemoryEffects::EffectInstance> effects;
1366 effectOp.getEffects(effects);
1367 return llvm::any_of(effects, [](
const auto &effect) {
1368 return !isa<MemoryEffects::Read>(effect.getEffect());
1371 return !op->
hasTrait<OpTrait::HasRecursiveMemoryEffects>();
1376static bool isFedByInnerBlockCombine(acc::PrivateLocalOp accumulator,
1377 Operation *selfCombine) {
1380 for (Operation *user : accumulator.getResult().getUsers()) {
1381 if (user == selfCombine)
1383 auto combineOp = dyn_cast<acc::ReductionCombineOp>(user);
1385 unwrapMemRefConversion(combineOp.getDestMemref()).getDefiningOp() !=
1386 accumulator.getOperation())
1388 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
1390 if (llvm::any_of(parDims, [](mlir::acc::GPUParallelDimAttr d) {
1391 return d.isAnyBlock();
1399static void classifyThreadYCombine(ThreadYBroadeningInfo &info,
1400 Operation *combineOp, Value src, Value dest,
1401 ArrayRef<GPUParallelDimAttr> parDims,
1402 acc::ComputeRegionOp computeRegion) {
1403 bool hasThreadY = llvm::any_of(
1404 parDims, [](GPUParallelDimAttr parDim) {
return parDim.isThreadY(); });
1405 bool hasBlock = llvm::any_of(
1406 parDims, [](GPUParallelDimAttr parDim) {
return parDim.isAnyBlock(); });
1407 acc::PrivateLocalOp srcPrivate =
1408 unwrapMemRefConversion(src).getDefiningOp<acc::PrivateLocalOp>();
1409 acc::PrivateLocalOp destPrivate =
1410 unwrapMemRefConversion(dest).getDefiningOp<acc::PrivateLocalOp>();
1411 bool hasPrivateDest = isa<acc::ReductionCombineOp>(combineOp) && srcPrivate &&
1415 if (hasThreadY && hasBlock &&
1416 isThreadYPrivate(srcPrivate, hasPrivateDest, computeRegion)) {
1417 info.hasActiveWorkerCombine =
true;
1424 if (hasThreadY && hasBlock &&
1425 isThreadYPrivate(srcPrivate,
true, computeRegion) &&
1426 isFedByInnerBlockCombine(srcPrivate, combineOp)) {
1427 info.hasActiveWorkerCombine =
true;
1431 info.hasExplicitInactiveCombine =
true;
1432 if (!info.diagnosticOp)
1433 info.diagnosticOp = combineOp;
1439static ThreadYBroadeningInfo
1440analyzeThreadYBroadening(
Block &predicateBlock,
1441 acc::ComputeRegionOp computeRegion) {
1442 ThreadYBroadeningInfo info;
1443 for (Operation &nestedOp : predicateBlock) {
1444 if (acc::PredicateRegionOp nestedPredicate =
1445 dyn_cast<acc::PredicateRegionOp>(nestedOp)) {
1446 ThreadYBroadeningInfo nestedInfo = analyzeThreadYBroadening(
1447 nestedPredicate.getRegion().front(), computeRegion);
1448 info.hasActiveWorkerCombine |= nestedInfo.hasActiveWorkerCombine;
1451 if (acc::ReductionCombineOp combineOp =
1452 dyn_cast<acc::ReductionCombineOp>(nestedOp)) {
1453 classifyThreadYCombine(
1454 info, combineOp, combineOp.getSrcMemref(), combineOp.getDestMemref(),
1458 if (acc::ReductionCombineRegionOp combineRegionOp =
1459 dyn_cast<acc::ReductionCombineRegionOp>(nestedOp)) {
1460 classifyThreadYCombine(info, combineRegionOp, combineRegionOp.getSrcVar(),
1461 combineRegionOp.getDestVar(),
1466 if (nestedOp.getNumRegions() != 0) {
1467 if (hasUnsafeEffectsWhenBroadening(&nestedOp)) {
1468 info.hasBroadeningConflict =
true;
1469 if (!info.diagnosticOp)
1470 info.diagnosticOp = &nestedOp;
1472 for (Region ®ion : nestedOp.getRegions())
1473 for (
Block &nestedBlock : region)
1474 info.merge(analyzeThreadYBroadening(nestedBlock, computeRegion));
1477 if (hasUnsafeEffectsWhenBroadening(&nestedOp)) {
1478 info.hasBroadeningConflict =
true;
1479 if (!info.diagnosticOp)
1480 info.diagnosticOp = &nestedOp;
1486std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
1487 SmallVector<mlir::acc::GPUParallelDimAttr>>
1488ACCCGToGPULowering::computeActiveAndInactiveParDims(Operation *op,
1490 MLIRContext *ctx = computeRegion->getContext();
1492 mlir::acc::GPUParallelDimAttr routineParDim;
1494 FunctionOpInterface funcOp =
1495 computeRegion->getParentOfType<FunctionOpInterface>();
1496 routineParDim = getSpecializedRoutineDim(funcOp, defaultPolicy);
1503 if (isa<acc::PrivateLocalOp, acc::PrivatizeOp, acc::PredicateRegionOp>(op)) {
1504 if (mlir::acc::ActiveParDimsAttr precomputedActiveParDims =
1506 mlir::acc::GPUParallelDimAttr lowestParDim =
1507 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
1509 SmallVector<mlir::acc::GPUParallelDimAttr> launchParDims;
1510 if (routineParDim) {
1511 for (mlir::acc::GPUParallelDimAttr parDim = routineParDim;
1512 parDim.getOrder() >= lowestParDim.getOrder();
1513 parDim = parDim.getOneLower()) {
1517 launchParDims = computeRegion.getLaunchParDims();
1520 SmallVector<mlir::acc::GPUParallelDimAttr> activeParDims(
1521 precomputedActiveParDims.getArray());
1522 SmallVector<mlir::acc::GPUParallelDimAttr> inactiveParDims;
1523 for (mlir::acc::GPUParallelDimAttr launchParDim : launchParDims) {
1524 if (launchParDim.getOrder() < lowestParDim.getOrder())
1526 if (!llvm::is_contained(activeParDims, launchParDim))
1527 inactiveParDims.push_back(launchParDim);
1529 return std::pair{activeParDims, inactiveParDims};
1533 SmallVector<mlir::acc::GPUParallelDimAttr> ancestorParDims =
1534 getAncestorParDims(op);
1539 bool noStructuralAncestorParDims =
1540 llvm::none_of(ancestorParDims, [](
auto pd) {
return !pd.isSeq(); });
1542 if (routineParDim) {
1543 if (routineParDim.isThreadX()) {
1545 mlir::acc::GPUParallelDimAttr::threadYDim(ctx));
1548 mlir::acc::GPUParallelDimAttr::blockXDim(ctx));
1552 if (acc::PrivateLocalOp privateLocalOp = dyn_cast<acc::PrivateLocalOp>(op)) {
1553 for (Operation *user : privateLocalOp.getResult().getUsers()) {
1554 if (acc::ReductionAccumulateOp accumulateOp =
1555 dyn_cast<acc::ReductionAccumulateOp>(user)) {
1556 if (accumulateOp.getMemref() == privateLocalOp.getResult()) {
1557 for (mlir::acc::GPUParallelDimAttr parDim :
1558 accumulateOp.getParDims().getArray()) {
1568 if (acc::ReductionCombineOp combineOp =
1569 dyn_cast<acc::ReductionCombineOp>(user)) {
1570 if (combineOp.getSrcMemref() == privateLocalOp.
getResult()) {
1571 for (mlir::acc::GPUParallelDimAttr parDim :
1577 if (
auto combineRegionOp =
1578 dyn_cast<acc::ReductionCombineRegionOp>(user)) {
1579 if (combineRegionOp.getSrcVar() == privateLocalOp.getResult()) {
1580 for (mlir::acc::GPUParallelDimAttr parDim :
1592 acc::PrivateType privTy =
1593 cast<acc::PrivateType>(privateLocalOp.getPrivatized().getType());
1595 privTy.getBaseTy(), computeRegion->getParentOfType<ModuleOp>());
1596 if (!baseTy.hasStaticShape()) {
1597 GPUParallelDimsAttr ownParDims =
1598 getPrivateParDims(privateLocalOp, computeRegion);
1600 for (GPUParallelDimAttr parDim : ownParDims.getArray())
1605 bool hasBlock =
false;
1606 for (mlir::acc::GPUParallelDimAttr parDim : ancestorParDims)
1607 if (parDim.isAnyBlock())
1610 mlir::acc::GPUParallelDimAttr lowestParDim =
1611 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
1613 ThreadYBroadeningInfo threadYInfo =
1614 analyzeThreadYBroadening(*block, computeRegion);
1616 auto applyCombineParDims =
1617 [&](ArrayRef<mlir::acc::GPUParallelDimAttr> combineParDims) {
1618 for (mlir::acc::GPUParallelDimAttr parDim : combineParDims)
1622 block->
walk([&](Operation *op) -> WalkResult {
1626 auto addPrivateStoreParDims = [&](Value
target) {
1627 if (
auto privateLocalOp = getPrivateLocalForMemref(
target)) {
1628 GPUParallelDimsAttr parDimsAttr =
1629 getPrivateParDims(privateLocalOp, computeRegion);
1631 for (
auto parDim : parDimsAttr.getArray())
1635 if (
auto memEffects = dyn_cast<MemoryEffectOpInterface>(op)) {
1636 SmallVector<MemoryEffects::EffectInstance> effects;
1637 memEffects.getEffects(effects);
1639 if (isa<MemoryEffects::Write>(effect.getEffect()) &&
1641 addPrivateStoreParDims(effect.getValue());
1646 if (CallOpInterface callOp = dyn_cast<CallOpInterface>(op)) {
1647 if (mlir::acc::GPUParallelDimAttr parDim =
1648 getAccRoutineCallParDim(callOp, defaultPolicy)) {
1649 if (parDim.isBlockZ())
1650 lowestParDim = parDim;
1652 lowestParDim = parDim.getOneHigher();
1658 if (acc::ReductionCombineOp reductionCombineOp =
1659 dyn_cast<acc::ReductionCombineOp>(op)) {
1660 if (
failed(applyCombineParDims(
1664 if (acc::ReductionCombineRegionOp combineRegionOp =
1665 dyn_cast<acc::ReductionCombineRegionOp>(op)) {
1666 if (
failed(applyCombineParDims(
1673 if (acc::ReductionAccumulateArrayOp accArrayOp =
1674 dyn_cast<acc::ReductionAccumulateArrayOp>(op)) {
1675 for (mlir::acc::GPUParallelDimAttr parDim :
1676 accArrayOp.getParDims().getArray()) {
1682 mlir::acc::GPUParallelDimAttr threadY =
1683 mlir::acc::GPUParallelDimAttr::threadYDim(ctx);
1684 bool baselineThreadYActive = llvm::is_contained(ancestorParDims, threadY);
1685 if (threadYInfo.hasActiveWorkerCombine && !baselineThreadYActive) {
1686 if (threadYInfo.hasExplicitInactiveCombine ||
1687 threadYInfo.hasBroadeningConflict) {
1688 Operation *diagnosticOp =
1689 threadYInfo.diagnosticOp ? threadYInfo.diagnosticOp : op;
1692 "operations in the same predicate region require incompatible "
1693 "ThreadY predication");
1702 SmallVector<mlir::acc::GPUParallelDimAttr> launchParDims;
1703 if (routineParDim) {
1704 for (mlir::acc::GPUParallelDimAttr parDim = routineParDim;
1705 parDim.getOrder() >= lowestParDim.getOrder();
1706 parDim = parDim.getOneLower()) {
1710 launchParDims = computeRegion.getLaunchParDims();
1714 SmallVector<mlir::acc::GPUParallelDimAttr> activeParDims, inactiveParDims;
1715 for (mlir::acc::GPUParallelDimAttr launchParDim : launchParDims) {
1716 if (launchParDim.getOrder() < lowestParDim.getOrder())
1718 if (llvm::find(ancestorParDims, launchParDim) != ancestorParDims.end() ||
1719 (launchParDim.isAnyBlock() &&
1720 (noStructuralAncestorParDims || hasBlock))) {
1721 activeParDims.push_back(launchParDim);
1723 inactiveParDims.push_back(launchParDim);
1727 return std::pair{activeParDims, inactiveParDims};
1730Value ACCCGToGPULowering::emitPredicate(
1731 Location loc, SmallVector<mlir::acc::GPUParallelDimAttr> &inactiveParDims) {
1733 for (mlir::acc::GPUParallelDimAttr inactiveParDim : inactiveParDims) {
1734 Value threadId = getGPUThreadIdFor(inactiveParDim.getProcessor());
1736 Value zero = arith::ConstantOp::create(rewriter, loc, zeroAttr);
1737 Value cmp = arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq,
1740 predicate = arith::AndIOp::create(rewriter, loc, cmp, predicate);
1747void ACCCGToGPULowering::createBarrier(
1748 Location loc, mlir::acc::GPUParallelDimsAttr parDimsAttr) {
1749 bool hasAnyBlock =
false, hasThreadY =
false, hasThreadX =
false;
1750 for (
auto parDim : parDimsAttr.getArray()) {
1751 if (parDim.isAnyBlock())
1753 if (parDim.isThreadY())
1755 if (parDim.isThreadX())
1759 if (hasAnyBlock || hasThreadY)
1760 emitGPUBarrierWorkgroup(rewriter, loc);
1761 else if (hasThreadX)
1762 createPerRowBarrier(loc);
1765void ACCCGToGPULowering::createPerRowBarrier(Location loc) {
1768 if (isSingleThreadWorkerLaunch())
1774 if (isWorkerOnlyShuffleLaunch() && staticBlockDimX <
options.subgroupSize) {
1775 emitGPUBarrierRow(rewriter, loc, staticBlockDimX,
options.subgroupSize);
1779 hasThreadYBarrier =
true;
1781 if (staticBlockDimX <=
options.subgroupSize) {
1782 emitGPUBarrierSubgroup(rewriter, loc);
1786 if (
options.deviceType != mlir::acc::DeviceType::Nvidia) {
1789 "per-row barrier to support worker parallelism on non-NVIDIA device");
1804 Value blockDimX = gpu::BlockDimOp::create(
1805 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
1806 Value blockDimY = gpu::BlockDimOp::create(
1807 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::y);
1809 Value isSingleWorker = arith::CmpIOp::create(
1810 rewriter, loc, arith::CmpIPredicate::eq, blockDimY, cst1);
1812 auto outerIf = scf::IfOp::create(rewriter, loc, isSingleWorker,
1817 emitGPUBarrierWorkgroup(rewriter, loc);
1821 Value cstSubgroupSize =
1823 Value isSubgroupSized = arith::CmpIOp::create(
1824 rewriter, loc, arith::CmpIPredicate::ule, blockDimX, cstSubgroupSize);
1826 auto innerIf = scf::IfOp::create(rewriter, loc, isSubgroupSized,
1832 emitGPUBarrierSubgroup(rewriter, loc);
1839 Value threadYId = gpu::ThreadIdOp::create(
1840 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::y);
1841 Value barrierId = arith::AddIOp::create(rewriter, loc, threadYId, cst1);
1844 arith::IndexCastOp::create(rewriter, loc, i32Ty, barrierId);
1845 Value numberOfThreads32 =
1846 arith::IndexCastOp::create(rewriter, loc, i32Ty, blockDimX);
1850 assert(
options.deviceType == mlir::acc::DeviceType::Nvidia);
1851 NVVM::BarrierOp::create(rewriter, loc, barrierId32, numberOfThreads32);
1860static bool hasSubsequentLoopSibling(Operation *loopOp) {
1861 for (Operation *next = loopOp->getNextNode(); next;
1862 next = next->getNextNode()) {
1863 if (isa<scf::ParallelOp, scf::ForOp>(next))
1865 bool nested =
false;
1866 next->walk([&](Operation *op) {
1867 if (isa<scf::ParallelOp, scf::ForOp>(op)) {
1880static LoopLikeOpInterface findFirstSequentialLoop(Operation *op) {
1881 auto isAllSequentialParDims = [](scf::ParallelOp par) ->
bool {
1883 if (!pd || pd.getArray().empty())
1885 return llvm::all_of(pd.getArray(), [](mlir::acc::GPUParallelDimAttr d) {
1892 if (isa<scf::ForOp>(p))
1893 return cast<LoopLikeOpInterface>(p);
1894 if (scf::ParallelOp parOp = dyn_cast<scf::ParallelOp>(p)) {
1895 if (isAllSequentialParDims(parOp))
1896 return cast<LoopLikeOpInterface>(p);
1912static bool isLoopBodyClosureOp(Operation *op) {
1913 return isa<scf::ReduceOp, scf::YieldOp, acc::YieldOp>(op);
1918static bool isDeferredBarrierFlushPoint(Operation *op) {
1919 if (isLoopBodyClosureOp(op))
1923 if (isa<scf::ForOp>(op))
1925 if (scf::ParallelOp parallelOp = dyn_cast<scf::ParallelOp>(op)) {
1927 if (mlir::acc::GPUParallelDimsAttr parDims =
1929 if (parDims.getArray().size() == 1 &&
1930 parDims.getArray().front().isSeq()) {
1942static bool hasTrailingSideEffectSiblings(Operation *loopOp) {
1943 for (Operation *next = loopOp->getNextNode(); next;
1944 next = next->getNextNode()) {
1945 return !isLoopBodyClosureOp(next);
1969void ACCCGToGPULowering::createBarrierAfterSeqLoop(Operation *loopOp) {
1978 if (mayWriteSharedMemory(loopOp) && hasSubsequentLoopSibling(loopOp))
1979 emitGPUBarrierWorkgroup(rewriter, loopOp->
getLoc());
1983 bool parentIsSeq =
false;
1984 if (mlir::acc::GPUParallelDimsAttr wsParDims =
1986 if (wsParDims.getArray().size() == 1 &&
1987 wsParDims.getArray().front().isSeq()) {
1997 bool hasThreadSubLoop =
false;
1998 loopOp->
walk([&](scf::ParallelOp innerPar) -> WalkResult {
1999 if (innerPar.getOperation() == loopOp)
2001 if (mlir::acc::GPUParallelDimsAttr dims =
2003 for (
auto d : dims.getArray()) {
2004 if (d.isThreadX() || d.isThreadY()) {
2005 hasThreadSubLoop =
true;
2012 if (!hasThreadSubLoop)
2014 scf::ParallelOp threadLoop = wsLoop->getParentOfType<scf::ParallelOp>();
2017 scf::ParallelOp blockLoop = threadLoop->getParentOfType<scf::ParallelOp>();
2020 mlir::acc::GPUParallelDimsAttr parDimsAttr =
2022 if (parDimsAttr.hasOnlyBlockLevel())
2023 createBarrier(loopOp->
getLoc(), parDimsAttr);
2029 scf::ParallelOp seqLoop = wsLoop->getParentOfType<scf::ParallelOp>();
2038 if (mayWriteSharedMemory(loopOp) && hasSubsequentLoopSibling(wsLoop))
2039 emitGPUBarrierWorkgroup(rewriter, loopOp->
getLoc());
2042 if (scf::ParallelOp outerParLoop =
2043 seqLoop->getParentOfType<scf::ParallelOp>()) {
2044 mlir::acc::GPUParallelDimsAttr parDimsAttr =
2046 if (parDimsAttr.hasOnlyBlockLevel()) {
2047 createBarrier(loopOp->
getLoc(), parDimsAttr);
2048 }
else if (parDimsAttr.hasOnlyThreadYLevel()) {
2049 createPerRowBarrier(loopOp->
getLoc());
2050 }
else if (parDimsAttr && parDimsAttr.isSeq()) {
2054 for (Operation *gangLoop =
2055 outerParLoop->getParentOfType<scf::ParallelOp>();
2056 gangLoop; gangLoop = gangLoop->getParentOfType<scf::ParallelOp>()) {
2057 mlir::acc::GPUParallelDimsAttr gangDims =
2061 if (gangDims.hasOnlyBlockLevel()) {
2062 createBarrier(loopOp->
getLoc(), gangDims);
2065 if (!gangDims.isSeq())
2079 mlir::acc::GPUParallelDimsAttr parDimsAttr =
2081 if (parDimsAttr && parDimsAttr.hasOnlyBlockLevel() &&
2082 mayWriteSharedMemory(loopOp)) {
2083 createBarrier(loopOp->
getLoc(), parDimsAttr);
2087bool ACCCGToGPULowering::mayWriteSharedMemory(Operation *loopOp) {
2089 loopOp->
walk([&](memref::StoreOp storeOp) {
2092 llvm::SmallVector<Value, 8> worklist{storeOp.getMemref()};
2093 llvm::SmallPtrSet<Value, 8> seen;
2094 while (!worklist.empty()) {
2095 Value v = worklist.pop_back_val();
2096 if (!seen.insert(v).second)
2101 if (acc::PrivateLocalOp privateLocal =
2102 dyn_cast<acc::PrivateLocalOp>(def)) {
2103 acc::PrivatizeOp privatizeOp =
2111 if (mlir::acc::GPUParallelDimsAttr parDims =
2112 privatizeOp.getParDimsAttr()) {
2113 bool hasBlock =
false, hasThread =
false;
2114 for (mlir::acc::GPUParallelDimAttr d : parDims.getArray()) {
2117 if (d.isThreadX() || d.isThreadY())
2120 if (hasBlock && !hasThread) {
2135ACCCGToGPULowering::getPrivateMemScope(acc::PrivatizeOp privatizeOp) {
2136 bool hasBlock =
false;
2137 bool hasThreadX =
false;
2138 bool hasThreadY =
false;
2139 if (mlir::acc::GPUParallelDimsAttr parDims = privatizeOp.getParDimsAttr()) {
2140 for (mlir::acc::GPUParallelDimAttr d : parDims.getArray()) {
2149 for (mlir::acc::GPUParallelDimAttr d : computeRegion.getLaunchParDims())
2153 return PrivateMemScope::Gang;
2154 return PrivateMemScope::Thread;
2157 return PrivateMemScope::Thread;
2158 if (hasBlock && hasThreadY)
2159 return PrivateMemScope::Worker;
2161 return PrivateMemScope::Gang;
2162 return PrivateMemScope::Thread;
2166static acc::PrivateLocalOp getPrivateLocalForMemref(Value memref) {
2167 llvm::SmallVector<Value, 8> worklist{memref};
2168 llvm::SmallPtrSet<Value, 8> seen;
2169 while (!worklist.empty()) {
2170 Value v = worklist.pop_back_val();
2171 if (!seen.insert(v).second)
2176 if (acc::PrivateLocalOp privateLocal = dyn_cast<acc::PrivateLocalOp>(def))
2177 return privateLocal;
2183PrivateMemScope ACCCGToGPULowering::getPrivateScopeForMemref(Value memref) {
2184 if (
auto privateLocal = getPrivateLocalForMemref(memref))
2185 return getPrivateMemScope(
getPrivatizeOp(privateLocal, computeRegion));
2186 return PrivateMemScope::None;
2189acc::PrivatizeOp ACCCGToGPULowering::getPrivatizeForMemref(Value memref) {
2190 if (
auto privateLocal = getPrivateLocalForMemref(memref))
2192 return acc::PrivatizeOp();
2196ACCCGToGPULowering::needsPreStoreReuseBarrier(acc::PredicateRegionOp interOp) {
2200 LoopLikeOpInterface seqLoopOp = findFirstSequentialLoop(interOp);
2202 return PrivateMemScope::None;
2206 PrivateMemScope storeScope = PrivateMemScope::None;
2207 llvm::SmallPtrSet<Operation *, 4> storePrivatizes;
2208 interOp.getRegion().walk([&](memref::StoreOp storeOp) {
2209 PrivateMemScope scope = getPrivateScopeForMemref(storeOp.getMemref());
2210 if (scope != PrivateMemScope::Gang && scope != PrivateMemScope::Worker)
2212 if (
auto privatize = getPrivatizeForMemref(storeOp.getMemref()))
2213 storePrivatizes.insert(privatize.getOperation());
2214 if (storeScope == PrivateMemScope::None)
2218 if (storeScope == PrivateMemScope::None || storePrivatizes.empty())
2219 return PrivateMemScope::None;
2222 bool hasParallelPrivateUse =
false;
2223 seqLoopOp.getOperation()->walk([&](Operation *op) {
2225 if (interOp->isAncestor(op))
2229 if (memref::LoadOp loadOp = dyn_cast<memref::LoadOp>(op))
2230 memref = loadOp.getMemref();
2231 else if (memref::StoreOp storeOp = dyn_cast<memref::StoreOp>(op))
2232 memref = storeOp.getMemref();
2236 PrivateMemScope scope = getPrivateScopeForMemref(memref);
2237 if (scope != storeScope)
2240 acc::PrivatizeOp usePrivatize = getPrivatizeForMemref(memref);
2241 if (!usePrivatize || !storePrivatizes.contains(usePrivatize.getOperation()))
2244 bool insideNestedParallel =
false;
2245 for (Operation *p = op->
getParentOp(); p && p != seqLoopOp.getOperation();
2247 if (scf::ParallelOp par = dyn_cast<scf::ParallelOp>(p)) {
2248 if (mlir::acc::GPUParallelDimsAttr pd =
2250 if (llvm::any_of(pd.getArray(), [](mlir::acc::GPUParallelDimAttr d) {
2253 insideNestedParallel =
true;
2259 if (!insideNestedParallel)
2262 hasParallelPrivateUse =
true;
2266 if (!hasParallelPrivateUse)
2267 return PrivateMemScope::None;
2272void ACCCGToGPULowering::processPredicateRegion(
2273 acc::PredicateRegionOp interOp) {
2274 LLVM_DEBUG(llvm::dbgs() <<
"processing predicate region: ";
2275 interOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2276 Location loc = interOp->getLoc();
2278 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
2279 SmallVector<mlir::acc::GPUParallelDimAttr>>
2280 parDimsPair = computeActiveAndInactiveParDims(
2281 interOp, &interOp.getRegion().front());
2292 if (hasThreadYReduction && !isSingleThreadWorkerLaunch()) {
2293 MLIRContext *ctx = computeRegion->getContext();
2294 mlir::acc::GPUParallelDimAttr threadXParDim =
2295 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
2296 bool hasThreadXInActive =
2297 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr pd) {
2298 return pd.isThreadX();
2300 bool hasThreadXInInactive =
2301 llvm::any_of(parDimsPair.second, [](mlir::acc::GPUParallelDimAttr pd) {
2302 return pd.isThreadX();
2308 bool regionHasThreadLevelRoutineCall =
false;
2309 if (hasThreadLevelRoutineCall) {
2310 interOp.getRegion().walk([&](CallOpInterface callOp) {
2311 if (mlir::acc::GPUParallelDimAttr parDim =
2312 getAccRoutineCallParDim(callOp, defaultPolicy)) {
2313 if (parDim.isThreadX() || parDim.isThreadY()) {
2314 regionHasThreadLevelRoutineCall =
true;
2322 if (!hasThreadXInActive && !hasThreadXInInactive &&
2323 !regionHasThreadLevelRoutineCall) {
2324 parDimsPair.second.push_back(threadXParDim);
2328 if (Value predicate = emitPredicate(loc, parDimsPair.second)) {
2329 LLVM_DEBUG(llvm::dbgs() <<
"predicate: " << predicate <<
"\n");
2330 bool isInsideThreadXLoop =
false;
2331 bool isInsideThreadYLoop =
false;
2332 for (
auto parDim : parDimsPair.first) {
2333 if (parDim.isThreadX())
2334 isInsideThreadXLoop =
true;
2335 if (parDim.isThreadY())
2336 isInsideThreadYLoop =
true;
2342 auto emitReconvergenceBarrier = [&]() {
2343 if (isInsideThreadXLoop) {
2345 }
else if (isInsideThreadYLoop) {
2350 bool predicatesThreadX = llvm::any_of(
2352 [](mlir::acc::GPUParallelDimAttr pd) { return pd.isThreadX(); });
2353 if (predicatesThreadX)
2354 createPerRowBarrier(loc);
2357 }
else if (!parDimsPair.first.empty()) {
2359 createBarrier(loc, mlir::acc::GPUParallelDimsAttr::get(
2360 interOp->getContext(), parDimsPair.first));
2363 createBarrier(loc, mlir::acc::GPUParallelDimsAttr::get(
2364 interOp->getContext(), parDimsPair.second));
2376 PrivateMemScope scope = needsPreStoreReuseBarrier(interOp);
2377 if (scope == PrivateMemScope::Gang)
2378 emitGPUBarrierWorkgroup(rewriter, loc);
2379 else if (scope == PrivateMemScope::Worker)
2380 createPerRowBarrier(loc);
2382 auto ifOp = scf::IfOp::create(rewriter, loc, predicate,
2384 Region &thenRegion = ifOp.getThenRegion();
2388 for (
auto &bodyOp : interOp.getRegion().front().getOperations()) {
2392 if (memref::StoreOp storeOp = dyn_cast<memref::StoreOp>(&bodyOp)) {
2393 std::optional<arith::AtomicRMWKind> blockReduceKind;
2394 bool failedReductionKind =
false;
2395 Value storeVal = storeOp.getValueToStore();
2402 Block *epilogueBlock = interOp->getBlock();
2403 auto findBlockAccLoad =
2405 Value val) -> std::optional<arith::AtomicRMWKind> {
2406 Operation *def = val.getDefiningOp();
2407 if (!def || def->
getBlock() != epilogueBlock)
2408 return std::nullopt;
2409 if (memref::LoadOp loadOp = dyn_cast<memref::LoadOp>(def)) {
2410 for (
auto *user : loadOp.getMemRef().getUsers()) {
2411 if (acc::ReductionAccumulateOp accOp =
2412 dyn_cast<acc::ReductionAccumulateOp>(user)) {
2413 if (llvm::any_of(accOp.getParDims().getArray(),
2414 [](mlir::acc::GPUParallelDimAttr pd) {
2415 return pd.isAnyBlock();
2417 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
2418 accOp.getReductionOperator(),
2419 accOp.getValue().getType(), accOp.getLoc());
2421 failedReductionKind =
true;
2422 return std::nullopt;
2428 return std::nullopt;
2431 if (
auto kind = self(self, operand))
2433 return std::nullopt;
2435 blockReduceKind = findBlockAccLoad(findBlockAccLoad, storeVal);
2437 if (failedReductionKind)
2439 if (blockReduceKind) {
2442 bool threadIsActive = llvm::any_of(
2443 parDimsPair.first, [](mlir::acc::GPUParallelDimAttr pd) {
2444 return !pd.isAnyBlock();
2446 if (!threadIsActive &&
2447 !isa_and_nonnull<memref::AllocaOp>(
2448 unwrapMemRefConversion(memref).getDefiningOp())) {
2454 MemRefType memrefTy = cast<MemRefType>(memref.
getType());
2456 SmallVector<Value> initIndices;
2457 for (Value idx : storeOp.getIndices())
2460 OpBuilder::InsertionGuard guard(rewriter);
2461 Block &launchBody = launch.getBody().front();
2462 Operation *insertBefore =
nullptr;
2464 launchBody.
walk([&](scf::ParallelOp parOp) -> WalkResult {
2465 for (Operation *parent = parOp->getParentOp(); parent;
2466 parent = parent->getParentOp()) {
2467 if (parent == launch.getOperation())
2469 if (isa<scf::ParallelOp>(parent))
2472 insertBefore = parOp.getOperation();
2485 DominanceInfo domInfo(launch);
2486 IRMapping initMapping;
2487 std::function<Value(Value)> materialize =
2488 [&](Value val) -> Value {
2489 Operation *defOp = val.getDefiningOp();
2500 materialize(operand);
2501 Operation *cloned = rewriter.
clone(*defOp, initMapping);
2502 for (
auto [orig, clonedRes] :
2504 initMapping.
map(orig, clonedRes);
2506 return initMapping.
lookup(val);
2508 Value initMemref = materialize(memref);
2509 for (
auto &idx : initIndices)
2510 idx = materialize(idx);
2512 rewriter, loc, memrefTy.getElementType(), *blockReduceKind,
2514 Value blockId = gpu::BlockIdOp::create(
2515 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
2516 Value threadId = gpu::ThreadIdOp::create(
2517 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
2519 Value isBlock0 = arith::CmpIOp::create(
2520 rewriter, loc, arith::CmpIPredicate::eq, blockId, zero);
2521 Value isThread0 = arith::CmpIOp::create(
2522 rewriter, loc, arith::CmpIPredicate::eq, threadId, zero);
2523 Value isFirstThread =
2524 arith::AndIOp::create(rewriter, loc, isBlock0, isThread0);
2525 auto initIf = scf::IfOp::create(rewriter, loc, isFirstThread,
2528 initIf.getThenRegion().back().getTerminator());
2529 memref::StoreOp::create(rewriter, loc, identityVal, initMemref,
2532 gpu::BarrierOp::create(rewriter, loc);
2534 SmallVector<Value> atomicIndices;
2535 for (Value idx : storeOp.getIndices())
2537 constructAtomicAccumulation(loc, memref, atomicIndices, input,
2546 emitReconvergenceBarrier();
2549 for (
auto &bodyOp : interOp.getRegion().front().getOperations())
2572Value ACCCGToGPULowering::allocatePrivatizeHeapStorage(
2573 acc::PrivatizeOp privatize, MemRefType baseTy,
2575 Location loc = privatize.getLoc();
2577 memref::AllocOp::create(rewriter, loc, baseTy, mappedDynamicSizes);
2578 OpBuilder::InsertionGuard guard(rewriter);
2582 memref::DeallocOp::create(rewriter, loc, mem);
2584 mapping.
map(privatize.getResult(), mem);
2587 if (!privatize->getParentOfType<acc::ComputeRegionOp>())
2592Value ACCCGToGPULowering::processPrivatize(acc::PrivatizeOp privatize) {
2593 LLVM_DEBUG(llvm::dbgs() <<
"processing privatize: ";
2594 privatize->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2595 Value tracked = privatize.getResult();
2596 if (acc::ComputeRegionOp insUser =
2597 dyn_cast<acc::ComputeRegionOp>(getOnlyUser(tracked))) {
2598 assert(privatize->hasOneUse() &&
2599 "expected acc.privatize op to have one use");
2600 tracked = insUser.getBody()->getArgument(
2601 privatize->use_begin()->getOperandNumber());
2603 Operation *privatizeUser = getOnlyUser(tracked);
2604 assert(privatizeUser &&
"expected PrivateLocalOp user for privatize");
2605 acc::PrivateLocalOp privateLocalUser =
2606 dyn_cast<acc::PrivateLocalOp>(privatizeUser);
2608 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
2609 SmallVector<mlir::acc::GPUParallelDimAttr>>
2610 parDimsPair = computeActiveAndInactiveParDims(privatizeUser,
nullptr);
2612 if (!privatize.getParDimsAttr()) {
2613 privatize.setParDimsAttr(mlir::acc::GPUParallelDimsAttr::get(
2617 Location loc = privatize->getLoc();
2618 acc::PrivateType privTy = cast<acc::PrivateType>(privatize.getType());
2619 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
2622 gpu::GPUFuncOp gpuFuncOp = computeRegion->getParentOfType<gpu::GPUFuncOp>();
2627 privatize->getParentOfType<acc::ComputeRegionOp>() != computeRegion) {
2628 return privatize.getResult();
2631 bool threadXActive =
2632 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr parDim) {
2633 return parDim.isThreadX();
2635 if (threadXActive &&
2636 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
2637 auto alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
2638 recordThreadPrivate(privateLocalUser);
2639 mapping.
map(privatize.getResult(), alloca.getResult());
2640 return alloca.getResult();
2644 return privatize.getResult();
2652 bool threadYIsActive =
2653 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr parDim) {
2654 return parDim.isThreadY();
2667 bool needsWorkgroupBarrier =
false;
2669 FunctionOpInterface funcOp =
2670 computeRegion->getParentOfType<FunctionOpInterface>();
2671 mlir::acc::GPUParallelDimAttr routineParDim =
2672 getSpecializedRoutineDim(funcOp, defaultPolicy);
2673 if (routineParDim.isThreadX()) {
2676 threadYIsActive =
true;
2677 }
else if (routineParDim.isThreadY()) {
2680 needsWorkgroupBarrier =
true;
2681 }
else if (routineParDim.isAnyBlock()) {
2682 needsWorkgroupBarrier =
true;
2686 llvm::SmallVector<Value> mappedDynamicSizes;
2687 for (
auto dynamicSize : privatize.getDynamicSizes()) {
2689 mappedDynamicSizes.push_back(mappedDynamicSize);
2692 computeRegion.isEffectivelySerial()) {
2693 if (mappedDynamicSizes.empty()) {
2696 memref::AllocaOp::create(rewriter, privatize->getLoc(), baseTy);
2697 recordThreadPrivate(privateLocalUser);
2698 mapping.
map(privatize.getResult(), alloca.getResult());
2699 return alloca.getResult();
2702 return allocatePrivatizeHeapStorage(privatize, baseTy, mappedDynamicSizes);
2705 if (threadXActive) {
2710 return allocatePrivatizeHeapStorage(privatize, baseTy, mappedDynamicSizes);
2715 SmallVector<mlir::acc::GPUParallelDimAttr> predicateDims;
2716 for (
auto parDim : parDimsPair.second) {
2718 if (threadYIsActive && parDim.isThreadY())
2720 predicateDims.push_back(parDim);
2722 Value predicate = emitPredicate(loc, predicateDims);
2724 predicate = arith::ConstantOp::create(
2727 auto ifOp = scf::IfOp::create(rewriter, loc, predicate,
2729 Region &thenRegion = ifOp.getThenRegion();
2732 auto mem = memref::AllocOp::create(rewriter, privatize->getLoc(), baseTy,
2733 mappedDynamicSizes);
2735 gpu::AddressSpaceAttr sharedMemoryAddressSpace = gpu::AddressSpaceAttr::get(
2736 computeRegion->getContext(), gpu::GPUDialect::getWorkgroupAddressSpace());
2740 constexpr int64_t kMaxThreadY = 32;
2741 MemRefType sharedMemTy =
2743 ? MemRefType::get({kMaxThreadY}, baseTy, MemRefLayoutAttrInterface{},
2744 sharedMemoryAddressSpace)
2745 : MemRefType::
get({}, baseTy, MemRefLayoutAttrInterface{},
2746 sharedMemoryAddressSpace);
2749 bool reuseBroadcast = !gpuFuncOp.isKernel();
2751 llvm::DenseMap<Type, Value>::iterator cachedSlot =
2752 reuseBroadcast ? privatizeBroadcastCache.find(sharedMemTy)
2753 : privatizeBroadcastCache.end();
2754 if (reuseBroadcast && cachedSlot != privatizeBroadcastCache.end()) {
2755 alloca = cachedSlot->second;
2758 mlir::acc::GPUParallelDimAttr dim =
2759 needsWorkgroupBarrier
2760 ? mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.
getContext())
2761 : mlir::
acc::GPUParallelDimAttr::threadXDim(rewriter.
getContext());
2763 loc, mlir::acc::GPUParallelDimsAttr::get(rewriter.
getContext(), {dim}));
2765 alloca = gpuFuncOp.addWorkgroupAttribution(sharedMemTy,
2770 unsigned index = gpuFuncOp.getNumWorkgroupAttributions() - 1;
2771 gpuFuncOp.setWorkgroupAttributionAttr(
index,
2772 LLVM::LLVMDialect::getAlignAttrName(),
2775 privatizeBroadcastCache[sharedMemTy] = alloca;
2778 if (threadYIsActive) {
2779 Value threadYId = getThreadId(loc, gpu::Dimension::y);
2780 memref::StoreOp::create(rewriter, privatize->getLoc(), mem, alloca,
2783 memref::StoreOp::create(rewriter, privatize->getLoc(), mem, alloca);
2789 if (needsWorkgroupBarrier) {
2790 mlir::acc::GPUParallelDimsAttr threadYDimsAttr =
2791 mlir::acc::GPUParallelDimsAttr::get(
2793 {mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.getContext())});
2794 createBarrier(loc, threadYDimsAttr);
2797 mlir::acc::GPUParallelDimsAttr threadXDimsAttr =
2798 mlir::acc::GPUParallelDimsAttr::get(
2800 {mlir::acc::GPUParallelDimAttr::threadXDim(rewriter.getContext())});
2801 createBarrier(loc, threadXDimsAttr);
2805 if (threadYIsActive) {
2806 Value threadYId = getThreadId(loc, gpu::Dimension::y);
2807 load = memref::LoadOp::create(rewriter, privatize->getLoc(), alloca,
2810 load = memref::LoadOp::create(rewriter, privatize->getLoc(), alloca,
2814 mapping.
map(privatize.getResult(),
load);
2818 if (!privatize->getParentOfType<acc::ComputeRegionOp>())
2822 if (needsWorkgroupBarrier) {
2823 mlir::acc::GPUParallelDimsAttr workerDimsAttr =
2824 mlir::acc::GPUParallelDimsAttr::get(
2826 {mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.getContext())});
2827 createBarrier(loc, workerDimsAttr);
2829 mlir::acc::GPUParallelDimsAttr vectorDimsAttr =
2830 mlir::acc::GPUParallelDimsAttr::get(
2832 {mlir::acc::GPUParallelDimAttr::threadXDim(rewriter.getContext())});
2833 createBarrier(loc, vectorDimsAttr);
2835 auto ifOp2 = scf::IfOp::create(rewriter, loc, predicate,
2837 Region &thenRegion2 = ifOp2.getThenRegion();
2840 memref::DeallocOp::create(rewriter, privatize->getLoc(),
load);
2851void ACCCGToGPULowering::processPrivateLocal(
2852 acc::PrivateLocalOp privateLocal, std::optional<int64_t> sharedMemCopies) {
2853 LLVM_DEBUG(llvm::dbgs() <<
"processing private local: ";
2854 privateLocal->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2855 Location loc = privateLocal.getLoc();
2856 acc::PrivateType privTy =
2857 cast<acc::PrivateType>(privateLocal.getPrivatized().getType());
2858 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
2860 MemRefType byteMemrefTy =
2861 MemRefType::get({ShapedType::kDynamic}, rewriter.
getI8Type());
2863 acc::PrivatizeOp privatizeOp =
getPrivatizeOp(privateLocal, computeRegion);
2865 if (privatizeOp->getParentOfType<acc::ComputeRegionOp>() == computeRegion) {
2869 privateLocal.getType());
2870 mapping.
map(privateLocal.getResult(),
result);
2878 acc::ReductionAccumulateArrayOp arrayAccum =
2879 perThreadArrayReductionAccum(privateLocal.getResult());
2881 (arrayAccum && storageHasThreadX(privateLocal, computeRegion))) &&
2882 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
2883 Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
2884 recordThreadPrivate(privateLocal);
2886 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
2887 arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
2890 initPerThreadArrayAccum(rewriter, loc, alloca, baseTy, *kind);
2893 privateLocal.getType());
2894 mapping.
map(privateLocal.getResult(), mem);
2900 std::optional<int64_t>
copies =
2901 sharedMemCopies ? sharedMemCopies
2902 : isEligibleForSharedMemory(privateLocal, baseTy);
2904 int64_t numCopies = *
copies;
2905 int64_t elementSize = getElementSizeInBytes(loc, baseTy.getElementType());
2906 int64_t numElements = 1;
2907 for (int64_t dim : baseTy.getShape())
2909 int64_t upperBound = elementSize * numElements * numCopies;
2911 if (tryAllocateSharedMemory(upperBound)) {
2912 std::string varName =
2914 sharedMemPrivateVarNames.push_back(varName.empty() ?
"(*)" : varName);
2916 gpu::AddressSpaceAttr workgroupAS = gpu::AddressSpaceAttr::get(
2917 computeRegion->getContext(),
2918 gpu::GPUDialect::getWorkgroupAddressSpace());
2919 MemRefType sharedMemTy =
2920 MemRefType::get(baseTy.getShape(), baseTy.getElementType(),
2921 MemRefLayoutAttrInterface{}, workgroupAS);
2922 Value sharedMem = acc::GPUSharedMemoryOp::create(
2930 privateLocal.getType());
2932 mapping.
map(privateLocal.getResult(),
result);
2937 OpBuilder::InsertionGuard guard(rewriter);
2939 inputMem = processPrivatize(privatizeOp);
2943 assert(inputMem &&
"expected input mem to be mapped");
2945 privateLocal.getType());
2946 mapping.
map(privateLocal.getResult(),
result);
2952 SmallVector<int64_t> viewShape;
2954 SmallVector<Value> viewDynSizes;
2956 SmallVector<OpFoldResult> subviewOffset;
2959 SmallVector<OpFoldResult> subviewSizes;
2961 SmallVector<int64_t> subviewStrides;
2963 SmallVector<int64_t> subviewShape;
2965 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
2966 SmallVector<mlir::acc::GPUParallelDimAttr>>
2967 parDimsPair = computeActiveAndInactiveParDims(privateLocal,
nullptr);
2968 acc::ReductionAccumulateArrayOp arrayAccum =
2969 perThreadArrayReductionAccum(privateLocal.getResult());
2970 for (mlir::acc::GPUParallelDimAttr parDim : parDimsPair.first) {
2971 if ((parDim.isThreadX() ||
2972 (arrayAccum && storageHasThreadX(privateLocal, computeRegion))) &&
2973 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
2974 Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
2975 recordThreadPrivate(privateLocal);
2977 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
2978 arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
2981 initPerThreadArrayAccum(rewriter, loc, alloca, baseTy, *kind);
2984 privateLocal.getType());
2985 mapping.
map(privateLocal.getResult(), mem);
2989 if (parDimsPair.first.empty()) {
2993 mlir::acc::GPUParallelDimAttr::blockXDim(privateLocal.getContext()));
2995 for (mlir::acc::GPUParallelDimAttr parDim : parDimsPair.first) {
2996 gpu::Processor gpuProc = parDim.getProcessor();
2997 Value gpuSize = getGPUSizeFor(gpuProc);
2998 viewDynSizes.push_back(gpuSize);
2999 viewShape.push_back(ShapedType::kDynamic);
3000 subviewOffset.push_back(getGPUThreadIdFor(gpuProc));
3004 SmallVector<Value> innerDynSizes =
3005 resolvePrivateLocalDynamicExtents(privateLocal);
3007 unsigned dynIdx = 0;
3008 for (
auto innerDim : baseTy.getShape()) {
3010 viewShape.push_back(innerDim);
3011 subviewShape.push_back(innerDim);
3012 if (innerDim == ShapedType::kDynamic) {
3013 assert(dynIdx < innerDynSizes.size() &&
3014 "not enough dynamic sizes for inner dimensions");
3015 viewDynSizes.push_back(innerDynSizes[dynIdx]);
3016 subviewSizes.push_back(innerDynSizes[dynIdx]);
3019 subviewSizes.push_back(rewriter.
getIndexAttr(innerDim));
3025 for (
auto innerDimIt = baseTy.getShape().rbegin();
3026 innerDimIt != baseTy.getShape().rend(); ++innerDimIt) {
3027 int64_t innerDim = *innerDimIt;
3028 subviewStrides.insert(subviewStrides.begin(), stride);
3029 if (innerDim == ShapedType::kDynamic)
3030 stride = ShapedType::kDynamic;
3031 if (stride != ShapedType::kDynamic)
3038 MemRefType viewType = MemRefType::get(viewShape, baseTy.getElementType());
3039 auto view = memref::ViewOp::create(rewriter, loc, viewType, memBuffer,
3040 c0.getResult(), viewDynSizes);
3043 StridedLayoutAttr stridedLayout = StridedLayoutAttr::get(
3044 computeRegion->getContext(), ShapedType::kDynamic, subviewStrides);
3045 MemRefType subviewType =
3046 MemRefType::get(subviewShape, baseTy.getElementType(), stridedLayout);
3047 SmallVector<OpFoldResult> ones(viewType.getRank(), rewriter.
getIndexAttr(1));
3048 Value subview = memref::SubViewOp::create(rewriter, loc, subviewType, view,
3049 subviewOffset, subviewSizes, ones);
3054 memref::ExtractStridedMetadataOp::create(rewriter, loc, subview);
3056 rewriter, loc, getElementSizeInBytes(loc, baseTy.getElementType()));
3058 arith::MulIOp::create(rewriter, loc, metadata.getOffset(), elementBytes);
3059 Value privateView = memref::ViewOp::create(rewriter, loc, baseTy, memBuffer,
3060 byteOffset, innerDynSizes);
3062 privateLocal.getType());
3063 mapping.
map(privateLocal.getResult(),
result);
3067template <
typename LoopOp>
3068void ACCCGToGPULowering::processSeqLoop(LoopOp loopOp) {
3072 LLVM_DEBUG(llvm::dbgs() <<
"processing seq loop: ";
3073 loopOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3074 llvm::SmallPtrSet<Operation *, 4> preProcessedPrivateLocals;
3075 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
3076 for (
auto &bodyOp : loopOp.getBody()->getOperations()) {
3077 if (acc::PrivateLocalOp privateLocal =
3078 dyn_cast<acc::PrivateLocalOp>(&bodyOp)) {
3079 acc::PrivateType privTy =
3080 cast<acc::PrivateType>(privateLocal.getPrivatized().getType());
3082 if (
auto copies = isEligibleForSharedMemory(privateLocal, baseTy)) {
3083 processPrivateLocal(privateLocal,
copies);
3084 preProcessedPrivateLocals.insert(privateLocal.getOperation());
3092 &newLoop.getRegion(), newLoop.getRegion().begin(),
3093 loopOp.getBody()->getArgumentTypes(),
3094 SmallVector<Location>(loopOp.getBody()->getArgumentTypes().size(),
3100 assert(blockArgs.size() &&
"expected block arguments for loop");
3101 mapping.
map(blockArgs, newLoop.getBody()->getArguments());
3103 for (
auto &bodyOp : loopOp.getBody()->getOperations()) {
3104 if (preProcessedPrivateLocals.contains(&bodyOp))
3109 mapping.
map(loopOp.getResults(), newLoop.getResults());
3114 if (hasTrailingSideEffectSiblings(loopOp.getOperation()))
3115 deferredBarrierSeqLoops.push_back(loopOp.getOperation());
3117 createBarrierAfterSeqLoop(loopOp.getOperation());
3120void ACCCGToGPULowering::flushDeferredBarriersBefore(Operation *beforeOp) {
3122 SmallVector<Operation *, 4> toFlush;
3123 for (Operation *loopOp : deferredBarrierSeqLoops)
3124 if (loopOp->getBlock() == block && loopOp->isBeforeInBlock(beforeOp))
3125 toFlush.push_back(loopOp);
3126 if (toFlush.empty())
3130 for (Operation *loopOp : toFlush)
3131 createBarrierAfterSeqLoop(loopOp);
3132 deferredBarrierSeqLoops.erase(
3133 std::remove_if(deferredBarrierSeqLoops.begin(),
3134 deferredBarrierSeqLoops.end(),
3135 [&](Operation *loopOp) {
3136 return loopOp->getBlock() == block &&
3137 loopOp->isBeforeInBlock(beforeOp);
3139 deferredBarrierSeqLoops.end());
3144void ACCCGToGPULowering::processParallelOp(scf::ParallelOp parallelOp) {
3145 LLVM_DEBUG(llvm::dbgs() <<
"processing par loop: ";
3146 parallelOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3148 "requires parallel dimensions attribute");
3149 mlir::acc::GPUParallelDimsAttr pDimsAttr =
3153 assert(pDimsAttr.getArray().size() == 1 &&
3154 "expected a single par dim in acc-cg-to-gpu");
3155 assert(parallelOp.getInductionVars().size() == 1 &&
3156 "expected a single induction variable in acc-cg-to-gpu");
3158 mlir::acc::GPUParallelDimAttr parDim = pDimsAttr.getArray().front();
3160 bool savedGridStrideFlag = insideAccumulateGridStride;
3161 Value savedReductionBuf = reductionSharedBuf;
3162 if (parDim.isThreadX()) {
3164 parallelOp.getBody()->walk([&](acc::ReductionAccumulateOp accOp) {
3165 bool hasBlockDim =
false;
3166 bool hasThreadDim =
false;
3167 for (
auto d : accOp.getParDims().getArray()) {
3170 if (d.isThreadX() || d.isThreadY())
3171 hasThreadDim =
true;
3173 if (hasThreadDim && !hasBlockDim) {
3180 insideAccumulateGridStride =
true;
3184 auto processLoopBody = [&]() {
3186 for (
auto &bodyOp : parallelOp.getBody()->getOperations()) {
3187 if (bodyOp.hasTrait<OpTrait::IsTerminator>()) {
3189 flushDeferredBarriersBefore(&bodyOp);
3197 if (parDim.isSeq()) {
3198 LLVM_DEBUG(llvm::dbgs() <<
"loop: parDim: " << parDim <<
" as gpu seq\n");
3204 bool needsAtomicReduction =
false;
3205 bool hasAccumulateSibling =
false;
3206 if (scf::ParallelOp parentPar =
3207 parallelOp->getParentOfType<scf::ParallelOp>()) {
3208 if (mlir::acc::GPUParallelDimsAttr parentDims =
3210 parentDims && llvm::any_of(parentDims.getArray(),
3211 [](
auto d) { return d.isThreadX(); })) {
3212 for (
auto &op : parentPar.getBody()->getOperations()) {
3213 if (acc::ReductionAccumulateOp acc =
3214 dyn_cast<acc::ReductionAccumulateOp>(op)) {
3215 bool hasBlockDim =
false;
3216 bool hasThreadDim =
false;
3217 for (
auto d : acc.getParDims().getArray()) {
3220 if (d.isThreadX() || d.isThreadY())
3221 hasThreadDim =
true;
3223 if (hasThreadDim && !hasBlockDim)
3224 hasAccumulateSibling =
true;
3229 if (insideAccumulateGridStride || hasAccumulateSibling) {
3230 for (
auto launchArg : computeRegion.getLaunchArgs()) {
3231 if (acc::ParWidthOp pw = launchArg.getDefiningOp<acc::ParWidthOp>()) {
3232 if (pw.getParDim().isThreadX()) {
3234 needsAtomicReduction = (*cval >=
options.subgroupSize);
3236 needsAtomicReduction =
true;
3242 if (needsAtomicReduction && !reductionSharedBuf) {
3244 parallelOp.getBody()->walk([&](acc::ReductionAccumulateOp accOp) {
3245 Type t = accOp.getValue().getType();
3246 if (isa<FloatType, IntegerType>(t))
3251 needsAtomicReduction =
false;
3253 if (needsAtomicReduction && !reductionSharedBuf) {
3254 Location seqLoc = parallelOp->getLoc();
3255 gpu::AddressSpaceAttr workgroupAS = gpu::AddressSpaceAttr::get(
3256 computeRegion->getContext(),
3257 gpu::GPUDialect::getWorkgroupAddressSpace());
3259 parallelOp.getBody()->
walk([&](acc::ReductionAccumulateOp accOp) {
3260 Type t = accOp.getValue().getType();
3261 if (isa<FloatType, IntegerType>(t))
3265 assert(elemTy &&
"expected scalar reduction element type");
3267 MemRefType bufTy = MemRefType::get({
options.subgroupSize}, elemTy,
3268 AffineMap{}, workgroupAS);
3269 reductionSharedBuf = acc::GPUSharedMemoryOp::create(
3273 Value tidY = getThreadId(seqLoc, gpu::Dimension::y);
3275 if (isa<FloatType>(elemTy)) {
3276 identity = arith::ConstantOp::create(
3277 rewriter, seqLoc, elemTy, rewriter.
getFloatAttr(elemTy, 0.0));
3281 memref::StoreOp::create(rewriter, seqLoc, identity, reductionSharedBuf,
3283 createPerRowBarrier(seqLoc);
3285 processSeqLoop(parallelOp);
3286 loopReductions.push_back(parallelOp);
3288 LLVM_DEBUG(llvm::dbgs()
3289 <<
"processing loop: parDim: " << parDim <<
" as gpu par\n");
3293 Value gpuThreadId = getGPUThreadIdFor(parDim.getProcessor());
3294 mapping.
map(parallelOp.getInductionVars()[0], gpuThreadId);
3301 llvm::for_each(parallelOp.getResults(), [&](Value v) {
3302 Type valTy = v.getType();
3303 TypedAttr zeroAttr = rewriter.getZeroAttr(valTy);
3304 auto zero = arith::ConstantOp::create(rewriter, parallelOp->getLoc(),
3306 mapping.map(v, zero);
3308 loopReductions.push_back(parallelOp);
3310 insideAccumulateGridStride = savedGridStrideFlag;
3311 if (!insideAccumulateGridStride && !savedReductionBuf)
3312 reductionSharedBuf = Value();
3316static gpu::AllReduceOperation
3317getAllReduceOperation(arith::AtomicRMWKind kind) {
3319 case arith::AtomicRMWKind::addf:
3320 case arith::AtomicRMWKind::addi:
3321 return gpu::AllReduceOperation::ADD;
3322 case arith::AtomicRMWKind::mulf:
3323 case arith::AtomicRMWKind::muli:
3324 return gpu::AllReduceOperation::MUL;
3325 case arith::AtomicRMWKind::minu:
3326 return gpu::AllReduceOperation::MINUI;
3327 case arith::AtomicRMWKind::mins:
3328 return gpu::AllReduceOperation::MINSI;
3329 case arith::AtomicRMWKind::minnumf:
3330 return gpu::AllReduceOperation::MINNUMF;
3331 case arith::AtomicRMWKind::maxu:
3332 return gpu::AllReduceOperation::MAXUI;
3333 case arith::AtomicRMWKind::maxs:
3334 return gpu::AllReduceOperation::MAXSI;
3335 case arith::AtomicRMWKind::maxnumf:
3336 return gpu::AllReduceOperation::MAXNUMF;
3337 case arith::AtomicRMWKind::ori:
3338 return gpu::AllReduceOperation::OR;
3339 case arith::AtomicRMWKind::andi:
3340 return gpu::AllReduceOperation::AND;
3341 case arith::AtomicRMWKind::xori:
3342 return gpu::AllReduceOperation::XOR;
3343 case arith::AtomicRMWKind::minimumf:
3344 return gpu::AllReduceOperation::MINIMUMF;
3345 case arith::AtomicRMWKind::maximumf:
3346 return gpu::AllReduceOperation::MAXIMUMF;
3347 case arith::AtomicRMWKind::assign:
3350 llvm_unreachable(
"unsupported atomic kind");
3353void ACCCGToGPULowering::constructAtomicAccumulation(
3355 arith::AtomicRMWKind kind) {
3357 "cannot lower atomic accumulation on an stack variable");
3369 MemRefType memrefTy = cast<MemRefType>(memref.
getType());
3370 unsigned rank = memrefTy.getRank();
3371 assert(
indices.size() == rank &&
"expected one index per memref dimension");
3372 SmallVector<OpFoldResult> offsets(
indices.begin(),
indices.end());
3373 SmallVector<OpFoldResult> sizes(rank, rewriter.
getIndexAttr(1));
3374 SmallVector<OpFoldResult> strides(rank, rewriter.
getIndexAttr(1));
3375 target = memref::SubViewOp::create(rewriter, loc, memref, offsets, sizes,
3379 auto atomicUpdateOp =
3380 acc::AtomicUpdateOp::create(rewriter, loc,
target, Value());
3381 Region ®ion = atomicUpdateOp->getRegion(0);
3385 Value reductionExpr =
3387 acc::YieldOp::create(rewriter, loc, reductionExpr);
3391void ACCCGToGPULowering::createGPUAllReduceOp(
3392 Location loc, Value input, Value memref, arith::AtomicRMWKind kind,
3394 bool isPerThreadPrivateTarget) {
3395 gpu::AllReduceOperationAttr attr = gpu::AllReduceOperationAttr::get(
3396 computeRegion->getContext(), getAllReduceOperation(kind));
3397 auto allReduceOp = gpu::AllReduceOp::create(rewriter, loc, input, attr,
true);
3405 SmallVector<mlir::acc::GPUParallelDimAttr> inactiveParDims;
3406 MLIRContext *ctx = computeRegion->getContext();
3407 bool hasThreadX =
false;
3408 for (
auto parDim : parDimsAttr.getArray()) {
3409 if (parDim.isAnyBlock())
3411 if (parDim.isThreadX())
3413 if (computeRegion.getLaunchArg(parDim) ||
3415 inactiveParDims.push_back(parDim);
3421 if (!hasThreadX && !isSingleThreadWorkerLaunch())
3422 inactiveParDims.push_back(mlir::acc::GPUParallelDimAttr::threadXDim(ctx));
3423 Value predicate = emitPredicate(loc, inactiveParDims);
3430 bool isPerThreadPrivate = isPerThreadPrivateTarget ||
3431 isa_and_nonnull<memref::AllocaOp>(
3432 unwrapMemRefConversion(memref).getDefiningOp());
3435 if (predicate && !isPerThreadPrivate) {
3437 scf::IfOp::create(rewriter, loc, predicate,
false);
3438 Region &thenRegion = ifOp.getThenRegion();
3442 memref::StoreOp::create(rewriter, loc, allReduceOp, memref,
indices);
3443 if (predicate && !isPerThreadPrivate)
3448 reductionAccumValue[memref] = allReduceOp;
3451void ACCCGToGPULowering::postprocessAccumulateOp(
3452 acc::ReductionAccumulateOp op) {
3453 Location loc = op->getLoc();
3461 bool hasThreadDim =
false;
3462 SmallVector<mlir::acc::GPUParallelDimAttr> threadParDims;
3463 for (
auto parDim : op.getParDims().getArray()) {
3464 if (!parDim.isAnyBlock()) {
3465 hasThreadDim =
true;
3466 threadParDims.push_back(parDim);
3470 std::optional<arith::AtomicRMWKind> kind;
3472 FailureOr<arith::AtomicRMWKind> kindOr = getReductionKind(
3473 op.getReductionOperator(), op.getValue().getType(), loc);
3479 if (hasThreadDim && reductionSharedBuf &&
3480 op.getValue().getType() ==
3481 cast<MemRefType>(reductionSharedBuf.getType()).getElementType()) {
3482 Value val = op.getValue();
3483 Value mem = op.getMemref();
3484 Value tidY = getThreadId(loc, gpu::Dimension::y);
3485 memref::AtomicRMWOp::create(rewriter, loc, *kind, val, reductionSharedBuf,
3487 createPerRowBarrier(loc);
3489 memref::LoadOp::create(rewriter, loc, reductionSharedBuf, tidY);
3490 memref::StoreOp::create(rewriter, loc,
result, mem);
3491 reductionAccumValue[mem] =
result;
3492 }
else if (hasThreadDim) {
3493 createGPUAllReduceOp(loc, op.getValue(), op.getMemref(), *kind,
3501 bool isPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
3502 unwrapMemRefConversion(mem).getDefiningOp());
3503 if (!isPerThreadPrivate) {
3504 SmallVector<mlir::acc::GPUParallelDimAttr> predDims;
3505 for (
auto parDim : computeRegion.getLaunchParDims())
3506 if (!parDim.isAnyBlock())
3507 predDims.push_back(parDim);
3508 if (predDims.empty()) {
3509 predDims.push_back(mlir::acc::GPUParallelDimAttr::threadXDim(
3510 computeRegion->getContext()));
3512 Value predicate = emitPredicate(loc, predDims);
3514 scf::IfOp::create(rewriter, loc, predicate,
false);
3516 memref::StoreOp::create(rewriter, loc, val, mem);
3519 memref::StoreOp::create(rewriter, loc, val, mem);
3527void ACCCGToGPULowering::postprocessLoopReduction(scf::ParallelOp parLoop) {
3528 if (parLoop.getNumReductions() == 0)
3531 for (
unsigned i = 0; i < parLoop.getNumResults(); ++i) {
3532 for (Operation *user :
3534 if (acc::ReductionAccumulateOp accumulateOp =
3535 dyn_cast<acc::ReductionAccumulateOp>(user)) {
3536 postprocessAccumulateOp(accumulateOp);
3542void ACCCGToGPULowering::processExecuteRegion(scf::ExecuteRegionOp op) {
3543 LLVM_DEBUG(llvm::dbgs() <<
"processing execute region op: ";
3544 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3545 Location loc = op->getLoc();
3546 auto types = op.getResultTypes();
3547 Region &oldRegion = op.getRegion();
3549 auto executeRegionOp = scf::ExecuteRegionOp::create(rewriter, loc, types);
3550 Region ®ion = executeRegionOp.getRegion();
3554 llvm::DenseMap<Block *, Block *> blockMap;
3555 blockMap[&oldRegion.
front()] = ®ion.
front();
3559 for (
auto &oldBlock : llvm::drop_begin(oldRegion.
getBlocks())) {
3560 TypeRange argTypes = oldBlock.getArgumentTypes();
3561 size_t numArgs = argTypes.size();
3564 SmallVector<Location>(numArgs, loc));
3565 blockMap[&oldBlock] = newBlock;
3572 for (
auto [oldBlock, newBlock] :
3574 OpBuilder::InsertionGuard blockGuard(rewriter);
3576 for (
auto &bodyOp : oldBlock.getOperations()) {
3578 if (bodyOp.hasTrait<OpTrait::IsTerminator>())
3584 Operation *oldTerminator = oldBlock.getTerminator();
3586 Operation *newTerminator = rewriter.
clone(*oldTerminator, mapping);
3591 Block *newDest = blockMap.lookup(oldDest);
3592 assert(newDest &&
"Successor block must be in blockMap");
3596 mapping.
map(op->getResults(), executeRegionOp->getResults());
3600void ACCCGToGPULowering::processAccumulateOp(acc::ReductionAccumulateOp op) {
3601 LLVM_DEBUG(llvm::dbgs() <<
"processing accumulate op: " << *op <<
"\n");
3602 Value accumulateValue = op.getValue();
3603 if (reductionSharedBuf &&
3605 cast<MemRefType>(reductionSharedBuf.getType()).getElementType()) {
3606 Location loc = op->getLoc();
3607 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
3608 op.getReductionOperator(), accumulateValue.
getType(), loc);
3613 Value tidY = getThreadId(loc, gpu::Dimension::y);
3614 memref::AtomicRMWOp::create(rewriter, loc, *kind, mappedValue,
3616 createPerRowBarrier(loc);
3618 memref::LoadOp::create(rewriter, loc, reductionSharedBuf, tidY);
3619 memref::StoreOp::create(rewriter, loc,
result, memref);
3620 reductionAccumValue[memref] =
result;
3624 Operation *newOp = rewriter.
clone(*op, mapping);
3626 }
else if (isRedundantChainAccumulate(op)) {
3632 LLVM_DEBUG(llvm::dbgs() <<
" skipped: redundant chain accumulate\n");
3633 gpu::BarrierOp::create(rewriter, op->getLoc());
3637 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
3638 op.getReductionOperator(), accumulateValue.
getType(), op.getLoc());
3641 createGPUAllReduceOp(op->getLoc(), mappedValue, memref, *kind,
3650static bool isThreadVarying(Value v, ArrayRef<Value> threadIds,
3652 if (!v || !visited.insert(v).second)
3654 if (llvm::is_contained(threadIds, v))
3656 if (
auto arg = dyn_cast<BlockArgument>(v)) {
3657 Operation *owner = arg.getOwner()->getParentOp();
3658 unsigned dim = arg.getArgNumber();
3659 if (
auto loop = dyn_cast<scf::ParallelOp>(owner)) {
3660 if (dim >= loop.getLowerBound().size())
3662 return isThreadVarying(loop.getLowerBound()[dim], threadIds, visited) ||
3663 isThreadVarying(loop.getStep()[dim], threadIds, visited);
3665 if (
auto loop = dyn_cast<scf::ForOp>(owner))
3667 (isThreadVarying(loop.getLowerBound(), threadIds, visited) ||
3668 isThreadVarying(loop.getStep(), threadIds, visited));
3674 if (isa<gpu::ThreadIdOp, gpu::LaneIdOp>(def))
3676 return llvm::any_of(def->
getOperands(), [&](Value o) {
3677 return isThreadVarying(o, threadIds, visited);
3682static Value accumulatorRoot(Value v) {
3684 if (
auto cast = dyn_cast<memref::MemorySpaceCastOp>(op)) {
3685 v = cast.getSource();
3688 if (
auto viewLike = dyn_cast<ViewLikeOpInterface>(op)) {
3689 if (isa<MemRefType>(viewLike.getViewSource().getType())) {
3690 v = viewLike.getViewSource();
3701static Value matchAccumulatorUpdate(memref::StoreOp store, Value accum) {
3702 if (accumulatorRoot(store.getMemRef()) != accum)
3704 Operation *
combine = store.getValueToStore().getDefiningOp();
3705 if (!combine ||
combine->getNumOperands() != 2)
3707 for (
unsigned i = 0; i != 2; ++i) {
3708 auto load =
combine->getOperand(i).getDefiningOp<memref::LoadOp>();
3709 if (!
load || accumulatorRoot(
load.getMemRef()) != accum)
3711 if (!llvm::equal(
load.getIndices(), store.getIndices()))
3713 return combine->getOperand(1 - i);
3721static void atomicizeSharedAccumulatorUpdates(Value accum,
3722 arith::AtomicRMWKind kind,
3723 ArrayRef<Value> threadIds,
3724 RewriterBase &rewriter) {
3725 OpBuilder::InsertionGuard guard(rewriter);
3726 SmallVector<memref::StoreOp> stores;
3727 SmallVector<Value> worklist{accum};
3729 while (!worklist.empty()) {
3730 Value cur = worklist.pop_back_val();
3731 if (!seen.insert(cur).second)
3733 for (Operation *user : cur.
getUsers()) {
3734 if (
auto store = dyn_cast<memref::StoreOp>(user))
3735 stores.push_back(store);
3736 else if (isa<ViewLikeOpInterface, memref::MemorySpaceCastOp>(user))
3737 llvm::append_range(worklist, user->getResults());
3741 for (memref::StoreOp store : stores) {
3742 Value contribution = matchAccumulatorUpdate(store, accum);
3747 if (llvm::any_of(store.getIndices(), [&](Value idx) {
3748 DenseSet<Value> visited;
3749 return isThreadVarying(idx, threadIds, visited);
3752 Operation *
combine = store.getValueToStore().getDefiningOp();
3754 memref::AtomicRMWOp::create(rewriter, store.getLoc(), kind, contribution,
3755 store.getMemRef(), store.getIndices());
3757 if (combine &&
combine->use_empty())
3762void ACCCGToGPULowering::processAccumulateArrayOp(
3763 acc::ReductionAccumulateArrayOp op) {
3764 LLVM_DEBUG(llvm::dbgs() <<
"processing accumulate array op: " << *op <<
"\n");
3765 Location loc = op.getLoc();
3768 MemRefType memrefTy = dyn_cast<MemRefType>(memref.
getType());
3770 (void)accSupport.
emitNYI(loc,
"reduction: non-MemRefTy accumulate array");
3773 FailureOr<arith::AtomicRMWKind> kindOr = getReductionKind(
3774 op.getReductionOperator(), memrefTy.getElementType(), loc);
3777 arith::AtomicRMWKind kind = *kindOr;
3782 .getDefiningOp<acc::DataBoundsOp>();
3783 assert(boundsOp &&
"expected acc.bounds defining op for array accumulate");
3784 auto eraseDeadBounds = [&] {
3785 if (boundsOp->use_empty())
3789 bool hasThreadDim =
false;
3790 bool hasBlockDim =
false;
3791 for (
auto pd : op.getParDims().getArray()) {
3792 hasThreadDim |= pd.isAnyThread();
3793 hasBlockDim |= pd.isAnyBlock();
3798 if (hasBlockDim && !hasThreadDim) {
3806 bool regionLaunchesBlocks = llvm::any_of(
3807 computeRegion.getLaunchParDims(),
3808 [](mlir::acc::GPUParallelDimAttr d) { return d.isAnyBlock(); });
3809 if (!reductionHasBlockContext(op) && regionLaunchesBlocks) {
3811 loc,
"reduction: thread-only array reduction accumulate");
3823 auto storageIsThreadXPrivate = [&](Value v) ->
bool {
3824 acc::PrivateLocalOp privateLocal = getPrivateLocalForMemref(v);
3825 GPUParallelDimsAttr dims =
3826 privateLocal ? getPrivateParDims(privateLocal, computeRegion)
3827 : GPUParallelDimsAttr();
3829 if (Operation *root = unwrapMemRefConversion(v).getDefiningOp())
3833 llvm::any_of(dims.getArray(), [](
auto d) { return d.isThreadX(); });
3835 Operation *rootOp = unwrapMemRefConversion(memref).getDefiningOp();
3836 bool isSharedStorage = isa_and_nonnull<memref::AllocOp>(rootOp) ||
3837 isa_and_nonnull<acc::GPUSharedMemoryOp>(rootOp);
3838 if (
auto addrSpace = dyn_cast_if_present<gpu::AddressSpaceAttr>(
3839 memrefTy.getMemorySpace())) {
3841 addrSpace.getValue() == gpu::GPUDialect::getWorkgroupAddressSpace();
3843 bool isPerThreadPrivate =
3844 !isSharedStorage && storageIsThreadXPrivate(op.getMemref()) &&
3845 (memrefTy.hasStaticShape()
3846 ? canUseStackAlloca(memrefTy, loc,
options.maxThreadPrivateStack)
3847 : llvm::any_of(op.getParDims().getArray(),
3848 [](mlir::acc::GPUParallelDimAttr d) {
3849 return d.isThreadX();
3851 if (!isPerThreadPrivate) {
3856 SmallVector<Value> threadIds;
3857 if (Value xId = getGPUThreadIdFor(gpu::Processor::ThreadX))
3858 threadIds.push_back(xId);
3859 if (Value yId = getGPUThreadIdFor(gpu::Processor::ThreadY))
3860 threadIds.push_back(yId);
3861 if (Value zId = getGPUThreadIdFor(gpu::Processor::ThreadZ))
3862 threadIds.push_back(zId);
3863 atomicizeSharedAccumulatorUpdates(accumulatorRoot(memref), kind, threadIds,
3870 auto toIndex = [&](Value v) -> Value {
3873 return arith::IndexCastOp::create(rewriter, loc, rewriter.
getIndexType(),
3880 boundsOp.getLowerbound() ? toIndex(boundsOp.getLowerbound()) : zero;
3881 Value step = boundsOp.getStride() ? toIndex(boundsOp.getStride()) : one;
3886 if (boundsOp.getExtent()) {
3887 Value span = arith::MulIOp::create(rewriter, loc,
3888 toIndex(boundsOp.getExtent()), step);
3889 ub = arith::AddIOp::create(rewriter, loc, lb, span);
3891 assert(boundsOp.getUpperbound() &&
3892 "acc.bounds must specify an extent or upperbound");
3893 ub = arith::AddIOp::create(rewriter, loc, toIndex(boundsOp.getUpperbound()),
3898 auto forOp = scf::ForOp::create(rewriter, loc, lb, ub, step);
3900 OpBuilder::InsertionGuard guard(rewriter);
3902 Value iv = forOp.getInductionVar();
3903 SmallVector<Value>
indices{iv};
3904 if (memrefTy.getRank() > 1) {
3905 indices.resize(memrefTy.getRank());
3906 Value linearIndex = iv;
3907 for (int64_t dim = memrefTy.getRank() - 1; dim >= 0; --dim) {
3909 memrefTy.isDynamicDim(dim)
3910 ? memref::DimOp::create(rewriter, loc, memref, dim).getResult()
3912 memrefTy.getDimSize(dim))
3915 arith::RemUIOp::create(rewriter, loc, linearIndex, dimSize);
3918 arith::DivUIOp::create(rewriter, loc, linearIndex, dimSize);
3921 Value elem = memref::LoadOp::create(rewriter, loc, memref,
indices);
3922 createGPUAllReduceOp(loc, elem, memref, kind, op.getParDims(),
indices,
3929void ACCCGToGPULowering::processReductionOp(acc::ReductionInitOp op) {
3931 op.getRegion().walk<WalkOrder::PreOrder>([&](Operation *innerOp) {
3932 if (acc::YieldOp yieldOp = dyn_cast<acc::YieldOp>(innerOp)) {
3933 op.getResult().replaceAllUsesWith(mapping.
lookup(yieldOp.getOperand(0)));
3936 if (innerOp->getNumRegions() > 0) {
3940 rewriter.
clone(*innerOp, mapping);
3945void ACCCGToGPULowering::processReductionCombineOp(acc::ReductionCombineOp op) {
3946 LLVM_DEBUG(llvm::dbgs() <<
"processing reduction combine op: ";
3947 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3948 Location loc = op.getLoc();
3949 MemRefType memrefType = dyn_cast<MemRefType>(op.getSrcMemref().getType());
3950 assert(memrefType &&
"expected memref type for reduction combine op");
3951 assert(memrefType.getRank() == 0 &&
3952 "expected scalar memref type for reduction combine op");
3953 Type elTy = memrefType.getElementType();
3954 FailureOr<arith::AtomicRMWKind> kindOr =
3955 getReductionKind(op.getReductionOperator(), elTy, loc);
3958 arith::AtomicRMWKind kind = *kindOr;
3969 bool destIsPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
3970 unwrapMemRefConversion(destMemref).getDefiningOp());
3972 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
3974 for (
auto parDim : parDims) {
3975 if (parDim.isAnyBlock() && !destIsPerThreadPrivate) {
3982 memref::LoadOp::create(rewriter, loc, srcMemref,
ValueRange{});
3983 pendingCombineReloads.push_back({srcMemref, srcLoad});
3984 constructAtomicAccumulation(loc, destMemref, {}, srcLoad,
3992 auto srcLoad = memref::LoadOp::create(rewriter, loc, srcMemref,
ValueRange{});
3994 memref::LoadOp::create(rewriter, loc, destMemref,
ValueRange{});
3996 memref::StoreOp::create(rewriter, loc, combine, destMemref,
ValueRange{});
3999void ACCCGToGPULowering::processCombineRegionOp(
4000 acc::ReductionCombineRegionOp op) {
4001 LLVM_DEBUG(llvm::dbgs() <<
"processing combine region op: ";
4002 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4006 bool destIsPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
4009 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
4011 for (
auto parDim : parDims) {
4012 if (parDim.isAnyBlock() && !destIsPerThreadPrivate) {
4016 for (Operation *user : op.getSrcVar().getUsers()) {
4017 if (acc::ReductionAccumulateOp accumulateOp =
4018 dyn_cast<acc::ReductionAccumulateOp>(user)) {
4019 Location loc = accumulateOp.getLoc();
4020 FailureOr<arith::AtomicRMWKind> kind =
4021 getReductionKind(accumulateOp.getReductionOperator(),
4022 accumulateOp.getValue().getType(), loc);
4027 auto reductionLoad =
4028 memref::LoadOp::create(rewriter, loc, srcMemref,
ValueRange{});
4029 pendingCombineReloads.push_back({srcMemref, reductionLoad});
4030 constructAtomicAccumulation(loc,
4032 {}, reductionLoad, *kind);
4040 MemRefType memrefTy = cast<MemRefType>(privateMemref.
getType());
4041 if (isa<ComplexType>(memrefTy.getElementType())) {
4042 Location loc = op.getLoc();
4043 Value reductionResult =
4044 memref::LoadOp::create(rewriter, loc, privateMemref,
ValueRange{});
4045 arith::AtomicRMWKind kind = arith::AtomicRMWKind::addf;
4046 op.getRegion().walk([&](Operation *innerOp) {
4047 if (isa<complex::MulOp>(innerOp))
4048 kind = arith::AtomicRMWKind::mulf;
4050 constructAtomicAccumulation(loc,
4052 {}, reductionResult, kind);
4057 op.getRegion().walk<WalkOrder::PreOrder>([&](Operation *innerOp) {
4058 if (acc::YieldOp yieldOp = dyn_cast<acc::YieldOp>(innerOp))
4060 if (innerOp->getNumRegions() > 0) {
4064 rewriter.
clone(*innerOp, mapping);
4069void ACCCGToGPULowering::processGenericOp(Operation *op) {
4072 LLVM_DEBUG(llvm::dbgs() <<
"processing generic op, cloning: ";
4073 op->
print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4074 Operation *newOp = rewriter.
clone(*op, mapping);
4079void ACCCGToGPULowering::processGenericOpWithRegions(Operation *op) {
4081 LLVM_DEBUG(llvm::dbgs() <<
"processing generic op with regions: ";
4082 op->
print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4088 for (
auto [oldRegion, newRegion] :
4089 llvm::zip(op->
getRegions(), newOp->getRegions())) {
4091 for (
auto &oldBlock : oldRegion.
getBlocks()) {
4092 TypeRange argTypes = oldBlock.getArgumentTypes();
4093 size_t numArgs = argTypes.size();
4096 rewriter.
createBlock(&newRegion, newRegion.end(), argTypes,
4097 SmallVector<Location>(numArgs, op->
getLoc()));
4103 for (
auto &innerOp : oldBlock.getOperations()) {
4104 OpBuilder::InsertionGuard guard(rewriter);
4106 processOp(&innerOp);
4117void ACCCGToGPULowering::processOp(Operation *op) {
4118 if (isDeferredBarrierFlushPoint(op))
4119 flushDeferredBarriersBefore(op);
4123 scf::ParallelOp parallelOp = cast<scf::ParallelOp>(op);
4124 processParallelOp(parallelOp);
4125 }
else if (scf::ForOp seqLoop = dyn_cast<scf::ForOp>(op)) {
4126 processSeqLoop(seqLoop);
4127 }
else if (acc::PrivatizeOp privatize = dyn_cast<acc::PrivatizeOp>(op)) {
4128 processPrivatize(privatize);
4129 }
else if (acc::PrivateLocalOp privateLocal =
4130 dyn_cast<acc::PrivateLocalOp>(op)) {
4131 processPrivateLocal(privateLocal);
4132 }
else if (acc::PredicateRegionOp predicateRegionOp =
4133 dyn_cast<acc::PredicateRegionOp>(op)) {
4134 processPredicateRegion(predicateRegionOp);
4135 }
else if (acc::ReductionAccumulateOp accumulateOp =
4136 dyn_cast<acc::ReductionAccumulateOp>(op)) {
4137 processAccumulateOp(accumulateOp);
4138 }
else if (
auto accumulateArrayOp =
4139 dyn_cast<acc::ReductionAccumulateArrayOp>(op)) {
4140 processAccumulateArrayOp(accumulateArrayOp);
4141 }
else if (acc::ReductionInitOp reductionInitOp =
4142 dyn_cast<acc::ReductionInitOp>(op)) {
4143 processReductionOp(reductionInitOp);
4144 }
else if (acc::ReductionCombineOp reductionCombineOp =
4145 dyn_cast<acc::ReductionCombineOp>(op)) {
4146 processReductionCombineOp(reductionCombineOp);
4147 }
else if (
auto combineRegionOp =
4148 dyn_cast<acc::ReductionCombineRegionOp>(op)) {
4149 processCombineRegionOp(combineRegionOp);
4150 }
else if (acc::ReductionOp accReductionOp = dyn_cast<acc::ReductionOp>(op)) {
4151 mapping.
map(accReductionOp->getResult(0), accReductionOp.getVarPtr());
4154 LLVM_DEBUG(llvm::dbgs() <<
"skipping mapped op: " << *op <<
"\n");
4155 }
else if (isa<acc::YieldOp>(op)) {
4156 for (
auto [operand,
result] :
4160 }
else if (isa<scf::ExecuteRegionOp>(op)) {
4161 processExecuteRegion(cast<scf::ExecuteRegionOp>(op));
4163 isa<acc::OpenACCDialect>(op->
getDialect())) {
4164 processGenericOp(op);
4166 processGenericOpWithRegions(op);
4171class RemoveParWidth :
public OpRewritePattern<acc::ParWidthOp> {
4172 using OpRewritePattern<acc::ParWidthOp>::OpRewritePattern;
4173 LogicalResult matchAndRewrite(acc::ParWidthOp op,
4174 PatternRewriter &rewriter)
const override {
4175 if (Value launchArg = op.getLaunchArg()) {
4186class ACCComputeRegionToGPUPattern
4187 :
public OpRewritePattern<acc::ComputeRegionOp> {
4189 ACCComputeRegionToGPUPattern(MLIRContext *context,
4190 acc::OpenACCSupport &accSupport,
4191 const ACCCGToGPUOptions &
options)
4192 : OpRewritePattern<acc::ComputeRegionOp>(context), accSupport(accSupport),
4195 LogicalResult matchAndRewrite(acc::ComputeRegionOp op,
4196 PatternRewriter &rewriter)
const override {
4197 ACCCGToGPULowering kernelOpRewriter(op, rewriter, accSupport,
options);
4198 return kernelOpRewriter.rewrite();
4202 acc::OpenACCSupport &accSupport;
4203 const ACCCGToGPUOptions &
options;
4206class ACCCGToGPU :
public acc::impl::ACCCGToGPUBase<ACCCGToGPU> {
4208 using acc::impl::ACCCGToGPUBase<ACCCGToGPU>::ACCCGToGPUBase;
4210 void runOnOperation()
override {
4211 FunctionOpInterface funcOp = getOperation();
4212 MLIRContext *context = funcOp->getContext();
4214 assert(deviceType != mlir::acc::DeviceType::Host &&
4215 deviceType != mlir::acc::DeviceType::Multicore &&
4216 "ACCCGToGPU only supports GPU device types");
4218 options.deviceType = deviceType;
4219 options.maxWorkgroupSharedMemory = maxWorkgroupSharedMemory;
4220 options.maxThreadPrivateStack = maxThreadPrivateStack;
4221 options.subgroupSize = subgroupSize;
4224 std::optional<std::reference_wrapper<acc::OpenACCSupport>> cachedAnalysis =
4225 getCachedParentAnalysis<acc::OpenACCSupport>(funcOp->getParentOp());
4226 acc::OpenACCSupport &accSupport = cachedAnalysis
4227 ? cachedAnalysis->get()
4228 : getAnalysis<acc::OpenACCSupport>();
4230 RewritePatternSet patterns(context);
4231 patterns.insert<ACCComputeRegionToGPUPattern>(context, accSupport,
options);
4232 patterns.insert<RemoveParWidth>(context);
4234 target.markUnknownOpDynamicallyLegal([](Operation *) {
return true; });
4235 target.addIllegalOp<acc::ComputeRegionOp, acc::ParWidthOp>();
4236 if (
failed(applyPartialConversion(getOperation(),
target,
4237 std::move(patterns)))) {
4238 signalPassFailure();
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()
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.
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...
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)
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.
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.
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.
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.
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
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...