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";
173static GPUParallelDimAttr
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();
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))
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(); });
402static bool isInsideConditional(
Block *block) {
406 parent && !isa<gpu::LaunchOp, FunctionOpInterface>(parent);
408 if (isa<RegionBranchOpInterface>(parent) &&
409 !isa<LoopLikeOpInterface, scf::ExecuteRegionOp>(parent))
416 gpu::BarrierOp::create(builder, loc);
421 gpu::BarrierOp::create(builder, loc,
ArrayAttr{},
423 gpu::BarrierScope::Subgroup);
431 assert(rowWidth > 0 && subgroupSize % rowWidth == 0 &&
432 "row must tile the subgroup");
434 int64_t rowsPerSubgroup = subgroupSize / rowWidth;
440 Value threadY32 = arith::IndexCastOp::create(builder, loc, i32Ty, threadY);
443 Value rowIndex = arith::AndIOp::create(builder, loc, threadY32, rowMask);
445 Value laneBase = arith::MulIOp::create(builder, loc, rowIndex, width);
449 Value mask = arith::ShLIOp::create(builder, loc, laneBits, laneBase);
450 NVVM::SyncWarpOp::create(builder, loc, mask);
454class ACCCGToGPULowering {
456 explicit ACCCGToGPULowering(acc::ComputeRegionOp computeRegion,
457 RewriterBase &rewriter,
458 acc::OpenACCSupport &accSupport,
459 const ACCCGToGPUOptions &options)
460 : rewriter(rewriter), computeRegion(computeRegion),
461 accSupport(accSupport), options(options),
463 options.maxWorkgroupSharedMemory,
469 gpu::LaunchOp getLaunch()
const {
return launch; }
471 bool hasFailed =
false;
472 bool insideAccumulateGridStride =
false;
473 Value reductionSharedBuf;
476 llvm::DenseMap<Value, Value> reductionAccumValue;
478 llvm::SmallVector<std::pair<Value, memref::LoadOp>> pendingCombineReloads;
482 void processParallelOp(scf::ParallelOp parallelOp);
484 template <
typename LoopOp>
485 void processSeqLoop(LoopOp loopOp);
487 void processPredicateRegion(acc::PredicateRegionOp interOp);
490 processPrivateLocal(acc::PrivateLocalOp privateLocal,
491 std::optional<int64_t> sharedMemCopies = std::nullopt);
493 Value processPrivatize(acc::PrivatizeOp privatize);
495 Value allocatePrivatizeHeapStorage(acc::PrivatizeOp privatize,
499 void processExecuteRegion(scf::ExecuteRegionOp op);
501 void processAccumulateOp(acc::ReductionAccumulateOp op);
503 void processAccumulateArrayOp(acc::ReductionAccumulateArrayOp op);
505 void processReductionOp(acc::ReductionInitOp op);
507 void processReductionCombineOp(acc::ReductionCombineOp op);
509 void processCombineRegionOp(acc::ReductionCombineRegionOp op);
511 void processGenericOp(Operation *op);
513 void processGenericOpWithRegions(Operation *op);
515 void processOp(Operation *op);
518 void constructAtomicAccumulation(Location loc, Value memref,
520 arith::AtomicRMWKind kind);
523 FailureOr<arith::AtomicRMWKind> getReductionKind(acc::ReductionOperator redOp,
524 Type type, Location loc);
528 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
529 SmallVector<mlir::acc::GPUParallelDimAttr>>
530 computeActiveAndInactiveParDims(Operation *op,
Block *block);
534 emitPredicate(Location loc,
535 SmallVector<mlir::acc::GPUParallelDimAttr> &inactiveParDims);
539 std::optional<int64_t>
540 isEligibleForSharedMemory(acc::PrivateLocalOp privateLocal,
544 bool tryAllocateSharedMemory(int64_t bytes);
550 void recordThreadPrivate(acc::PrivateLocalOp privateLocal);
553 int64_t getElementSizeInBytes(Location loc, Type elementType)
const;
556 bool canUseStackAlloca(MemRefType baseTy, Location loc,
557 int64_t maxThreadPrivateStack)
const;
560 void createBarrier(Location loc, mlir::acc::GPUParallelDimsAttr parDimsAttr);
566 void createPerRowBarrier(Location loc);
570 void createBarrierAfterSeqLoop(Operation *loopOp);
573 void flushDeferredBarriersBefore(Operation *beforeOp);
576 bool mayWriteSharedMemory(Operation *loopOp);
579 PrivateMemScope getPrivateMemScope(acc::PrivatizeOp privatizeOp);
582 PrivateMemScope getPrivateScopeForMemref(Value memref);
585 acc::PrivatizeOp getPrivatizeForMemref(Value memref);
591 void collectSharedPrivateStores(
592 Operation *root, llvm::SmallPtrSetImpl<Operation *> &gangPrivatizes,
593 llvm::SmallPtrSetImpl<Operation *> &workerPrivatizes);
597 bool isMatchingPrivateMemref(
598 Value memref, PrivateMemScope storeScope,
599 const llvm::SmallPtrSetImpl<Operation *> &storePrivatizes);
603 bool hasParallelPrivateReuse(
604 LoopLikeOpInterface seqLoopOp, Operation *skipOp,
605 PrivateMemScope storeScope,
606 const llvm::SmallPtrSetImpl<Operation *> &storePrivatizes);
610 PrivateMemScope needsPreStoreReuseBarrier(acc::PredicateRegionOp interOp);
614 bool needsInLoopReuseBarrier(Operation *loopOp);
617 void createGPUAllReduceOp(Location loc, Value input, Value memref,
618 arith::AtomicRMWKind kind,
619 mlir::acc::GPUParallelDimsAttr parDimsAttr,
621 bool isPerThreadPrivateTarget =
false);
624 void postprocessAccumulateOp(acc::ReductionAccumulateOp op);
627 void postprocessLoopReduction(scf::ParallelOp parLoop);
633 llvm::DenseMap<gpu::Processor, Value> &ids,
634 llvm::DenseMap<gpu::Processor, Value> &dims) {
635 ids[gpu::Processor::BlockX] = gpu::BlockIdOp::create(
636 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
637 ids[gpu::Processor::BlockY] = gpu::BlockIdOp::create(
638 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
639 ids[gpu::Processor::BlockZ] = gpu::BlockIdOp::create(
640 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
641 ids[gpu::Processor::ThreadX] = gpu::ThreadIdOp::create(
642 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
643 ids[gpu::Processor::ThreadY] = gpu::ThreadIdOp::create(
644 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
645 ids[gpu::Processor::ThreadZ] = gpu::ThreadIdOp::create(
646 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
647 dims[gpu::Processor::BlockX] = gpu::GridDimOp::create(
648 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
649 dims[gpu::Processor::BlockY] = gpu::GridDimOp::create(
650 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
651 dims[gpu::Processor::BlockZ] = gpu::GridDimOp::create(
652 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
653 dims[gpu::Processor::ThreadX] = gpu::BlockDimOp::create(
654 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
655 dims[gpu::Processor::ThreadY] = gpu::BlockDimOp::create(
656 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
657 dims[gpu::Processor::ThreadZ] = gpu::BlockDimOp::create(
658 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
663 BlockArgument getOrAppendInsBlockArg(Value outside) {
664 if (std::optional<BlockArgument> blockArg =
665 computeRegion.getBlockArg(outside)) {
668 return computeRegion.appendInputArg(outside);
672 void preparePrivatizeExtentInsOperands() {
673 computeRegion.walk([&](acc::PrivateLocalOp privateLocal) {
674 acc::PrivatizeOp privatizeOp =
676 if (privatizeOp->getParentOfType<acc::ComputeRegionOp>() == computeRegion)
678 for (Value extent : privatizeOp.getDynamicSizes())
679 getOrAppendInsBlockArg(extent);
685 resolvePrivateLocalDynamicExtents(acc::PrivateLocalOp privateLocal) {
686 acc::PrivatizeOp privatizeOp =
getPrivatizeOp(privateLocal, computeRegion);
687 SmallVector<Value> extents;
688 for (Value extent : privatizeOp.getDynamicSizes()) {
689 if (std::optional<BlockArgument> blockArg =
690 computeRegion.getBlockArg(extent)) {
691 extents.push_back(mapping.lookupOrDefault(*blockArg));
694 extents.push_back(mapping.lookupOrDefault(extent));
699 RewriterBase &rewriter;
700 acc::ComputeRegionOp computeRegion;
702 acc::OpenACCSupport &accSupport;
703 const ACCCGToGPUOptions &options;
704 gpu::LaunchOp launch;
706 llvm::SmallVector<scf::ParallelOp> loopReductions;
707 llvm::DenseMap<gpu::Processor, Value> threadIdMap;
708 llvm::DenseMap<gpu::Processor, Value> dimensionMap;
710 bool hasThreadYReduction =
false;
712 bool hasThreadXReduction =
false;
714 bool hasThreadLevelArrayReduction =
false;
716 bool hasThreadLevelRoutineCall =
false;
718 bool hasThreadYBarrier =
false;
721 llvm::DenseMap<Type, Value> privatizeBroadcastCache;
723 int64_t staticBlockDimX = 1024;
724 int64_t staticBlockDimY = 1024;
725 int64_t staticBlockDimZ = 1024;
730 bool isSingleThreadWorkerLaunch()
const {
731 return staticBlockDimX == 1 && staticBlockDimZ == 1 &&
732 staticBlockDimY > 1 && staticBlockDimY <= options.subgroupSize;
739 bool isWorkerOnlyShuffleLaunch()
const {
740 return hasThreadYReduction && !hasThreadXReduction &&
741 !hasThreadLevelArrayReduction && staticBlockDimZ == 1 &&
742 staticBlockDimY > 1 && staticBlockDimY <= options.subgroupSize &&
743 options.subgroupSize % staticBlockDimX == 0;
746 acc::DefaultACCToGPUMappingPolicy defaultPolicy;
747 SharedMemoryBudget sharedMemBudget;
748 SmallVector<std::string> sharedMemPrivateVarNames;
749 SmallVector<std::string> threadPrivateVarNames;
750 llvm::SmallVector<Operation *, 4> deferredBarrierSeqLoops;
752 Value getThreadId(Location loc, gpu::Dimension dim) {
753 return gpu::ThreadIdOp::create(rewriter, loc, rewriter.getIndexType(), dim);
756 Value getBlockDim(Location loc, gpu::Dimension dim) {
757 return gpu::BlockDimOp::create(rewriter, loc, rewriter.getIndexType(), dim);
761 Value getGPUThreadIdFor(gpu::Processor proc) {
766 Value getGPUSizeFor(gpu::Processor proc) {
767 return getGPUSize(proc, getLaunch(), dimensionMap);
772 Type elementType)
const {
773 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
774 if (std::optional<acc::TypeSizeAndAlignment> sizeAndAlignment =
776 return sizeAndAlignment->first.getFixedValue();
779 llvm::raw_string_ostream os(msg);
780 os <<
"element size computation for unsupported type: " << elementType;
781 (void)accSupport.
emitNYI(loc, os.str());
785bool ACCCGToGPULowering::canUseStackAlloca(
786 MemRefType baseTy, Location loc, int64_t maxThreadPrivateStack)
const {
787 for (int64_t dim : baseTy.getShape())
788 if (dim == ShapedType::kDynamic)
790 int64_t elementSize = getElementSizeInBytes(loc, baseTy.getElementType());
791 int64_t numElements = 1;
792 for (int64_t dim : baseTy.getShape()) {
793 if (numElements > maxThreadPrivateStack / std::max<int64_t>(dim, 1))
797 return elementSize * numElements < maxThreadPrivateStack;
805static bool reductionHasBlockContext(acc::ReductionAccumulateArrayOp accArr) {
806 auto hasBlock = [](mlir::acc::GPUParallelDimsAttr parDims) {
807 return parDims && llvm::any_of(parDims.getArray(),
808 [](
auto pd) { return pd.isAnyBlock(); });
810 if (hasBlock(accArr.getParDimsAttr()))
812 for (scf::ParallelOp loop = accArr->getParentOfType<scf::ParallelOp>(); loop;
813 loop = loop->getParentOfType<scf::ParallelOp>()) {
823static acc::ReductionAccumulateArrayOp perThreadArrayReductionAccum(Value v) {
824 SmallVector<Value> worklist{v};
826 while (!worklist.empty()) {
827 Value cur = worklist.pop_back_val();
828 if (!seen.insert(cur).second)
830 for (Operation *user : cur.
getUsers()) {
831 if (acc::ReductionAccumulateArrayOp accArr =
832 dyn_cast<acc::ReductionAccumulateArrayOp>(user)) {
833 bool hasThread =
false;
834 for (
auto pd : accArr.getParDims().getArray())
835 hasThread |= pd.isAnyThread();
836 if (hasThread && reductionHasBlockContext(accArr))
840 SmallVector<Value> through;
841 if (getPassThroughResults(user, cur, through))
842 worklist.append(through.begin(), through.end());
843 else if (isa<ViewLikeOpInterface>(user))
844 worklist.append(user->result_begin(), user->result_end());
853static void initPerThreadArrayAccum(OpBuilder &
b, Location loc, Value alloca,
855 arith::AtomicRMWKind kind) {
856 assert(baseTy.getRank() > 0 && baseTy.hasStaticShape() &&
857 "per-thread array reduction accumulator must be static ranked");
863 auto buildLoopNest = [&](
auto &&self,
unsigned dim) ->
void {
864 if (dim == baseTy.getRank()) {
865 memref::StoreOp::create(
b, loc, ident, alloca,
indices);
870 auto forOp = scf::ForOp::create(
b, loc, lb, ub, step);
871 OpBuilder::InsertionGuard g(
b);
872 b.setInsertionPoint(forOp.getBody()->getTerminator());
873 indices.push_back(forOp.getInductionVar());
880std::optional<int64_t>
881ACCCGToGPULowering::isEligibleForSharedMemory(acc::PrivateLocalOp privateLocal,
886 if (perThreadArrayReductionAccum(privateLocal.getResult()) &&
887 storageHasThreadX(privateLocal, computeRegion))
889 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
891 privateLocal, computeRegion, module, defaultPolicy, &accSupport);
892 if (
failed(isCandidate)) {
896 if (!isCandidate.value())
898 std::optional<int64_t> upperBound =
900 module, defaultPolicy);
901 assert(upperBound &&
"candidate private_local must have an upper bound");
902 int64_t elementSize =
903 getElementSizeInBytes(privateLocal.getLoc(), baseTy.getElementType());
904 int64_t numElements = 1;
905 for (int64_t dim : baseTy.getShape())
907 return *upperBound / (elementSize * numElements);
910bool ACCCGToGPULowering::tryAllocateSharedMemory(int64_t bytes) {
914void ACCCGToGPULowering::recordThreadPrivate(acc::PrivateLocalOp privateLocal) {
915 std::string varName = accSupport.
getVariableName(privateLocal.getResult());
920 if (!llvm::is_contained(threadPrivateVarNames, varName))
921 threadPrivateVarNames.push_back(varName);
924FailureOr<arith::AtomicRMWKind>
925ACCCGToGPULowering::getReductionKind(acc::ReductionOperator redOp, Type type,
927 if (std::optional<arith::AtomicRMWKind> kind =
932 llvm::raw_string_ostream os(msg);
933 os <<
"reduction operator (" << redOp <<
") for type " << type;
934 (void)accSupport.
emitNYI(loc, os.str());
938LogicalResult ACCCGToGPULowering::rewrite() {
944 computeRegion->walk([&](acc::ReductionAccumulateOp op) {
945 for (
auto parDim : op.getParDimsAttr().getArray()) {
946 hasThreadXReduction |= parDim.isThreadX();
947 hasThreadYReduction |= parDim.isThreadY();
950 computeRegion->walk([&](acc::ReductionAccumulateArrayOp op) {
951 for (
auto parDim : op.getParDimsAttr().getArray()) {
952 if (!parDim.isAnyBlock())
953 hasThreadLevelArrayReduction =
true;
961 computeRegion->walk([&](CallOpInterface callOp) -> WalkResult {
962 if (mlir::acc::GPUParallelDimAttr parDim =
963 getAccRoutineCallParDim(callOp, defaultPolicy)) {
964 if (parDim.isThreadX() || parDim.isThreadY()) {
965 hasThreadLevelRoutineCall =
true;
972 Location loc = computeRegion->getLoc();
975 auto launchArgument = [&](gpu::Processor processor) -> Value {
976 mlir::acc::GPUParallelDimAttr parDim = mlir::acc::GPUParallelDimAttr::get(
977 computeRegion->getContext(), processor);
978 std::optional<Value> maybeLaunchArg =
979 computeRegion.getKnownLaunchArg(parDim);
980 LLVM_DEBUG(llvm::dbgs() <<
"ACCCGToGPU: launch-arg: "
981 <<
" parDim: " << parDim <<
" gpu: " << processor
983 << maybeLaunchArg.value_or(constantOne) <<
"\n");
987 maybeLaunchArg.value_or(constantOne));
989 LLVM_DEBUG(llvm::dbgs() <<
"ACCCGToGPU: creating gpu launch op: \n");
993 auto mapLaunchArguments = [&](gpu::Processor processor, Value launchArg) {
994 mlir::acc::GPUParallelDimAttr parDim = mlir::acc::GPUParallelDimAttr::get(
995 computeRegion->getContext(), processor);
996 std::optional<Value> kernelArg = computeRegion.getLaunchArg(parDim);
998 mapping.
map(computeRegion.gpuParWidth(processor), launchArg);
1001 llvm::StringRef blockDimXName =
"blockDim.x";
1002 llvm::StringRef blockDimYName =
"blockDim.y";
1003 std::string deviceLabel = getDeviceRemarkQualifier(
options.deviceType);
1005 if (!computeRegion->getParentOfType<gpu::GPUFuncOp>()) {
1006 Value blockDimX = launchArgument(gpu::Processor::ThreadX);
1009 staticBlockDimX = bdxVal.getSExtValue();
1010 Value blockDimY = launchArgument(gpu::Processor::ThreadY);
1013 staticBlockDimY = bdyVal.getSExtValue();
1014 Value blockDimZ = launchArgument(gpu::Processor::ThreadZ);
1017 staticBlockDimZ = bdzVal.getSExtValue();
1018 Value gridDimX = launchArgument(gpu::Processor::BlockX);
1019 Value gridDimY = launchArgument(gpu::Processor::BlockY);
1020 Value gridDimZ = launchArgument(gpu::Processor::BlockZ);
1026 auto getName = [&](Value val) -> std::string {
1028 return name.empty() ?
"(*)" : name;
1030 bool isEffectivelySerial =
1031 sameEffectiveValue(blockDimX, 1) &&
1032 sameEffectiveValue(blockDimY, 1) &&
1033 sameEffectiveValue(blockDimZ, 1) && sameEffectiveValue(gridDimX, 1) &&
1034 sameEffectiveValue(gridDimY, 1) && sameEffectiveValue(gridDimZ, 1);
1035 return (llvm::Twine(
"Generating ") +
1036 llvm::Twine(isEffectivelySerial ?
"serial " :
"") + deviceLabel +
1037 " code with gridDim=" + getName(gridDimX) +
"x" +
1038 getName(gridDimY) +
"x" + getName(gridDimZ) +
1039 " blockDim=" + getName(blockDimX) +
"x" + getName(blockDimY) +
1040 "x" + getName(blockDimZ))
1045 if (mlir::Value streamValue = computeRegion.getStream()) {
1046 LLVM_DEBUG(llvm::dbgs()
1047 <<
"\nDEBUG: Creating async gpu.launch with stream: "
1048 << streamValue <<
"\n");
1049 launch = gpu::LaunchOp::create(
1050 rewriter, loc, gridDimX, gridDimY, gridDimZ, blockDimX, blockDimY,
1056 launch.getAsyncDependenciesMutable().append(streamValue);
1058 LLVM_DEBUG(llvm::dbgs()
1059 <<
"\nDEBUG: No stream, creating sync gpu.launch\n");
1060 launch = gpu::LaunchOp::create(rewriter, loc, gridDimX, gridDimY,
1061 gridDimZ, blockDimX, blockDimY, blockDimZ);
1066 if (
auto kernelFuncName = computeRegion.getKernelFuncNameAttr())
1067 launch.setFunctionAttr(kernelFuncName);
1068 if (
auto kernelModuleName = computeRegion.getKernelModuleNameAttr())
1069 launch.setModuleAttr(kernelModuleName);
1072 gpu::TerminatorOp::create(rewriter, loc);
1074 mapLaunchArguments(gpu::Processor::BlockX,
1075 gpu::GridDimOp::create(rewriter, loc,
1077 gpu::Dimension::x));
1078 mapLaunchArguments(gpu::Processor::BlockY,
1079 gpu::GridDimOp::create(rewriter, loc,
1081 gpu::Dimension::y));
1082 mapLaunchArguments(gpu::Processor::BlockZ,
1083 gpu::GridDimOp::create(rewriter, loc,
1085 gpu::Dimension::z));
1086 mapLaunchArguments(gpu::Processor::ThreadX,
1087 gpu::BlockDimOp::create(rewriter, loc,
1089 gpu::Dimension::x));
1090 mapLaunchArguments(gpu::Processor::ThreadY,
1091 gpu::BlockDimOp::create(rewriter, loc,
1093 gpu::Dimension::y));
1094 mapLaunchArguments(gpu::Processor::ThreadZ,
1095 gpu::BlockDimOp::create(rewriter, loc,
1097 gpu::Dimension::z));
1102 OpBuilder::InsertionGuard guard(rewriter);
1105 mapLaunchArguments(gpu::Processor::BlockX,
1106 dimensionMap[gpu::Processor::BlockX]);
1107 mapLaunchArguments(gpu::Processor::BlockY,
1108 dimensionMap[gpu::Processor::BlockY]);
1109 mapLaunchArguments(gpu::Processor::BlockZ,
1110 dimensionMap[gpu::Processor::BlockZ]);
1111 mapLaunchArguments(gpu::Processor::ThreadX,
1112 dimensionMap[gpu::Processor::ThreadX]);
1113 mapLaunchArguments(gpu::Processor::ThreadY,
1114 dimensionMap[gpu::Processor::ThreadY]);
1115 mapLaunchArguments(gpu::Processor::ThreadZ,
1116 dimensionMap[gpu::Processor::ThreadZ]);
1121 preparePrivatizeExtentInsOperands();
1122 Block *body = computeRegion.getBody();
1123 unsigned numLaunchArgs = computeRegion.getLaunchArgs().size();
1124 ValueRange inputArgs = computeRegion.getInputArgs();
1128 assert(computeRegion.getRegion().hasOneBlock() &&
1129 "compute region only supports one block region for now");
1131 for (
auto &op : computeRegion.getRegion().getBlocks().front().getOperations())
1134 for (
auto &parLoop : loopReductions)
1135 postprocessLoopReduction(parLoop);
1139 if (!pendingCombineReloads.empty() && launch) {
1140 DominanceInfo domInfo(launch);
1141 for (
auto &[slot, loadOp] : pendingCombineReloads) {
1142 llvm::DenseMap<Value, Value>::iterator it =
1143 reductionAccumValue.find(slot);
1144 if (it == reductionAccumValue.end())
1146 if (!domInfo.dominates(it->second, loadOp.getOperation()))
1153 const int64_t subgroupSize =
options.subgroupSize;
1154 const int64_t subgroupAlignMask = subgroupSize - 1;
1162 bool needsThreadXAlign =
false;
1163 bool needsThreadYAlign =
false;
1164 bool alignThreadXReduction =
1170 auto classifyAllReduce = [&](gpu::AllReduceOp allReduce) {
1171 bool hasThreadX =
false;
1172 bool hasThreadY =
false;
1174 hasThreadX |= parDim.isThreadX();
1175 hasThreadY |= parDim.isThreadY();
1177 if (hasThreadX && alignThreadXReduction)
1178 needsThreadXAlign =
true;
1179 else if (hasThreadY)
1180 needsThreadYAlign =
true;
1183 launch.walk(classifyAllReduce);
1185 launch.walk([&](func::CallOp callOp) {
1186 if (gpu::GPUFuncOp callee =
1187 callOp->getParentOfType<ModuleOp>().lookupSymbol<gpu::GPUFuncOp>(
1188 callOp.getCallee()))
1189 callee.walk(classifyAllReduce);
1192 bool isShuffleEnabled = needsThreadXAlign || needsThreadYAlign;
1194 std::optional<int64_t> constBlockDimX =
1196 std::optional<int64_t> constBlockDimY =
1198 std::optional<int64_t> constBlockDimZ =
1206 bool skipAlign = constBlockDimX && constBlockDimY && constBlockDimZ &&
1207 *constBlockDimX > 1 && *constBlockDimX < subgroupSize &&
1208 *constBlockDimY == 1 && *constBlockDimZ == 1;
1213 if (isSingleThreadWorkerLaunch())
1215 if (needsThreadYAlign && !needsThreadXAlign && !hasThreadYBarrier &&
1216 isWorkerOnlyShuffleLaunch())
1219 if ((isShuffleEnabled || hasThreadYBarrier) && !skipAlign) {
1222 Value curBlockDimX = launch.getBlockSizeX();
1223 Value curBlockDimY = launch.getBlockSizeY();
1224 Value curBlockDimZ = launch.getBlockSizeZ();
1228 auto getName = [&](Value val) -> std::string {
1230 return name.empty() ?
"(*)" : name;
1232 std::string blockDimXValStr = getName(curBlockDimX);
1233 std::string blockDimYValStr = getName(curBlockDimY);
1234 llvm::StringRef kind =
1235 isShuffleEnabled ?
"Shuffle reduction" :
"ThreadY barrier";
1236 return (llvm::Twine(kind) +
1237 " is generated while adjusting the number of threads into "
1239 llvm::Twine(subgroupSize) +
".\n\t" + blockDimXName +
": `" +
1240 blockDimXValStr +
"` to `((" + blockDimXValStr +
" + " +
1241 llvm::Twine(subgroupAlignMask) +
") / " +
1242 llvm::Twine(subgroupSize) +
") * " + llvm::Twine(subgroupSize) +
1243 "`\n" +
"\t" + blockDimYName +
": `" + blockDimYValStr +
1244 "` to `max(1, (new-" + blockDimXName +
" * " + blockDimYValStr +
1245 ") / new-" + blockDimXName +
")`")
1254 Value newBlockDimX, newBlockDimY, newBlockDimZ;
1255 if (constBlockDimX && constBlockDimY && constBlockDimZ) {
1256 int64_t bdx = *constBlockDimX;
1257 int64_t bdy = *constBlockDimY;
1258 int64_t bdz = *constBlockDimZ;
1259 int64_t alignedBdx =
1260 ((bdx + subgroupAlignMask) / subgroupSize) * subgroupSize;
1261 int64_t numXYThreads = bdx * bdy;
1262 int64_t numThreads = numXYThreads * bdz;
1263 int64_t newBdy = std::max<int64_t>(1, numXYThreads / alignedBdx);
1265 std::max<int64_t>(1, numThreads / (alignedBdx * newBdy));
1272 Value numXYThreads =
1273 arith::MulIOp::create(rewriter, loc, curBlockDimX, curBlockDimY);
1275 arith::MulIOp::create(rewriter, loc, numXYThreads, curBlockDimZ);
1279 Value cstSubgroupSize =
1282 arith::AddIOp::create(rewriter, loc, curBlockDimX, cstMask);
1283 Value subgroupsRequired =
1284 arith::DivUIOp::create(rewriter, loc, padded, cstSubgroupSize);
1285 newBlockDimX = arith::MulIOp::create(rewriter, loc, subgroupsRequired,
1289 arith::DivUIOp::create(rewriter, loc, numXYThreads, newBlockDimX);
1291 newBlockDimY = arith::MaxUIOp::create(rewriter, loc, cst1, quotient);
1293 Value newNumXYThreads =
1294 arith::MulIOp::create(rewriter, loc, newBlockDimX, newBlockDimY);
1296 arith::DivUIOp::create(rewriter, loc, numThreads, newNumXYThreads);
1297 newBlockDimZ = arith::MaxUIOp::create(rewriter, loc, cst1, quotient);
1300 launch.getBlockSizeXMutable().assign(newBlockDimX);
1301 launch.getBlockSizeYMutable().assign(newBlockDimY);
1302 launch.getBlockSizeZMutable().assign(newBlockDimZ);
1309 if (!threadPrivateVarNames.empty()) {
1311 return (llvm::Twine(
"Local memory or registers used for ") +
1312 llvm::join(threadPrivateVarNames,
","))
1317 if (!sharedMemPrivateVarNames.empty()) {
1319 return (llvm::Twine(
"GPU shared memory used for ") +
1320 llvm::join(sharedMemPrivateVarNames,
","))
1325 rewriter.
eraseOp(computeRegion);
1341static bool isRedundantChainAccumulate(acc::ReductionAccumulateOp op) {
1342 Value memref = op.getMemref();
1343 memref::LoadOp loadOp = op.getValue().
getDefiningOp<memref::LoadOp>();
1344 if (!loadOp || loadOp.getMemRef() != memref)
1346 for (Operation *user : memref.
getUsers()) {
1347 acc::ReductionCombineOp combineOp = dyn_cast<acc::ReductionCombineOp>(user);
1348 if (!combineOp || combineOp.getDestMemref() != memref)
1350 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
1352 if (llvm::any_of(parDims, [](mlir::acc::GPUParallelDimAttr d) {
1353 return d.isAnyBlock();
1361static GPUParallelDimsAttr
1362getPrivateParDims(acc::PrivateLocalOp privateLocal,
1363 acc::ComputeRegionOp computeRegion) {
1366 if (acc::PrivatizeOp privatize =
getPrivatizeOp(privateLocal, computeRegion))
1367 return privatize.getParDimsAttr();
1372static bool isThreadYPrivate(acc::PrivateLocalOp privateLocal,
bool allowBlock,
1373 acc::ComputeRegionOp computeRegion) {
1376 GPUParallelDimsAttr parDims = getPrivateParDims(privateLocal, computeRegion);
1379 return llvm::any_of(parDims.getArray(),
1380 [](
auto dim) { return dim.isThreadY(); }) &&
1381 llvm::all_of(parDims.getArray(), [=](
auto dim) {
1382 return dim.isThreadY() || (allowBlock && dim.isAnyBlock());
1386struct ThreadYBroadeningInfo {
1387 bool hasActiveWorkerCombine =
false;
1388 bool hasExplicitInactiveCombine =
false;
1389 bool hasBroadeningConflict =
false;
1390 Operation *diagnosticOp =
nullptr;
1392 void merge(
const ThreadYBroadeningInfo &other) {
1393 hasActiveWorkerCombine |= other.hasActiveWorkerCombine;
1394 hasExplicitInactiveCombine |= other.hasExplicitInactiveCombine;
1395 hasBroadeningConflict |= other.hasBroadeningConflict;
1397 diagnosticOp = other.diagnosticOp;
1402static bool hasUnsafeEffectsWhenBroadening(Operation *op) {
1403 if (
auto effectOp = dyn_cast<MemoryEffectOpInterface>(op)) {
1404 SmallVector<MemoryEffects::EffectInstance> effects;
1405 effectOp.getEffects(effects);
1406 return llvm::any_of(effects, [](
const auto &effect) {
1407 return !isa<MemoryEffects::Read>(effect.getEffect());
1410 return !op->
hasTrait<OpTrait::HasRecursiveMemoryEffects>();
1415static bool isFedByInnerBlockCombine(acc::PrivateLocalOp accumulator,
1416 Operation *selfCombine) {
1419 for (Operation *user : accumulator.getResult().getUsers()) {
1420 if (user == selfCombine)
1422 auto combineOp = dyn_cast<acc::ReductionCombineOp>(user);
1424 unwrapMemRefConversion(combineOp.getDestMemref()).getDefiningOp() !=
1425 accumulator.getOperation())
1427 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
1429 if (llvm::any_of(parDims, [](mlir::acc::GPUParallelDimAttr d) {
1430 return d.isAnyBlock();
1438static void classifyThreadYCombine(ThreadYBroadeningInfo &info,
1439 Operation *combineOp, Value src, Value dest,
1440 ArrayRef<GPUParallelDimAttr> parDims,
1441 acc::ComputeRegionOp computeRegion) {
1442 bool hasThreadY = llvm::any_of(
1443 parDims, [](GPUParallelDimAttr parDim) {
return parDim.isThreadY(); });
1444 bool hasBlock = llvm::any_of(
1445 parDims, [](GPUParallelDimAttr parDim) {
return parDim.isAnyBlock(); });
1446 acc::PrivateLocalOp srcPrivate =
1447 unwrapMemRefConversion(src).getDefiningOp<acc::PrivateLocalOp>();
1448 acc::PrivateLocalOp destPrivate =
1449 unwrapMemRefConversion(dest).getDefiningOp<acc::PrivateLocalOp>();
1450 bool hasPrivateDest = isa<acc::ReductionCombineOp>(combineOp) && srcPrivate &&
1454 if (hasThreadY && hasBlock &&
1455 isThreadYPrivate(srcPrivate, hasPrivateDest, computeRegion)) {
1456 info.hasActiveWorkerCombine =
true;
1463 if (hasThreadY && hasBlock &&
1464 isThreadYPrivate(srcPrivate,
true, computeRegion) &&
1465 isFedByInnerBlockCombine(srcPrivate, combineOp)) {
1466 info.hasActiveWorkerCombine =
true;
1470 info.hasExplicitInactiveCombine =
true;
1471 if (!info.diagnosticOp)
1472 info.diagnosticOp = combineOp;
1478static ThreadYBroadeningInfo
1479analyzeThreadYBroadening(
Block &predicateBlock,
1480 acc::ComputeRegionOp computeRegion) {
1481 ThreadYBroadeningInfo info;
1482 for (Operation &nestedOp : predicateBlock) {
1483 if (acc::PredicateRegionOp nestedPredicate =
1484 dyn_cast<acc::PredicateRegionOp>(nestedOp)) {
1485 ThreadYBroadeningInfo nestedInfo = analyzeThreadYBroadening(
1486 nestedPredicate.getRegion().front(), computeRegion);
1487 info.hasActiveWorkerCombine |= nestedInfo.hasActiveWorkerCombine;
1490 if (acc::ReductionCombineOp combineOp =
1491 dyn_cast<acc::ReductionCombineOp>(nestedOp)) {
1492 classifyThreadYCombine(
1493 info, combineOp, combineOp.getSrcMemref(), combineOp.getDestMemref(),
1497 if (acc::ReductionCombineRegionOp combineRegionOp =
1498 dyn_cast<acc::ReductionCombineRegionOp>(nestedOp)) {
1499 classifyThreadYCombine(info, combineRegionOp, combineRegionOp.getSrcVar(),
1500 combineRegionOp.getDestVar(),
1505 if (nestedOp.getNumRegions() != 0) {
1506 if (hasUnsafeEffectsWhenBroadening(&nestedOp)) {
1507 info.hasBroadeningConflict =
true;
1508 if (!info.diagnosticOp)
1509 info.diagnosticOp = &nestedOp;
1511 for (Region ®ion : nestedOp.getRegions())
1512 for (
Block &nestedBlock : region)
1513 info.merge(analyzeThreadYBroadening(nestedBlock, computeRegion));
1516 if (hasUnsafeEffectsWhenBroadening(&nestedOp)) {
1517 info.hasBroadeningConflict =
true;
1518 if (!info.diagnosticOp)
1519 info.diagnosticOp = &nestedOp;
1525std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
1526 SmallVector<mlir::acc::GPUParallelDimAttr>>
1527ACCCGToGPULowering::computeActiveAndInactiveParDims(Operation *op,
1529 MLIRContext *ctx = computeRegion->getContext();
1531 mlir::acc::GPUParallelDimAttr routineParDim;
1533 FunctionOpInterface funcOp =
1534 computeRegion->getParentOfType<FunctionOpInterface>();
1535 routineParDim = getSpecializedRoutineDim(funcOp, defaultPolicy);
1542 if (isa<acc::PrivateLocalOp, acc::PrivatizeOp, acc::PredicateRegionOp>(op)) {
1543 if (mlir::acc::ActiveParDimsAttr precomputedActiveParDims =
1545 mlir::acc::GPUParallelDimAttr lowestParDim =
1546 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
1548 SmallVector<mlir::acc::GPUParallelDimAttr> launchParDims;
1549 if (routineParDim) {
1550 for (mlir::acc::GPUParallelDimAttr parDim = routineParDim;
1551 parDim.getOrder() >= lowestParDim.getOrder();
1552 parDim = parDim.getOneLower()) {
1556 launchParDims = computeRegion.getLaunchParDims();
1559 SmallVector<mlir::acc::GPUParallelDimAttr> activeParDims(
1560 precomputedActiveParDims.getArray());
1561 SmallVector<mlir::acc::GPUParallelDimAttr> inactiveParDims;
1562 for (mlir::acc::GPUParallelDimAttr launchParDim : launchParDims) {
1563 if (launchParDim.getOrder() < lowestParDim.getOrder())
1565 if (!llvm::is_contained(activeParDims, launchParDim))
1566 inactiveParDims.push_back(launchParDim);
1568 return std::pair{activeParDims, inactiveParDims};
1572 SmallVector<mlir::acc::GPUParallelDimAttr> ancestorParDims =
1573 getAncestorParDims(op);
1578 bool noStructuralAncestorParDims =
1579 llvm::none_of(ancestorParDims, [](
auto pd) {
return !pd.isSeq(); });
1581 if (routineParDim) {
1582 if (routineParDim.isThreadX()) {
1584 mlir::acc::GPUParallelDimAttr::threadYDim(ctx));
1587 mlir::acc::GPUParallelDimAttr::blockXDim(ctx));
1591 if (acc::PrivateLocalOp privateLocalOp = dyn_cast<acc::PrivateLocalOp>(op)) {
1592 for (Operation *user : privateLocalOp.getResult().getUsers()) {
1593 if (acc::ReductionAccumulateOp accumulateOp =
1594 dyn_cast<acc::ReductionAccumulateOp>(user)) {
1595 if (accumulateOp.getMemref() == privateLocalOp.getResult()) {
1596 for (mlir::acc::GPUParallelDimAttr parDim :
1597 accumulateOp.getParDims().getArray()) {
1607 if (acc::ReductionCombineOp combineOp =
1608 dyn_cast<acc::ReductionCombineOp>(user)) {
1609 if (combineOp.getSrcMemref() == privateLocalOp.
getResult()) {
1610 for (mlir::acc::GPUParallelDimAttr parDim :
1616 if (
auto combineRegionOp =
1617 dyn_cast<acc::ReductionCombineRegionOp>(user)) {
1618 if (combineRegionOp.getSrcVar() == privateLocalOp.getResult()) {
1619 for (mlir::acc::GPUParallelDimAttr parDim :
1631 acc::PrivateType privTy =
1632 cast<acc::PrivateType>(privateLocalOp.getPrivatized().getType());
1634 privTy.getBaseTy(), computeRegion->getParentOfType<ModuleOp>());
1635 if (!baseTy.hasStaticShape()) {
1636 GPUParallelDimsAttr ownParDims =
1637 getPrivateParDims(privateLocalOp, computeRegion);
1639 for (GPUParallelDimAttr parDim : ownParDims.getArray())
1644 bool hasBlock =
false;
1645 for (mlir::acc::GPUParallelDimAttr parDim : ancestorParDims)
1646 if (parDim.isAnyBlock())
1649 mlir::acc::GPUParallelDimAttr lowestParDim =
1650 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
1652 ThreadYBroadeningInfo threadYInfo =
1653 analyzeThreadYBroadening(*block, computeRegion);
1655 auto applyCombineParDims =
1656 [&](ArrayRef<mlir::acc::GPUParallelDimAttr> combineParDims) {
1657 for (mlir::acc::GPUParallelDimAttr parDim : combineParDims)
1661 block->
walk([&](Operation *op) -> WalkResult {
1665 auto addPrivateStoreParDims = [&](Value
target) {
1666 if (
auto privateLocalOp = getPrivateLocalForMemref(
target)) {
1667 GPUParallelDimsAttr parDimsAttr =
1668 getPrivateParDims(privateLocalOp, computeRegion);
1670 for (
auto parDim : parDimsAttr.getArray())
1674 if (
auto memEffects = dyn_cast<MemoryEffectOpInterface>(op)) {
1675 SmallVector<MemoryEffects::EffectInstance> effects;
1676 memEffects.getEffects(effects);
1678 if (isa<MemoryEffects::Write>(effect.getEffect()) &&
1680 addPrivateStoreParDims(effect.getValue());
1685 if (CallOpInterface callOp = dyn_cast<CallOpInterface>(op)) {
1686 if (mlir::acc::GPUParallelDimAttr parDim =
1687 getAccRoutineCallParDim(callOp, defaultPolicy)) {
1688 if (parDim.isBlockZ())
1689 lowestParDim = parDim;
1691 lowestParDim = parDim.getOneHigher();
1697 if (acc::ReductionCombineOp reductionCombineOp =
1698 dyn_cast<acc::ReductionCombineOp>(op)) {
1699 if (
failed(applyCombineParDims(
1703 if (acc::ReductionCombineRegionOp combineRegionOp =
1704 dyn_cast<acc::ReductionCombineRegionOp>(op)) {
1705 if (
failed(applyCombineParDims(
1712 if (acc::ReductionAccumulateArrayOp accArrayOp =
1713 dyn_cast<acc::ReductionAccumulateArrayOp>(op)) {
1714 for (mlir::acc::GPUParallelDimAttr parDim :
1715 accArrayOp.getParDims().getArray()) {
1721 mlir::acc::GPUParallelDimAttr threadY =
1722 mlir::acc::GPUParallelDimAttr::threadYDim(ctx);
1723 bool baselineThreadYActive = llvm::is_contained(ancestorParDims, threadY);
1724 if (threadYInfo.hasActiveWorkerCombine && !baselineThreadYActive) {
1725 if (threadYInfo.hasExplicitInactiveCombine ||
1726 threadYInfo.hasBroadeningConflict) {
1727 Operation *diagnosticOp =
1728 threadYInfo.diagnosticOp ? threadYInfo.diagnosticOp : op;
1731 "operations in the same predicate region require incompatible "
1732 "ThreadY predication");
1741 SmallVector<mlir::acc::GPUParallelDimAttr> launchParDims;
1742 if (routineParDim) {
1743 for (mlir::acc::GPUParallelDimAttr parDim = routineParDim;
1744 parDim.getOrder() >= lowestParDim.getOrder();
1745 parDim = parDim.getOneLower()) {
1749 launchParDims = computeRegion.getLaunchParDims();
1753 SmallVector<mlir::acc::GPUParallelDimAttr> activeParDims, inactiveParDims;
1754 for (mlir::acc::GPUParallelDimAttr launchParDim : launchParDims) {
1755 if (launchParDim.getOrder() < lowestParDim.getOrder())
1757 if (llvm::find(ancestorParDims, launchParDim) != ancestorParDims.end() ||
1758 (launchParDim.isAnyBlock() &&
1759 (noStructuralAncestorParDims || hasBlock))) {
1760 activeParDims.push_back(launchParDim);
1762 inactiveParDims.push_back(launchParDim);
1766 return std::pair{activeParDims, inactiveParDims};
1769Value ACCCGToGPULowering::emitPredicate(
1770 Location loc, SmallVector<mlir::acc::GPUParallelDimAttr> &inactiveParDims) {
1772 for (mlir::acc::GPUParallelDimAttr inactiveParDim : inactiveParDims) {
1773 Value threadId = getGPUThreadIdFor(inactiveParDim.getProcessor());
1775 Value zero = arith::ConstantOp::create(rewriter, loc, zeroAttr);
1776 Value cmp = arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq,
1779 predicate = arith::AndIOp::create(rewriter, loc, cmp, predicate);
1786void ACCCGToGPULowering::createBarrier(
1787 Location loc, mlir::acc::GPUParallelDimsAttr parDimsAttr) {
1788 bool hasAnyBlock =
false, hasThreadY =
false, hasThreadX =
false;
1789 for (
auto parDim : parDimsAttr.getArray()) {
1790 if (parDim.isAnyBlock())
1792 if (parDim.isThreadY())
1794 if (parDim.isThreadX())
1798 if (hasAnyBlock || hasThreadY)
1799 emitGPUBarrierWorkgroup(rewriter, loc);
1800 else if (hasThreadX)
1801 createPerRowBarrier(loc);
1804void ACCCGToGPULowering::createPerRowBarrier(Location loc) {
1807 if (isSingleThreadWorkerLaunch())
1813 if (isWorkerOnlyShuffleLaunch() && staticBlockDimX <
options.subgroupSize) {
1814 emitGPUBarrierRow(rewriter, loc, staticBlockDimX,
options.subgroupSize);
1818 hasThreadYBarrier =
true;
1820 if (staticBlockDimX <=
options.subgroupSize) {
1821 emitGPUBarrierSubgroup(rewriter, loc);
1825 if (
options.deviceType != mlir::acc::DeviceType::Nvidia) {
1828 "per-row barrier to support worker parallelism on non-NVIDIA device");
1843 Value blockDimX = gpu::BlockDimOp::create(
1844 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
1845 Value blockDimY = gpu::BlockDimOp::create(
1846 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::y);
1848 Value isSingleWorker = arith::CmpIOp::create(
1849 rewriter, loc, arith::CmpIPredicate::eq, blockDimY, cst1);
1851 auto outerIf = scf::IfOp::create(rewriter, loc, isSingleWorker,
1856 emitGPUBarrierWorkgroup(rewriter, loc);
1860 Value cstSubgroupSize =
1862 Value isSubgroupSized = arith::CmpIOp::create(
1863 rewriter, loc, arith::CmpIPredicate::ule, blockDimX, cstSubgroupSize);
1865 auto innerIf = scf::IfOp::create(rewriter, loc, isSubgroupSized,
1871 emitGPUBarrierSubgroup(rewriter, loc);
1878 Value threadYId = gpu::ThreadIdOp::create(
1879 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::y);
1880 Value barrierId = arith::AddIOp::create(rewriter, loc, threadYId, cst1);
1883 arith::IndexCastOp::create(rewriter, loc, i32Ty, barrierId);
1884 Value numberOfThreads32 =
1885 arith::IndexCastOp::create(rewriter, loc, i32Ty, blockDimX);
1889 assert(
options.deviceType == mlir::acc::DeviceType::Nvidia);
1890 NVVM::BarrierOp::create(rewriter, loc, barrierId32, numberOfThreads32);
1899static bool hasSubsequentLoopSibling(Operation *loopOp) {
1900 for (Operation *next = loopOp->getNextNode(); next;
1901 next =
next->getNextNode()) {
1902 if (isa<scf::ParallelOp, scf::ForOp>(next))
1904 bool nested =
false;
1905 next->walk([&](Operation *op) {
1906 if (isa<scf::ParallelOp, scf::ForOp>(op)) {
1919static LoopLikeOpInterface findFirstSequentialLoop(Operation *op) {
1920 auto isAllSequentialParDims = [](scf::ParallelOp par) ->
bool {
1922 if (!pd || pd.getArray().empty())
1924 return llvm::all_of(pd.getArray(), [](mlir::acc::GPUParallelDimAttr d) {
1931 if (isa<scf::ForOp>(p))
1932 return cast<LoopLikeOpInterface>(p);
1933 if (scf::ParallelOp parOp = dyn_cast<scf::ParallelOp>(p)) {
1934 if (isAllSequentialParDims(parOp))
1935 return cast<LoopLikeOpInterface>(p);
1951static bool isLoopBodyClosureOp(Operation *op) {
1952 return isa<scf::ReduceOp, scf::YieldOp, acc::YieldOp>(op);
1957static bool isDeferredBarrierFlushPoint(Operation *op) {
1958 if (isLoopBodyClosureOp(op))
1962 if (isa<scf::ForOp>(op))
1964 if (scf::ParallelOp parallelOp = dyn_cast<scf::ParallelOp>(op)) {
1966 if (mlir::acc::GPUParallelDimsAttr parDims =
1968 if (parDims.getArray().size() == 1 &&
1969 parDims.getArray().front().isSeq()) {
1981static bool hasTrailingSideEffectSiblings(Operation *loopOp) {
1982 for (Operation *next = loopOp->getNextNode(); next;
1983 next =
next->getNextNode()) {
1984 return !isLoopBodyClosureOp(next);
2008void ACCCGToGPULowering::createBarrierAfterSeqLoop(Operation *loopOp) {
2010 auto getParentParallel = [&](Operation *op) -> scf::ParallelOp {
2012 if (parent && computeRegion->isAncestor(parent))
2016 scf::ParallelOp wsLoop = getParentParallel(loopOp);
2024 if (mayWriteSharedMemory(loopOp) && hasSubsequentLoopSibling(loopOp))
2025 emitGPUBarrierWorkgroup(rewriter, loopOp->
getLoc());
2031 wsParDims.getArray().size() == 1 && wsParDims.getArray().front().isSeq();
2038 bool hasThreadSubLoop =
false;
2039 loopOp->
walk([&](scf::ParallelOp innerPar) -> WalkResult {
2040 if (innerPar.getOperation() == loopOp)
2043 for (
auto d : dims.getArray()) {
2044 if (d.isThreadX() || d.isThreadY()) {
2045 hasThreadSubLoop =
true;
2051 if (!hasThreadSubLoop)
2053 scf::ParallelOp threadLoop = getParentParallel(wsLoop);
2056 scf::ParallelOp blockLoop = getParentParallel(threadLoop);
2059 mlir::acc::GPUParallelDimsAttr parDimsAttr =
2061 if (parDimsAttr.hasOnlyBlockLevel())
2062 createBarrier(loopOp->
getLoc(), parDimsAttr);
2068 scf::ParallelOp seqLoop = getParentParallel(wsLoop);
2077 if (mayWriteSharedMemory(loopOp) && hasSubsequentLoopSibling(wsLoop))
2078 emitGPUBarrierWorkgroup(rewriter, loopOp->
getLoc());
2081 if (scf::ParallelOp outerParLoop = getParentParallel(seqLoop)) {
2082 mlir::acc::GPUParallelDimsAttr parDimsAttr =
2084 if (parDimsAttr.hasOnlyBlockLevel()) {
2085 createBarrier(loopOp->
getLoc(), parDimsAttr);
2086 }
else if (parDimsAttr.hasOnlyThreadYLevel()) {
2087 createPerRowBarrier(loopOp->
getLoc());
2088 }
else if (parDimsAttr && parDimsAttr.isSeq()) {
2092 for (scf::ParallelOp gangLoop = getParentParallel(outerParLoop); gangLoop;
2093 gangLoop = getParentParallel(gangLoop)) {
2094 mlir::acc::GPUParallelDimsAttr gangDims =
2096 if (gangDims.hasOnlyBlockLevel()) {
2097 createBarrier(loopOp->
getLoc(), gangDims);
2100 if (!gangDims.isSeq())
2114 mlir::acc::GPUParallelDimsAttr parDimsAttr =
2116 if (parDimsAttr.hasOnlyBlockLevel() && mayWriteSharedMemory(loopOp)) {
2117 createBarrier(loopOp->
getLoc(), parDimsAttr);
2121bool ACCCGToGPULowering::mayWriteSharedMemory(Operation *loopOp) {
2123 loopOp->
walk([&](memref::StoreOp storeOp) {
2126 llvm::SmallVector<Value, 8> worklist{storeOp.getMemref()};
2127 llvm::SmallPtrSet<Value, 8> seen;
2128 while (!worklist.empty()) {
2129 Value v = worklist.pop_back_val();
2130 if (!seen.insert(v).second)
2135 if (acc::PrivateLocalOp privateLocal =
2136 dyn_cast<acc::PrivateLocalOp>(def)) {
2137 acc::PrivatizeOp privatizeOp =
2145 if (mlir::acc::GPUParallelDimsAttr parDims =
2146 privatizeOp.getParDimsAttr()) {
2147 bool hasBlock =
false, hasThread =
false;
2148 for (mlir::acc::GPUParallelDimAttr d : parDims.getArray()) {
2151 if (d.isThreadX() || d.isThreadY())
2154 if (hasBlock && !hasThread) {
2169ACCCGToGPULowering::getPrivateMemScope(acc::PrivatizeOp privatizeOp) {
2170 bool hasBlock =
false;
2171 bool hasThreadX =
false;
2172 bool hasThreadY =
false;
2173 if (mlir::acc::GPUParallelDimsAttr parDims = privatizeOp.getParDimsAttr()) {
2174 for (mlir::acc::GPUParallelDimAttr d : parDims.getArray()) {
2183 for (mlir::acc::GPUParallelDimAttr d : computeRegion.getLaunchParDims())
2187 return PrivateMemScope::Gang;
2188 return PrivateMemScope::Thread;
2191 return PrivateMemScope::Thread;
2192 if (hasBlock && hasThreadY)
2193 return PrivateMemScope::Worker;
2195 return PrivateMemScope::Gang;
2196 return PrivateMemScope::Thread;
2200static acc::PrivateLocalOp getPrivateLocalForMemref(Value memref) {
2201 llvm::SmallVector<Value, 8> worklist{memref};
2202 llvm::SmallPtrSet<Value, 8> seen;
2203 while (!worklist.empty()) {
2204 Value v = worklist.pop_back_val();
2205 if (!seen.insert(v).second)
2210 if (acc::PrivateLocalOp privateLocal = dyn_cast<acc::PrivateLocalOp>(def))
2211 return privateLocal;
2217PrivateMemScope ACCCGToGPULowering::getPrivateScopeForMemref(Value memref) {
2218 if (
auto privateLocal = getPrivateLocalForMemref(memref))
2219 return getPrivateMemScope(
getPrivatizeOp(privateLocal, computeRegion));
2220 return PrivateMemScope::None;
2223acc::PrivatizeOp ACCCGToGPULowering::getPrivatizeForMemref(Value memref) {
2224 if (
auto privateLocal = getPrivateLocalForMemref(memref))
2226 return acc::PrivatizeOp();
2229void ACCCGToGPULowering::collectSharedPrivateStores(
2230 Operation *root, llvm::SmallPtrSetImpl<Operation *> &gangPrivatizes,
2231 llvm::SmallPtrSetImpl<Operation *> &workerPrivatizes) {
2232 root->
walk([&](memref::StoreOp storeOp) {
2233 PrivateMemScope scope = getPrivateScopeForMemref(storeOp.getMemref());
2234 if (scope != PrivateMemScope::Gang && scope != PrivateMemScope::Worker)
2236 acc::PrivatizeOp privatize = getPrivatizeForMemref(storeOp.getMemref());
2239 if (scope == PrivateMemScope::Gang)
2240 gangPrivatizes.insert(privatize.getOperation());
2242 workerPrivatizes.insert(privatize.getOperation());
2246bool ACCCGToGPULowering::isMatchingPrivateMemref(
2247 Value memref, PrivateMemScope storeScope,
2248 const llvm::SmallPtrSetImpl<Operation *> &storePrivatizes) {
2249 if (getPrivateScopeForMemref(memref) != storeScope)
2251 acc::PrivatizeOp usePrivatize = getPrivatizeForMemref(memref);
2252 return usePrivatize && storePrivatizes.contains(usePrivatize.getOperation());
2255bool ACCCGToGPULowering::hasParallelPrivateReuse(
2256 LoopLikeOpInterface seqLoopOp, Operation *skipOp,
2257 PrivateMemScope storeScope,
2258 const llvm::SmallPtrSetImpl<Operation *> &storePrivatizes) {
2259 if (storePrivatizes.empty())
2262 bool hasParallelPrivateUse =
false;
2263 seqLoopOp.getOperation()->walk([&](Operation *op) {
2269 if (memref::LoadOp loadOp = dyn_cast<memref::LoadOp>(op))
2270 memref = loadOp.getMemref();
2271 else if (memref::StoreOp storeOp = dyn_cast<memref::StoreOp>(op))
2272 memref = storeOp.getMemref();
2276 if (!isMatchingPrivateMemref(memref, storeScope, storePrivatizes))
2279 bool insideNestedParallel =
false;
2280 for (Operation *p = op->
getParentOp(); p && p != seqLoopOp.getOperation();
2282 if (scf::ParallelOp par = dyn_cast<scf::ParallelOp>(p)) {
2283 if (mlir::acc::GPUParallelDimsAttr pd =
2285 if (llvm::any_of(pd.getArray(), [](mlir::acc::GPUParallelDimAttr d) {
2288 insideNestedParallel =
true;
2294 if (!insideNestedParallel)
2297 hasParallelPrivateUse =
true;
2301 return hasParallelPrivateUse;
2305ACCCGToGPULowering::needsPreStoreReuseBarrier(acc::PredicateRegionOp interOp) {
2309 LoopLikeOpInterface seqLoopOp = findFirstSequentialLoop(interOp);
2311 return PrivateMemScope::None;
2315 llvm::SmallPtrSet<Operation *, 4> gangPrivatizes, workerPrivatizes;
2316 collectSharedPrivateStores(interOp, gangPrivatizes, workerPrivatizes);
2322 if (hasParallelPrivateReuse(seqLoopOp, interOp, PrivateMemScope::Gang,
2324 return PrivateMemScope::Gang;
2325 if (hasParallelPrivateReuse(seqLoopOp, interOp, PrivateMemScope::Worker,
2327 return PrivateMemScope::Worker;
2328 return PrivateMemScope::None;
2331bool ACCCGToGPULowering::needsInLoopReuseBarrier(Operation *loopOp) {
2336 if (scf::ParallelOp par = dyn_cast<scf::ParallelOp>(loopOp))
2343 if (!hasThreadLevelRoutineCall)
2345 bool callsThreadLevelRoutine =
false;
2346 loopOp->
walk([&](CallOpInterface callOp) -> WalkResult {
2347 if (mlir::acc::GPUParallelDimAttr parDim =
2348 getAccRoutineCallParDim(callOp, defaultPolicy)) {
2349 if (parDim.isThreadX() || parDim.isThreadY()) {
2350 callsThreadLevelRoutine =
true;
2356 if (!callsThreadLevelRoutine)
2362 llvm::SmallPtrSet<Operation *, 4> storedPrivatizes, workerPrivatizes;
2363 collectSharedPrivateStores(loopOp, storedPrivatizes, workerPrivatizes);
2364 if (storedPrivatizes.empty())
2370 bool reusesGangPrivate =
false;
2371 loopOp->
walk([&](Operation *op) -> WalkResult {
2372 if (memref::LoadOp loadOp = dyn_cast<memref::LoadOp>(op))
2373 reusesGangPrivate = isMatchingPrivateMemref(
2374 loadOp.getMemref(), PrivateMemScope::Gang, storedPrivatizes);
2375 else if (CallOpInterface callOp = dyn_cast<CallOpInterface>(op))
2376 reusesGangPrivate = llvm::any_of(callOp.getArgOperands(), [&](Value arg) {
2377 return isMatchingPrivateMemref(arg, PrivateMemScope::Gang,
2382 if (!reusesGangPrivate)
2389 p && p != computeRegion.getOperation(); p = p->
getParentOp()) {
2390 scf::ParallelOp par = dyn_cast<scf::ParallelOp>(p);
2394 for (mlir::acc::GPUParallelDimAttr d : parDims.getArray())
2395 if (d.isThreadX() || d.isThreadY())
2401void ACCCGToGPULowering::processPredicateRegion(
2402 acc::PredicateRegionOp interOp) {
2403 LLVM_DEBUG(llvm::dbgs() <<
"processing predicate region: ";
2404 interOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2405 Location loc = interOp->getLoc();
2407 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
2408 SmallVector<mlir::acc::GPUParallelDimAttr>>
2409 parDimsPair = computeActiveAndInactiveParDims(
2410 interOp, &interOp.getRegion().front());
2421 if (hasThreadYReduction && !isSingleThreadWorkerLaunch()) {
2422 MLIRContext *ctx = computeRegion->getContext();
2423 mlir::acc::GPUParallelDimAttr threadXParDim =
2424 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
2425 bool hasThreadXInActive =
2426 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr pd) {
2427 return pd.isThreadX();
2429 bool hasThreadXInInactive =
2430 llvm::any_of(parDimsPair.second, [](mlir::acc::GPUParallelDimAttr pd) {
2431 return pd.isThreadX();
2437 bool regionHasThreadLevelRoutineCall =
false;
2438 if (hasThreadLevelRoutineCall) {
2439 interOp.getRegion().walk([&](CallOpInterface callOp) {
2440 if (mlir::acc::GPUParallelDimAttr parDim =
2441 getAccRoutineCallParDim(callOp, defaultPolicy)) {
2442 if (parDim.isThreadX() || parDim.isThreadY()) {
2443 regionHasThreadLevelRoutineCall =
true;
2451 if (!hasThreadXInActive && !hasThreadXInInactive &&
2452 !regionHasThreadLevelRoutineCall) {
2453 parDimsPair.second.push_back(threadXParDim);
2457 Value predicate = emitPredicate(loc, parDimsPair.second);
2460 if (!predicate && llvm::all_of(computeRegion.getLaunchParDims(),
2461 [](mlir::acc::GPUParallelDimAttr pd) {
2462 return pd.isAnyBlock();
2466 LLVM_DEBUG(llvm::dbgs() <<
"predicate: " << predicate <<
"\n");
2467 bool isInsideThreadXLoop =
false;
2468 bool isInsideThreadYLoop =
false;
2469 for (
auto parDim : parDimsPair.first) {
2470 if (parDim.isThreadX())
2471 isInsideThreadXLoop =
true;
2472 if (parDim.isThreadY())
2473 isInsideThreadYLoop =
true;
2479 auto emitReconvergenceBarrier = [&]() {
2480 if (isInsideThreadXLoop) {
2482 }
else if (isInsideThreadYLoop) {
2487 bool predicatesThreadX = llvm::any_of(
2489 [](mlir::acc::GPUParallelDimAttr pd) { return pd.isThreadX(); });
2490 if (predicatesThreadX)
2491 createPerRowBarrier(loc);
2494 }
else if (!parDimsPair.first.empty()) {
2496 createBarrier(loc, mlir::acc::GPUParallelDimsAttr::get(
2497 interOp->getContext(), parDimsPair.first));
2500 createBarrier(loc, mlir::acc::GPUParallelDimsAttr::get(
2501 interOp->getContext(), parDimsPair.second));
2513 PrivateMemScope scope = needsPreStoreReuseBarrier(interOp);
2514 if (scope == PrivateMemScope::Gang)
2515 emitGPUBarrierWorkgroup(rewriter, loc);
2516 else if (scope == PrivateMemScope::Worker)
2517 createPerRowBarrier(loc);
2519 auto ifOp = scf::IfOp::create(rewriter, loc, predicate,
2521 Region &thenRegion = ifOp.getThenRegion();
2525 for (
auto &bodyOp : interOp.getRegion().front().getOperations()) {
2529 if (memref::StoreOp storeOp = dyn_cast<memref::StoreOp>(&bodyOp)) {
2530 std::optional<arith::AtomicRMWKind> blockReduceKind;
2531 bool failedReductionKind =
false;
2532 Value storeVal = storeOp.getValueToStore();
2539 Block *epilogueBlock = interOp->getBlock();
2540 auto findBlockAccLoad =
2542 Value val) -> std::optional<arith::AtomicRMWKind> {
2543 Operation *def = val.getDefiningOp();
2544 if (!def || def->
getBlock() != epilogueBlock)
2545 return std::nullopt;
2546 if (memref::LoadOp loadOp = dyn_cast<memref::LoadOp>(def)) {
2547 for (
auto *user : loadOp.getMemRef().getUsers()) {
2548 if (acc::ReductionAccumulateOp accOp =
2549 dyn_cast<acc::ReductionAccumulateOp>(user)) {
2550 if (llvm::any_of(accOp.getParDims().getArray(),
2551 [](mlir::acc::GPUParallelDimAttr pd) {
2552 return pd.isAnyBlock();
2554 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
2555 accOp.getReductionOperator(),
2556 accOp.getValue().getType(), accOp.getLoc());
2558 failedReductionKind =
true;
2559 return std::nullopt;
2565 return std::nullopt;
2568 if (
auto kind = self(self, operand))
2570 return std::nullopt;
2572 blockReduceKind = findBlockAccLoad(findBlockAccLoad, storeVal);
2574 if (failedReductionKind)
2576 if (blockReduceKind) {
2579 bool threadIsActive = llvm::any_of(
2580 parDimsPair.first, [](mlir::acc::GPUParallelDimAttr pd) {
2581 return !pd.isAnyBlock();
2583 if (!threadIsActive &&
2584 !isa_and_nonnull<memref::AllocaOp>(
2585 unwrapMemRefConversion(memref).getDefiningOp())) {
2591 MemRefType memrefTy = cast<MemRefType>(memref.
getType());
2593 SmallVector<Value> initIndices;
2594 for (Value idx : storeOp.getIndices())
2597 OpBuilder::InsertionGuard guard(rewriter);
2598 Block &launchBody = launch.getBody().front();
2599 Operation *insertBefore =
nullptr;
2601 launchBody.
walk([&](scf::ParallelOp parOp) -> WalkResult {
2602 for (Operation *parent = parOp->getParentOp(); parent;
2603 parent = parent->getParentOp()) {
2604 if (parent == launch.getOperation())
2606 if (isa<scf::ParallelOp>(parent))
2609 insertBefore = parOp.getOperation();
2615 std::function<bool(Value)> fromLaunch = [&](Value v) ->
bool {
2616 if (
auto arg = dyn_cast<BlockArgument>(v))
2617 return launch->isAncestor(arg.getOwner()->getParentOp());
2619 if (!launch->isAncestor(def) ||
2620 def->
hasTrait<OpTrait::ConstantLike>())
2626 if (!fromLaunch(memref) &&
2627 llvm::none_of(initIndices, fromLaunch)) {
2630 Value token = launch.getAsyncToken();
2631 auto initLaunch = gpu::LaunchOp::create(
2632 rewriter, loc, one, one, one, one, one, one,
2634 token ? token.
getType() : Type(),
2635 launch.getAsyncDependencies());
2637 &initLaunch.getBody().front());
2639 gpu::TerminatorOp::create(rewriter, loc));
2640 }
else if (insertBefore)
2650 DominanceInfo domInfo(launch);
2651 IRMapping initMapping;
2652 std::function<Value(Value)> materialize =
2653 [&](Value val) -> Value {
2654 Operation *defOp = val.getDefiningOp();
2665 materialize(operand);
2666 Operation *cloned = rewriter.
clone(*defOp, initMapping);
2667 for (
auto [orig, clonedRes] :
2669 initMapping.
map(orig, clonedRes);
2671 return initMapping.
lookup(val);
2673 Value initMemref = materialize(memref);
2674 for (
auto &idx : initIndices)
2675 idx = materialize(idx);
2677 rewriter, loc, memrefTy.getElementType(), *blockReduceKind,
2679 Value blockId = gpu::BlockIdOp::create(
2680 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
2681 Value threadId = gpu::ThreadIdOp::create(
2682 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
2684 Value isBlock0 = arith::CmpIOp::create(
2685 rewriter, loc, arith::CmpIPredicate::eq, blockId, zero);
2686 Value isThread0 = arith::CmpIOp::create(
2687 rewriter, loc, arith::CmpIPredicate::eq, threadId, zero);
2688 Value isFirstThread =
2689 arith::AndIOp::create(rewriter, loc, isBlock0, isThread0);
2690 auto initIf = scf::IfOp::create(rewriter, loc, isFirstThread,
2693 initIf.getThenRegion().back().getTerminator());
2694 memref::StoreOp::create(rewriter, loc, identityVal, initMemref,
2697 gpu::BarrierOp::create(rewriter, loc);
2699 SmallVector<Value> atomicIndices;
2700 for (Value idx : storeOp.getIndices())
2702 constructAtomicAccumulation(loc, memref, atomicIndices, input,
2711 emitReconvergenceBarrier();
2714 for (
auto &bodyOp : interOp.getRegion().front().getOperations())
2737Value ACCCGToGPULowering::allocatePrivatizeHeapStorage(
2738 acc::PrivatizeOp privatize, MemRefType baseTy,
2740 Location loc = privatize.getLoc();
2742 memref::AllocOp::create(rewriter, loc, baseTy, mappedDynamicSizes);
2743 OpBuilder::InsertionGuard guard(rewriter);
2747 memref::DeallocOp::create(rewriter, loc, mem);
2749 mapping.
map(privatize.getResult(), mem);
2752 if (!privatize->getParentOfType<acc::ComputeRegionOp>())
2757Value ACCCGToGPULowering::processPrivatize(acc::PrivatizeOp privatize) {
2758 LLVM_DEBUG(llvm::dbgs() <<
"processing privatize: ";
2759 privatize->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2760 Value tracked = privatize.getResult();
2761 if (acc::ComputeRegionOp insUser =
2762 dyn_cast<acc::ComputeRegionOp>(getOnlyUser(tracked))) {
2763 assert(privatize->hasOneUse() &&
2764 "expected acc.privatize op to have one use");
2765 tracked = insUser.getBody()->getArgument(
2766 privatize->use_begin()->getOperandNumber());
2768 Operation *privatizeUser = getOnlyUser(tracked);
2769 assert(privatizeUser &&
"expected PrivateLocalOp user for privatize");
2770 acc::PrivateLocalOp privateLocalUser =
2771 dyn_cast<acc::PrivateLocalOp>(privatizeUser);
2773 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
2774 SmallVector<mlir::acc::GPUParallelDimAttr>>
2775 parDimsPair = computeActiveAndInactiveParDims(privatizeUser,
nullptr);
2777 if (!privatize.getParDimsAttr()) {
2778 privatize.setParDimsAttr(mlir::acc::GPUParallelDimsAttr::get(
2782 Location loc = privatize->getLoc();
2783 acc::PrivateType privTy = cast<acc::PrivateType>(privatize.getType());
2784 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
2787 gpu::GPUFuncOp gpuFuncOp = computeRegion->getParentOfType<gpu::GPUFuncOp>();
2792 privatize->getParentOfType<acc::ComputeRegionOp>() != computeRegion) {
2793 return privatize.getResult();
2796 bool threadXActive =
2797 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr parDim) {
2798 return parDim.isThreadX();
2800 if (threadXActive &&
2801 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
2802 auto alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
2803 recordThreadPrivate(privateLocalUser);
2804 mapping.
map(privatize.getResult(), alloca.getResult());
2805 return alloca.getResult();
2809 return privatize.getResult();
2817 bool threadYIsActive =
2818 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr parDim) {
2819 return parDim.isThreadY();
2832 bool needsWorkgroupBarrier =
false;
2834 FunctionOpInterface funcOp =
2835 computeRegion->getParentOfType<FunctionOpInterface>();
2836 mlir::acc::GPUParallelDimAttr routineParDim =
2837 getSpecializedRoutineDim(funcOp, defaultPolicy);
2838 if (routineParDim.isThreadX()) {
2841 threadYIsActive =
true;
2842 }
else if (routineParDim.isThreadY()) {
2845 needsWorkgroupBarrier =
true;
2846 }
else if (routineParDim.isAnyBlock()) {
2847 needsWorkgroupBarrier =
true;
2851 llvm::SmallVector<Value> mappedDynamicSizes;
2852 for (
auto dynamicSize : privatize.getDynamicSizes()) {
2854 mappedDynamicSizes.push_back(mappedDynamicSize);
2857 computeRegion.isEffectivelySerial()) {
2858 if (mappedDynamicSizes.empty()) {
2861 memref::AllocaOp::create(rewriter, privatize->getLoc(), baseTy);
2862 recordThreadPrivate(privateLocalUser);
2863 mapping.
map(privatize.getResult(), alloca.getResult());
2864 return alloca.getResult();
2867 return allocatePrivatizeHeapStorage(privatize, baseTy, mappedDynamicSizes);
2870 if (threadXActive) {
2875 return allocatePrivatizeHeapStorage(privatize, baseTy, mappedDynamicSizes);
2880 SmallVector<mlir::acc::GPUParallelDimAttr> predicateDims;
2881 for (
auto parDim : parDimsPair.second) {
2883 if (threadYIsActive && parDim.isThreadY())
2885 predicateDims.push_back(parDim);
2887 Value predicate = emitPredicate(loc, predicateDims);
2889 predicate = arith::ConstantOp::create(
2892 auto ifOp = scf::IfOp::create(rewriter, loc, predicate,
2894 Region &thenRegion = ifOp.getThenRegion();
2897 auto mem = memref::AllocOp::create(rewriter, privatize->getLoc(), baseTy,
2898 mappedDynamicSizes);
2900 gpu::AddressSpaceAttr sharedMemoryAddressSpace = gpu::AddressSpaceAttr::get(
2901 computeRegion->getContext(), gpu::GPUDialect::getWorkgroupAddressSpace());
2905 constexpr int64_t kMaxThreadY = 32;
2906 MemRefType sharedMemTy =
2908 ? MemRefType::get({kMaxThreadY}, baseTy, MemRefLayoutAttrInterface{},
2909 sharedMemoryAddressSpace)
2910 : MemRefType::
get({}, baseTy, MemRefLayoutAttrInterface{},
2911 sharedMemoryAddressSpace);
2914 bool reuseBroadcast = !gpuFuncOp.isKernel();
2916 llvm::DenseMap<Type, Value>::iterator cachedSlot =
2917 reuseBroadcast ? privatizeBroadcastCache.find(sharedMemTy)
2918 : privatizeBroadcastCache.end();
2919 if (reuseBroadcast && cachedSlot != privatizeBroadcastCache.end()) {
2920 alloca = cachedSlot->second;
2923 mlir::acc::GPUParallelDimAttr dim =
2924 needsWorkgroupBarrier
2925 ? mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.
getContext())
2926 : mlir::
acc::GPUParallelDimAttr::threadXDim(rewriter.
getContext());
2928 loc, mlir::acc::GPUParallelDimsAttr::get(rewriter.
getContext(), {dim}));
2930 alloca = gpuFuncOp.addWorkgroupAttribution(sharedMemTy,
2935 unsigned index = gpuFuncOp.getNumWorkgroupAttributions() - 1;
2936 gpuFuncOp.setWorkgroupAttributionAttr(
index,
2937 LLVM::LLVMDialect::getAlignAttrName(),
2940 privatizeBroadcastCache[sharedMemTy] = alloca;
2943 if (threadYIsActive) {
2944 Value threadYId = getThreadId(loc, gpu::Dimension::y);
2945 memref::StoreOp::create(rewriter, privatize->getLoc(), mem, alloca,
2948 memref::StoreOp::create(rewriter, privatize->getLoc(), mem, alloca);
2954 if (needsWorkgroupBarrier) {
2955 mlir::acc::GPUParallelDimsAttr threadYDimsAttr =
2956 mlir::acc::GPUParallelDimsAttr::get(
2958 {mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.getContext())});
2959 createBarrier(loc, threadYDimsAttr);
2962 mlir::acc::GPUParallelDimsAttr threadXDimsAttr =
2963 mlir::acc::GPUParallelDimsAttr::get(
2965 {mlir::acc::GPUParallelDimAttr::threadXDim(rewriter.getContext())});
2966 createBarrier(loc, threadXDimsAttr);
2970 if (threadYIsActive) {
2971 Value threadYId = getThreadId(loc, gpu::Dimension::y);
2972 load = memref::LoadOp::create(rewriter, privatize->getLoc(), alloca,
2975 load = memref::LoadOp::create(rewriter, privatize->getLoc(), alloca,
2979 mapping.
map(privatize.getResult(),
load);
2983 if (!privatize->getParentOfType<acc::ComputeRegionOp>())
2987 if (needsWorkgroupBarrier) {
2988 mlir::acc::GPUParallelDimsAttr workerDimsAttr =
2989 mlir::acc::GPUParallelDimsAttr::get(
2991 {mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.getContext())});
2992 createBarrier(loc, workerDimsAttr);
2994 mlir::acc::GPUParallelDimsAttr vectorDimsAttr =
2995 mlir::acc::GPUParallelDimsAttr::get(
2997 {mlir::acc::GPUParallelDimAttr::threadXDim(rewriter.getContext())});
2998 createBarrier(loc, vectorDimsAttr);
3000 auto ifOp2 = scf::IfOp::create(rewriter, loc, predicate,
3002 Region &thenRegion2 = ifOp2.getThenRegion();
3005 memref::DeallocOp::create(rewriter, privatize->getLoc(),
load);
3016void ACCCGToGPULowering::processPrivateLocal(
3017 acc::PrivateLocalOp privateLocal, std::optional<int64_t> sharedMemCopies) {
3018 LLVM_DEBUG(llvm::dbgs() <<
"processing private local: ";
3019 privateLocal->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3020 Location loc = privateLocal.getLoc();
3021 acc::PrivateType privTy =
3022 cast<acc::PrivateType>(privateLocal.getPrivatized().getType());
3023 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
3025 MemRefType byteMemrefTy =
3026 MemRefType::get({ShapedType::kDynamic}, rewriter.
getI8Type());
3028 acc::PrivatizeOp privatizeOp =
getPrivatizeOp(privateLocal, computeRegion);
3030 if (privatizeOp->getParentOfType<acc::ComputeRegionOp>() == computeRegion) {
3034 privateLocal.getType());
3035 mapping.
map(privateLocal.getResult(),
result);
3043 acc::ReductionAccumulateArrayOp arrayAccum =
3044 perThreadArrayReductionAccum(privateLocal.getResult());
3046 (arrayAccum && storageHasThreadX(privateLocal, computeRegion))) &&
3047 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
3048 Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
3049 recordThreadPrivate(privateLocal);
3051 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
3052 arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
3055 initPerThreadArrayAccum(rewriter, loc, alloca, baseTy, *kind);
3058 privateLocal.getType());
3059 mapping.
map(privateLocal.getResult(), mem);
3065 std::optional<int64_t>
copies =
3066 sharedMemCopies ? sharedMemCopies
3067 : isEligibleForSharedMemory(privateLocal, baseTy);
3069 int64_t numCopies = *
copies;
3070 int64_t elementSize = getElementSizeInBytes(loc, baseTy.getElementType());
3071 int64_t numElements = 1;
3072 for (int64_t dim : baseTy.getShape())
3074 int64_t upperBound = elementSize * numElements * numCopies;
3076 if (tryAllocateSharedMemory(upperBound)) {
3077 std::string varName =
3079 sharedMemPrivateVarNames.push_back(varName.empty() ?
"(*)" : varName);
3081 gpu::AddressSpaceAttr workgroupAS = gpu::AddressSpaceAttr::get(
3082 computeRegion->getContext(),
3083 gpu::GPUDialect::getWorkgroupAddressSpace());
3084 MemRefType sharedMemTy =
3085 MemRefType::get(baseTy.getShape(), baseTy.getElementType(),
3086 MemRefLayoutAttrInterface{}, workgroupAS);
3087 Value sharedMem = acc::GPUSharedMemoryOp::create(
3095 privateLocal.getType());
3097 mapping.
map(privateLocal.getResult(),
result);
3102 OpBuilder::InsertionGuard guard(rewriter);
3104 inputMem = processPrivatize(privatizeOp);
3108 assert(inputMem &&
"expected input mem to be mapped");
3110 privateLocal.getType());
3111 mapping.
map(privateLocal.getResult(),
result);
3117 SmallVector<int64_t> viewShape;
3119 SmallVector<Value> viewDynSizes;
3121 SmallVector<OpFoldResult> subviewOffset;
3124 SmallVector<OpFoldResult> subviewSizes;
3126 SmallVector<int64_t> subviewStrides;
3128 SmallVector<int64_t> subviewShape;
3130 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
3131 SmallVector<mlir::acc::GPUParallelDimAttr>>
3132 parDimsPair = computeActiveAndInactiveParDims(privateLocal,
nullptr);
3133 acc::ReductionAccumulateArrayOp arrayAccum =
3134 perThreadArrayReductionAccum(privateLocal.getResult());
3135 for (mlir::acc::GPUParallelDimAttr parDim : parDimsPair.first) {
3136 if ((parDim.isThreadX() ||
3137 (arrayAccum && storageHasThreadX(privateLocal, computeRegion))) &&
3138 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
3139 Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
3140 recordThreadPrivate(privateLocal);
3142 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
3143 arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
3146 initPerThreadArrayAccum(rewriter, loc, alloca, baseTy, *kind);
3149 privateLocal.getType());
3150 mapping.
map(privateLocal.getResult(), mem);
3154 if (parDimsPair.first.empty()) {
3158 mlir::acc::GPUParallelDimAttr::blockXDim(privateLocal.getContext()));
3160 for (mlir::acc::GPUParallelDimAttr parDim : parDimsPair.first) {
3161 gpu::Processor gpuProc = parDim.getProcessor();
3162 Value gpuSize = getGPUSizeFor(gpuProc);
3163 viewDynSizes.push_back(gpuSize);
3164 viewShape.push_back(ShapedType::kDynamic);
3165 subviewOffset.push_back(getGPUThreadIdFor(gpuProc));
3169 SmallVector<Value> innerDynSizes =
3170 resolvePrivateLocalDynamicExtents(privateLocal);
3172 unsigned dynIdx = 0;
3173 for (
auto innerDim : baseTy.getShape()) {
3175 viewShape.push_back(innerDim);
3176 subviewShape.push_back(innerDim);
3177 if (innerDim == ShapedType::kDynamic) {
3178 assert(dynIdx < innerDynSizes.size() &&
3179 "not enough dynamic sizes for inner dimensions");
3180 viewDynSizes.push_back(innerDynSizes[dynIdx]);
3181 subviewSizes.push_back(innerDynSizes[dynIdx]);
3184 subviewSizes.push_back(rewriter.
getIndexAttr(innerDim));
3190 for (
auto innerDimIt = baseTy.getShape().rbegin();
3191 innerDimIt != baseTy.getShape().rend(); ++innerDimIt) {
3192 int64_t innerDim = *innerDimIt;
3193 subviewStrides.insert(subviewStrides.begin(), stride);
3194 if (innerDim == ShapedType::kDynamic)
3195 stride = ShapedType::kDynamic;
3196 if (stride != ShapedType::kDynamic)
3203 MemRefType viewType = MemRefType::get(viewShape, baseTy.getElementType());
3204 auto view = memref::ViewOp::create(rewriter, loc, viewType, memBuffer,
3205 c0.getResult(), viewDynSizes);
3208 StridedLayoutAttr stridedLayout = StridedLayoutAttr::get(
3209 computeRegion->getContext(), ShapedType::kDynamic, subviewStrides);
3210 MemRefType subviewType =
3211 MemRefType::get(subviewShape, baseTy.getElementType(), stridedLayout);
3212 SmallVector<OpFoldResult> ones(viewType.getRank(), rewriter.
getIndexAttr(1));
3213 Value subview = memref::SubViewOp::create(rewriter, loc, subviewType, view,
3214 subviewOffset, subviewSizes, ones);
3219 memref::ExtractStridedMetadataOp::create(rewriter, loc, subview);
3221 rewriter, loc, getElementSizeInBytes(loc, baseTy.getElementType()));
3223 arith::MulIOp::create(rewriter, loc, metadata.getOffset(), elementBytes);
3224 Value privateView = memref::ViewOp::create(rewriter, loc, baseTy, memBuffer,
3225 byteOffset, innerDynSizes);
3227 privateLocal.getType());
3228 mapping.
map(privateLocal.getResult(),
result);
3232template <
typename LoopOp>
3233void ACCCGToGPULowering::processSeqLoop(LoopOp loopOp) {
3237 LLVM_DEBUG(llvm::dbgs() <<
"processing seq loop: ";
3238 loopOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3239 llvm::SmallPtrSet<Operation *, 4> preProcessedPrivateLocals;
3240 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
3241 for (
auto &bodyOp : loopOp.getBody()->getOperations()) {
3242 if (acc::PrivateLocalOp privateLocal =
3243 dyn_cast<acc::PrivateLocalOp>(&bodyOp)) {
3244 acc::PrivateType privTy =
3245 cast<acc::PrivateType>(privateLocal.getPrivatized().getType());
3247 if (
auto copies = isEligibleForSharedMemory(privateLocal, baseTy)) {
3248 processPrivateLocal(privateLocal,
copies);
3249 preProcessedPrivateLocals.insert(privateLocal.getOperation());
3257 &newLoop.getRegion(), newLoop.getRegion().begin(),
3258 loopOp.getBody()->getArgumentTypes(),
3259 SmallVector<Location>(loopOp.getBody()->getArgumentTypes().size(),
3265 assert(blockArgs.size() &&
"expected block arguments for loop");
3266 mapping.
map(blockArgs, newLoop.getBody()->getArguments());
3272 if (needsInLoopReuseBarrier(loopOp.getOperation()) &&
3274 emitGPUBarrierWorkgroup(rewriter, loopOp->getLoc());
3276 for (
auto &bodyOp : loopOp.getBody()->getOperations()) {
3277 if (preProcessedPrivateLocals.contains(&bodyOp))
3282 mapping.
map(loopOp.getResults(), newLoop.getResults());
3287 if (hasTrailingSideEffectSiblings(loopOp.getOperation()))
3288 deferredBarrierSeqLoops.push_back(loopOp.getOperation());
3290 createBarrierAfterSeqLoop(loopOp.getOperation());
3293void ACCCGToGPULowering::flushDeferredBarriersBefore(Operation *beforeOp) {
3295 SmallVector<Operation *, 4> toFlush;
3296 for (Operation *loopOp : deferredBarrierSeqLoops)
3297 if (loopOp->getBlock() == block && loopOp->isBeforeInBlock(beforeOp))
3298 toFlush.push_back(loopOp);
3299 if (toFlush.empty())
3303 for (Operation *loopOp : toFlush)
3304 createBarrierAfterSeqLoop(loopOp);
3305 deferredBarrierSeqLoops.erase(
3306 std::remove_if(deferredBarrierSeqLoops.begin(),
3307 deferredBarrierSeqLoops.end(),
3308 [&](Operation *loopOp) {
3309 return loopOp->getBlock() == block &&
3310 loopOp->isBeforeInBlock(beforeOp);
3312 deferredBarrierSeqLoops.end());
3317void ACCCGToGPULowering::processParallelOp(scf::ParallelOp parallelOp) {
3318 LLVM_DEBUG(llvm::dbgs() <<
"processing par loop: ";
3319 parallelOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3321 "requires parallel dimensions attribute");
3322 mlir::acc::GPUParallelDimsAttr pDimsAttr =
3326 assert(pDimsAttr.getArray().size() == 1 &&
3327 "expected a single par dim in acc-cg-to-gpu");
3328 assert(parallelOp.getInductionVars().size() == 1 &&
3329 "expected a single induction variable in acc-cg-to-gpu");
3331 mlir::acc::GPUParallelDimAttr parDim = pDimsAttr.getArray().front();
3333 bool savedGridStrideFlag = insideAccumulateGridStride;
3334 Value savedReductionBuf = reductionSharedBuf;
3335 if (parDim.isThreadX()) {
3337 parallelOp.getBody()->walk([&](acc::ReductionAccumulateOp accOp) {
3338 bool hasBlockDim =
false;
3339 bool hasThreadDim =
false;
3340 for (
auto d : accOp.getParDims().getArray()) {
3343 if (d.isThreadX() || d.isThreadY())
3344 hasThreadDim =
true;
3346 if (hasThreadDim && !hasBlockDim) {
3353 insideAccumulateGridStride =
true;
3357 auto processLoopBody = [&]() {
3359 for (
auto &bodyOp : parallelOp.getBody()->getOperations()) {
3360 if (bodyOp.hasTrait<OpTrait::IsTerminator>()) {
3362 flushDeferredBarriersBefore(&bodyOp);
3370 if (parDim.isSeq()) {
3371 LLVM_DEBUG(llvm::dbgs() <<
"loop: parDim: " << parDim <<
" as gpu seq\n");
3377 bool needsAtomicReduction =
false;
3378 bool hasAccumulateSibling =
false;
3379 if (scf::ParallelOp parentPar =
3380 parallelOp->getParentOfType<scf::ParallelOp>()) {
3381 if (mlir::acc::GPUParallelDimsAttr parentDims =
3383 parentDims && llvm::any_of(parentDims.getArray(),
3384 [](
auto d) { return d.isThreadX(); })) {
3385 for (
auto &op : parentPar.getBody()->getOperations()) {
3386 if (acc::ReductionAccumulateOp acc =
3387 dyn_cast<acc::ReductionAccumulateOp>(op)) {
3388 bool hasBlockDim =
false;
3389 bool hasThreadDim =
false;
3390 for (
auto d : acc.getParDims().getArray()) {
3393 if (d.isThreadX() || d.isThreadY())
3394 hasThreadDim =
true;
3396 if (hasThreadDim && !hasBlockDim)
3397 hasAccumulateSibling =
true;
3402 if (insideAccumulateGridStride || hasAccumulateSibling) {
3403 for (
auto launchArg : computeRegion.getLaunchArgs()) {
3404 if (acc::ParWidthOp pw = launchArg.getDefiningOp<acc::ParWidthOp>()) {
3405 if (pw.getParDim().isThreadX()) {
3407 needsAtomicReduction = (*cval >=
options.subgroupSize);
3409 needsAtomicReduction =
true;
3415 if (needsAtomicReduction && !reductionSharedBuf) {
3417 parallelOp.getBody()->walk([&](acc::ReductionAccumulateOp accOp) {
3418 Type t = accOp.getValue().getType();
3419 if (isa<FloatType, IntegerType>(t))
3424 needsAtomicReduction =
false;
3426 if (needsAtomicReduction && !reductionSharedBuf) {
3427 Location seqLoc = parallelOp->getLoc();
3428 gpu::AddressSpaceAttr workgroupAS = gpu::AddressSpaceAttr::get(
3429 computeRegion->getContext(),
3430 gpu::GPUDialect::getWorkgroupAddressSpace());
3432 parallelOp.getBody()->
walk([&](acc::ReductionAccumulateOp accOp) {
3433 Type t = accOp.getValue().getType();
3434 if (isa<FloatType, IntegerType>(t))
3438 assert(elemTy &&
"expected scalar reduction element type");
3440 MemRefType bufTy = MemRefType::get({
options.subgroupSize}, elemTy,
3441 AffineMap{}, workgroupAS);
3442 reductionSharedBuf = acc::GPUSharedMemoryOp::create(
3446 Value tidY = getThreadId(seqLoc, gpu::Dimension::y);
3448 if (isa<FloatType>(elemTy)) {
3449 identity = arith::ConstantOp::create(
3450 rewriter, seqLoc, elemTy, rewriter.
getFloatAttr(elemTy, 0.0));
3454 memref::StoreOp::create(rewriter, seqLoc, identity, reductionSharedBuf,
3456 createPerRowBarrier(seqLoc);
3458 processSeqLoop(parallelOp);
3459 loopReductions.push_back(parallelOp);
3461 LLVM_DEBUG(llvm::dbgs()
3462 <<
"processing loop: parDim: " << parDim <<
" as gpu par\n");
3466 Value gpuThreadId = getGPUThreadIdFor(parDim.getProcessor());
3467 mapping.
map(parallelOp.getInductionVars()[0], gpuThreadId);
3474 llvm::for_each(parallelOp.getResults(), [&](Value v) {
3475 Type valTy = v.getType();
3476 TypedAttr zeroAttr = rewriter.getZeroAttr(valTy);
3477 auto zero = arith::ConstantOp::create(rewriter, parallelOp->getLoc(),
3479 mapping.map(v, zero);
3481 loopReductions.push_back(parallelOp);
3483 insideAccumulateGridStride = savedGridStrideFlag;
3484 if (!insideAccumulateGridStride && !savedReductionBuf)
3485 reductionSharedBuf = Value();
3489static gpu::AllReduceOperation
3490getAllReduceOperation(arith::AtomicRMWKind kind) {
3492 case arith::AtomicRMWKind::addf:
3493 case arith::AtomicRMWKind::addi:
3494 return gpu::AllReduceOperation::ADD;
3495 case arith::AtomicRMWKind::mulf:
3496 case arith::AtomicRMWKind::muli:
3497 return gpu::AllReduceOperation::MUL;
3498 case arith::AtomicRMWKind::minu:
3499 return gpu::AllReduceOperation::MINUI;
3500 case arith::AtomicRMWKind::mins:
3501 return gpu::AllReduceOperation::MINSI;
3502 case arith::AtomicRMWKind::minnumf:
3503 return gpu::AllReduceOperation::MINNUMF;
3504 case arith::AtomicRMWKind::maxu:
3505 return gpu::AllReduceOperation::MAXUI;
3506 case arith::AtomicRMWKind::maxs:
3507 return gpu::AllReduceOperation::MAXSI;
3508 case arith::AtomicRMWKind::maxnumf:
3509 return gpu::AllReduceOperation::MAXNUMF;
3510 case arith::AtomicRMWKind::ori:
3511 return gpu::AllReduceOperation::OR;
3512 case arith::AtomicRMWKind::andi:
3513 return gpu::AllReduceOperation::AND;
3514 case arith::AtomicRMWKind::xori:
3515 return gpu::AllReduceOperation::XOR;
3516 case arith::AtomicRMWKind::minimumf:
3517 return gpu::AllReduceOperation::MINIMUMF;
3518 case arith::AtomicRMWKind::maximumf:
3519 return gpu::AllReduceOperation::MAXIMUMF;
3520 case arith::AtomicRMWKind::assign:
3523 llvm_unreachable(
"unsupported atomic kind");
3526void ACCCGToGPULowering::constructAtomicAccumulation(
3528 arith::AtomicRMWKind kind) {
3530 "cannot lower atomic accumulation on an stack variable");
3542 MemRefType memrefTy = cast<MemRefType>(memref.
getType());
3543 unsigned rank = memrefTy.getRank();
3544 assert(
indices.size() == rank &&
"expected one index per memref dimension");
3545 SmallVector<OpFoldResult> offsets(
indices.begin(),
indices.end());
3546 SmallVector<OpFoldResult> sizes(rank, rewriter.
getIndexAttr(1));
3547 SmallVector<OpFoldResult> strides(rank, rewriter.
getIndexAttr(1));
3548 target = memref::SubViewOp::create(rewriter, loc, memref, offsets, sizes,
3552 auto atomicUpdateOp =
3553 acc::AtomicUpdateOp::create(rewriter, loc,
target, Value());
3554 Region ®ion = atomicUpdateOp->getRegion(0);
3558 Value reductionExpr =
3560 acc::YieldOp::create(rewriter, loc, reductionExpr);
3564void ACCCGToGPULowering::createGPUAllReduceOp(
3565 Location loc, Value input, Value memref, arith::AtomicRMWKind kind,
3567 bool isPerThreadPrivateTarget) {
3568 gpu::AllReduceOperationAttr attr = gpu::AllReduceOperationAttr::get(
3569 computeRegion->getContext(), getAllReduceOperation(kind));
3570 auto allReduceOp = gpu::AllReduceOp::create(rewriter, loc, input, attr,
true);
3578 SmallVector<mlir::acc::GPUParallelDimAttr> inactiveParDims;
3579 MLIRContext *ctx = computeRegion->getContext();
3580 bool hasThreadX =
false;
3581 for (
auto parDim : parDimsAttr.getArray()) {
3582 if (parDim.isAnyBlock())
3584 if (parDim.isThreadX())
3586 if (computeRegion.getLaunchArg(parDim) ||
3588 inactiveParDims.push_back(parDim);
3594 if (!hasThreadX && !isSingleThreadWorkerLaunch())
3595 inactiveParDims.push_back(mlir::acc::GPUParallelDimAttr::threadXDim(ctx));
3596 Value predicate = emitPredicate(loc, inactiveParDims);
3603 bool isPerThreadPrivate = isPerThreadPrivateTarget ||
3604 isa_and_nonnull<memref::AllocaOp>(
3605 unwrapMemRefConversion(memref).getDefiningOp());
3608 if (predicate && !isPerThreadPrivate) {
3610 scf::IfOp::create(rewriter, loc, predicate,
false);
3611 Region &thenRegion = ifOp.getThenRegion();
3615 memref::StoreOp::create(rewriter, loc, allReduceOp, memref,
indices);
3616 if (predicate && !isPerThreadPrivate)
3621 reductionAccumValue[memref] = allReduceOp;
3624void ACCCGToGPULowering::postprocessAccumulateOp(
3625 acc::ReductionAccumulateOp op) {
3626 Location loc = op->getLoc();
3634 bool hasThreadDim =
false;
3635 SmallVector<mlir::acc::GPUParallelDimAttr> threadParDims;
3636 for (
auto parDim : op.getParDims().getArray()) {
3637 if (!parDim.isAnyBlock()) {
3638 hasThreadDim =
true;
3639 threadParDims.push_back(parDim);
3643 std::optional<arith::AtomicRMWKind> kind;
3645 FailureOr<arith::AtomicRMWKind> kindOr = getReductionKind(
3646 op.getReductionOperator(), op.getValue().getType(), loc);
3652 if (hasThreadDim && reductionSharedBuf &&
3653 op.getValue().getType() ==
3654 cast<MemRefType>(reductionSharedBuf.getType()).getElementType()) {
3655 Value val = op.getValue();
3656 Value mem = op.getMemref();
3657 Value tidY = getThreadId(loc, gpu::Dimension::y);
3658 memref::AtomicRMWOp::create(rewriter, loc, *kind, val, reductionSharedBuf,
3660 createPerRowBarrier(loc);
3662 memref::LoadOp::create(rewriter, loc, reductionSharedBuf, tidY);
3663 memref::StoreOp::create(rewriter, loc,
result, mem);
3664 reductionAccumValue[mem] =
result;
3665 }
else if (hasThreadDim) {
3666 createGPUAllReduceOp(loc, op.getValue(), op.getMemref(), *kind,
3674 bool isPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
3675 unwrapMemRefConversion(mem).getDefiningOp());
3676 if (!isPerThreadPrivate) {
3677 SmallVector<mlir::acc::GPUParallelDimAttr> predDims;
3678 for (
auto parDim : computeRegion.getLaunchParDims())
3679 if (!parDim.isAnyBlock())
3680 predDims.push_back(parDim);
3681 if (predDims.empty()) {
3682 predDims.push_back(mlir::acc::GPUParallelDimAttr::threadXDim(
3683 computeRegion->getContext()));
3685 Value predicate = emitPredicate(loc, predDims);
3687 scf::IfOp::create(rewriter, loc, predicate,
false);
3689 memref::StoreOp::create(rewriter, loc, val, mem);
3692 memref::StoreOp::create(rewriter, loc, val, mem);
3700void ACCCGToGPULowering::postprocessLoopReduction(scf::ParallelOp parLoop) {
3701 if (parLoop.getNumReductions() == 0)
3704 for (
unsigned i = 0; i < parLoop.getNumResults(); ++i) {
3705 for (Operation *user :
3707 if (acc::ReductionAccumulateOp accumulateOp =
3708 dyn_cast<acc::ReductionAccumulateOp>(user)) {
3709 postprocessAccumulateOp(accumulateOp);
3715void ACCCGToGPULowering::processExecuteRegion(scf::ExecuteRegionOp op) {
3716 LLVM_DEBUG(llvm::dbgs() <<
"processing execute region op: ";
3717 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3718 Location loc = op->getLoc();
3719 auto types = op.getResultTypes();
3720 Region &oldRegion = op.getRegion();
3722 auto executeRegionOp = scf::ExecuteRegionOp::create(rewriter, loc, types);
3723 Region ®ion = executeRegionOp.getRegion();
3727 llvm::DenseMap<Block *, Block *> blockMap;
3728 blockMap[&oldRegion.
front()] = ®ion.
front();
3732 for (
auto &oldBlock : llvm::drop_begin(oldRegion.
getBlocks())) {
3733 TypeRange argTypes = oldBlock.getArgumentTypes();
3734 size_t numArgs = argTypes.size();
3737 SmallVector<Location>(numArgs, loc));
3738 blockMap[&oldBlock] = newBlock;
3745 for (
auto [oldBlock, newBlock] :
3747 OpBuilder::InsertionGuard blockGuard(rewriter);
3749 for (
auto &bodyOp : oldBlock.getOperations()) {
3751 if (bodyOp.hasTrait<OpTrait::IsTerminator>())
3757 Operation *oldTerminator = oldBlock.getTerminator();
3759 Operation *newTerminator = rewriter.
clone(*oldTerminator, mapping);
3764 Block *newDest = blockMap.lookup(oldDest);
3765 assert(newDest &&
"Successor block must be in blockMap");
3769 mapping.
map(op->getResults(), executeRegionOp->getResults());
3773void ACCCGToGPULowering::processAccumulateOp(acc::ReductionAccumulateOp op) {
3774 LLVM_DEBUG(llvm::dbgs() <<
"processing accumulate op: " << *op <<
"\n");
3775 Value accumulateValue = op.getValue();
3776 if (reductionSharedBuf &&
3778 cast<MemRefType>(reductionSharedBuf.getType()).getElementType()) {
3779 Location loc = op->getLoc();
3780 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
3781 op.getReductionOperator(), accumulateValue.
getType(), loc);
3786 Value tidY = getThreadId(loc, gpu::Dimension::y);
3787 memref::AtomicRMWOp::create(rewriter, loc, *kind, mappedValue,
3789 createPerRowBarrier(loc);
3791 memref::LoadOp::create(rewriter, loc, reductionSharedBuf, tidY);
3792 memref::StoreOp::create(rewriter, loc,
result, memref);
3793 reductionAccumValue[memref] =
result;
3797 Operation *newOp = rewriter.
clone(*op, mapping);
3799 }
else if (isRedundantChainAccumulate(op)) {
3805 LLVM_DEBUG(llvm::dbgs() <<
" skipped: redundant chain accumulate\n");
3806 gpu::BarrierOp::create(rewriter, op->getLoc());
3810 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
3811 op.getReductionOperator(), accumulateValue.
getType(), op.getLoc());
3814 createGPUAllReduceOp(op->getLoc(), mappedValue, memref, *kind,
3823static bool isThreadVarying(Value v, ArrayRef<Value> threadIds,
3825 if (!v || !visited.insert(v).second)
3827 if (llvm::is_contained(threadIds, v))
3829 if (
auto arg = dyn_cast<BlockArgument>(v)) {
3830 Operation *owner = arg.getOwner()->getParentOp();
3831 unsigned dim = arg.getArgNumber();
3832 if (
auto loop = dyn_cast<scf::ParallelOp>(owner)) {
3833 if (dim >= loop.getLowerBound().size())
3835 return isThreadVarying(loop.getLowerBound()[dim], threadIds, visited) ||
3836 isThreadVarying(loop.getStep()[dim], threadIds, visited);
3838 if (
auto loop = dyn_cast<scf::ForOp>(owner))
3840 (isThreadVarying(loop.getLowerBound(), threadIds, visited) ||
3841 isThreadVarying(loop.getStep(), threadIds, visited));
3847 if (isa<gpu::ThreadIdOp, gpu::LaneIdOp>(def))
3849 return llvm::any_of(def->
getOperands(), [&](Value o) {
3850 return isThreadVarying(o, threadIds, visited);
3855static Value accumulatorRoot(Value v) {
3857 if (
auto cast = dyn_cast<memref::MemorySpaceCastOp>(op)) {
3858 v = cast.getSource();
3861 if (
auto viewLike = dyn_cast<ViewLikeOpInterface>(op)) {
3862 if (isa<MemRefType>(viewLike.getViewSource().getType())) {
3863 v = viewLike.getViewSource();
3874static Value matchAccumulatorUpdate(memref::StoreOp store, Value accum) {
3875 if (accumulatorRoot(store.getMemRef()) != accum)
3877 Operation *
combine = store.getValueToStore().getDefiningOp();
3878 if (!combine ||
combine->getNumOperands() != 2)
3880 for (
unsigned i = 0; i != 2; ++i) {
3881 auto load =
combine->getOperand(i).getDefiningOp<memref::LoadOp>();
3882 if (!
load || accumulatorRoot(
load.getMemRef()) != accum)
3884 if (!llvm::equal(
load.getIndices(), store.getIndices()))
3886 return combine->getOperand(1 - i);
3894static void atomicizeSharedAccumulatorUpdates(Value accum,
3895 arith::AtomicRMWKind kind,
3896 ArrayRef<Value> threadIds,
3897 RewriterBase &rewriter) {
3898 OpBuilder::InsertionGuard guard(rewriter);
3899 SmallVector<memref::StoreOp> stores;
3900 SmallVector<Value> worklist{accum};
3902 while (!worklist.empty()) {
3903 Value cur = worklist.pop_back_val();
3904 if (!seen.insert(cur).second)
3906 for (Operation *user : cur.
getUsers()) {
3907 if (
auto store = dyn_cast<memref::StoreOp>(user))
3908 stores.push_back(store);
3909 else if (isa<ViewLikeOpInterface, memref::MemorySpaceCastOp>(user))
3910 llvm::append_range(worklist, user->getResults());
3914 for (memref::StoreOp store : stores) {
3915 Value contribution = matchAccumulatorUpdate(store, accum);
3920 if (llvm::any_of(store.getIndices(), [&](Value idx) {
3921 DenseSet<Value> visited;
3922 return isThreadVarying(idx, threadIds, visited);
3925 Operation *
combine = store.getValueToStore().getDefiningOp();
3927 memref::AtomicRMWOp::create(rewriter, store.getLoc(), kind, contribution,
3928 store.getMemRef(), store.getIndices());
3930 if (combine &&
combine->use_empty())
3935void ACCCGToGPULowering::processAccumulateArrayOp(
3936 acc::ReductionAccumulateArrayOp op) {
3937 LLVM_DEBUG(llvm::dbgs() <<
"processing accumulate array op: " << *op <<
"\n");
3938 Location loc = op.getLoc();
3941 MemRefType memrefTy = dyn_cast<MemRefType>(memref.
getType());
3943 (void)accSupport.
emitNYI(loc,
"reduction: non-MemRefTy accumulate array");
3946 FailureOr<arith::AtomicRMWKind> kindOr = getReductionKind(
3947 op.getReductionOperator(), memrefTy.getElementType(), loc);
3950 arith::AtomicRMWKind kind = *kindOr;
3955 .getDefiningOp<acc::DataBoundsOp>();
3956 assert(boundsOp &&
"expected acc.bounds defining op for array accumulate");
3957 auto eraseDeadBounds = [&] {
3958 if (boundsOp->use_empty())
3962 bool hasThreadDim =
false;
3963 bool hasBlockDim =
false;
3964 for (
auto pd : op.getParDims().getArray()) {
3965 hasThreadDim |= pd.isAnyThread();
3966 hasBlockDim |= pd.isAnyBlock();
3971 if (hasBlockDim && !hasThreadDim) {
3979 bool regionLaunchesBlocks = llvm::any_of(
3980 computeRegion.getLaunchParDims(),
3981 [](mlir::acc::GPUParallelDimAttr d) { return d.isAnyBlock(); });
3982 if (!reductionHasBlockContext(op) && regionLaunchesBlocks) {
3984 loc,
"reduction: thread-only array reduction accumulate");
3996 auto storageIsThreadXPrivate = [&](Value v) ->
bool {
3997 acc::PrivateLocalOp privateLocal = getPrivateLocalForMemref(v);
3998 GPUParallelDimsAttr dims =
3999 privateLocal ? getPrivateParDims(privateLocal, computeRegion)
4000 : GPUParallelDimsAttr();
4002 if (Operation *root = unwrapMemRefConversion(v).getDefiningOp())
4006 llvm::any_of(dims.getArray(), [](
auto d) { return d.isThreadX(); });
4008 Operation *rootOp = unwrapMemRefConversion(memref).getDefiningOp();
4009 bool isSharedStorage = isa_and_nonnull<memref::AllocOp>(rootOp) ||
4010 isa_and_nonnull<acc::GPUSharedMemoryOp>(rootOp);
4011 if (
auto addrSpace = dyn_cast_if_present<gpu::AddressSpaceAttr>(
4012 memrefTy.getMemorySpace())) {
4014 addrSpace.getValue() == gpu::GPUDialect::getWorkgroupAddressSpace();
4016 bool isPerThreadPrivate =
4017 !isSharedStorage && storageIsThreadXPrivate(op.getMemref()) &&
4018 (memrefTy.hasStaticShape()
4019 ? canUseStackAlloca(memrefTy, loc,
options.maxThreadPrivateStack)
4020 : llvm::any_of(op.getParDims().getArray(),
4021 [](mlir::acc::GPUParallelDimAttr d) {
4022 return d.isThreadX();
4024 if (!isPerThreadPrivate) {
4029 SmallVector<Value> threadIds;
4030 if (Value xId = getGPUThreadIdFor(gpu::Processor::ThreadX))
4031 threadIds.push_back(xId);
4032 if (Value yId = getGPUThreadIdFor(gpu::Processor::ThreadY))
4033 threadIds.push_back(yId);
4034 if (Value zId = getGPUThreadIdFor(gpu::Processor::ThreadZ))
4035 threadIds.push_back(zId);
4036 atomicizeSharedAccumulatorUpdates(accumulatorRoot(memref), kind, threadIds,
4043 auto toIndex = [&](Value v) -> Value {
4046 return arith::IndexCastOp::create(rewriter, loc, rewriter.
getIndexType(),
4053 boundsOp.getLowerbound() ? toIndex(boundsOp.getLowerbound()) : zero;
4054 Value step = boundsOp.getStride() ? toIndex(boundsOp.getStride()) : one;
4059 if (boundsOp.getExtent()) {
4060 Value span = arith::MulIOp::create(rewriter, loc,
4061 toIndex(boundsOp.getExtent()), step);
4062 ub = arith::AddIOp::create(rewriter, loc, lb, span);
4064 assert(boundsOp.getUpperbound() &&
4065 "acc.bounds must specify an extent or upperbound");
4066 ub = arith::AddIOp::create(rewriter, loc, toIndex(boundsOp.getUpperbound()),
4071 auto forOp = scf::ForOp::create(rewriter, loc, lb, ub, step);
4073 OpBuilder::InsertionGuard guard(rewriter);
4075 Value iv = forOp.getInductionVar();
4076 SmallVector<Value>
indices{iv};
4077 if (memrefTy.getRank() > 1) {
4078 indices.resize(memrefTy.getRank());
4079 Value linearIndex = iv;
4080 for (int64_t dim = memrefTy.getRank() - 1; dim >= 0; --dim) {
4082 memrefTy.isDynamicDim(dim)
4083 ? memref::DimOp::create(rewriter, loc, memref, dim).getResult()
4085 memrefTy.getDimSize(dim))
4088 arith::RemUIOp::create(rewriter, loc, linearIndex, dimSize);
4091 arith::DivUIOp::create(rewriter, loc, linearIndex, dimSize);
4094 Value elem = memref::LoadOp::create(rewriter, loc, memref,
indices);
4095 createGPUAllReduceOp(loc, elem, memref, kind, op.getParDims(),
indices,
4102void ACCCGToGPULowering::processReductionOp(acc::ReductionInitOp op) {
4104 op.getRegion().walk<WalkOrder::PreOrder>([&](Operation *innerOp) {
4105 if (acc::YieldOp yieldOp = dyn_cast<acc::YieldOp>(innerOp)) {
4106 op.getResult().replaceAllUsesWith(mapping.
lookup(yieldOp.getOperand(0)));
4109 if (innerOp->getNumRegions() > 0) {
4113 rewriter.
clone(*innerOp, mapping);
4118void ACCCGToGPULowering::processReductionCombineOp(acc::ReductionCombineOp op) {
4119 LLVM_DEBUG(llvm::dbgs() <<
"processing reduction combine op: ";
4120 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4121 Location loc = op.getLoc();
4122 MemRefType memrefType = dyn_cast<MemRefType>(op.getSrcMemref().getType());
4123 assert(memrefType &&
"expected memref type for reduction combine op");
4124 assert(memrefType.getRank() == 0 &&
4125 "expected scalar memref type for reduction combine op");
4126 Type elTy = memrefType.getElementType();
4127 FailureOr<arith::AtomicRMWKind> kindOr =
4128 getReductionKind(op.getReductionOperator(), elTy, loc);
4131 arith::AtomicRMWKind kind = *kindOr;
4142 bool destIsPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
4143 unwrapMemRefConversion(destMemref).getDefiningOp());
4145 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
4147 for (
auto parDim : parDims) {
4148 if (parDim.isAnyBlock() && !destIsPerThreadPrivate) {
4155 memref::LoadOp::create(rewriter, loc, srcMemref,
ValueRange{});
4156 pendingCombineReloads.push_back({srcMemref, srcLoad});
4157 constructAtomicAccumulation(loc, destMemref, {}, srcLoad,
4165 auto srcLoad = memref::LoadOp::create(rewriter, loc, srcMemref,
ValueRange{});
4167 memref::LoadOp::create(rewriter, loc, destMemref,
ValueRange{});
4169 memref::StoreOp::create(rewriter, loc, combine, destMemref,
ValueRange{});
4172void ACCCGToGPULowering::processCombineRegionOp(
4173 acc::ReductionCombineRegionOp op) {
4174 LLVM_DEBUG(llvm::dbgs() <<
"processing combine region op: ";
4175 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4179 bool destIsPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
4182 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
4184 for (
auto parDim : parDims) {
4185 if (parDim.isAnyBlock() && !destIsPerThreadPrivate) {
4189 for (Operation *user : op.getSrcVar().getUsers()) {
4190 if (acc::ReductionAccumulateOp accumulateOp =
4191 dyn_cast<acc::ReductionAccumulateOp>(user)) {
4192 Location loc = accumulateOp.getLoc();
4193 FailureOr<arith::AtomicRMWKind> kind =
4194 getReductionKind(accumulateOp.getReductionOperator(),
4195 accumulateOp.getValue().getType(), loc);
4200 auto reductionLoad =
4201 memref::LoadOp::create(rewriter, loc, srcMemref,
ValueRange{});
4202 pendingCombineReloads.push_back({srcMemref, reductionLoad});
4203 constructAtomicAccumulation(loc,
4205 {}, reductionLoad, *kind);
4213 MemRefType memrefTy = cast<MemRefType>(privateMemref.
getType());
4214 if (isa<ComplexType>(memrefTy.getElementType())) {
4215 Location loc = op.getLoc();
4216 Value reductionResult =
4217 memref::LoadOp::create(rewriter, loc, privateMemref,
ValueRange{});
4218 arith::AtomicRMWKind kind = arith::AtomicRMWKind::addf;
4219 op.getRegion().walk([&](Operation *innerOp) {
4220 if (isa<complex::MulOp>(innerOp))
4221 kind = arith::AtomicRMWKind::mulf;
4223 constructAtomicAccumulation(loc,
4225 {}, reductionResult, kind);
4230 op.getRegion().walk<WalkOrder::PreOrder>([&](Operation *innerOp) {
4231 if (acc::YieldOp yieldOp = dyn_cast<acc::YieldOp>(innerOp))
4233 if (innerOp->getNumRegions() > 0) {
4237 rewriter.
clone(*innerOp, mapping);
4242void ACCCGToGPULowering::processGenericOp(Operation *op) {
4245 LLVM_DEBUG(llvm::dbgs() <<
"processing generic op, cloning: ";
4246 op->
print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4247 Operation *newOp = rewriter.
clone(*op, mapping);
4252void ACCCGToGPULowering::processGenericOpWithRegions(Operation *op) {
4254 LLVM_DEBUG(llvm::dbgs() <<
"processing generic op with regions: ";
4255 op->
print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4261 for (
auto [oldRegion, newRegion] :
4262 llvm::zip(op->
getRegions(), newOp->getRegions())) {
4264 for (
auto &oldBlock : oldRegion.
getBlocks()) {
4265 TypeRange argTypes = oldBlock.getArgumentTypes();
4266 size_t numArgs = argTypes.size();
4269 rewriter.
createBlock(&newRegion, newRegion.end(), argTypes,
4270 SmallVector<Location>(numArgs, op->
getLoc()));
4276 for (
auto &innerOp : oldBlock.getOperations()) {
4277 OpBuilder::InsertionGuard guard(rewriter);
4279 processOp(&innerOp);
4290void ACCCGToGPULowering::processOp(Operation *op) {
4291 if (isDeferredBarrierFlushPoint(op))
4292 flushDeferredBarriersBefore(op);
4296 scf::ParallelOp parallelOp = cast<scf::ParallelOp>(op);
4297 processParallelOp(parallelOp);
4298 }
else if (scf::ForOp seqLoop = dyn_cast<scf::ForOp>(op)) {
4299 processSeqLoop(seqLoop);
4300 }
else if (acc::PrivatizeOp privatize = dyn_cast<acc::PrivatizeOp>(op)) {
4301 processPrivatize(privatize);
4302 }
else if (acc::PrivateLocalOp privateLocal =
4303 dyn_cast<acc::PrivateLocalOp>(op)) {
4304 processPrivateLocal(privateLocal);
4305 }
else if (acc::PredicateRegionOp predicateRegionOp =
4306 dyn_cast<acc::PredicateRegionOp>(op)) {
4307 processPredicateRegion(predicateRegionOp);
4308 }
else if (acc::ReductionAccumulateOp accumulateOp =
4309 dyn_cast<acc::ReductionAccumulateOp>(op)) {
4310 processAccumulateOp(accumulateOp);
4311 }
else if (
auto accumulateArrayOp =
4312 dyn_cast<acc::ReductionAccumulateArrayOp>(op)) {
4313 processAccumulateArrayOp(accumulateArrayOp);
4314 }
else if (acc::ReductionInitOp reductionInitOp =
4315 dyn_cast<acc::ReductionInitOp>(op)) {
4316 processReductionOp(reductionInitOp);
4317 }
else if (acc::ReductionCombineOp reductionCombineOp =
4318 dyn_cast<acc::ReductionCombineOp>(op)) {
4319 processReductionCombineOp(reductionCombineOp);
4320 }
else if (
auto combineRegionOp =
4321 dyn_cast<acc::ReductionCombineRegionOp>(op)) {
4322 processCombineRegionOp(combineRegionOp);
4323 }
else if (acc::ReductionOp accReductionOp = dyn_cast<acc::ReductionOp>(op)) {
4324 mapping.
map(accReductionOp->getResult(0), accReductionOp.getVarPtr());
4327 LLVM_DEBUG(llvm::dbgs() <<
"skipping mapped op: " << *op <<
"\n");
4328 }
else if (isa<acc::YieldOp>(op)) {
4329 for (
auto [operand,
result] :
4333 }
else if (isa<scf::ExecuteRegionOp>(op)) {
4334 processExecuteRegion(cast<scf::ExecuteRegionOp>(op));
4336 isa<acc::OpenACCDialect>(op->
getDialect())) {
4337 processGenericOp(op);
4339 processGenericOpWithRegions(op);
4344class RemoveParWidth :
public OpRewritePattern<acc::ParWidthOp> {
4345 using OpRewritePattern<acc::ParWidthOp>::OpRewritePattern;
4346 LogicalResult matchAndRewrite(acc::ParWidthOp op,
4347 PatternRewriter &rewriter)
const override {
4348 if (Value launchArg = op.getLaunchArg()) {
4359class ACCComputeRegionToGPUPattern
4360 :
public OpRewritePattern<acc::ComputeRegionOp> {
4362 ACCComputeRegionToGPUPattern(MLIRContext *context,
4363 acc::OpenACCSupport &accSupport,
4364 const ACCCGToGPUOptions &
options)
4365 : OpRewritePattern<acc::ComputeRegionOp>(context), accSupport(accSupport),
4368 LogicalResult matchAndRewrite(acc::ComputeRegionOp op,
4369 PatternRewriter &rewriter)
const override {
4370 ACCCGToGPULowering kernelOpRewriter(op, rewriter, accSupport,
options);
4371 return kernelOpRewriter.rewrite();
4375 acc::OpenACCSupport &accSupport;
4376 const ACCCGToGPUOptions &
options;
4379class ACCCGToGPU :
public acc::impl::ACCCGToGPUBase<ACCCGToGPU> {
4381 using acc::impl::ACCCGToGPUBase<ACCCGToGPU>::ACCCGToGPUBase;
4383 void runOnOperation()
override {
4384 FunctionOpInterface funcOp = getOperation();
4385 MLIRContext *context = funcOp->getContext();
4387 assert(deviceType != mlir::acc::DeviceType::Host &&
4388 deviceType != mlir::acc::DeviceType::Multicore &&
4389 "ACCCGToGPU only supports GPU device types");
4393 WalkResult
result = funcOp->walk([](scf::ParallelOp loop) {
4394 if (!loop->getParentOfType<acc::ComputeRegionOp>() ||
4397 loop.emitOpError(
"requires an 'acc.par_dims' attribute");
4400 if (
result.wasInterrupted()) {
4401 signalPassFailure();
4406 options.deviceType = deviceType;
4407 options.maxWorkgroupSharedMemory = maxWorkgroupSharedMemory;
4408 options.maxThreadPrivateStack = maxThreadPrivateStack;
4409 options.subgroupSize = subgroupSize;
4412 std::optional<std::reference_wrapper<acc::OpenACCSupport>> cachedAnalysis =
4413 getCachedParentAnalysis<acc::OpenACCSupport>(funcOp->getParentOp());
4414 acc::OpenACCSupport &accSupport = cachedAnalysis
4415 ? cachedAnalysis->get()
4416 : getAnalysis<acc::OpenACCSupport>();
4418 RewritePatternSet patterns(context);
4419 patterns.insert<ACCComputeRegionToGPUPattern>(context, accSupport,
options);
4420 patterns.insert<RemoveParWidth>(context);
4422 target.markUnknownOpDynamicallyLegal([](Operation *) {
return true; });
4423 target.addIllegalOp<acc::ComputeRegionOp, acc::ParWidthOp>();
4424 if (
failed(applyPartialConversion(getOperation(),
target,
4425 std::move(patterns)))) {
4426 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()
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
IntegerAttr getIndexAttr(int64_t value)
IntegerAttr getI32IntegerAttr(int32_t value)
IntegerAttr getIntegerAttr(Type type, int64_t value)
FloatAttr getFloatAttr(Type type, double value)
IntegerAttr getI64IntegerAttr(int64_t value)
TypedAttr getZeroAttr(Type type)
MLIRContext * getContext() const
auto lookupOrDefault(T from) const
Lookup a mapped value within the map.
auto lookup(T from) const
Lookup a mapped value within the map.
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
bool contains(T from) const
Checks to see if a mapping for 'from' exists.
auto lookupOrNull(T from) const
Lookup a mapped value within the map.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
MLIRContext is the top-level object for a collection of MLIR operations.
RAII guard to reset the insertion point of the builder when destroyed.
This class helps build Operations.
Block::iterator getInsertionPoint() const
Returns the current insertion point of the builder.
Block * createBlock(Region *parent, Region::iterator insertPt={}, TypeRange argTypes={}, ArrayRef< Location > locs={})
Add new block with 'argTypes' arguments and set the insertion point to the end of it.
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
Block * getInsertionBlock() const
Return the block the current insertion point belongs to.
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Operation * cloneWithoutRegions(Operation &op, IRMapping &mapper)
Creates a deep copy of this operation but keep the operation regions empty.
Operation is the basic unit of execution within MLIR.
Dialect * getDialect()
Return the dialect this operation is associated with, or nullptr if the associated dialect is not loa...
Value getOperand(unsigned idx)
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
unsigned getNumSuccessors()
bool isBeforeInBlock(Operation *other)
Given an operation 'other' that is within the same parent block, return whether the current operation...
result_iterator result_begin()
Block * getBlock()
Returns the operation block that contains this operation.
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
unsigned getNumRegions()
Returns the number of regions held by this operation.
Location getLoc()
The source location the operation was defined or derived from.
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
unsigned getNumOperands()
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
void print(raw_ostream &os, const OpPrintingFlags &flags={})
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
result_iterator result_end()
operand_range getOperands()
Returns an iterator on the underlying Value's.
void setSuccessor(Block *block, unsigned index)
bool isAncestor(Operation *other)
Return true if this operation is an ancestor of the other operation.
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Block * getSuccessor(unsigned index)
result_range getResults()
This class contains a list of basic blocks and a link to the parent operation it is attached to.
BlockListType & getBlocks()
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
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.
InFlightDiagnostic & next(InFlightDiagnostic &diag)
Starts a new message part in an in-flight diagnostic.
LoopNest buildLoopNest(OpBuilder &builder, Location loc, ValueRange lbs, ValueRange ubs, ValueRange steps, ValueRange iterArgs, function_ref< ValueVector(OpBuilder &, Location, ValueRange, ValueRange)> bodyBuilder=nullptr)
Creates a perfect nest of "for" loops, i.e.
Value constantOne(OpBuilder &builder, Location loc, Type tp)
Generates a 1-valued constant of the given type.
OwningOpRef< spirv::ModuleOp > combine(ArrayRef< spirv::ModuleOp > inputModules, OpBuilder &combinedModuleBuilder, SymbolRenameListener symRenameListener)
Combines a list of SPIR-V inputModules into one.
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
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...