122#include "llvm/ADT/ArrayRef.h"
123#include "llvm/ADT/DenseMap.h"
124#include "llvm/ADT/STLExtras.h"
125#include "llvm/ADT/StringExtras.h"
126#include "llvm/ADT/Twine.h"
127#include "llvm/Support/Debug.h"
134#define GEN_PASS_DEF_ACCCGTOGPU
135#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
139#define DEBUG_TYPE "acc-cg-to-gpu"
145enum class PrivateMemScope { Thread, Worker, Gang,
None };
148static std::string getDeviceRemarkQualifier(DeviceType deviceType) {
149 switch (deviceType) {
150 case DeviceType::None:
151 case DeviceType::Star:
152 case DeviceType::Default:
156 llvm::StringRef deviceName = stringifyDeviceType(deviceType);
157 name.reserve(deviceName.size());
158 for (
char c : deviceName)
159 name.push_back(llvm::toUpper(c));
160 return name +
" GPU";
172static GPUParallelDimAttr
175 if (routineOp.getGangDimValue() ||
176 routineOp.getGangDimValue(DeviceType::Nvidia)) {
177 int64_t gangDimValue = routineOp.getGangDimValue(DeviceType::Nvidia)
178 ? *routineOp.getGangDimValue(DeviceType::Nvidia)
179 : *routineOp.getGangDimValue();
183 if (routineOp.hasGang() || routineOp.hasGang(DeviceType::Nvidia))
184 return policy.
gangDim(ctx, ParLevel::gang_dim1);
185 if (routineOp.hasWorker() || routineOp.hasWorker(DeviceType::Nvidia))
187 if (routineOp.hasVector() || routineOp.hasVector(DeviceType::Nvidia))
193static RoutineOp getRoutineOpForAccRoutineFunction(FunctionOpInterface funcOp,
196 SpecializedRoutineAttr attr = funcOp->getAttrOfType<SpecializedRoutineAttr>(
198 return symTab.
lookup<RoutineOp>(attr.getRoutine().getLeafReference());
200 RoutineInfoAttr routineInfo =
202 if (!routineInfo || routineInfo.getAccRoutines().empty())
204 return symTab.
lookup<RoutineOp>(
205 routineInfo.getAccRoutines().front().getLeafReference());
209static GPUParallelDimAttr
210getSpecializedRoutineDim(FunctionOpInterface funcOp,
212 SpecializedRoutineAttr specAttr =
213 funcOp->getAttrOfType<SpecializedRoutineAttr>(
215 assert(specAttr &&
"expected specialized routine attribute");
216 return policy.
map(funcOp->getContext(), specAttr.getLevel().getValue());
220static GPUParallelDimAttr
221getAccRoutineCallParDim(CallOpInterface callOp,
223 std::optional<CallInterfaceCallable> callee = callOp.getCallableForCallee();
226 SymbolRefAttr calleeSymbolRef = dyn_cast<SymbolRefAttr>(*callee);
227 if (!calleeSymbolRef)
229 ModuleOp moduleOp = callOp->getParentOfType<ModuleOp>();
234 FunctionOpInterface funcOp =
235 symTab.lookup<FunctionOpInterface>(calleeSymbolRef.getLeafReference());
240 return getSpecializedRoutineDim(funcOp, policy);
241 if (RoutineOp routineOp = getRoutineOpForAccRoutineFunction(funcOp, symTab))
242 return getAccRoutineParDim(routineOp, funcOp.getContext(), policy);
249 ComputeRegionOp computeRegion = op->
getParentOfType<ComputeRegionOp>();
250 assert(computeRegion &&
"missing enclosing acc.compute_region");
253 bool isInnermostParallelParent =
true;
255 bool hasNonSeqParDim =
false;
256 if (GPUParallelDimsAttr parDimsAttr =
getParDimsAttr(parentLoop)) {
257 for (GPUParallelDimAttr parDim : parDimsAttr.getArray()) {
260 hasNonSeqParDim =
true;
271 (hasNonSeqParDim || isInnermostParallelParent))
272 for (GPUParallelDimAttr parDim : computeRegion.getLaunchParDims())
274 isInnermostParallelParent =
false;
275 parentLoop = parentLoop->getParentOfType<scf::ParallelOp>();
278 if (GPUParallelDimsAttr parDimsAttr =
getParDimsAttr(computeRegion))
279 for (GPUParallelDimAttr parDim : parDimsAttr.getArray())
285static Value stripIndexCastsFromValue(
Value x) {
289 while (arith::IndexCastOp castOp = dyn_cast<arith::IndexCastOp>(op)) {
298static FailureOr<int64_t> extractIntConst(
Value x,
299 bool stripIndexCasts =
false) {
301 x = stripIndexCastsFromValue(x);
305 assert(constOp.getType().getIntOrFloatBitWidth() <= 64);
306 return constOp.value();
309 return constOp.value();
316 x = stripIndexCastsFromValue(x);
317 FailureOr<int64_t> conX = extractIntConst(x);
324static bool getPassThroughResults(
Operation *userOp,
Value trackedOperand,
326 if (ViewLikeOpInterface viewLikeOp = dyn_cast<ViewLikeOpInterface>(userOp)) {
327 if (viewLikeOp.getViewSource() == trackedOperand) {
328 passThroughResults.push_back(viewLikeOp.getViewDest());
337 if (acc::PartialEntityAccessOpInterface partialAccess =
338 dyn_cast<acc::PartialEntityAccessOpInterface>(userOp)) {
339 if (partialAccess.getBaseEntity() == trackedOperand) {
351 if (ViewLikeOpInterface viewLike = dyn_cast<ViewLikeOpInterface>(op)) {
352 if (isa<MemRefType>(viewLike.getViewSource().getType()) ||
353 isa<MemRefType>(viewLike.getViewDest().getType())) {
354 v = viewLike.getViewSource();
366 if (value.
getType() == resultType)
368 if (PointerLikeType ptrLike = dyn_cast<PointerLikeType>(value.
getType())) {
369 if (
Value casted = ptrLike.genCast(builder, loc, value, resultType))
372 if (PointerLikeType ptrLike = dyn_cast<PointerLikeType>(resultType)) {
373 if (
Value casted = ptrLike.genCast(builder, loc, value, resultType))
376 emitError(loc) <<
"unsupported pointer-like type cast from "
377 << value.
getType() <<
" to " << resultType;
383static acc::PrivateLocalOp getPrivateLocalForMemref(
Value memref);
386static GPUParallelDimsAttr
387getPrivateParDims(acc::PrivateLocalOp privateLocal,
388 acc::ComputeRegionOp computeRegion);
392static bool storageHasThreadX(acc::PrivateLocalOp privateLocal,
393 acc::ComputeRegionOp computeRegion) {
394 GPUParallelDimsAttr dims = getPrivateParDims(privateLocal, computeRegion);
395 return !dims || llvm::any_of(dims.getArray(), [](GPUParallelDimAttr d) {
396 return d.isThreadX();
409 if (GPUParallelDimsAttr parDimsAttr = privatize.getParDimsAttr())
410 return llvm::any_of(parDimsAttr.getArray(),
411 [](GPUParallelDimAttr d) { return d.isThreadX(); });
417 gpu::BarrierOp::create(builder, loc);
422 gpu::BarrierOp::create(builder, loc,
ArrayAttr{},
424 gpu::BarrierScope::Subgroup);
432 assert(rowWidth > 0 && subgroupSize % rowWidth == 0 &&
433 "row must tile the subgroup");
435 int64_t rowsPerSubgroup = subgroupSize / rowWidth;
441 Value threadY32 = arith::IndexCastOp::create(builder, loc, i32Ty, threadY);
444 Value rowIndex = arith::AndIOp::create(builder, loc, threadY32, rowMask);
446 Value laneBase = arith::MulIOp::create(builder, loc, rowIndex, width);
450 Value mask = arith::ShLIOp::create(builder, loc, laneBits, laneBase);
451 NVVM::SyncWarpOp::create(builder, loc, mask);
455class ACCCGToGPULowering {
457 explicit ACCCGToGPULowering(acc::ComputeRegionOp computeRegion,
458 RewriterBase &rewriter,
459 acc::OpenACCSupport &accSupport,
460 const ACCCGToGPUOptions &options)
461 : rewriter(rewriter), computeRegion(computeRegion),
462 accSupport(accSupport), options(options),
464 options.maxWorkgroupSharedMemory,
470 gpu::LaunchOp getLaunch()
const {
return launch; }
472 bool hasFailed =
false;
473 bool insideAccumulateGridStride =
false;
474 Value reductionSharedBuf;
477 llvm::DenseMap<Value, Value> reductionAccumValue;
479 llvm::SmallVector<std::pair<Value, memref::LoadOp>> pendingCombineReloads;
483 void processParallelOp(scf::ParallelOp parallelOp);
485 template <
typename LoopOp>
486 void processSeqLoop(LoopOp loopOp);
488 void processPredicateRegion(acc::PredicateRegionOp interOp);
491 processPrivateLocal(acc::PrivateLocalOp privateLocal,
492 std::optional<int64_t> sharedMemCopies = std::nullopt);
494 Value processPrivatize(acc::PrivatizeOp privatize);
496 void processExecuteRegion(scf::ExecuteRegionOp op);
498 void processAccumulateOp(acc::ReductionAccumulateOp op);
500 void processAccumulateArrayOp(acc::ReductionAccumulateArrayOp op);
502 void processReductionOp(acc::ReductionInitOp op);
504 void processReductionCombineOp(acc::ReductionCombineOp op);
506 void processCombineRegionOp(acc::ReductionCombineRegionOp op);
508 void processGenericOp(Operation *op);
510 void processGenericOpWithRegions(Operation *op);
512 void processOp(Operation *op);
515 void constructAtomicAccumulation(Location loc, Value memref,
517 arith::AtomicRMWKind kind);
520 FailureOr<arith::AtomicRMWKind> getReductionKind(acc::ReductionOperator redOp,
521 Type type, Location loc);
525 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
526 SmallVector<mlir::acc::GPUParallelDimAttr>>
527 computeActiveAndInactiveParDims(Operation *op,
Block *block);
531 emitPredicate(Location loc,
532 SmallVector<mlir::acc::GPUParallelDimAttr> &inactiveParDims);
536 std::optional<int64_t>
537 isEligibleForSharedMemory(acc::PrivateLocalOp privateLocal,
541 bool tryAllocateSharedMemory(int64_t bytes);
544 int64_t getElementSizeInBytes(Location loc, Type elementType)
const;
547 bool canUseStackAlloca(MemRefType baseTy, Location loc,
548 int64_t maxThreadPrivateStack)
const;
551 void createBarrier(Location loc, mlir::acc::GPUParallelDimsAttr parDimsAttr);
557 void createPerRowBarrier(Location loc);
561 void createBarrierAfterSeqLoop(Operation *loopOp);
564 void flushDeferredBarriersBefore(Operation *beforeOp);
567 bool mayWriteSharedMemory(Operation *loopOp);
570 PrivateMemScope getPrivateMemScope(acc::PrivatizeOp privatizeOp);
573 PrivateMemScope getPrivateScopeForMemref(Value memref);
576 acc::PrivatizeOp getPrivatizeForMemref(Value memref);
580 PrivateMemScope needsPreStoreReuseBarrier(acc::PredicateRegionOp interOp);
583 void createGPUAllReduceOp(Location loc, Value input, Value memref,
584 arith::AtomicRMWKind kind,
585 mlir::acc::GPUParallelDimsAttr parDimsAttr,
587 bool isPerThreadPrivateTarget =
false);
590 void postprocessAccumulateOp(acc::ReductionAccumulateOp op);
593 void postprocessLoopReduction(scf::ParallelOp parLoop);
599 llvm::DenseMap<gpu::Processor, Value> &ids,
600 llvm::DenseMap<gpu::Processor, Value> &dims) {
601 ids[gpu::Processor::BlockX] = gpu::BlockIdOp::create(
602 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
603 ids[gpu::Processor::BlockY] = gpu::BlockIdOp::create(
604 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
605 ids[gpu::Processor::BlockZ] = gpu::BlockIdOp::create(
606 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
607 ids[gpu::Processor::ThreadX] = gpu::ThreadIdOp::create(
608 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
609 ids[gpu::Processor::ThreadY] = gpu::ThreadIdOp::create(
610 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
611 ids[gpu::Processor::ThreadZ] = gpu::ThreadIdOp::create(
612 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
613 dims[gpu::Processor::BlockX] = gpu::GridDimOp::create(
614 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
615 dims[gpu::Processor::BlockY] = gpu::GridDimOp::create(
616 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
617 dims[gpu::Processor::BlockZ] = gpu::GridDimOp::create(
618 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
619 dims[gpu::Processor::ThreadX] = gpu::BlockDimOp::create(
620 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
621 dims[gpu::Processor::ThreadY] = gpu::BlockDimOp::create(
622 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
623 dims[gpu::Processor::ThreadZ] = gpu::BlockDimOp::create(
624 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
629 BlockArgument getOrAppendInsBlockArg(Value outside) {
630 if (std::optional<BlockArgument> blockArg =
631 computeRegion.getBlockArg(outside)) {
634 return computeRegion.appendInputArg(outside);
638 void preparePrivatizeExtentInsOperands() {
639 computeRegion.walk([&](acc::PrivateLocalOp privateLocal) {
640 acc::PrivatizeOp privatizeOp =
642 if (privatizeOp->getParentOfType<acc::ComputeRegionOp>() == computeRegion)
644 for (Value extent : privatizeOp.getDynamicSizes())
645 getOrAppendInsBlockArg(extent);
651 resolvePrivateLocalDynamicExtents(acc::PrivateLocalOp privateLocal) {
652 acc::PrivatizeOp privatizeOp =
getPrivatizeOp(privateLocal, computeRegion);
653 SmallVector<Value> extents;
654 for (Value extent : privatizeOp.getDynamicSizes()) {
655 if (std::optional<BlockArgument> blockArg =
656 computeRegion.getBlockArg(extent)) {
657 extents.push_back(mapping.lookupOrDefault(*blockArg));
660 extents.push_back(mapping.lookupOrDefault(extent));
665 RewriterBase &rewriter;
666 acc::ComputeRegionOp computeRegion;
668 acc::OpenACCSupport &accSupport;
669 const ACCCGToGPUOptions &options;
670 gpu::LaunchOp launch;
672 llvm::SmallVector<scf::ParallelOp> loopReductions;
673 llvm::DenseMap<gpu::Processor, Value> threadIdMap;
674 llvm::DenseMap<gpu::Processor, Value> dimensionMap;
676 bool hasThreadYReduction =
false;
678 bool hasThreadXReduction =
false;
680 bool hasThreadLevelArrayReduction =
false;
682 bool hasThreadLevelRoutineCall =
false;
684 bool hasThreadYBarrier =
false;
687 llvm::DenseMap<Type, Value> privatizeBroadcastCache;
689 int64_t staticBlockDimX = 1024;
690 int64_t staticBlockDimY = 1024;
691 int64_t staticBlockDimZ = 1024;
696 bool isSingleThreadWorkerLaunch()
const {
697 return staticBlockDimX == 1 && staticBlockDimZ == 1 &&
698 staticBlockDimY > 1 && staticBlockDimY <= options.subgroupSize;
705 bool isWorkerOnlyShuffleLaunch()
const {
706 return hasThreadYReduction && !hasThreadXReduction &&
707 !hasThreadLevelArrayReduction && staticBlockDimZ == 1 &&
708 staticBlockDimY > 1 && staticBlockDimY <= options.subgroupSize &&
709 options.subgroupSize % staticBlockDimX == 0;
712 acc::DefaultACCToGPUMappingPolicy defaultPolicy;
713 SharedMemoryBudget sharedMemBudget;
714 SmallVector<std::string> sharedMemPrivateVarNames;
715 llvm::SmallVector<Operation *, 4> deferredBarrierSeqLoops;
717 Value getThreadId(Location loc, gpu::Dimension dim) {
718 return gpu::ThreadIdOp::create(rewriter, loc, rewriter.getIndexType(), dim);
721 Value getBlockDim(Location loc, gpu::Dimension dim) {
722 return gpu::BlockDimOp::create(rewriter, loc, rewriter.getIndexType(), dim);
726 Value getGPUThreadIdFor(gpu::Processor proc) {
731 Value getGPUSizeFor(gpu::Processor proc) {
732 return getGPUSize(proc, getLaunch(), dimensionMap);
737 Type elementType)
const {
738 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
739 if (std::optional<acc::TypeSizeAndAlignment> sizeAndAlignment =
741 return sizeAndAlignment->first.getFixedValue();
744 llvm::raw_string_ostream os(msg);
745 os <<
"element size computation for unsupported type: " << elementType;
746 (void)accSupport.
emitNYI(loc, os.str());
750bool ACCCGToGPULowering::canUseStackAlloca(
751 MemRefType baseTy, Location loc, int64_t maxThreadPrivateStack)
const {
752 for (int64_t dim : baseTy.getShape())
753 if (dim == ShapedType::kDynamic)
755 int64_t elementSize = getElementSizeInBytes(loc, baseTy.getElementType());
756 int64_t numElements = 1;
757 for (int64_t dim : baseTy.getShape()) {
758 if (numElements > maxThreadPrivateStack / std::max<int64_t>(dim, 1))
762 return elementSize * numElements < maxThreadPrivateStack;
770static bool reductionHasBlockContext(acc::ReductionAccumulateArrayOp accArr) {
771 auto hasBlock = [](mlir::acc::GPUParallelDimsAttr parDims) {
772 return parDims && llvm::any_of(parDims.getArray(),
773 [](
auto pd) { return pd.isAnyBlock(); });
775 if (hasBlock(accArr.getParDimsAttr()))
777 for (scf::ParallelOp loop = accArr->getParentOfType<scf::ParallelOp>(); loop;
778 loop = loop->getParentOfType<scf::ParallelOp>()) {
788static acc::ReductionAccumulateArrayOp perThreadArrayReductionAccum(Value v) {
789 SmallVector<Value> worklist{v};
791 while (!worklist.empty()) {
792 Value cur = worklist.pop_back_val();
793 if (!seen.insert(cur).second)
795 for (Operation *user : cur.
getUsers()) {
796 if (acc::ReductionAccumulateArrayOp accArr =
797 dyn_cast<acc::ReductionAccumulateArrayOp>(user)) {
798 bool hasThread =
false;
799 for (
auto pd : accArr.getParDims().getArray())
800 hasThread |= pd.isAnyThread();
801 if (hasThread && reductionHasBlockContext(accArr))
805 SmallVector<Value> through;
806 if (getPassThroughResults(user, cur, through))
807 worklist.append(through.begin(), through.end());
808 else if (isa<ViewLikeOpInterface>(user))
809 worklist.append(user->result_begin(), user->result_end());
818static void initPerThreadArrayAccum(OpBuilder &
b, Location loc, Value alloca,
820 arith::AtomicRMWKind kind) {
821 assert(baseTy.getRank() > 0 && baseTy.hasStaticShape() &&
822 "per-thread array reduction accumulator must be static ranked");
828 auto buildLoopNest = [&](
auto &&self,
unsigned dim) ->
void {
829 if (dim == baseTy.getRank()) {
830 memref::StoreOp::create(
b, loc, ident, alloca,
indices);
835 auto forOp = scf::ForOp::create(
b, loc, lb, ub, step);
836 OpBuilder::InsertionGuard g(
b);
837 b.setInsertionPoint(forOp.getBody()->getTerminator());
838 indices.push_back(forOp.getInductionVar());
845std::optional<int64_t>
846ACCCGToGPULowering::isEligibleForSharedMemory(acc::PrivateLocalOp privateLocal,
851 if (perThreadArrayReductionAccum(privateLocal.getResult()) &&
852 storageHasThreadX(privateLocal, computeRegion))
854 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
856 privateLocal, computeRegion, module, defaultPolicy, &accSupport);
857 if (
failed(isCandidate)) {
861 if (!isCandidate.value())
863 std::optional<int64_t> upperBound =
865 module, defaultPolicy);
866 assert(upperBound &&
"candidate private_local must have an upper bound");
867 int64_t elementSize =
868 getElementSizeInBytes(privateLocal.getLoc(), baseTy.getElementType());
869 int64_t numElements = 1;
870 for (int64_t dim : baseTy.getShape())
872 return *upperBound / (elementSize * numElements);
875bool ACCCGToGPULowering::tryAllocateSharedMemory(int64_t bytes) {
879FailureOr<arith::AtomicRMWKind>
880ACCCGToGPULowering::getReductionKind(acc::ReductionOperator redOp, Type type,
882 if (std::optional<arith::AtomicRMWKind> kind =
887 llvm::raw_string_ostream os(msg);
888 os <<
"reduction operator (" << redOp <<
") for type " << type;
889 (void)accSupport.
emitNYI(loc, os.str());
893LogicalResult ACCCGToGPULowering::rewrite() {
899 computeRegion->walk([&](acc::ReductionAccumulateOp op) {
900 for (
auto parDim : op.getParDimsAttr().getArray()) {
901 hasThreadXReduction |= parDim.isThreadX();
902 hasThreadYReduction |= parDim.isThreadY();
905 computeRegion->walk([&](acc::ReductionAccumulateArrayOp op) {
906 for (
auto parDim : op.getParDimsAttr().getArray()) {
907 if (!parDim.isAnyBlock())
908 hasThreadLevelArrayReduction =
true;
916 computeRegion->walk([&](CallOpInterface callOp) -> WalkResult {
917 if (mlir::acc::GPUParallelDimAttr parDim =
918 getAccRoutineCallParDim(callOp, defaultPolicy)) {
919 if (parDim.isThreadX() || parDim.isThreadY()) {
920 hasThreadLevelRoutineCall =
true;
927 Location loc = computeRegion->getLoc();
930 auto launchArgument = [&](gpu::Processor processor) -> Value {
931 mlir::acc::GPUParallelDimAttr parDim = mlir::acc::GPUParallelDimAttr::get(
932 computeRegion->getContext(), processor);
933 std::optional<Value> maybeLaunchArg =
934 computeRegion.getKnownLaunchArg(parDim);
935 LLVM_DEBUG(llvm::dbgs() <<
"ACCCGToGPU: launch-arg: "
936 <<
" parDim: " << parDim <<
" gpu: " << processor
938 << maybeLaunchArg.value_or(constantOne) <<
"\n");
942 maybeLaunchArg.value_or(constantOne));
944 LLVM_DEBUG(llvm::dbgs() <<
"ACCCGToGPU: creating gpu launch op: \n");
948 auto mapLaunchArguments = [&](gpu::Processor processor, Value launchArg) {
949 mlir::acc::GPUParallelDimAttr parDim = mlir::acc::GPUParallelDimAttr::get(
950 computeRegion->getContext(), processor);
951 std::optional<Value> kernelArg = computeRegion.getLaunchArg(parDim);
953 mapping.
map(computeRegion.gpuParWidth(processor), launchArg);
956 llvm::StringRef blockDimXName =
"blockDim.x";
957 llvm::StringRef blockDimYName =
"blockDim.y";
958 std::string deviceLabel = getDeviceRemarkQualifier(
options.deviceType);
960 if (!computeRegion->getParentOfType<gpu::GPUFuncOp>()) {
961 Value blockDimX = launchArgument(gpu::Processor::ThreadX);
964 staticBlockDimX = bdxVal.getSExtValue();
965 Value blockDimY = launchArgument(gpu::Processor::ThreadY);
968 staticBlockDimY = bdyVal.getSExtValue();
969 Value blockDimZ = launchArgument(gpu::Processor::ThreadZ);
972 staticBlockDimZ = bdzVal.getSExtValue();
973 Value gridDimX = launchArgument(gpu::Processor::BlockX);
974 Value gridDimY = launchArgument(gpu::Processor::BlockY);
975 Value gridDimZ = launchArgument(gpu::Processor::BlockZ);
981 auto getName = [&](Value val) -> std::string {
983 return name.empty() ?
"(*)" : name;
985 bool isEffectivelySerial =
986 sameEffectiveValue(blockDimX, 1) &&
987 sameEffectiveValue(blockDimY, 1) &&
988 sameEffectiveValue(blockDimZ, 1) && sameEffectiveValue(gridDimX, 1) &&
989 sameEffectiveValue(gridDimY, 1) && sameEffectiveValue(gridDimZ, 1);
990 return (llvm::Twine(
"Generating ") +
991 llvm::Twine(isEffectivelySerial ?
"serial " :
"") + deviceLabel +
992 " code with gridDim=" + getName(gridDimX) +
"x" +
993 getName(gridDimY) +
"x" + getName(gridDimZ) +
994 " blockDim=" + getName(blockDimX) +
"x" + getName(blockDimY) +
995 "x" + getName(blockDimZ))
1000 if (mlir::Value streamValue = computeRegion.getStream()) {
1001 LLVM_DEBUG(llvm::dbgs()
1002 <<
"\nDEBUG: Creating async gpu.launch with stream: "
1003 << streamValue <<
"\n");
1004 launch = gpu::LaunchOp::create(
1005 rewriter, loc, gridDimX, gridDimY, gridDimZ, blockDimX, blockDimY,
1011 launch.getAsyncDependenciesMutable().append(streamValue);
1013 LLVM_DEBUG(llvm::dbgs()
1014 <<
"\nDEBUG: No stream, creating sync gpu.launch\n");
1015 launch = gpu::LaunchOp::create(rewriter, loc, gridDimX, gridDimY,
1016 gridDimZ, blockDimX, blockDimY, blockDimZ);
1021 if (
auto kernelFuncName = computeRegion.getKernelFuncNameAttr())
1022 launch.setFunctionAttr(kernelFuncName);
1023 if (
auto kernelModuleName = computeRegion.getKernelModuleNameAttr())
1024 launch.setModuleAttr(kernelModuleName);
1027 gpu::TerminatorOp::create(rewriter, loc);
1029 mapLaunchArguments(gpu::Processor::BlockX,
1030 gpu::GridDimOp::create(rewriter, loc,
1032 gpu::Dimension::x));
1033 mapLaunchArguments(gpu::Processor::BlockY,
1034 gpu::GridDimOp::create(rewriter, loc,
1036 gpu::Dimension::y));
1037 mapLaunchArguments(gpu::Processor::BlockZ,
1038 gpu::GridDimOp::create(rewriter, loc,
1040 gpu::Dimension::z));
1041 mapLaunchArguments(gpu::Processor::ThreadX,
1042 gpu::BlockDimOp::create(rewriter, loc,
1044 gpu::Dimension::x));
1045 mapLaunchArguments(gpu::Processor::ThreadY,
1046 gpu::BlockDimOp::create(rewriter, loc,
1048 gpu::Dimension::y));
1049 mapLaunchArguments(gpu::Processor::ThreadZ,
1050 gpu::BlockDimOp::create(rewriter, loc,
1052 gpu::Dimension::z));
1057 OpBuilder::InsertionGuard guard(rewriter);
1060 mapLaunchArguments(gpu::Processor::BlockX,
1061 dimensionMap[gpu::Processor::BlockX]);
1062 mapLaunchArguments(gpu::Processor::BlockY,
1063 dimensionMap[gpu::Processor::BlockY]);
1064 mapLaunchArguments(gpu::Processor::BlockZ,
1065 dimensionMap[gpu::Processor::BlockZ]);
1066 mapLaunchArguments(gpu::Processor::ThreadX,
1067 dimensionMap[gpu::Processor::ThreadX]);
1068 mapLaunchArguments(gpu::Processor::ThreadY,
1069 dimensionMap[gpu::Processor::ThreadY]);
1070 mapLaunchArguments(gpu::Processor::ThreadZ,
1071 dimensionMap[gpu::Processor::ThreadZ]);
1076 preparePrivatizeExtentInsOperands();
1077 Block *body = computeRegion.getBody();
1078 unsigned numLaunchArgs = computeRegion.getLaunchArgs().size();
1079 ValueRange inputArgs = computeRegion.getInputArgs();
1083 assert(computeRegion.getRegion().hasOneBlock() &&
1084 "compute region only supports one block region for now");
1086 for (
auto &op : computeRegion.getRegion().getBlocks().front().getOperations())
1089 for (
auto &parLoop : loopReductions)
1090 postprocessLoopReduction(parLoop);
1094 if (!pendingCombineReloads.empty() && launch) {
1095 DominanceInfo domInfo(launch);
1096 for (
auto &[slot, loadOp] : pendingCombineReloads) {
1097 llvm::DenseMap<Value, Value>::iterator it =
1098 reductionAccumValue.find(slot);
1099 if (it == reductionAccumValue.end())
1101 if (!domInfo.dominates(it->second, loadOp.getOperation()))
1108 const int64_t subgroupSize =
options.subgroupSize;
1109 const int64_t subgroupAlignMask = subgroupSize - 1;
1117 bool needsThreadXAlign =
false;
1118 bool needsThreadYAlign =
false;
1119 bool alignThreadXReduction =
1125 auto classifyAllReduce = [&](gpu::AllReduceOp allReduce) {
1126 bool hasThreadX =
false;
1127 bool hasThreadY =
false;
1129 hasThreadX |= parDim.isThreadX();
1130 hasThreadY |= parDim.isThreadY();
1132 if (hasThreadX && alignThreadXReduction)
1133 needsThreadXAlign =
true;
1134 else if (hasThreadY)
1135 needsThreadYAlign =
true;
1138 launch.walk(classifyAllReduce);
1140 launch.walk([&](func::CallOp callOp) {
1141 if (gpu::GPUFuncOp callee =
1142 callOp->getParentOfType<ModuleOp>().lookupSymbol<gpu::GPUFuncOp>(
1143 callOp.getCallee()))
1144 callee.walk(classifyAllReduce);
1147 bool isShuffleEnabled = needsThreadXAlign || needsThreadYAlign;
1149 std::optional<int64_t> constBlockDimX =
1151 std::optional<int64_t> constBlockDimY =
1153 std::optional<int64_t> constBlockDimZ =
1161 bool skipAlign = constBlockDimX && constBlockDimY && constBlockDimZ &&
1162 *constBlockDimX > 1 && *constBlockDimX < subgroupSize &&
1163 *constBlockDimY == 1 && *constBlockDimZ == 1;
1168 if (isSingleThreadWorkerLaunch())
1170 if (needsThreadYAlign && !needsThreadXAlign && !hasThreadYBarrier &&
1171 isWorkerOnlyShuffleLaunch())
1174 if ((isShuffleEnabled || hasThreadYBarrier) && !skipAlign) {
1177 Value curBlockDimX = launch.getBlockSizeX();
1178 Value curBlockDimY = launch.getBlockSizeY();
1179 Value curBlockDimZ = launch.getBlockSizeZ();
1183 auto getName = [&](Value val) -> std::string {
1185 return name.empty() ?
"(*)" : name;
1187 std::string blockDimXValStr = getName(curBlockDimX);
1188 std::string blockDimYValStr = getName(curBlockDimY);
1189 llvm::StringRef kind =
1190 isShuffleEnabled ?
"Shuffle reduction" :
"ThreadY barrier";
1191 return (llvm::Twine(kind) +
1192 " is generated while adjusting the number of threads into "
1194 llvm::Twine(subgroupSize) +
".\n\t" + blockDimXName +
": `" +
1195 blockDimXValStr +
"` to `((" + blockDimXValStr +
" + " +
1196 llvm::Twine(subgroupAlignMask) +
") / " +
1197 llvm::Twine(subgroupSize) +
") * " + llvm::Twine(subgroupSize) +
1198 "`\n" +
"\t" + blockDimYName +
": `" + blockDimYValStr +
1199 "` to `max(1, (new-" + blockDimXName +
" * " + blockDimYValStr +
1200 ") / new-" + blockDimXName +
")`")
1209 Value newBlockDimX, newBlockDimY, newBlockDimZ;
1210 if (constBlockDimX && constBlockDimY && constBlockDimZ) {
1211 int64_t bdx = *constBlockDimX;
1212 int64_t bdy = *constBlockDimY;
1213 int64_t bdz = *constBlockDimZ;
1214 int64_t alignedBdx =
1215 ((bdx + subgroupAlignMask) / subgroupSize) * subgroupSize;
1216 int64_t numXYThreads = bdx * bdy;
1217 int64_t numThreads = numXYThreads * bdz;
1218 int64_t newBdy = std::max<int64_t>(1, numXYThreads / alignedBdx);
1220 std::max<int64_t>(1, numThreads / (alignedBdx * newBdy));
1227 Value numXYThreads =
1228 arith::MulIOp::create(rewriter, loc, curBlockDimX, curBlockDimY);
1230 arith::MulIOp::create(rewriter, loc, numXYThreads, curBlockDimZ);
1234 Value cstSubgroupSize =
1237 arith::AddIOp::create(rewriter, loc, curBlockDimX, cstMask);
1238 Value subgroupsRequired =
1239 arith::DivUIOp::create(rewriter, loc, padded, cstSubgroupSize);
1240 newBlockDimX = arith::MulIOp::create(rewriter, loc, subgroupsRequired,
1244 arith::DivUIOp::create(rewriter, loc, numXYThreads, newBlockDimX);
1246 newBlockDimY = arith::MaxUIOp::create(rewriter, loc, cst1, quotient);
1248 Value newNumXYThreads =
1249 arith::MulIOp::create(rewriter, loc, newBlockDimX, newBlockDimY);
1251 arith::DivUIOp::create(rewriter, loc, numThreads, newNumXYThreads);
1252 newBlockDimZ = arith::MaxUIOp::create(rewriter, loc, cst1, quotient);
1255 launch.getBlockSizeXMutable().assign(newBlockDimX);
1256 launch.getBlockSizeYMutable().assign(newBlockDimY);
1257 launch.getBlockSizeZMutable().assign(newBlockDimZ);
1264 if (!sharedMemPrivateVarNames.empty()) {
1266 return (llvm::Twine(
"GPU shared memory used for ") +
1267 llvm::join(sharedMemPrivateVarNames,
","))
1272 rewriter.
eraseOp(computeRegion);
1288static bool isRedundantChainAccumulate(acc::ReductionAccumulateOp op) {
1289 Value memref = op.getMemref();
1290 memref::LoadOp loadOp = op.getValue().
getDefiningOp<memref::LoadOp>();
1291 if (!loadOp || loadOp.getMemRef() != memref)
1293 for (Operation *user : memref.
getUsers()) {
1294 acc::ReductionCombineOp combineOp = dyn_cast<acc::ReductionCombineOp>(user);
1295 if (!combineOp || combineOp.getDestMemref() != memref)
1297 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
1299 if (llvm::any_of(parDims, [](mlir::acc::GPUParallelDimAttr d) {
1300 return d.isAnyBlock();
1308static GPUParallelDimsAttr
1309getPrivateParDims(acc::PrivateLocalOp privateLocal,
1310 acc::ComputeRegionOp computeRegion) {
1313 if (acc::PrivatizeOp privatize =
getPrivatizeOp(privateLocal, computeRegion))
1314 return privatize.getParDimsAttr();
1319static bool isThreadYPrivate(acc::PrivateLocalOp privateLocal,
bool allowBlock,
1320 acc::ComputeRegionOp computeRegion) {
1323 GPUParallelDimsAttr parDims = getPrivateParDims(privateLocal, computeRegion);
1326 return llvm::any_of(parDims.getArray(),
1327 [](
auto dim) { return dim.isThreadY(); }) &&
1328 llvm::all_of(parDims.getArray(), [=](
auto dim) {
1329 return dim.isThreadY() || (allowBlock && dim.isAnyBlock());
1333struct ThreadYBroadeningInfo {
1334 bool hasActiveWorkerCombine =
false;
1335 bool hasExplicitInactiveCombine =
false;
1336 bool hasBroadeningConflict =
false;
1337 Operation *diagnosticOp =
nullptr;
1339 void merge(
const ThreadYBroadeningInfo &other) {
1340 hasActiveWorkerCombine |= other.hasActiveWorkerCombine;
1341 hasExplicitInactiveCombine |= other.hasExplicitInactiveCombine;
1342 hasBroadeningConflict |= other.hasBroadeningConflict;
1344 diagnosticOp = other.diagnosticOp;
1349static bool hasUnsafeEffectsWhenBroadening(Operation *op) {
1350 if (
auto effectOp = dyn_cast<MemoryEffectOpInterface>(op)) {
1351 SmallVector<MemoryEffects::EffectInstance> effects;
1352 effectOp.getEffects(effects);
1353 return llvm::any_of(effects, [](
const auto &effect) {
1354 return !isa<MemoryEffects::Read>(effect.getEffect());
1357 return !op->
hasTrait<OpTrait::HasRecursiveMemoryEffects>();
1362static bool isFedByInnerBlockCombine(acc::PrivateLocalOp accumulator,
1363 Operation *selfCombine) {
1366 for (Operation *user : accumulator.getResult().getUsers()) {
1367 if (user == selfCombine)
1369 auto combineOp = dyn_cast<acc::ReductionCombineOp>(user);
1371 unwrapMemRefConversion(combineOp.getDestMemref()).getDefiningOp() !=
1372 accumulator.getOperation())
1374 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
1376 if (llvm::any_of(parDims, [](mlir::acc::GPUParallelDimAttr d) {
1377 return d.isAnyBlock();
1385static void classifyThreadYCombine(ThreadYBroadeningInfo &info,
1386 Operation *combineOp, Value src, Value dest,
1387 ArrayRef<GPUParallelDimAttr> parDims,
1388 acc::ComputeRegionOp computeRegion) {
1389 bool hasThreadY = llvm::any_of(
1390 parDims, [](GPUParallelDimAttr parDim) {
return parDim.isThreadY(); });
1391 bool hasBlock = llvm::any_of(
1392 parDims, [](GPUParallelDimAttr parDim) {
return parDim.isAnyBlock(); });
1393 acc::PrivateLocalOp srcPrivate =
1394 unwrapMemRefConversion(src).getDefiningOp<acc::PrivateLocalOp>();
1395 acc::PrivateLocalOp destPrivate =
1396 unwrapMemRefConversion(dest).getDefiningOp<acc::PrivateLocalOp>();
1397 bool hasPrivateDest = isa<acc::ReductionCombineOp>(combineOp) && srcPrivate &&
1401 if (hasThreadY && hasBlock &&
1402 isThreadYPrivate(srcPrivate, hasPrivateDest, computeRegion)) {
1403 info.hasActiveWorkerCombine =
true;
1410 if (hasThreadY && hasBlock &&
1411 isThreadYPrivate(srcPrivate,
true, computeRegion) &&
1412 isFedByInnerBlockCombine(srcPrivate, combineOp)) {
1413 info.hasActiveWorkerCombine =
true;
1417 info.hasExplicitInactiveCombine =
true;
1418 if (!info.diagnosticOp)
1419 info.diagnosticOp = combineOp;
1425static ThreadYBroadeningInfo
1426analyzeThreadYBroadening(
Block &predicateBlock,
1427 acc::ComputeRegionOp computeRegion) {
1428 ThreadYBroadeningInfo info;
1429 for (Operation &nestedOp : predicateBlock) {
1430 if (acc::PredicateRegionOp nestedPredicate =
1431 dyn_cast<acc::PredicateRegionOp>(nestedOp)) {
1432 ThreadYBroadeningInfo nestedInfo = analyzeThreadYBroadening(
1433 nestedPredicate.getRegion().front(), computeRegion);
1434 info.hasActiveWorkerCombine |= nestedInfo.hasActiveWorkerCombine;
1437 if (acc::ReductionCombineOp combineOp =
1438 dyn_cast<acc::ReductionCombineOp>(nestedOp)) {
1439 classifyThreadYCombine(
1440 info, combineOp, combineOp.getSrcMemref(), combineOp.getDestMemref(),
1444 if (acc::ReductionCombineRegionOp combineRegionOp =
1445 dyn_cast<acc::ReductionCombineRegionOp>(nestedOp)) {
1446 classifyThreadYCombine(info, combineRegionOp, combineRegionOp.getSrcVar(),
1447 combineRegionOp.getDestVar(),
1452 if (nestedOp.getNumRegions() != 0) {
1453 if (hasUnsafeEffectsWhenBroadening(&nestedOp)) {
1454 info.hasBroadeningConflict =
true;
1455 if (!info.diagnosticOp)
1456 info.diagnosticOp = &nestedOp;
1458 for (Region ®ion : nestedOp.getRegions())
1459 for (
Block &nestedBlock : region)
1460 info.merge(analyzeThreadYBroadening(nestedBlock, computeRegion));
1463 if (hasUnsafeEffectsWhenBroadening(&nestedOp)) {
1464 info.hasBroadeningConflict =
true;
1465 if (!info.diagnosticOp)
1466 info.diagnosticOp = &nestedOp;
1472std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
1473 SmallVector<mlir::acc::GPUParallelDimAttr>>
1474ACCCGToGPULowering::computeActiveAndInactiveParDims(Operation *op,
1476 MLIRContext *ctx = computeRegion->getContext();
1478 mlir::acc::GPUParallelDimAttr routineParDim;
1480 FunctionOpInterface funcOp =
1481 computeRegion->getParentOfType<FunctionOpInterface>();
1482 routineParDim = getSpecializedRoutineDim(funcOp, defaultPolicy);
1488 if (isa<acc::PrivateLocalOp, acc::PrivatizeOp>(op)) {
1489 if (mlir::acc::ActiveParDimsAttr precomputedActiveParDims =
1491 mlir::acc::GPUParallelDimAttr lowestParDim =
1492 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
1494 SmallVector<mlir::acc::GPUParallelDimAttr> launchParDims;
1495 if (routineParDim) {
1496 for (mlir::acc::GPUParallelDimAttr parDim = routineParDim;
1497 parDim.getOrder() >= lowestParDim.getOrder();
1498 parDim = parDim.getOneLower()) {
1502 launchParDims = computeRegion.getLaunchParDims();
1505 SmallVector<mlir::acc::GPUParallelDimAttr> activeParDims(
1506 precomputedActiveParDims.getArray());
1507 SmallVector<mlir::acc::GPUParallelDimAttr> inactiveParDims;
1508 for (mlir::acc::GPUParallelDimAttr launchParDim : launchParDims) {
1509 if (launchParDim.getOrder() < lowestParDim.getOrder())
1511 if (!llvm::is_contained(activeParDims, launchParDim))
1512 inactiveParDims.push_back(launchParDim);
1514 return std::pair{activeParDims, inactiveParDims};
1518 SmallVector<mlir::acc::GPUParallelDimAttr> ancestorParDims =
1519 getAncestorParDims(op);
1524 bool noStructuralAncestorParDims =
1525 llvm::none_of(ancestorParDims, [](
auto pd) {
return !pd.isSeq(); });
1527 if (routineParDim) {
1528 if (routineParDim.isThreadX()) {
1530 mlir::acc::GPUParallelDimAttr::threadYDim(ctx));
1533 mlir::acc::GPUParallelDimAttr::blockXDim(ctx));
1537 if (acc::PrivateLocalOp privateLocalOp = dyn_cast<acc::PrivateLocalOp>(op)) {
1538 for (Operation *user : privateLocalOp.getResult().getUsers()) {
1539 if (acc::ReductionAccumulateOp accumulateOp =
1540 dyn_cast<acc::ReductionAccumulateOp>(user)) {
1541 if (accumulateOp.getMemref() == privateLocalOp.getResult()) {
1542 for (mlir::acc::GPUParallelDimAttr parDim :
1543 accumulateOp.getParDims().getArray()) {
1553 if (acc::ReductionCombineOp combineOp =
1554 dyn_cast<acc::ReductionCombineOp>(user)) {
1555 if (combineOp.getSrcMemref() == privateLocalOp.
getResult()) {
1556 for (mlir::acc::GPUParallelDimAttr parDim :
1562 if (
auto combineRegionOp =
1563 dyn_cast<acc::ReductionCombineRegionOp>(user)) {
1564 if (combineRegionOp.getSrcVar() == privateLocalOp.getResult()) {
1565 for (mlir::acc::GPUParallelDimAttr parDim :
1577 acc::PrivateType privTy =
1578 cast<acc::PrivateType>(privateLocalOp.getPrivatized().getType());
1580 privTy.getBaseTy(), computeRegion->getParentOfType<ModuleOp>());
1581 if (!baseTy.hasStaticShape()) {
1582 GPUParallelDimsAttr ownParDims =
1583 getPrivateParDims(privateLocalOp, computeRegion);
1585 for (GPUParallelDimAttr parDim : ownParDims.getArray())
1590 bool hasBlock =
false;
1591 for (mlir::acc::GPUParallelDimAttr parDim : ancestorParDims)
1592 if (parDim.isAnyBlock())
1595 mlir::acc::GPUParallelDimAttr lowestParDim =
1596 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
1598 ThreadYBroadeningInfo threadYInfo =
1599 analyzeThreadYBroadening(*block, computeRegion);
1601 auto applyCombineParDims =
1602 [&](ArrayRef<mlir::acc::GPUParallelDimAttr> combineParDims) {
1603 for (mlir::acc::GPUParallelDimAttr parDim : combineParDims)
1607 block->
walk([&](Operation *op) -> WalkResult {
1611 auto addPrivateStoreParDims = [&](Value
target) {
1612 if (
auto privateLocalOp = getPrivateLocalForMemref(
target)) {
1613 GPUParallelDimsAttr parDimsAttr =
1614 getPrivateParDims(privateLocalOp, computeRegion);
1616 for (
auto parDim : parDimsAttr.getArray())
1620 if (
auto memEffects = dyn_cast<MemoryEffectOpInterface>(op)) {
1621 SmallVector<MemoryEffects::EffectInstance> effects;
1622 memEffects.getEffects(effects);
1624 if (isa<MemoryEffects::Write>(effect.getEffect()) &&
1626 addPrivateStoreParDims(effect.getValue());
1631 if (CallOpInterface callOp = dyn_cast<CallOpInterface>(op)) {
1632 if (mlir::acc::GPUParallelDimAttr parDim =
1633 getAccRoutineCallParDim(callOp, defaultPolicy)) {
1634 if (parDim.isBlockZ())
1635 lowestParDim = parDim;
1637 lowestParDim = parDim.getOneHigher();
1643 if (acc::ReductionCombineOp reductionCombineOp =
1644 dyn_cast<acc::ReductionCombineOp>(op)) {
1645 if (
failed(applyCombineParDims(
1649 if (acc::ReductionCombineRegionOp combineRegionOp =
1650 dyn_cast<acc::ReductionCombineRegionOp>(op)) {
1651 if (
failed(applyCombineParDims(
1658 if (acc::ReductionAccumulateArrayOp accArrayOp =
1659 dyn_cast<acc::ReductionAccumulateArrayOp>(op)) {
1660 for (mlir::acc::GPUParallelDimAttr parDim :
1661 accArrayOp.getParDims().getArray()) {
1667 mlir::acc::GPUParallelDimAttr threadY =
1668 mlir::acc::GPUParallelDimAttr::threadYDim(ctx);
1669 bool baselineThreadYActive = llvm::is_contained(ancestorParDims, threadY);
1670 if (threadYInfo.hasActiveWorkerCombine && !baselineThreadYActive) {
1671 if (threadYInfo.hasExplicitInactiveCombine ||
1672 threadYInfo.hasBroadeningConflict) {
1673 Operation *diagnosticOp =
1674 threadYInfo.diagnosticOp ? threadYInfo.diagnosticOp : op;
1677 "operations in the same predicate region require incompatible "
1678 "ThreadY predication");
1687 SmallVector<mlir::acc::GPUParallelDimAttr> launchParDims;
1688 if (routineParDim) {
1689 for (mlir::acc::GPUParallelDimAttr parDim = routineParDim;
1690 parDim.getOrder() >= lowestParDim.getOrder();
1691 parDim = parDim.getOneLower()) {
1695 launchParDims = computeRegion.getLaunchParDims();
1699 SmallVector<mlir::acc::GPUParallelDimAttr> activeParDims, inactiveParDims;
1700 for (mlir::acc::GPUParallelDimAttr launchParDim : launchParDims) {
1701 if (launchParDim.getOrder() < lowestParDim.getOrder())
1703 if (llvm::find(ancestorParDims, launchParDim) != ancestorParDims.end() ||
1704 (launchParDim.isAnyBlock() &&
1705 (noStructuralAncestorParDims || hasBlock))) {
1706 activeParDims.push_back(launchParDim);
1708 inactiveParDims.push_back(launchParDim);
1712 return std::pair{activeParDims, inactiveParDims};
1715Value ACCCGToGPULowering::emitPredicate(
1716 Location loc, SmallVector<mlir::acc::GPUParallelDimAttr> &inactiveParDims) {
1718 for (mlir::acc::GPUParallelDimAttr inactiveParDim : inactiveParDims) {
1719 Value threadId = getGPUThreadIdFor(inactiveParDim.getProcessor());
1721 Value zero = arith::ConstantOp::create(rewriter, loc, zeroAttr);
1722 Value cmp = arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq,
1725 predicate = arith::AndIOp::create(rewriter, loc, cmp, predicate);
1732void ACCCGToGPULowering::createBarrier(
1733 Location loc, mlir::acc::GPUParallelDimsAttr parDimsAttr) {
1734 bool hasAnyBlock =
false, hasThreadY =
false, hasThreadX =
false;
1735 for (
auto parDim : parDimsAttr.getArray()) {
1736 if (parDim.isAnyBlock())
1738 if (parDim.isThreadY())
1740 if (parDim.isThreadX())
1744 if (hasAnyBlock || hasThreadY)
1745 emitGPUBarrierWorkgroup(rewriter, loc);
1746 else if (hasThreadX)
1747 createPerRowBarrier(loc);
1750void ACCCGToGPULowering::createPerRowBarrier(Location loc) {
1753 if (isSingleThreadWorkerLaunch())
1759 if (isWorkerOnlyShuffleLaunch() && staticBlockDimX <
options.subgroupSize) {
1760 emitGPUBarrierRow(rewriter, loc, staticBlockDimX,
options.subgroupSize);
1764 hasThreadYBarrier =
true;
1766 if (staticBlockDimX <=
options.subgroupSize) {
1767 emitGPUBarrierSubgroup(rewriter, loc);
1771 if (
options.deviceType != mlir::acc::DeviceType::Nvidia) {
1774 "per-row barrier to support worker parallelism on non-NVIDIA device");
1789 Value blockDimX = gpu::BlockDimOp::create(
1790 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
1791 Value blockDimY = gpu::BlockDimOp::create(
1792 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::y);
1794 Value isSingleWorker = arith::CmpIOp::create(
1795 rewriter, loc, arith::CmpIPredicate::eq, blockDimY, cst1);
1797 auto outerIf = scf::IfOp::create(rewriter, loc, isSingleWorker,
1802 emitGPUBarrierWorkgroup(rewriter, loc);
1806 Value cstSubgroupSize =
1808 Value isSubgroupSized = arith::CmpIOp::create(
1809 rewriter, loc, arith::CmpIPredicate::ule, blockDimX, cstSubgroupSize);
1811 auto innerIf = scf::IfOp::create(rewriter, loc, isSubgroupSized,
1817 emitGPUBarrierSubgroup(rewriter, loc);
1824 Value threadYId = gpu::ThreadIdOp::create(
1825 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::y);
1826 Value barrierId = arith::AddIOp::create(rewriter, loc, threadYId, cst1);
1829 arith::IndexCastOp::create(rewriter, loc, i32Ty, barrierId);
1830 Value numberOfThreads32 =
1831 arith::IndexCastOp::create(rewriter, loc, i32Ty, blockDimX);
1835 assert(
options.deviceType == mlir::acc::DeviceType::Nvidia);
1836 NVVM::BarrierOp::create(rewriter, loc, barrierId32, numberOfThreads32);
1845static bool hasSubsequentLoopSibling(Operation *loopOp) {
1846 for (Operation *next = loopOp->getNextNode(); next;
1847 next = next->getNextNode()) {
1848 if (isa<scf::ParallelOp, scf::ForOp>(next))
1850 bool nested =
false;
1851 next->walk([&](Operation *op) {
1852 if (isa<scf::ParallelOp, scf::ForOp>(op)) {
1865static LoopLikeOpInterface findFirstSequentialLoop(Operation *op) {
1866 auto isAllSequentialParDims = [](scf::ParallelOp par) ->
bool {
1868 if (!pd || pd.getArray().empty())
1870 return llvm::all_of(pd.getArray(), [](mlir::acc::GPUParallelDimAttr d) {
1877 if (isa<scf::ForOp>(p))
1878 return cast<LoopLikeOpInterface>(p);
1879 if (scf::ParallelOp parOp = dyn_cast<scf::ParallelOp>(p)) {
1880 if (isAllSequentialParDims(parOp))
1881 return cast<LoopLikeOpInterface>(p);
1897static bool isLoopBodyClosureOp(Operation *op) {
1898 return isa<scf::ReduceOp, scf::YieldOp, acc::YieldOp>(op);
1903static bool isDeferredBarrierFlushPoint(Operation *op) {
1904 if (isLoopBodyClosureOp(op))
1908 if (isa<scf::ForOp>(op))
1910 if (scf::ParallelOp parallelOp = dyn_cast<scf::ParallelOp>(op)) {
1912 if (mlir::acc::GPUParallelDimsAttr parDims =
1914 if (parDims.getArray().size() == 1 &&
1915 parDims.getArray().front().isSeq()) {
1927static bool hasTrailingSideEffectSiblings(Operation *loopOp) {
1928 for (Operation *next = loopOp->getNextNode(); next;
1929 next = next->getNextNode()) {
1930 return !isLoopBodyClosureOp(next);
1954void ACCCGToGPULowering::createBarrierAfterSeqLoop(Operation *loopOp) {
1963 if (mayWriteSharedMemory(loopOp) && hasSubsequentLoopSibling(loopOp))
1964 emitGPUBarrierWorkgroup(rewriter, loopOp->
getLoc());
1968 bool parentIsSeq =
false;
1969 if (mlir::acc::GPUParallelDimsAttr wsParDims =
1971 if (wsParDims.getArray().size() == 1 &&
1972 wsParDims.getArray().front().isSeq()) {
1982 bool hasThreadSubLoop =
false;
1983 loopOp->
walk([&](scf::ParallelOp innerPar) -> WalkResult {
1984 if (innerPar.getOperation() == loopOp)
1986 if (mlir::acc::GPUParallelDimsAttr dims =
1988 for (
auto d : dims.getArray()) {
1989 if (d.isThreadX() || d.isThreadY()) {
1990 hasThreadSubLoop =
true;
1997 if (!hasThreadSubLoop)
1999 scf::ParallelOp threadLoop = wsLoop->getParentOfType<scf::ParallelOp>();
2002 scf::ParallelOp blockLoop = threadLoop->getParentOfType<scf::ParallelOp>();
2005 mlir::acc::GPUParallelDimsAttr parDimsAttr =
2007 if (parDimsAttr.hasOnlyBlockLevel())
2008 createBarrier(loopOp->
getLoc(), parDimsAttr);
2014 scf::ParallelOp seqLoop = wsLoop->getParentOfType<scf::ParallelOp>();
2023 if (mayWriteSharedMemory(loopOp) && hasSubsequentLoopSibling(wsLoop))
2024 emitGPUBarrierWorkgroup(rewriter, loopOp->
getLoc());
2027 if (scf::ParallelOp outerParLoop =
2028 seqLoop->getParentOfType<scf::ParallelOp>()) {
2029 mlir::acc::GPUParallelDimsAttr parDimsAttr =
2031 if (parDimsAttr.hasOnlyBlockLevel()) {
2032 createBarrier(loopOp->
getLoc(), parDimsAttr);
2033 }
else if (parDimsAttr.hasOnlyThreadYLevel()) {
2034 createPerRowBarrier(loopOp->
getLoc());
2035 }
else if (parDimsAttr && parDimsAttr.isSeq()) {
2039 for (Operation *gangLoop =
2040 outerParLoop->getParentOfType<scf::ParallelOp>();
2041 gangLoop; gangLoop = gangLoop->getParentOfType<scf::ParallelOp>()) {
2042 mlir::acc::GPUParallelDimsAttr gangDims =
2046 if (gangDims.hasOnlyBlockLevel()) {
2047 createBarrier(loopOp->
getLoc(), gangDims);
2050 if (!gangDims.isSeq())
2064 mlir::acc::GPUParallelDimsAttr parDimsAttr =
2066 if (parDimsAttr && parDimsAttr.hasOnlyBlockLevel() &&
2067 mayWriteSharedMemory(loopOp)) {
2068 createBarrier(loopOp->
getLoc(), parDimsAttr);
2072bool ACCCGToGPULowering::mayWriteSharedMemory(Operation *loopOp) {
2074 loopOp->
walk([&](memref::StoreOp storeOp) {
2077 llvm::SmallVector<Value, 8> worklist{storeOp.getMemref()};
2078 llvm::SmallPtrSet<Value, 8> seen;
2079 while (!worklist.empty()) {
2080 Value v = worklist.pop_back_val();
2081 if (!seen.insert(v).second)
2086 if (acc::PrivateLocalOp privateLocal =
2087 dyn_cast<acc::PrivateLocalOp>(def)) {
2088 acc::PrivatizeOp privatizeOp =
2096 if (mlir::acc::GPUParallelDimsAttr parDims =
2097 privatizeOp.getParDimsAttr()) {
2098 bool hasBlock =
false, hasThread =
false;
2099 for (mlir::acc::GPUParallelDimAttr d : parDims.getArray()) {
2102 if (d.isThreadX() || d.isThreadY())
2105 if (hasBlock && !hasThread) {
2120ACCCGToGPULowering::getPrivateMemScope(acc::PrivatizeOp privatizeOp) {
2121 bool hasBlock =
false;
2122 bool hasThreadX =
false;
2123 bool hasThreadY =
false;
2124 if (mlir::acc::GPUParallelDimsAttr parDims = privatizeOp.getParDimsAttr()) {
2125 for (mlir::acc::GPUParallelDimAttr d : parDims.getArray()) {
2134 for (mlir::acc::GPUParallelDimAttr d : computeRegion.getLaunchParDims())
2138 return PrivateMemScope::Gang;
2139 return PrivateMemScope::Thread;
2142 return PrivateMemScope::Thread;
2143 if (hasBlock && hasThreadY)
2144 return PrivateMemScope::Worker;
2146 return PrivateMemScope::Gang;
2147 return PrivateMemScope::Thread;
2151static acc::PrivateLocalOp getPrivateLocalForMemref(Value memref) {
2152 llvm::SmallVector<Value, 8> worklist{memref};
2153 llvm::SmallPtrSet<Value, 8> seen;
2154 while (!worklist.empty()) {
2155 Value v = worklist.pop_back_val();
2156 if (!seen.insert(v).second)
2161 if (acc::PrivateLocalOp privateLocal = dyn_cast<acc::PrivateLocalOp>(def))
2162 return privateLocal;
2168PrivateMemScope ACCCGToGPULowering::getPrivateScopeForMemref(Value memref) {
2169 if (
auto privateLocal = getPrivateLocalForMemref(memref))
2170 return getPrivateMemScope(
getPrivatizeOp(privateLocal, computeRegion));
2171 return PrivateMemScope::None;
2174acc::PrivatizeOp ACCCGToGPULowering::getPrivatizeForMemref(Value memref) {
2175 if (
auto privateLocal = getPrivateLocalForMemref(memref))
2177 return acc::PrivatizeOp();
2181ACCCGToGPULowering::needsPreStoreReuseBarrier(acc::PredicateRegionOp interOp) {
2185 LoopLikeOpInterface seqLoopOp = findFirstSequentialLoop(interOp);
2187 return PrivateMemScope::None;
2191 PrivateMemScope storeScope = PrivateMemScope::None;
2192 llvm::SmallPtrSet<Operation *, 4> storePrivatizes;
2193 interOp.getRegion().walk([&](memref::StoreOp storeOp) {
2194 PrivateMemScope scope = getPrivateScopeForMemref(storeOp.getMemref());
2195 if (scope != PrivateMemScope::Gang && scope != PrivateMemScope::Worker)
2197 if (
auto privatize = getPrivatizeForMemref(storeOp.getMemref()))
2198 storePrivatizes.insert(privatize.getOperation());
2199 if (storeScope == PrivateMemScope::None)
2203 if (storeScope == PrivateMemScope::None || storePrivatizes.empty())
2204 return PrivateMemScope::None;
2207 bool hasParallelPrivateUse =
false;
2208 seqLoopOp.getOperation()->walk([&](Operation *op) {
2210 if (interOp->isAncestor(op))
2214 if (memref::LoadOp loadOp = dyn_cast<memref::LoadOp>(op))
2215 memref = loadOp.getMemref();
2216 else if (memref::StoreOp storeOp = dyn_cast<memref::StoreOp>(op))
2217 memref = storeOp.getMemref();
2221 PrivateMemScope scope = getPrivateScopeForMemref(memref);
2222 if (scope != storeScope)
2225 acc::PrivatizeOp usePrivatize = getPrivatizeForMemref(memref);
2226 if (!usePrivatize || !storePrivatizes.contains(usePrivatize.getOperation()))
2229 bool insideNestedParallel =
false;
2230 for (Operation *p = op->
getParentOp(); p && p != seqLoopOp.getOperation();
2232 if (scf::ParallelOp par = dyn_cast<scf::ParallelOp>(p)) {
2233 if (mlir::acc::GPUParallelDimsAttr pd =
2235 if (llvm::any_of(pd.getArray(), [](mlir::acc::GPUParallelDimAttr d) {
2238 insideNestedParallel =
true;
2244 if (!insideNestedParallel)
2247 hasParallelPrivateUse =
true;
2251 if (!hasParallelPrivateUse)
2252 return PrivateMemScope::None;
2257void ACCCGToGPULowering::processPredicateRegion(
2258 acc::PredicateRegionOp interOp) {
2259 LLVM_DEBUG(llvm::dbgs() <<
"processing predicate region: ";
2260 interOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2261 Location loc = interOp->getLoc();
2263 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
2264 SmallVector<mlir::acc::GPUParallelDimAttr>>
2265 parDimsPair = computeActiveAndInactiveParDims(
2266 interOp, &interOp.getRegion().front());
2277 if (hasThreadYReduction && !isSingleThreadWorkerLaunch()) {
2278 MLIRContext *ctx = computeRegion->getContext();
2279 mlir::acc::GPUParallelDimAttr threadXParDim =
2280 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
2281 bool hasThreadXInActive =
2282 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr pd) {
2283 return pd.isThreadX();
2285 bool hasThreadXInInactive =
2286 llvm::any_of(parDimsPair.second, [](mlir::acc::GPUParallelDimAttr pd) {
2287 return pd.isThreadX();
2293 bool regionHasThreadLevelRoutineCall =
false;
2294 if (hasThreadLevelRoutineCall) {
2295 interOp.getRegion().walk([&](CallOpInterface callOp) {
2296 if (mlir::acc::GPUParallelDimAttr parDim =
2297 getAccRoutineCallParDim(callOp, defaultPolicy)) {
2298 if (parDim.isThreadX() || parDim.isThreadY()) {
2299 regionHasThreadLevelRoutineCall =
true;
2307 if (!hasThreadXInActive && !hasThreadXInInactive &&
2308 !regionHasThreadLevelRoutineCall) {
2309 parDimsPair.second.push_back(threadXParDim);
2313 if (Value predicate = emitPredicate(loc, parDimsPair.second)) {
2314 LLVM_DEBUG(llvm::dbgs() <<
"predicate: " << predicate <<
"\n");
2315 bool isInsideThreadXLoop =
false;
2316 bool isInsideThreadYLoop =
false;
2317 for (
auto parDim : parDimsPair.first) {
2318 if (parDim.isThreadX())
2319 isInsideThreadXLoop =
true;
2320 if (parDim.isThreadY())
2321 isInsideThreadYLoop =
true;
2327 auto emitReconvergenceBarrier = [&]() {
2328 if (isInsideThreadXLoop) {
2330 }
else if (isInsideThreadYLoop) {
2335 bool predicatesThreadX = llvm::any_of(
2337 [](mlir::acc::GPUParallelDimAttr pd) { return pd.isThreadX(); });
2338 if (predicatesThreadX) {
2339 createBarrier(loc, mlir::acc::GPUParallelDimsAttr::get(
2340 interOp->getContext(), parDimsPair.second));
2344 }
else if (!parDimsPair.first.empty()) {
2346 createBarrier(loc, mlir::acc::GPUParallelDimsAttr::get(
2347 interOp->getContext(), parDimsPair.first));
2350 createBarrier(loc, mlir::acc::GPUParallelDimsAttr::get(
2351 interOp->getContext(), parDimsPair.second));
2363 PrivateMemScope scope = needsPreStoreReuseBarrier(interOp);
2364 if (scope == PrivateMemScope::Gang)
2365 emitGPUBarrierWorkgroup(rewriter, loc);
2366 else if (scope == PrivateMemScope::Worker)
2367 createPerRowBarrier(loc);
2369 auto ifOp = scf::IfOp::create(rewriter, loc, predicate,
2371 Region &thenRegion = ifOp.getThenRegion();
2375 for (
auto &bodyOp : interOp.getRegion().front().getOperations()) {
2379 if (memref::StoreOp storeOp = dyn_cast<memref::StoreOp>(&bodyOp)) {
2380 std::optional<arith::AtomicRMWKind> blockReduceKind;
2381 bool failedReductionKind =
false;
2382 Value storeVal = storeOp.getValueToStore();
2389 Block *epilogueBlock = interOp->getBlock();
2390 auto findBlockAccLoad =
2392 Value val) -> std::optional<arith::AtomicRMWKind> {
2393 Operation *def = val.getDefiningOp();
2394 if (!def || def->
getBlock() != epilogueBlock)
2395 return std::nullopt;
2396 if (memref::LoadOp loadOp = dyn_cast<memref::LoadOp>(def)) {
2397 for (
auto *user : loadOp.getMemRef().getUsers()) {
2398 if (acc::ReductionAccumulateOp accOp =
2399 dyn_cast<acc::ReductionAccumulateOp>(user)) {
2400 if (llvm::any_of(accOp.getParDims().getArray(),
2401 [](mlir::acc::GPUParallelDimAttr pd) {
2402 return pd.isAnyBlock();
2404 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
2405 accOp.getReductionOperator(),
2406 accOp.getValue().getType(), accOp.getLoc());
2408 failedReductionKind =
true;
2409 return std::nullopt;
2415 return std::nullopt;
2418 if (
auto kind = self(self, operand))
2420 return std::nullopt;
2422 blockReduceKind = findBlockAccLoad(findBlockAccLoad, storeVal);
2424 if (failedReductionKind)
2426 if (blockReduceKind) {
2429 bool threadIsActive = llvm::any_of(
2430 parDimsPair.first, [](mlir::acc::GPUParallelDimAttr pd) {
2431 return !pd.isAnyBlock();
2433 if (!threadIsActive &&
2434 !isa_and_nonnull<memref::AllocaOp>(
2435 unwrapMemRefConversion(memref).getDefiningOp())) {
2441 MemRefType memrefTy = cast<MemRefType>(memref.
getType());
2443 SmallVector<Value> initIndices;
2444 for (Value idx : storeOp.getIndices())
2447 OpBuilder::InsertionGuard guard(rewriter);
2448 Block &launchBody = launch.getBody().front();
2449 Operation *insertBefore =
nullptr;
2451 launchBody.
walk([&](scf::ParallelOp parOp) -> WalkResult {
2452 for (Operation *parent = parOp->getParentOp(); parent;
2453 parent = parent->getParentOp()) {
2454 if (parent == launch.getOperation())
2456 if (isa<scf::ParallelOp>(parent))
2459 insertBefore = parOp.getOperation();
2472 DominanceInfo domInfo(launch);
2473 IRMapping initMapping;
2474 std::function<Value(Value)> materialize =
2475 [&](Value val) -> Value {
2476 Operation *defOp = val.getDefiningOp();
2487 materialize(operand);
2488 Operation *cloned = rewriter.
clone(*defOp, initMapping);
2489 for (
auto [orig, clonedRes] :
2491 initMapping.
map(orig, clonedRes);
2493 return initMapping.
lookup(val);
2495 Value initMemref = materialize(memref);
2496 for (
auto &idx : initIndices)
2497 idx = materialize(idx);
2499 rewriter, loc, memrefTy.getElementType(), *blockReduceKind,
2501 Value blockId = gpu::BlockIdOp::create(
2502 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
2503 Value threadId = gpu::ThreadIdOp::create(
2504 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
2506 Value isBlock0 = arith::CmpIOp::create(
2507 rewriter, loc, arith::CmpIPredicate::eq, blockId, zero);
2508 Value isThread0 = arith::CmpIOp::create(
2509 rewriter, loc, arith::CmpIPredicate::eq, threadId, zero);
2510 Value isFirstThread =
2511 arith::AndIOp::create(rewriter, loc, isBlock0, isThread0);
2512 auto initIf = scf::IfOp::create(rewriter, loc, isFirstThread,
2515 initIf.getThenRegion().back().getTerminator());
2516 memref::StoreOp::create(rewriter, loc, identityVal, initMemref,
2519 gpu::BarrierOp::create(rewriter, loc);
2521 SmallVector<Value> atomicIndices;
2522 for (Value idx : storeOp.getIndices())
2524 constructAtomicAccumulation(loc, memref, atomicIndices, input,
2533 emitReconvergenceBarrier();
2536 for (
auto &bodyOp : interOp.getRegion().front().getOperations())
2559Value ACCCGToGPULowering::processPrivatize(acc::PrivatizeOp privatize) {
2560 LLVM_DEBUG(llvm::dbgs() <<
"processing privatize: ";
2561 privatize->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2562 Value tracked = privatize.getResult();
2563 if (acc::ComputeRegionOp insUser =
2564 dyn_cast<acc::ComputeRegionOp>(getOnlyUser(tracked))) {
2565 assert(privatize->hasOneUse() &&
2566 "expected acc.privatize op to have one use");
2567 tracked = insUser.getBody()->getArgument(
2568 privatize->use_begin()->getOperandNumber());
2570 Operation *privatizeUser = getOnlyUser(tracked);
2571 assert(privatizeUser &&
"expected PrivateLocalOp user for privatize");
2573 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
2574 SmallVector<mlir::acc::GPUParallelDimAttr>>
2575 parDimsPair = computeActiveAndInactiveParDims(privatizeUser,
nullptr);
2577 if (!privatize.getParDimsAttr()) {
2578 privatize.setParDimsAttr(mlir::acc::GPUParallelDimsAttr::get(
2582 Location loc = privatize->getLoc();
2583 acc::PrivateType privTy = cast<acc::PrivateType>(privatize.getType());
2584 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
2587 gpu::GPUFuncOp gpuFuncOp = computeRegion->getParentOfType<gpu::GPUFuncOp>();
2592 privatize->getParentOfType<acc::ComputeRegionOp>() != computeRegion) {
2593 return privatize.getResult();
2596 for (mlir::acc::GPUParallelDimAttr parDim : parDimsPair.first) {
2597 if (parDim.isThreadX() &&
2598 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
2599 auto alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
2600 mapping.
map(privatize.getResult(), alloca.getResult());
2601 return alloca.getResult();
2606 return privatize.getResult();
2614 bool threadYIsActive =
2615 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr parDim) {
2616 return parDim.isThreadY();
2629 bool needsWorkgroupBarrier =
false;
2631 FunctionOpInterface funcOp =
2632 computeRegion->getParentOfType<FunctionOpInterface>();
2633 mlir::acc::GPUParallelDimAttr routineParDim =
2634 getSpecializedRoutineDim(funcOp, defaultPolicy);
2635 if (routineParDim.isThreadX()) {
2638 threadYIsActive =
true;
2639 }
else if (routineParDim.isThreadY()) {
2642 needsWorkgroupBarrier =
true;
2643 }
else if (routineParDim.isAnyBlock()) {
2644 needsWorkgroupBarrier =
true;
2648 llvm::SmallVector<Value> mappedDynamicSizes;
2649 for (
auto dynamicSize : privatize.getDynamicSizes()) {
2651 mappedDynamicSizes.push_back(mappedDynamicSize);
2654 computeRegion.isEffectivelySerial()) {
2655 if (mappedDynamicSizes.empty()) {
2658 memref::AllocaOp::create(rewriter, privatize->getLoc(), baseTy);
2659 mapping.
map(privatize.getResult(), alloca.getResult());
2660 return alloca.getResult();
2663 auto alloc = memref::AllocOp::create(rewriter, privatize->getLoc(), baseTy,
2664 mappedDynamicSizes);
2671 memref::DeallocOp::create(rewriter, privatize->getLoc(), alloc);
2675 mapping.
map(privatize.getResult(), alloc.getResult());
2676 return alloc.getResult();
2681 SmallVector<mlir::acc::GPUParallelDimAttr> predicateDims;
2682 for (
auto parDim : parDimsPair.second) {
2684 if (threadYIsActive && parDim.isThreadY())
2686 predicateDims.push_back(parDim);
2688 Value predicate = emitPredicate(loc, predicateDims);
2690 predicate = arith::ConstantOp::create(
2693 auto ifOp = scf::IfOp::create(rewriter, loc, predicate,
2695 Region &thenRegion = ifOp.getThenRegion();
2698 auto mem = memref::AllocOp::create(rewriter, privatize->getLoc(), baseTy,
2699 mappedDynamicSizes);
2701 gpu::AddressSpaceAttr sharedMemoryAddressSpace = gpu::AddressSpaceAttr::get(
2702 computeRegion->getContext(), gpu::GPUDialect::getWorkgroupAddressSpace());
2706 constexpr int64_t kMaxThreadY = 32;
2707 MemRefType sharedMemTy =
2709 ? MemRefType::get({kMaxThreadY}, baseTy, MemRefLayoutAttrInterface{},
2710 sharedMemoryAddressSpace)
2711 : MemRefType::
get({}, baseTy, MemRefLayoutAttrInterface{},
2712 sharedMemoryAddressSpace);
2715 bool reuseBroadcast = !gpuFuncOp.isKernel();
2717 llvm::DenseMap<Type, Value>::iterator cachedSlot =
2718 reuseBroadcast ? privatizeBroadcastCache.find(sharedMemTy)
2719 : privatizeBroadcastCache.end();
2720 if (reuseBroadcast && cachedSlot != privatizeBroadcastCache.end()) {
2721 alloca = cachedSlot->second;
2724 mlir::acc::GPUParallelDimAttr dim =
2725 needsWorkgroupBarrier
2726 ? mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.
getContext())
2727 : mlir::
acc::GPUParallelDimAttr::threadXDim(rewriter.
getContext());
2729 loc, mlir::acc::GPUParallelDimsAttr::get(rewriter.
getContext(), {dim}));
2731 alloca = gpuFuncOp.addWorkgroupAttribution(sharedMemTy,
2736 unsigned index = gpuFuncOp.getNumWorkgroupAttributions() - 1;
2737 gpuFuncOp.setWorkgroupAttributionAttr(
index,
2738 LLVM::LLVMDialect::getAlignAttrName(),
2741 privatizeBroadcastCache[sharedMemTy] = alloca;
2744 if (threadYIsActive) {
2745 Value threadYId = getThreadId(loc, gpu::Dimension::y);
2746 memref::StoreOp::create(rewriter, privatize->getLoc(), mem, alloca,
2749 memref::StoreOp::create(rewriter, privatize->getLoc(), mem, alloca);
2755 if (needsWorkgroupBarrier) {
2756 mlir::acc::GPUParallelDimsAttr threadYDimsAttr =
2757 mlir::acc::GPUParallelDimsAttr::get(
2759 {mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.getContext())});
2760 createBarrier(loc, threadYDimsAttr);
2763 mlir::acc::GPUParallelDimsAttr threadXDimsAttr =
2764 mlir::acc::GPUParallelDimsAttr::get(
2766 {mlir::acc::GPUParallelDimAttr::threadXDim(rewriter.getContext())});
2767 createBarrier(loc, threadXDimsAttr);
2771 if (threadYIsActive) {
2772 Value threadYId = getThreadId(loc, gpu::Dimension::y);
2773 load = memref::LoadOp::create(rewriter, privatize->getLoc(), baseTy, alloca,
2777 memref::LoadOp::create(rewriter, privatize->getLoc(), baseTy, alloca);
2780 mapping.
map(privatize.getResult(),
load);
2784 if (!privatize->getParentOfType<acc::ComputeRegionOp>())
2788 if (needsWorkgroupBarrier) {
2789 mlir::acc::GPUParallelDimsAttr workerDimsAttr =
2790 mlir::acc::GPUParallelDimsAttr::get(
2792 {mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.getContext())});
2793 createBarrier(loc, workerDimsAttr);
2795 mlir::acc::GPUParallelDimsAttr vectorDimsAttr =
2796 mlir::acc::GPUParallelDimsAttr::get(
2798 {mlir::acc::GPUParallelDimAttr::threadXDim(rewriter.getContext())});
2799 createBarrier(loc, vectorDimsAttr);
2801 auto ifOp2 = scf::IfOp::create(rewriter, loc, predicate,
2803 Region &thenRegion2 = ifOp2.getThenRegion();
2806 memref::DeallocOp::create(rewriter, privatize->getLoc(),
load);
2817void ACCCGToGPULowering::processPrivateLocal(
2818 acc::PrivateLocalOp privateLocal, std::optional<int64_t> sharedMemCopies) {
2819 LLVM_DEBUG(llvm::dbgs() <<
"processing private local: ";
2820 privateLocal->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2821 Location loc = privateLocal.getLoc();
2822 acc::PrivateType privTy =
2823 cast<acc::PrivateType>(privateLocal.getPrivatized().getType());
2824 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
2826 MemRefType byteMemrefTy =
2827 MemRefType::get({ShapedType::kDynamic}, rewriter.
getI8Type());
2829 acc::PrivatizeOp privatizeOp =
getPrivatizeOp(privateLocal, computeRegion);
2831 if (privatizeOp->getParentOfType<acc::ComputeRegionOp>() == computeRegion) {
2834 Value
result = castPointerLikeTypeIfNeeded(rewriter, loc, inputMem,
2835 privateLocal.getType());
2836 mapping.
map(privateLocal.getResult(),
result);
2844 acc::ReductionAccumulateArrayOp arrayAccum =
2845 perThreadArrayReductionAccum(privateLocal.getResult());
2847 (arrayAccum && storageHasThreadX(privateLocal, computeRegion))) &&
2848 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
2849 Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
2851 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
2852 arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
2855 initPerThreadArrayAccum(rewriter, loc, alloca, baseTy, *kind);
2857 Value mem = castPointerLikeTypeIfNeeded(rewriter, loc, alloca,
2858 privateLocal.getType());
2859 mapping.
map(privateLocal.getResult(), mem);
2865 std::optional<int64_t>
copies =
2866 sharedMemCopies ? sharedMemCopies
2867 : isEligibleForSharedMemory(privateLocal, baseTy);
2869 int64_t numCopies = *
copies;
2870 int64_t elementSize = getElementSizeInBytes(loc, baseTy.getElementType());
2871 int64_t numElements = 1;
2872 for (int64_t dim : baseTy.getShape())
2874 int64_t upperBound = elementSize * numElements * numCopies;
2876 if (tryAllocateSharedMemory(upperBound)) {
2877 std::string varName =
2879 sharedMemPrivateVarNames.push_back(varName.empty() ?
"(*)" : varName);
2881 gpu::AddressSpaceAttr workgroupAS = gpu::AddressSpaceAttr::get(
2882 computeRegion->getContext(),
2883 gpu::GPUDialect::getWorkgroupAddressSpace());
2884 MemRefType sharedMemTy =
2885 MemRefType::get(baseTy.getShape(), baseTy.getElementType(),
2886 MemRefLayoutAttrInterface{}, workgroupAS);
2887 Value sharedMem = acc::GPUSharedMemoryOp::create(
2893 castPointerLikeTypeIfNeeded(rewriter, loc, sharedMem, baseTy);
2894 Value
result = castPointerLikeTypeIfNeeded(rewriter, loc, mem,
2895 privateLocal.getType());
2897 mapping.
map(privateLocal.getResult(),
result);
2902 OpBuilder::InsertionGuard guard(rewriter);
2904 inputMem = processPrivatize(privatizeOp);
2908 assert(inputMem &&
"expected input mem to be mapped");
2909 Value
result = castPointerLikeTypeIfNeeded(rewriter, loc, inputMem,
2910 privateLocal.getType());
2911 mapping.
map(privateLocal.getResult(),
result);
2917 SmallVector<int64_t> viewShape;
2919 SmallVector<Value> viewDynSizes;
2921 SmallVector<OpFoldResult> subviewOffset;
2924 SmallVector<OpFoldResult> subviewSizes;
2926 SmallVector<int64_t> subviewStrides;
2928 SmallVector<int64_t> subviewShape;
2930 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
2931 SmallVector<mlir::acc::GPUParallelDimAttr>>
2932 parDimsPair = computeActiveAndInactiveParDims(privateLocal,
nullptr);
2933 acc::ReductionAccumulateArrayOp arrayAccum =
2934 perThreadArrayReductionAccum(privateLocal.getResult());
2935 for (mlir::acc::GPUParallelDimAttr parDim : parDimsPair.first) {
2936 if ((parDim.isThreadX() ||
2937 (arrayAccum && storageHasThreadX(privateLocal, computeRegion))) &&
2938 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
2939 Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
2941 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
2942 arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
2945 initPerThreadArrayAccum(rewriter, loc, alloca, baseTy, *kind);
2947 Value mem = castPointerLikeTypeIfNeeded(rewriter, loc, alloca,
2948 privateLocal.getType());
2949 mapping.
map(privateLocal.getResult(), mem);
2953 if (parDimsPair.first.empty()) {
2957 mlir::acc::GPUParallelDimAttr::blockXDim(privateLocal.getContext()));
2959 for (mlir::acc::GPUParallelDimAttr parDim : parDimsPair.first) {
2960 gpu::Processor gpuProc = parDim.getProcessor();
2961 Value gpuSize = getGPUSizeFor(gpuProc);
2962 viewDynSizes.push_back(gpuSize);
2963 viewShape.push_back(ShapedType::kDynamic);
2964 subviewOffset.push_back(getGPUThreadIdFor(gpuProc));
2968 SmallVector<Value> innerDynSizes =
2969 resolvePrivateLocalDynamicExtents(privateLocal);
2971 unsigned dynIdx = 0;
2972 for (
auto innerDim : baseTy.getShape()) {
2974 viewShape.push_back(innerDim);
2975 subviewShape.push_back(innerDim);
2976 if (innerDim == ShapedType::kDynamic) {
2977 assert(dynIdx < innerDynSizes.size() &&
2978 "not enough dynamic sizes for inner dimensions");
2979 viewDynSizes.push_back(innerDynSizes[dynIdx]);
2980 subviewSizes.push_back(innerDynSizes[dynIdx]);
2983 subviewSizes.push_back(rewriter.
getIndexAttr(innerDim));
2989 for (
auto innerDimIt = baseTy.getShape().rbegin();
2990 innerDimIt != baseTy.getShape().rend(); ++innerDimIt) {
2991 int64_t innerDim = *innerDimIt;
2992 subviewStrides.insert(subviewStrides.begin(), stride);
2993 if (innerDim == ShapedType::kDynamic)
2994 stride = ShapedType::kDynamic;
2995 if (stride != ShapedType::kDynamic)
3000 castPointerLikeTypeIfNeeded(rewriter, loc, inputMem, byteMemrefTy);
3002 MemRefType viewType = MemRefType::get(viewShape, baseTy.getElementType());
3003 auto view = memref::ViewOp::create(rewriter, loc, viewType, memBuffer,
3004 c0.getResult(), viewDynSizes);
3007 StridedLayoutAttr stridedLayout = StridedLayoutAttr::get(
3008 computeRegion->getContext(), ShapedType::kDynamic, subviewStrides);
3009 MemRefType subviewType =
3010 MemRefType::get(subviewShape, baseTy.getElementType(), stridedLayout);
3011 SmallVector<OpFoldResult> ones(viewType.getRank(), rewriter.
getIndexAttr(1));
3012 Value subview = memref::SubViewOp::create(rewriter, loc, subviewType, view,
3013 subviewOffset, subviewSizes, ones);
3018 memref::ExtractStridedMetadataOp::create(rewriter, loc, subview);
3020 rewriter, loc, getElementSizeInBytes(loc, baseTy.getElementType()));
3022 arith::MulIOp::create(rewriter, loc, metadata.getOffset(), elementBytes);
3023 Value privateView = memref::ViewOp::create(rewriter, loc, baseTy, memBuffer,
3024 byteOffset, innerDynSizes);
3025 Value
result = castPointerLikeTypeIfNeeded(rewriter, loc, privateView,
3026 privateLocal.getType());
3027 mapping.
map(privateLocal.getResult(),
result);
3031template <
typename LoopOp>
3032void ACCCGToGPULowering::processSeqLoop(LoopOp loopOp) {
3036 LLVM_DEBUG(llvm::dbgs() <<
"processing seq loop: ";
3037 loopOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3038 llvm::SmallPtrSet<Operation *, 4> preProcessedPrivateLocals;
3039 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
3040 for (
auto &bodyOp : loopOp.getBody()->getOperations()) {
3041 if (acc::PrivateLocalOp privateLocal =
3042 dyn_cast<acc::PrivateLocalOp>(&bodyOp)) {
3043 acc::PrivateType privTy =
3044 cast<acc::PrivateType>(privateLocal.getPrivatized().getType());
3046 if (
auto copies = isEligibleForSharedMemory(privateLocal, baseTy)) {
3047 processPrivateLocal(privateLocal,
copies);
3048 preProcessedPrivateLocals.insert(privateLocal.getOperation());
3056 &newLoop.getRegion(), newLoop.getRegion().begin(),
3057 loopOp.getBody()->getArgumentTypes(),
3058 SmallVector<Location>(loopOp.getBody()->getArgumentTypes().size(),
3064 assert(blockArgs.size() &&
"expected block arguments for loop");
3065 mapping.
map(blockArgs, newLoop.getBody()->getArguments());
3067 for (
auto &bodyOp : loopOp.getBody()->getOperations()) {
3068 if (preProcessedPrivateLocals.contains(&bodyOp))
3073 mapping.
map(loopOp.getResults(), newLoop.getResults());
3078 if (hasTrailingSideEffectSiblings(loopOp.getOperation()))
3079 deferredBarrierSeqLoops.push_back(loopOp.getOperation());
3081 createBarrierAfterSeqLoop(loopOp.getOperation());
3084void ACCCGToGPULowering::flushDeferredBarriersBefore(Operation *beforeOp) {
3086 SmallVector<Operation *, 4> toFlush;
3087 for (Operation *loopOp : deferredBarrierSeqLoops)
3088 if (loopOp->getBlock() == block && loopOp->isBeforeInBlock(beforeOp))
3089 toFlush.push_back(loopOp);
3090 if (toFlush.empty())
3094 for (Operation *loopOp : toFlush)
3095 createBarrierAfterSeqLoop(loopOp);
3096 deferredBarrierSeqLoops.erase(
3097 std::remove_if(deferredBarrierSeqLoops.begin(),
3098 deferredBarrierSeqLoops.end(),
3099 [&](Operation *loopOp) {
3100 return loopOp->getBlock() == block &&
3101 loopOp->isBeforeInBlock(beforeOp);
3103 deferredBarrierSeqLoops.end());
3108void ACCCGToGPULowering::processParallelOp(scf::ParallelOp parallelOp) {
3109 LLVM_DEBUG(llvm::dbgs() <<
"processing par loop: ";
3110 parallelOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3112 "requires parallel dimensions attribute");
3113 mlir::acc::GPUParallelDimsAttr pDimsAttr =
3117 assert(pDimsAttr.getArray().size() == 1 &&
3118 "expected a single par dim in acc-cg-to-gpu");
3119 assert(parallelOp.getInductionVars().size() == 1 &&
3120 "expected a single induction variable in acc-cg-to-gpu");
3122 mlir::acc::GPUParallelDimAttr parDim = pDimsAttr.getArray().front();
3124 bool savedGridStrideFlag = insideAccumulateGridStride;
3125 Value savedReductionBuf = reductionSharedBuf;
3126 if (parDim.isThreadX()) {
3128 parallelOp.getBody()->walk([&](acc::ReductionAccumulateOp accOp) {
3129 bool hasBlockDim =
false;
3130 bool hasThreadDim =
false;
3131 for (
auto d : accOp.getParDims().getArray()) {
3134 if (d.isThreadX() || d.isThreadY())
3135 hasThreadDim =
true;
3137 if (hasThreadDim && !hasBlockDim) {
3144 insideAccumulateGridStride =
true;
3148 auto processLoopBody = [&]() {
3150 for (
auto &bodyOp : parallelOp.getBody()->getOperations()) {
3151 if (bodyOp.hasTrait<OpTrait::IsTerminator>()) {
3153 flushDeferredBarriersBefore(&bodyOp);
3161 if (parDim.isSeq()) {
3162 LLVM_DEBUG(llvm::dbgs() <<
"loop: parDim: " << parDim <<
" as gpu seq\n");
3168 bool needsAtomicReduction =
false;
3169 bool hasAccumulateSibling =
false;
3170 if (scf::ParallelOp parentPar =
3171 parallelOp->getParentOfType<scf::ParallelOp>()) {
3172 if (mlir::acc::GPUParallelDimsAttr parentDims =
3174 parentDims && llvm::any_of(parentDims.getArray(),
3175 [](
auto d) { return d.isThreadX(); })) {
3176 for (
auto &op : parentPar.getBody()->getOperations()) {
3177 if (acc::ReductionAccumulateOp acc =
3178 dyn_cast<acc::ReductionAccumulateOp>(op)) {
3179 bool hasBlockDim =
false;
3180 bool hasThreadDim =
false;
3181 for (
auto d : acc.getParDims().getArray()) {
3184 if (d.isThreadX() || d.isThreadY())
3185 hasThreadDim =
true;
3187 if (hasThreadDim && !hasBlockDim)
3188 hasAccumulateSibling =
true;
3193 if (insideAccumulateGridStride || hasAccumulateSibling) {
3194 for (
auto launchArg : computeRegion.getLaunchArgs()) {
3195 if (acc::ParWidthOp pw = launchArg.getDefiningOp<acc::ParWidthOp>()) {
3196 if (pw.getParDim().isThreadX()) {
3198 needsAtomicReduction = (*cval >=
options.subgroupSize);
3200 needsAtomicReduction =
true;
3206 if (needsAtomicReduction && !reductionSharedBuf) {
3208 parallelOp.getBody()->walk([&](acc::ReductionAccumulateOp accOp) {
3209 Type t = accOp.getValue().getType();
3210 if (isa<FloatType, IntegerType>(t))
3215 needsAtomicReduction =
false;
3217 if (needsAtomicReduction && !reductionSharedBuf) {
3218 Location seqLoc = parallelOp->getLoc();
3219 gpu::AddressSpaceAttr workgroupAS = gpu::AddressSpaceAttr::get(
3220 computeRegion->getContext(),
3221 gpu::GPUDialect::getWorkgroupAddressSpace());
3223 parallelOp.getBody()->
walk([&](acc::ReductionAccumulateOp accOp) {
3224 Type t = accOp.getValue().getType();
3225 if (isa<FloatType, IntegerType>(t))
3229 assert(elemTy &&
"expected scalar reduction element type");
3231 MemRefType bufTy = MemRefType::get({
options.subgroupSize}, elemTy,
3232 AffineMap{}, workgroupAS);
3233 reductionSharedBuf = acc::GPUSharedMemoryOp::create(
3237 Value tidY = getThreadId(seqLoc, gpu::Dimension::y);
3239 if (isa<FloatType>(elemTy)) {
3240 identity = arith::ConstantOp::create(
3241 rewriter, seqLoc, elemTy, rewriter.
getFloatAttr(elemTy, 0.0));
3245 memref::StoreOp::create(rewriter, seqLoc, identity, reductionSharedBuf,
3247 createPerRowBarrier(seqLoc);
3249 processSeqLoop(parallelOp);
3250 loopReductions.push_back(parallelOp);
3252 LLVM_DEBUG(llvm::dbgs()
3253 <<
"processing loop: parDim: " << parDim <<
" as gpu par\n");
3257 Value gpuThreadId = getGPUThreadIdFor(parDim.getProcessor());
3258 mapping.
map(parallelOp.getInductionVars()[0], gpuThreadId);
3265 llvm::for_each(parallelOp.getResults(), [&](Value v) {
3266 Type valTy = v.getType();
3267 TypedAttr zeroAttr = rewriter.getZeroAttr(valTy);
3268 auto zero = arith::ConstantOp::create(rewriter, parallelOp->getLoc(),
3270 mapping.map(v, zero);
3272 loopReductions.push_back(parallelOp);
3274 insideAccumulateGridStride = savedGridStrideFlag;
3275 if (!insideAccumulateGridStride && !savedReductionBuf)
3276 reductionSharedBuf = Value();
3280static gpu::AllReduceOperation
3281getAllReduceOperation(arith::AtomicRMWKind kind) {
3283 case arith::AtomicRMWKind::addf:
3284 case arith::AtomicRMWKind::addi:
3285 return gpu::AllReduceOperation::ADD;
3286 case arith::AtomicRMWKind::mulf:
3287 case arith::AtomicRMWKind::muli:
3288 return gpu::AllReduceOperation::MUL;
3289 case arith::AtomicRMWKind::minu:
3290 return gpu::AllReduceOperation::MINUI;
3291 case arith::AtomicRMWKind::mins:
3292 return gpu::AllReduceOperation::MINSI;
3293 case arith::AtomicRMWKind::minnumf:
3294 return gpu::AllReduceOperation::MINNUMF;
3295 case arith::AtomicRMWKind::maxu:
3296 return gpu::AllReduceOperation::MAXUI;
3297 case arith::AtomicRMWKind::maxs:
3298 return gpu::AllReduceOperation::MAXSI;
3299 case arith::AtomicRMWKind::maxnumf:
3300 return gpu::AllReduceOperation::MAXNUMF;
3301 case arith::AtomicRMWKind::ori:
3302 return gpu::AllReduceOperation::OR;
3303 case arith::AtomicRMWKind::andi:
3304 return gpu::AllReduceOperation::AND;
3305 case arith::AtomicRMWKind::xori:
3306 return gpu::AllReduceOperation::XOR;
3307 case arith::AtomicRMWKind::minimumf:
3308 return gpu::AllReduceOperation::MINIMUMF;
3309 case arith::AtomicRMWKind::maximumf:
3310 return gpu::AllReduceOperation::MAXIMUMF;
3311 case arith::AtomicRMWKind::assign:
3314 llvm_unreachable(
"unsupported atomic kind");
3317void ACCCGToGPULowering::constructAtomicAccumulation(
3319 arith::AtomicRMWKind kind) {
3321 "cannot lower atomic accumulation on an stack variable");
3333 MemRefType memrefTy = cast<MemRefType>(memref.
getType());
3334 unsigned rank = memrefTy.getRank();
3335 assert(
indices.size() == rank &&
"expected one index per memref dimension");
3336 SmallVector<OpFoldResult> offsets(
indices.begin(),
indices.end());
3337 SmallVector<OpFoldResult> sizes(rank, rewriter.
getIndexAttr(1));
3338 SmallVector<OpFoldResult> strides(rank, rewriter.
getIndexAttr(1));
3339 target = memref::SubViewOp::create(rewriter, loc, memref, offsets, sizes,
3343 auto atomicUpdateOp =
3344 acc::AtomicUpdateOp::create(rewriter, loc,
target, Value());
3345 Region ®ion = atomicUpdateOp->getRegion(0);
3349 Value reductionExpr =
3351 acc::YieldOp::create(rewriter, loc, reductionExpr);
3355void ACCCGToGPULowering::createGPUAllReduceOp(
3356 Location loc, Value input, Value memref, arith::AtomicRMWKind kind,
3358 bool isPerThreadPrivateTarget) {
3359 gpu::AllReduceOperationAttr attr = gpu::AllReduceOperationAttr::get(
3360 computeRegion->getContext(), getAllReduceOperation(kind));
3361 auto allReduceOp = gpu::AllReduceOp::create(rewriter, loc, input, attr,
true);
3369 SmallVector<mlir::acc::GPUParallelDimAttr> inactiveParDims;
3370 MLIRContext *ctx = computeRegion->getContext();
3371 bool hasThreadX =
false;
3372 for (
auto parDim : parDimsAttr.getArray()) {
3373 if (parDim.isAnyBlock())
3375 if (parDim.isThreadX())
3377 if (computeRegion.getLaunchArg(parDim) ||
3379 inactiveParDims.push_back(parDim);
3385 if (!hasThreadX && !isSingleThreadWorkerLaunch())
3386 inactiveParDims.push_back(mlir::acc::GPUParallelDimAttr::threadXDim(ctx));
3387 Value predicate = emitPredicate(loc, inactiveParDims);
3394 bool isPerThreadPrivate = isPerThreadPrivateTarget ||
3395 isa_and_nonnull<memref::AllocaOp>(
3396 unwrapMemRefConversion(memref).getDefiningOp());
3399 if (predicate && !isPerThreadPrivate) {
3401 scf::IfOp::create(rewriter, loc, predicate,
false);
3402 Region &thenRegion = ifOp.getThenRegion();
3406 memref::StoreOp::create(rewriter, loc, allReduceOp, memref,
indices);
3407 if (predicate && !isPerThreadPrivate)
3412 reductionAccumValue[memref] = allReduceOp;
3415void ACCCGToGPULowering::postprocessAccumulateOp(
3416 acc::ReductionAccumulateOp op) {
3417 Location loc = op->getLoc();
3425 bool hasThreadDim =
false;
3426 SmallVector<mlir::acc::GPUParallelDimAttr> threadParDims;
3427 for (
auto parDim : op.getParDims().getArray()) {
3428 if (!parDim.isAnyBlock()) {
3429 hasThreadDim =
true;
3430 threadParDims.push_back(parDim);
3434 std::optional<arith::AtomicRMWKind> kind;
3436 FailureOr<arith::AtomicRMWKind> kindOr = getReductionKind(
3437 op.getReductionOperator(), op.getValue().getType(), loc);
3443 if (hasThreadDim && reductionSharedBuf &&
3444 op.getValue().getType() ==
3445 cast<MemRefType>(reductionSharedBuf.getType()).getElementType()) {
3446 Value val = op.getValue();
3447 Value mem = op.getMemref();
3448 Value tidY = getThreadId(loc, gpu::Dimension::y);
3449 memref::AtomicRMWOp::create(rewriter, loc, *kind, val, reductionSharedBuf,
3451 createPerRowBarrier(loc);
3453 memref::LoadOp::create(rewriter, loc, reductionSharedBuf, tidY);
3454 memref::StoreOp::create(rewriter, loc,
result, mem);
3455 reductionAccumValue[mem] =
result;
3456 }
else if (hasThreadDim) {
3457 createGPUAllReduceOp(loc, op.getValue(), op.getMemref(), *kind,
3465 bool isPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
3466 unwrapMemRefConversion(mem).getDefiningOp());
3467 if (!isPerThreadPrivate) {
3468 SmallVector<mlir::acc::GPUParallelDimAttr> predDims;
3469 for (
auto parDim : computeRegion.getLaunchParDims())
3470 if (!parDim.isAnyBlock())
3471 predDims.push_back(parDim);
3472 if (predDims.empty()) {
3473 predDims.push_back(mlir::acc::GPUParallelDimAttr::threadXDim(
3474 computeRegion->getContext()));
3476 Value predicate = emitPredicate(loc, predDims);
3478 scf::IfOp::create(rewriter, loc, predicate,
false);
3480 memref::StoreOp::create(rewriter, loc, val, mem);
3483 memref::StoreOp::create(rewriter, loc, val, mem);
3491void ACCCGToGPULowering::postprocessLoopReduction(scf::ParallelOp parLoop) {
3492 if (parLoop.getNumReductions() == 0)
3495 for (
unsigned i = 0; i < parLoop.getNumResults(); ++i) {
3496 for (Operation *user :
3498 if (acc::ReductionAccumulateOp accumulateOp =
3499 dyn_cast<acc::ReductionAccumulateOp>(user)) {
3500 postprocessAccumulateOp(accumulateOp);
3506void ACCCGToGPULowering::processExecuteRegion(scf::ExecuteRegionOp op) {
3507 LLVM_DEBUG(llvm::dbgs() <<
"processing execute region op: ";
3508 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3509 Location loc = op->getLoc();
3510 auto types = op.getResultTypes();
3511 Region &oldRegion = op.getRegion();
3513 auto executeRegionOp = scf::ExecuteRegionOp::create(rewriter, loc, types);
3514 Region ®ion = executeRegionOp.getRegion();
3518 llvm::DenseMap<Block *, Block *> blockMap;
3519 blockMap[&oldRegion.
front()] = ®ion.
front();
3523 for (
auto &oldBlock : llvm::drop_begin(oldRegion.
getBlocks())) {
3524 TypeRange argTypes = oldBlock.getArgumentTypes();
3525 size_t numArgs = argTypes.size();
3528 SmallVector<Location>(numArgs, loc));
3529 blockMap[&oldBlock] = newBlock;
3536 for (
auto [oldBlock, newBlock] :
3538 OpBuilder::InsertionGuard blockGuard(rewriter);
3540 for (
auto &bodyOp : oldBlock.getOperations()) {
3542 if (bodyOp.hasTrait<OpTrait::IsTerminator>())
3548 Operation *oldTerminator = oldBlock.getTerminator();
3550 Operation *newTerminator = rewriter.
clone(*oldTerminator, mapping);
3555 Block *newDest = blockMap.lookup(oldDest);
3556 assert(newDest &&
"Successor block must be in blockMap");
3560 mapping.
map(op->getResults(), executeRegionOp->getResults());
3564void ACCCGToGPULowering::processAccumulateOp(acc::ReductionAccumulateOp op) {
3565 LLVM_DEBUG(llvm::dbgs() <<
"processing accumulate op: " << *op <<
"\n");
3566 Value accumulateValue = op.getValue();
3567 if (reductionSharedBuf &&
3569 cast<MemRefType>(reductionSharedBuf.getType()).getElementType()) {
3570 Location loc = op->getLoc();
3571 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
3572 op.getReductionOperator(), accumulateValue.
getType(), loc);
3577 Value tidY = getThreadId(loc, gpu::Dimension::y);
3578 memref::AtomicRMWOp::create(rewriter, loc, *kind, mappedValue,
3580 createPerRowBarrier(loc);
3582 memref::LoadOp::create(rewriter, loc, reductionSharedBuf, tidY);
3583 memref::StoreOp::create(rewriter, loc,
result, memref);
3584 reductionAccumValue[memref] =
result;
3588 Operation *newOp = rewriter.
clone(*op, mapping);
3590 }
else if (isRedundantChainAccumulate(op)) {
3596 LLVM_DEBUG(llvm::dbgs() <<
" skipped: redundant chain accumulate\n");
3597 gpu::BarrierOp::create(rewriter, op->getLoc());
3601 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
3602 op.getReductionOperator(), accumulateValue.
getType(), op.getLoc());
3605 createGPUAllReduceOp(op->getLoc(), mappedValue, memref, *kind,
3614static bool isThreadVarying(Value v, ArrayRef<Value> threadIds,
3616 if (!v || !visited.insert(v).second)
3618 if (llvm::is_contained(threadIds, v))
3620 if (
auto arg = dyn_cast<BlockArgument>(v)) {
3621 Operation *owner = arg.getOwner()->getParentOp();
3622 unsigned dim = arg.getArgNumber();
3623 if (
auto loop = dyn_cast<scf::ParallelOp>(owner)) {
3624 if (dim >= loop.getLowerBound().size())
3626 return isThreadVarying(loop.getLowerBound()[dim], threadIds, visited) ||
3627 isThreadVarying(loop.getStep()[dim], threadIds, visited);
3629 if (
auto loop = dyn_cast<scf::ForOp>(owner))
3631 (isThreadVarying(loop.getLowerBound(), threadIds, visited) ||
3632 isThreadVarying(loop.getStep(), threadIds, visited));
3638 if (isa<gpu::ThreadIdOp, gpu::LaneIdOp>(def))
3640 return llvm::any_of(def->
getOperands(), [&](Value o) {
3641 return isThreadVarying(o, threadIds, visited);
3646static Value accumulatorRoot(Value v) {
3648 if (
auto cast = dyn_cast<memref::MemorySpaceCastOp>(op)) {
3649 v = cast.getSource();
3652 if (
auto viewLike = dyn_cast<ViewLikeOpInterface>(op)) {
3653 if (isa<MemRefType>(viewLike.getViewSource().getType())) {
3654 v = viewLike.getViewSource();
3665static Value matchAccumulatorUpdate(memref::StoreOp store, Value accum) {
3666 if (accumulatorRoot(store.getMemRef()) != accum)
3668 Operation *
combine = store.getValueToStore().getDefiningOp();
3669 if (!combine ||
combine->getNumOperands() != 2)
3671 for (
unsigned i = 0; i != 2; ++i) {
3672 auto load =
combine->getOperand(i).getDefiningOp<memref::LoadOp>();
3673 if (!
load || accumulatorRoot(
load.getMemRef()) != accum)
3675 if (!llvm::equal(
load.getIndices(), store.getIndices()))
3677 return combine->getOperand(1 - i);
3685static void atomicizeSharedAccumulatorUpdates(Value accum,
3686 arith::AtomicRMWKind kind,
3687 ArrayRef<Value> threadIds,
3688 RewriterBase &rewriter) {
3689 OpBuilder::InsertionGuard guard(rewriter);
3690 SmallVector<memref::StoreOp> stores;
3691 SmallVector<Value> worklist{accum};
3693 while (!worklist.empty()) {
3694 Value cur = worklist.pop_back_val();
3695 if (!seen.insert(cur).second)
3697 for (Operation *user : cur.
getUsers()) {
3698 if (
auto store = dyn_cast<memref::StoreOp>(user))
3699 stores.push_back(store);
3700 else if (isa<ViewLikeOpInterface, memref::MemorySpaceCastOp>(user))
3701 llvm::append_range(worklist, user->getResults());
3705 for (memref::StoreOp store : stores) {
3706 Value contribution = matchAccumulatorUpdate(store, accum);
3711 if (llvm::any_of(store.getIndices(), [&](Value idx) {
3712 DenseSet<Value> visited;
3713 return isThreadVarying(idx, threadIds, visited);
3716 Operation *
combine = store.getValueToStore().getDefiningOp();
3718 memref::AtomicRMWOp::create(rewriter, store.getLoc(), kind, contribution,
3719 store.getMemRef(), store.getIndices());
3721 if (combine &&
combine->use_empty())
3726void ACCCGToGPULowering::processAccumulateArrayOp(
3727 acc::ReductionAccumulateArrayOp op) {
3728 LLVM_DEBUG(llvm::dbgs() <<
"processing accumulate array op: " << *op <<
"\n");
3729 Location loc = op.getLoc();
3732 MemRefType memrefTy = dyn_cast<MemRefType>(memref.
getType());
3734 (void)accSupport.
emitNYI(loc,
"reduction: non-MemRefTy accumulate array");
3737 FailureOr<arith::AtomicRMWKind> kindOr = getReductionKind(
3738 op.getReductionOperator(), memrefTy.getElementType(), loc);
3741 arith::AtomicRMWKind kind = *kindOr;
3746 .getDefiningOp<acc::DataBoundsOp>();
3747 assert(boundsOp &&
"expected acc.bounds defining op for array accumulate");
3748 auto eraseDeadBounds = [&] {
3749 if (boundsOp->use_empty())
3753 bool hasThreadDim =
false;
3754 bool hasBlockDim =
false;
3755 for (
auto pd : op.getParDims().getArray()) {
3756 hasThreadDim |= pd.isAnyThread();
3757 hasBlockDim |= pd.isAnyBlock();
3762 if (hasBlockDim && !hasThreadDim) {
3770 bool regionLaunchesBlocks = llvm::any_of(
3771 computeRegion.getLaunchParDims(),
3772 [](mlir::acc::GPUParallelDimAttr d) { return d.isAnyBlock(); });
3773 if (!reductionHasBlockContext(op) && regionLaunchesBlocks) {
3775 loc,
"reduction: thread-only array reduction accumulate");
3787 auto storageIsThreadXPrivate = [&](Value v) ->
bool {
3788 acc::PrivateLocalOp privateLocal = getPrivateLocalForMemref(v);
3789 GPUParallelDimsAttr dims =
3790 privateLocal ? getPrivateParDims(privateLocal, computeRegion)
3791 : GPUParallelDimsAttr();
3793 if (Operation *root = unwrapMemRefConversion(v).getDefiningOp())
3797 llvm::any_of(dims.getArray(), [](
auto d) { return d.isThreadX(); });
3799 Operation *rootOp = unwrapMemRefConversion(memref).getDefiningOp();
3800 bool isSharedStorage = isa_and_nonnull<memref::AllocOp>(rootOp) ||
3801 isa_and_nonnull<acc::GPUSharedMemoryOp>(rootOp);
3802 if (
auto addrSpace = dyn_cast_if_present<gpu::AddressSpaceAttr>(
3803 memrefTy.getMemorySpace())) {
3805 addrSpace.getValue() == gpu::GPUDialect::getWorkgroupAddressSpace();
3807 bool isPerThreadPrivate =
3808 !isSharedStorage && storageIsThreadXPrivate(op.getMemref()) &&
3809 (memrefTy.hasStaticShape()
3810 ? canUseStackAlloca(memrefTy, loc,
options.maxThreadPrivateStack)
3811 : llvm::any_of(op.getParDims().getArray(),
3812 [](mlir::acc::GPUParallelDimAttr d) {
3813 return d.isThreadX();
3815 if (!isPerThreadPrivate) {
3820 SmallVector<Value> threadIds;
3821 if (Value xId = getGPUThreadIdFor(gpu::Processor::ThreadX))
3822 threadIds.push_back(xId);
3823 if (Value yId = getGPUThreadIdFor(gpu::Processor::ThreadY))
3824 threadIds.push_back(yId);
3825 if (Value zId = getGPUThreadIdFor(gpu::Processor::ThreadZ))
3826 threadIds.push_back(zId);
3827 atomicizeSharedAccumulatorUpdates(accumulatorRoot(memref), kind, threadIds,
3834 auto toIndex = [&](Value v) -> Value {
3837 return arith::IndexCastOp::create(rewriter, loc, rewriter.
getIndexType(),
3844 boundsOp.getLowerbound() ? toIndex(boundsOp.getLowerbound()) : zero;
3845 Value step = boundsOp.getStride() ? toIndex(boundsOp.getStride()) : one;
3850 if (boundsOp.getExtent()) {
3851 Value span = arith::MulIOp::create(rewriter, loc,
3852 toIndex(boundsOp.getExtent()), step);
3853 ub = arith::AddIOp::create(rewriter, loc, lb, span);
3855 assert(boundsOp.getUpperbound() &&
3856 "acc.bounds must specify an extent or upperbound");
3857 ub = arith::AddIOp::create(rewriter, loc, toIndex(boundsOp.getUpperbound()),
3862 auto forOp = scf::ForOp::create(rewriter, loc, lb, ub, step);
3864 OpBuilder::InsertionGuard guard(rewriter);
3866 Value iv = forOp.getInductionVar();
3867 SmallVector<Value>
indices{iv};
3868 if (memrefTy.getRank() > 1) {
3869 indices.resize(memrefTy.getRank());
3870 Value linearIndex = iv;
3871 for (int64_t dim = memrefTy.getRank() - 1; dim >= 0; --dim) {
3873 memrefTy.isDynamicDim(dim)
3874 ? memref::DimOp::create(rewriter, loc, memref, dim).getResult()
3876 memrefTy.getDimSize(dim))
3879 arith::RemUIOp::create(rewriter, loc, linearIndex, dimSize);
3882 arith::DivUIOp::create(rewriter, loc, linearIndex, dimSize);
3885 Value elem = memref::LoadOp::create(rewriter, loc, memref,
indices);
3886 createGPUAllReduceOp(loc, elem, memref, kind, op.getParDims(),
indices,
3893void ACCCGToGPULowering::processReductionOp(acc::ReductionInitOp op) {
3895 op.getRegion().walk<WalkOrder::PreOrder>([&](Operation *innerOp) {
3896 if (acc::YieldOp yieldOp = dyn_cast<acc::YieldOp>(innerOp)) {
3897 op.getResult().replaceAllUsesWith(mapping.
lookup(yieldOp.getOperand(0)));
3900 if (innerOp->getNumRegions() > 0) {
3904 rewriter.
clone(*innerOp, mapping);
3909void ACCCGToGPULowering::processReductionCombineOp(acc::ReductionCombineOp op) {
3910 LLVM_DEBUG(llvm::dbgs() <<
"processing reduction combine op: ";
3911 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3912 Location loc = op.getLoc();
3913 MemRefType memrefType = dyn_cast<MemRefType>(op.getSrcMemref().getType());
3914 assert(memrefType &&
"expected memref type for reduction combine op");
3915 assert(memrefType.getRank() == 0 &&
3916 "expected scalar memref type for reduction combine op");
3917 Type elTy = memrefType.getElementType();
3918 FailureOr<arith::AtomicRMWKind> kindOr =
3919 getReductionKind(op.getReductionOperator(), elTy, loc);
3922 arith::AtomicRMWKind kind = *kindOr;
3933 bool destIsPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
3934 unwrapMemRefConversion(destMemref).getDefiningOp());
3936 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
3938 for (
auto parDim : parDims) {
3939 if (parDim.isAnyBlock() && !destIsPerThreadPrivate) {
3945 auto srcLoad = memref::LoadOp::create(rewriter, loc, srcMemref);
3946 pendingCombineReloads.push_back({srcMemref, srcLoad});
3947 constructAtomicAccumulation(loc, destMemref, {}, srcLoad,
3955 auto srcLoad = memref::LoadOp::create(rewriter, loc, srcMemref,
ValueRange{});
3957 memref::LoadOp::create(rewriter, loc, destMemref,
ValueRange{});
3959 memref::StoreOp::create(rewriter, loc, combine, destMemref,
ValueRange{});
3962void ACCCGToGPULowering::processCombineRegionOp(
3963 acc::ReductionCombineRegionOp op) {
3964 LLVM_DEBUG(llvm::dbgs() <<
"processing combine region op: ";
3965 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3969 bool destIsPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
3972 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
3974 for (
auto parDim : parDims) {
3975 if (parDim.isAnyBlock() && !destIsPerThreadPrivate) {
3979 for (Operation *user : op.getSrcVar().getUsers()) {
3980 if (acc::ReductionAccumulateOp accumulateOp =
3981 dyn_cast<acc::ReductionAccumulateOp>(user)) {
3982 Location loc = accumulateOp.getLoc();
3983 FailureOr<arith::AtomicRMWKind> kind =
3984 getReductionKind(accumulateOp.getReductionOperator(),
3985 accumulateOp.getValue().getType(), loc);
3990 auto reductionLoad = memref::LoadOp::create(rewriter, loc, srcMemref);
3991 pendingCombineReloads.push_back({srcMemref, reductionLoad});
3992 constructAtomicAccumulation(loc,
3994 {}, reductionLoad, *kind);
4002 MemRefType memrefTy = cast<MemRefType>(privateMemref.
getType());
4003 if (isa<ComplexType>(memrefTy.getElementType())) {
4004 Location loc = op.getLoc();
4005 Value reductionResult =
4006 memref::LoadOp::create(rewriter, loc, privateMemref);
4007 arith::AtomicRMWKind kind = arith::AtomicRMWKind::addf;
4008 op.getRegion().walk([&](Operation *innerOp) {
4009 if (isa<complex::MulOp>(innerOp))
4010 kind = arith::AtomicRMWKind::mulf;
4012 constructAtomicAccumulation(loc,
4014 {}, reductionResult, kind);
4019 op.getRegion().walk<WalkOrder::PreOrder>([&](Operation *innerOp) {
4020 if (acc::YieldOp yieldOp = dyn_cast<acc::YieldOp>(innerOp))
4022 if (innerOp->getNumRegions() > 0) {
4026 rewriter.
clone(*innerOp, mapping);
4031void ACCCGToGPULowering::processGenericOp(Operation *op) {
4034 LLVM_DEBUG(llvm::dbgs() <<
"processing generic op, cloning: ";
4035 op->
print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4036 Operation *newOp = rewriter.
clone(*op, mapping);
4041void ACCCGToGPULowering::processGenericOpWithRegions(Operation *op) {
4043 LLVM_DEBUG(llvm::dbgs() <<
"processing generic op with regions: ";
4044 op->
print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4050 for (
auto [oldRegion, newRegion] :
4051 llvm::zip(op->
getRegions(), newOp->getRegions())) {
4053 for (
auto &oldBlock : oldRegion.
getBlocks()) {
4054 TypeRange argTypes = oldBlock.getArgumentTypes();
4055 size_t numArgs = argTypes.size();
4058 rewriter.
createBlock(&newRegion, newRegion.end(), argTypes,
4059 SmallVector<Location>(numArgs, op->
getLoc()));
4065 for (
auto &innerOp : oldBlock.getOperations()) {
4066 OpBuilder::InsertionGuard guard(rewriter);
4068 processOp(&innerOp);
4079void ACCCGToGPULowering::processOp(Operation *op) {
4080 if (isDeferredBarrierFlushPoint(op))
4081 flushDeferredBarriersBefore(op);
4085 scf::ParallelOp parallelOp = cast<scf::ParallelOp>(op);
4086 processParallelOp(parallelOp);
4087 }
else if (scf::ForOp seqLoop = dyn_cast<scf::ForOp>(op)) {
4088 processSeqLoop(seqLoop);
4089 }
else if (acc::PrivatizeOp privatize = dyn_cast<acc::PrivatizeOp>(op)) {
4090 processPrivatize(privatize);
4091 }
else if (acc::PrivateLocalOp privateLocal =
4092 dyn_cast<acc::PrivateLocalOp>(op)) {
4093 processPrivateLocal(privateLocal);
4094 }
else if (acc::PredicateRegionOp predicateRegionOp =
4095 dyn_cast<acc::PredicateRegionOp>(op)) {
4096 processPredicateRegion(predicateRegionOp);
4097 }
else if (acc::ReductionAccumulateOp accumulateOp =
4098 dyn_cast<acc::ReductionAccumulateOp>(op)) {
4099 processAccumulateOp(accumulateOp);
4100 }
else if (
auto accumulateArrayOp =
4101 dyn_cast<acc::ReductionAccumulateArrayOp>(op)) {
4102 processAccumulateArrayOp(accumulateArrayOp);
4103 }
else if (acc::ReductionInitOp reductionInitOp =
4104 dyn_cast<acc::ReductionInitOp>(op)) {
4105 processReductionOp(reductionInitOp);
4106 }
else if (acc::ReductionCombineOp reductionCombineOp =
4107 dyn_cast<acc::ReductionCombineOp>(op)) {
4108 processReductionCombineOp(reductionCombineOp);
4109 }
else if (
auto combineRegionOp =
4110 dyn_cast<acc::ReductionCombineRegionOp>(op)) {
4111 processCombineRegionOp(combineRegionOp);
4112 }
else if (acc::ReductionOp accReductionOp = dyn_cast<acc::ReductionOp>(op)) {
4113 mapping.
map(accReductionOp->getResult(0), accReductionOp.getVarPtr());
4116 LLVM_DEBUG(llvm::dbgs() <<
"skipping mapped op: " << *op <<
"\n");
4117 }
else if (isa<acc::YieldOp>(op)) {
4118 for (
auto [operand,
result] :
4122 }
else if (isa<scf::ExecuteRegionOp>(op)) {
4123 processExecuteRegion(cast<scf::ExecuteRegionOp>(op));
4125 isa<acc::OpenACCDialect>(op->
getDialect())) {
4126 processGenericOp(op);
4128 processGenericOpWithRegions(op);
4133class RemoveParWidth :
public OpRewritePattern<acc::ParWidthOp> {
4134 using OpRewritePattern<acc::ParWidthOp>::OpRewritePattern;
4135 LogicalResult matchAndRewrite(acc::ParWidthOp op,
4136 PatternRewriter &rewriter)
const override {
4137 if (Value launchArg = op.getLaunchArg()) {
4148class ACCComputeRegionToGPUPattern
4149 :
public OpRewritePattern<acc::ComputeRegionOp> {
4151 ACCComputeRegionToGPUPattern(MLIRContext *context,
4152 acc::OpenACCSupport &accSupport,
4153 const ACCCGToGPUOptions &
options)
4154 : OpRewritePattern<acc::ComputeRegionOp>(context), accSupport(accSupport),
4157 LogicalResult matchAndRewrite(acc::ComputeRegionOp op,
4158 PatternRewriter &rewriter)
const override {
4159 ACCCGToGPULowering kernelOpRewriter(op, rewriter, accSupport,
options);
4160 return kernelOpRewriter.rewrite();
4164 acc::OpenACCSupport &accSupport;
4165 const ACCCGToGPUOptions &
options;
4168class ACCCGToGPU :
public acc::impl::ACCCGToGPUBase<ACCCGToGPU> {
4170 using acc::impl::ACCCGToGPUBase<ACCCGToGPU>::ACCCGToGPUBase;
4172 void runOnOperation()
override {
4173 FunctionOpInterface funcOp = getOperation();
4174 MLIRContext *context = funcOp->getContext();
4176 assert(deviceType != mlir::acc::DeviceType::Host &&
4177 deviceType != mlir::acc::DeviceType::Multicore &&
4178 "ACCCGToGPU only supports GPU device types");
4180 options.deviceType = deviceType;
4181 options.maxWorkgroupSharedMemory = maxWorkgroupSharedMemory;
4182 options.maxThreadPrivateStack = maxThreadPrivateStack;
4183 options.subgroupSize = subgroupSize;
4186 std::optional<std::reference_wrapper<acc::OpenACCSupport>> cachedAnalysis =
4187 getCachedParentAnalysis<acc::OpenACCSupport>(funcOp->getParentOp());
4188 acc::OpenACCSupport &accSupport = cachedAnalysis
4189 ? cachedAnalysis->get()
4190 : getAnalysis<acc::OpenACCSupport>();
4192 RewritePatternSet patterns(context);
4193 patterns.insert<ACCComputeRegionToGPUPattern>(context, accSupport,
options);
4194 patterns.insert<RemoveParWidth>(context);
4196 target.markUnknownOpDynamicallyLegal([](Operation *) {
return true; });
4197 target.addIllegalOp<acc::ComputeRegionOp, acc::ParWidthOp>();
4198 if (
failed(applyPartialConversion(getOperation(),
target,
4199 std::move(patterns)))) {
4200 signalPassFailure();
static void createForAllDimensions(OpBuilder &builder, Location loc, SmallVectorImpl< Value > &values)
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing copies
Creates a buffer in the faster memory space for the specified memref region (memref has to be non-zer...
static llvm::ManagedStatic< PassManagerOptions > options
static void rewrite(DataFlowSolver &solver, MLIRContext *context, MutableArrayRef< Region > initialRegions)
Rewrite the given regions using the computing analysis.
Block represents an ordered list of Operations.
MutableArrayRef< BlockArgument > BlockArgListType
BlockArgument getArgument(unsigned i)
unsigned getNumArguments()
RetT walk(FnT &&callback)
Walk all nested operations, blocks (including this block) or regions, depending on the type of callba...
Operation * getTerminator()
Get the terminator operation of this block.
bool mightHaveTerminator()
Return "true" if this block might have a terminator.
BlockArgListType getArguments()
IntegerAttr getIndexAttr(int64_t value)
IntegerAttr getI32IntegerAttr(int32_t value)
IntegerAttr getIntegerAttr(Type type, int64_t value)
FloatAttr getFloatAttr(Type type, double value)
IntegerAttr getI64IntegerAttr(int64_t value)
TypedAttr getZeroAttr(Type type)
MLIRContext * getContext() const
auto lookupOrDefault(T from) const
Lookup a mapped value within the map.
auto lookup(T from) const
Lookup a mapped value within the map.
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
bool contains(T from) const
Checks to see if a mapping for 'from' exists.
auto lookupOrNull(T from) const
Lookup a mapped value within the map.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
MLIRContext is the top-level object for a collection of MLIR operations.
RAII guard to reset the insertion point of the builder when destroyed.
This class helps build Operations.
InsertPoint saveInsertionPoint() const
Return a saved insertion point.
Block::iterator getInsertionPoint() const
Returns the current insertion point of the builder.
Block * createBlock(Region *parent, Region::iterator insertPt={}, TypeRange argTypes={}, ArrayRef< Location > locs={})
Add new block with 'argTypes' arguments and set the insertion point to the end of it.
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
void restoreInsertionPoint(InsertPoint ip)
Restore the insert point to a previously saved point.
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Operation * cloneWithoutRegions(Operation &op, IRMapping &mapper)
Creates a deep copy of this operation but keep the operation regions empty.
Operation is the basic unit of execution within MLIR.
Dialect * getDialect()
Return the dialect this operation is associated with, or nullptr if the associated dialect is not loa...
Value getOperand(unsigned idx)
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
unsigned getNumSuccessors()
bool isBeforeInBlock(Operation *other)
Given an operation 'other' that is within the same parent block, return whether the current operation...
result_iterator result_begin()
Block * getBlock()
Returns the operation block that contains this operation.
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
unsigned getNumRegions()
Returns the number of regions held by this operation.
Location getLoc()
The source location the operation was defined or derived from.
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
void print(raw_ostream &os, const OpPrintingFlags &flags={})
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
result_iterator result_end()
operand_range getOperands()
Returns an iterator on the underlying Value's.
void setSuccessor(Block *block, unsigned index)
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Block * getSuccessor(unsigned index)
result_range getResults()
This class contains a list of basic blocks and a link to the parent operation it is attached to.
BlockListType & getBlocks()
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
This class allows for representing and managing the symbol table used by operations with the 'SymbolT...
Operation * lookup(StringRef name) const
Look up a symbol with the specified name, returning null if no such name exists.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
auto walk(WalkFns &&...walkFns)
Walk this type and all attibutes/types nested within using the provided walk functions.
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
This class provides an abstraction over the different types of ranges over Values.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
user_iterator user_begin() const
user_range getUsers() const
bool hasOneUse() const
Returns true if this value has exactly one use.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
static WalkResult advance()
static WalkResult interrupt()
ParDimAttrT seqDim(MLIRContext *ctx) const
virtual ParDimAttrT map(MLIRContext *ctx, ParLevel level) const =0
Map an OpenACC parallelism level to target dimension.
ParDimAttrT vectorDim(MLIRContext *ctx) const
ParDimAttrT workerDim(MLIRContext *ctx) const
ParDimAttrT gangDim(MLIRContext *ctx, ParLevel level) const
Convenience methods for specific parallelism levels.
remark::detail::InFlightRemark emitRemark(Operation *op, std::function< std::string()> messageFn, llvm::StringRef category="openacc")
Emit an OpenACC remark with lazy message generation.
InFlightDiagnostic emitNYI(Location loc, const Twine &message)
Report a case that is not yet supported by the implementation.
std::string getVariableName(Value v)
Get the variable name for a given value.
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()
int64_t sumExistingSharedMemoryBytes(Region ®ion)
Sum aligned static_upper_bound_bytes for all acc.gpu_shared_memory in region.
Value getGPUThreadId(gpu::Processor processor, gpu::LaunchOp launch, const llvm::DenseMap< gpu::Processor, Value > &indexOps)
Return the thread/block index for processor from launch, or from indexOps when launch is null.
PrivatizeOp getPrivatizeOp(PrivateLocalOp privateLocal, ComputeRegionOp computeRegion)
Resolve the acc.privatize operation associated with a private local.
bool hasGPUBlockRedundantAttr(Operation *op)
Return whether op is marked with the acc.gpu_block_redundant attribute, i.e.
Value generateReductionOp(OpBuilder &b, Location loc, Value lhs, Value rhs, arith::AtomicRMWKind kind)
Combines two reduction partial values using the operator for kind.
void removeParDim(llvm::SmallVector< GPUParallelDimAttr > &parDims, GPUParallelDimAttr parDim)
Remove parDim from parDims if present.
void setParDimsAttr(Operation *op, GPUParallelDimsAttr attr)
Set parallel dimensions on op.
ActiveParDimsAttr getActiveParDimsAttr(Operation *op)
Obtain the active parallel dimensions carried by op, if any.
std::optional< int64_t > getPrivateLocalSharedMemoryUpperBoundBytes(PrivateLocalOp privateLocal, ComputeRegionOp computeRegion, ModuleOp module, const ACCToGPUMappingPolicy &policy, OpenACCSupport *support=nullptr)
Upper-bound byte size for a shared-memory private_local candidate, or std::nullopt when not eligible ...
static bool isThreadXPrivatize(PrivatizeOp privatize)
ACCParMappingPolicy< mlir::acc::GPUParallelDimAttr > ACCToGPUMappingPolicy
Type alias for the GPU-specific mapping policy.
LoopNest buildLoopNest(OpBuilder &builder, Location loc, ValueRange lbs, ValueRange ubs, ValueRange steps, ValueRange iterArgs, function_ref< ValueVector(OpBuilder &, Location, ValueRange, ValueRange)> bodyBuilder=nullptr)
Creates a perfect nest of "for" loops, i.e.
Value constantOne(OpBuilder &builder, Location loc, Type tp)
Generates a 1-valued constant of the given type.
OwningOpRef< spirv::ModuleOp > combine(ArrayRef< spirv::ModuleOp > inputModules, OpBuilder &combinedModuleBuilder, SymbolRenameListener symRenameListener)
Combines a list of SPIR-V inputModules into one.
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
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...