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";
167 FunctionOpInterface funcOp = op->
getParentOfType<FunctionOpInterface>();
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();
181 return policy.
gangDim(ctx, gangLevel);
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))
189 return policy.
seqDim(ctx);
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");
254 for (GPUParallelDimAttr parDim : parDimsAttr.getArray())
259 for (GPUParallelDimAttr parDim : computeRegion.getLaunchParDims())
261 parentLoop = parentLoop->getParentOfType<scf::ParallelOp>();
264 if (GPUParallelDimsAttr parDimsAttr =
getParDimsAttr(computeRegion))
265 for (GPUParallelDimAttr parDim : parDimsAttr.getArray())
271static Value stripIndexCastsFromValue(
Value x) {
275 while (arith::IndexCastOp castOp = dyn_cast<arith::IndexCastOp>(op)) {
284static FailureOr<int64_t> extractIntConst(
Value x,
285 bool stripIndexCasts =
false) {
287 x = stripIndexCastsFromValue(x);
291 assert(constOp.getType().getIntOrFloatBitWidth() <= 64);
292 return constOp.value();
295 return constOp.value();
302 x = stripIndexCastsFromValue(x);
303 FailureOr<int64_t> conX = extractIntConst(x);
310static bool getPassThroughResults(
Operation *userOp,
Value trackedOperand,
312 if (ViewLikeOpInterface viewLikeOp = dyn_cast<ViewLikeOpInterface>(userOp)) {
313 if (viewLikeOp.getViewSource() == trackedOperand) {
314 passThroughResults.push_back(viewLikeOp.getViewDest());
323 if (acc::PartialEntityAccessOpInterface partialAccess =
324 dyn_cast<acc::PartialEntityAccessOpInterface>(userOp)) {
325 if (partialAccess.getBaseEntity() == trackedOperand) {
337 if (ViewLikeOpInterface viewLike = dyn_cast<ViewLikeOpInterface>(op)) {
338 if (isa<MemRefType>(viewLike.getViewSource().getType()) ||
339 isa<MemRefType>(viewLike.getViewDest().getType())) {
340 v = viewLike.getViewSource();
352 if (value.
getType() == resultType)
354 if (PointerLikeType ptrLike = dyn_cast<PointerLikeType>(value.
getType())) {
355 if (
Value casted = ptrLike.genCast(builder, loc, value, resultType))
358 if (PointerLikeType ptrLike = dyn_cast<PointerLikeType>(resultType)) {
359 if (
Value casted = ptrLike.genCast(builder, loc, value, resultType))
362 emitError(loc) <<
"unsupported pointer-like type cast from "
363 << value.
getType() <<
" to " << resultType;
376 if (GPUParallelDimsAttr parDimsAttr = privatize.getParDimsAttr())
377 return llvm::any_of(parDimsAttr.getArray(),
378 [](GPUParallelDimAttr d) { return d.isThreadX(); });
384 gpu::BarrierOp::create(builder, loc);
389 gpu::BarrierOp::create(builder, loc,
ArrayAttr{},
391 gpu::BarrierScope::Subgroup);
395class ACCCGToGPULowering {
397 explicit ACCCGToGPULowering(acc::ComputeRegionOp computeRegion,
398 RewriterBase &rewriter,
399 acc::OpenACCSupport &accSupport,
400 const ACCCGToGPUOptions &options)
401 : rewriter(rewriter), computeRegion(computeRegion),
402 accSupport(accSupport), options(options),
404 options.maxWorkgroupSharedMemory,
410 gpu::LaunchOp getLaunch()
const {
return launch; }
412 bool hasFailed =
false;
413 bool insideAccumulateGridStride =
false;
414 Value reductionSharedBuf;
417 llvm::DenseMap<Value, Value> reductionAccumValue;
419 llvm::SmallVector<std::pair<Value, memref::LoadOp>> pendingCombineReloads;
423 void processParallelOp(scf::ParallelOp parallelOp);
425 template <
typename LoopOp>
426 void processSeqLoop(LoopOp loopOp);
428 void processPredicateRegion(acc::PredicateRegionOp interOp);
431 processPrivateLocal(acc::PrivateLocalOp privateLocal,
432 std::optional<int64_t> sharedMemCopies = std::nullopt);
434 Value processPrivatize(acc::PrivatizeOp privatize);
436 void processExecuteRegion(scf::ExecuteRegionOp op);
438 void processAccumulateOp(acc::ReductionAccumulateOp op);
440 void processAccumulateArrayOp(acc::ReductionAccumulateArrayOp op);
442 void processReductionOp(acc::ReductionInitOp op);
444 void processReductionCombineOp(acc::ReductionCombineOp op);
446 void processCombineRegionOp(acc::ReductionCombineRegionOp op);
448 void processGenericOp(Operation *op);
450 void processGenericOpWithRegions(Operation *op);
452 void processOp(Operation *op);
455 void constructAtomicAccumulation(Location loc, Value memref,
457 arith::AtomicRMWKind kind);
460 FailureOr<arith::AtomicRMWKind> getReductionKind(acc::ReductionOperator redOp,
461 Type type, Location loc);
465 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
466 SmallVector<mlir::acc::GPUParallelDimAttr>>
467 computeActiveAndInactiveParDims(Operation *op,
Block *block);
471 emitPredicate(Location loc,
472 SmallVector<mlir::acc::GPUParallelDimAttr> &inactiveParDims);
476 std::optional<int64_t>
477 isEligibleForSharedMemory(acc::PrivateLocalOp privateLocal,
481 bool tryAllocateSharedMemory(int64_t bytes);
484 int64_t getElementSizeInBytes(Location loc, Type elementType)
const;
487 bool canUseStackAlloca(MemRefType baseTy, Location loc,
488 int64_t maxThreadPrivateStack)
const;
491 void createBarrier(Location loc, mlir::acc::GPUParallelDimsAttr parDimsAttr);
497 void createPerRowBarrier(Location loc);
501 void createBarrierAfterSeqLoop(Operation *loopOp);
504 void flushDeferredBarriersBefore(Operation *beforeOp);
507 bool mayWriteSharedMemory(Operation *loopOp);
510 PrivateMemScope getPrivateMemScope(acc::PrivatizeOp privatizeOp);
513 PrivateMemScope getPrivateScopeForMemref(Value memref);
516 acc::PrivatizeOp getPrivatizeForMemref(Value memref);
520 PrivateMemScope needsPreStoreReuseBarrier(acc::PredicateRegionOp interOp);
523 void createGPUAllReduceOp(Location loc, Value input, Value memref,
524 arith::AtomicRMWKind kind,
525 mlir::acc::GPUParallelDimsAttr parDimsAttr,
529 void postprocessAccumulateOp(acc::ReductionAccumulateOp op);
532 void postprocessLoopReduction(scf::ParallelOp parLoop);
538 llvm::DenseMap<gpu::Processor, Value> &ids,
539 llvm::DenseMap<gpu::Processor, Value> &dims) {
540 ids[gpu::Processor::BlockX] = gpu::BlockIdOp::create(
541 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
542 ids[gpu::Processor::BlockY] = gpu::BlockIdOp::create(
543 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
544 ids[gpu::Processor::BlockZ] = gpu::BlockIdOp::create(
545 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
546 ids[gpu::Processor::ThreadX] = gpu::ThreadIdOp::create(
547 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
548 ids[gpu::Processor::ThreadY] = gpu::ThreadIdOp::create(
549 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
550 ids[gpu::Processor::ThreadZ] = gpu::ThreadIdOp::create(
551 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
552 dims[gpu::Processor::BlockX] = gpu::GridDimOp::create(
553 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
554 dims[gpu::Processor::BlockY] = gpu::GridDimOp::create(
555 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
556 dims[gpu::Processor::BlockZ] = gpu::GridDimOp::create(
557 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
558 dims[gpu::Processor::ThreadX] = gpu::BlockDimOp::create(
559 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
560 dims[gpu::Processor::ThreadY] = gpu::BlockDimOp::create(
561 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
562 dims[gpu::Processor::ThreadZ] = gpu::BlockDimOp::create(
563 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
568 BlockArgument getOrAppendInsBlockArg(Value outside) {
569 if (std::optional<BlockArgument> blockArg =
570 computeRegion.getBlockArg(outside)) {
573 return computeRegion.appendInputArg(outside);
577 void preparePrivatizeExtentInsOperands() {
578 computeRegion.walk([&](acc::PrivateLocalOp privateLocal) {
579 acc::PrivatizeOp privatizeOp =
581 if (privatizeOp->getParentOfType<acc::ComputeRegionOp>() == computeRegion)
583 for (Value extent : privatizeOp.getDynamicSizes())
584 getOrAppendInsBlockArg(extent);
590 resolvePrivateLocalDynamicExtents(acc::PrivateLocalOp privateLocal) {
591 acc::PrivatizeOp privatizeOp =
getPrivatizeOp(privateLocal, computeRegion);
592 SmallVector<Value> extents;
593 for (Value extent : privatizeOp.getDynamicSizes()) {
594 if (std::optional<BlockArgument> blockArg =
595 computeRegion.getBlockArg(extent)) {
596 extents.push_back(mapping.lookupOrDefault(*blockArg));
599 extents.push_back(mapping.lookupOrDefault(extent));
604 RewriterBase &rewriter;
605 acc::ComputeRegionOp computeRegion;
607 acc::OpenACCSupport &accSupport;
608 const ACCCGToGPUOptions &options;
609 gpu::LaunchOp launch;
611 llvm::SmallVector<scf::ParallelOp> loopReductions;
612 llvm::DenseMap<gpu::Processor, Value> threadIdMap;
613 llvm::DenseMap<gpu::Processor, Value> dimensionMap;
615 bool hasThreadYReduction =
false;
617 bool hasThreadLevelRoutineCall =
false;
619 bool hasThreadYBarrier =
false;
622 llvm::DenseMap<Type, Value> privatizeBroadcastCache;
624 int64_t staticBlockDimX = 1024;
625 acc::DefaultACCToGPUMappingPolicy defaultPolicy;
626 SharedMemoryBudget sharedMemBudget;
627 SmallVector<std::string> sharedMemPrivateVarNames;
628 llvm::SmallVector<Operation *, 4> deferredBarrierSeqLoops;
630 Value getThreadId(Location loc, gpu::Dimension dim) {
631 return gpu::ThreadIdOp::create(rewriter, loc, rewriter.getIndexType(), dim);
634 Value getBlockDim(Location loc, gpu::Dimension dim) {
635 return gpu::BlockDimOp::create(rewriter, loc, rewriter.getIndexType(), dim);
639 Value getGPUThreadIdFor(gpu::Processor proc) {
644 Value getGPUSizeFor(gpu::Processor proc) {
645 return getGPUSize(proc, getLaunch(), dimensionMap);
650 Type elementType)
const {
651 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
652 if (std::optional<acc::TypeSizeAndAlignment> sizeAndAlignment =
654 return sizeAndAlignment->first.getFixedValue();
657 llvm::raw_string_ostream os(msg);
658 os <<
"element size computation for unsupported type: " << elementType;
659 (void)accSupport.
emitNYI(loc, os.str());
663bool ACCCGToGPULowering::canUseStackAlloca(
664 MemRefType baseTy, Location loc, int64_t maxThreadPrivateStack)
const {
665 for (int64_t dim : baseTy.getShape())
666 if (dim == ShapedType::kDynamic)
668 int64_t elementSize = getElementSizeInBytes(loc, baseTy.getElementType());
669 int64_t numElements = 1;
670 for (int64_t dim : baseTy.getShape()) {
671 if (numElements > maxThreadPrivateStack / std::max<int64_t>(dim, 1))
675 return elementSize * numElements < maxThreadPrivateStack;
683static bool reductionHasBlockContext(acc::ReductionAccumulateArrayOp accArr) {
684 auto hasBlock = [](mlir::acc::GPUParallelDimsAttr parDims) {
685 return parDims && llvm::any_of(parDims.getArray(),
686 [](
auto pd) { return pd.isAnyBlock(); });
688 if (hasBlock(accArr.getParDimsAttr()))
690 for (scf::ParallelOp loop = accArr->getParentOfType<scf::ParallelOp>(); loop;
691 loop = loop->getParentOfType<scf::ParallelOp>()) {
701static acc::ReductionAccumulateArrayOp perThreadArrayReductionAccum(Value v) {
702 SmallVector<Value> worklist{v};
704 while (!worklist.empty()) {
705 Value cur = worklist.pop_back_val();
706 if (!seen.insert(cur).second)
708 for (Operation *user : cur.
getUsers()) {
709 if (acc::ReductionAccumulateArrayOp accArr =
710 dyn_cast<acc::ReductionAccumulateArrayOp>(user)) {
711 bool hasThread =
false;
712 for (
auto pd : accArr.getParDims().getArray())
713 hasThread |= pd.isAnyThread();
714 if (hasThread && reductionHasBlockContext(accArr))
718 SmallVector<Value> through;
719 if (getPassThroughResults(user, cur, through))
720 worklist.append(through.begin(), through.end());
721 else if (isa<ViewLikeOpInterface>(user))
722 worklist.append(user->result_begin(), user->result_end());
731static void initPerThreadArrayAccum(OpBuilder &
b, Location loc, Value alloca,
733 arith::AtomicRMWKind kind) {
734 assert(baseTy.getRank() == 1 && baseTy.hasStaticShape() &&
735 "per-thread array reduction accumulator must be static rank-1");
741 auto forOp = scf::ForOp::create(
b, loc, lb, ub, step);
742 OpBuilder::InsertionGuard g(
b);
743 b.setInsertionPoint(forOp.getBody()->getTerminator());
744 memref::StoreOp::create(
b, loc, ident, alloca, forOp.getInductionVar());
747std::optional<int64_t>
748ACCCGToGPULowering::isEligibleForSharedMemory(acc::PrivateLocalOp privateLocal,
751 if (perThreadArrayReductionAccum(privateLocal.getResult()))
753 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
755 privateLocal, computeRegion, module, defaultPolicy, &accSupport);
756 if (
failed(isCandidate)) {
760 if (!isCandidate.value())
762 std::optional<int64_t> upperBound =
764 module, defaultPolicy);
765 assert(upperBound &&
"candidate private_local must have an upper bound");
766 int64_t elementSize =
767 getElementSizeInBytes(privateLocal.getLoc(), baseTy.getElementType());
768 int64_t numElements = 1;
769 for (int64_t dim : baseTy.getShape())
771 return *upperBound / (elementSize * numElements);
774bool ACCCGToGPULowering::tryAllocateSharedMemory(int64_t bytes) {
778FailureOr<arith::AtomicRMWKind>
779ACCCGToGPULowering::getReductionKind(acc::ReductionOperator redOp, Type type,
781 if (std::optional<arith::AtomicRMWKind> kind =
786 llvm::raw_string_ostream os(msg);
787 os <<
"reduction operator (" << redOp <<
") for type " << type;
788 (void)accSupport.
emitNYI(loc, os.str());
792LogicalResult ACCCGToGPULowering::rewrite() {
797 computeRegion->walk([&](acc::ReductionAccumulateOp op) -> WalkResult {
798 for (
auto parDim : op.getParDimsAttr().getArray()) {
799 if (parDim.isThreadY()) {
800 hasThreadYReduction =
true;
811 computeRegion->walk([&](CallOpInterface callOp) -> WalkResult {
812 if (mlir::acc::GPUParallelDimAttr parDim =
813 getAccRoutineCallParDim(callOp, defaultPolicy)) {
814 if (parDim.isThreadX() || parDim.isThreadY()) {
815 hasThreadLevelRoutineCall =
true;
822 Location loc = computeRegion->getLoc();
825 auto launchArgument = [&](gpu::Processor processor) -> Value {
826 mlir::acc::GPUParallelDimAttr parDim = mlir::acc::GPUParallelDimAttr::get(
827 computeRegion->getContext(), processor);
828 std::optional<Value> maybeLaunchArg =
829 computeRegion.getKnownLaunchArg(parDim);
830 LLVM_DEBUG(llvm::dbgs() <<
"ACCCGToGPU: launch-arg: "
831 <<
" parDim: " << parDim <<
" gpu: " << processor
833 << maybeLaunchArg.value_or(constantOne) <<
"\n");
837 maybeLaunchArg.value_or(constantOne));
839 LLVM_DEBUG(llvm::dbgs() <<
"ACCCGToGPU: creating gpu launch op: \n");
843 auto mapLaunchArguments = [&](gpu::Processor processor, Value launchArg) {
844 mlir::acc::GPUParallelDimAttr parDim = mlir::acc::GPUParallelDimAttr::get(
845 computeRegion->getContext(), processor);
846 std::optional<Value> kernelArg = computeRegion.getLaunchArg(parDim);
848 mapping.
map(computeRegion.gpuParWidth(processor), launchArg);
851 llvm::StringRef blockDimXName =
"blockDim.x";
852 llvm::StringRef blockDimYName =
"blockDim.y";
853 std::string deviceLabel = getDeviceRemarkQualifier(
options.deviceType);
855 if (!computeRegion->getParentOfType<gpu::GPUFuncOp>()) {
856 Value blockDimX = launchArgument(gpu::Processor::ThreadX);
859 staticBlockDimX = bdxVal.getSExtValue();
860 Value blockDimY = launchArgument(gpu::Processor::ThreadY);
861 Value blockDimZ = launchArgument(gpu::Processor::ThreadZ);
862 Value gridDimX = launchArgument(gpu::Processor::BlockX);
863 Value gridDimY = launchArgument(gpu::Processor::BlockY);
864 Value gridDimZ = launchArgument(gpu::Processor::BlockZ);
870 auto getName = [&](Value val) -> std::string {
872 return name.empty() ?
"(*)" : name;
874 bool isEffectivelySerial =
875 sameEffectiveValue(blockDimX, 1) &&
876 sameEffectiveValue(blockDimY, 1) &&
877 sameEffectiveValue(blockDimZ, 1) && sameEffectiveValue(gridDimX, 1) &&
878 sameEffectiveValue(gridDimY, 1) && sameEffectiveValue(gridDimZ, 1);
879 return (llvm::Twine(
"Generating ") +
880 llvm::Twine(isEffectivelySerial ?
"serial " :
"") + deviceLabel +
881 " code with gridDim=" + getName(gridDimX) +
"x" +
882 getName(gridDimY) +
"x" + getName(gridDimZ) +
883 " blockDim=" + getName(blockDimX) +
"x" + getName(blockDimY) +
884 "x" + getName(blockDimZ))
889 if (mlir::Value streamValue = computeRegion.getStream()) {
890 LLVM_DEBUG(llvm::dbgs()
891 <<
"\nDEBUG: Creating async gpu.launch with stream: "
892 << streamValue <<
"\n");
893 launch = gpu::LaunchOp::create(
894 rewriter, loc, gridDimX, gridDimY, gridDimZ, blockDimX, blockDimY,
900 launch.getAsyncDependenciesMutable().append(streamValue);
902 LLVM_DEBUG(llvm::dbgs()
903 <<
"\nDEBUG: No stream, creating sync gpu.launch\n");
904 launch = gpu::LaunchOp::create(rewriter, loc, gridDimX, gridDimY,
905 gridDimZ, blockDimX, blockDimY, blockDimZ);
910 if (
auto kernelFuncName = computeRegion.getKernelFuncNameAttr())
911 launch.setFunctionAttr(kernelFuncName);
912 if (
auto kernelModuleName = computeRegion.getKernelModuleNameAttr())
913 launch.setModuleAttr(kernelModuleName);
916 gpu::TerminatorOp::create(rewriter, loc);
918 mapLaunchArguments(gpu::Processor::BlockX,
919 gpu::GridDimOp::create(rewriter, loc,
922 mapLaunchArguments(gpu::Processor::BlockY,
923 gpu::GridDimOp::create(rewriter, loc,
926 mapLaunchArguments(gpu::Processor::BlockZ,
927 gpu::GridDimOp::create(rewriter, loc,
930 mapLaunchArguments(gpu::Processor::ThreadX,
931 gpu::BlockDimOp::create(rewriter, loc,
934 mapLaunchArguments(gpu::Processor::ThreadY,
935 gpu::BlockDimOp::create(rewriter, loc,
938 mapLaunchArguments(gpu::Processor::ThreadZ,
939 gpu::BlockDimOp::create(rewriter, loc,
946 OpBuilder::InsertionGuard guard(rewriter);
949 mapLaunchArguments(gpu::Processor::BlockX,
950 dimensionMap[gpu::Processor::BlockX]);
951 mapLaunchArguments(gpu::Processor::BlockY,
952 dimensionMap[gpu::Processor::BlockY]);
953 mapLaunchArguments(gpu::Processor::BlockZ,
954 dimensionMap[gpu::Processor::BlockZ]);
955 mapLaunchArguments(gpu::Processor::ThreadX,
956 dimensionMap[gpu::Processor::ThreadX]);
957 mapLaunchArguments(gpu::Processor::ThreadY,
958 dimensionMap[gpu::Processor::ThreadY]);
959 mapLaunchArguments(gpu::Processor::ThreadZ,
960 dimensionMap[gpu::Processor::ThreadZ]);
965 preparePrivatizeExtentInsOperands();
966 Block *body = computeRegion.getBody();
967 unsigned numLaunchArgs = computeRegion.getLaunchArgs().size();
968 ValueRange inputArgs = computeRegion.getInputArgs();
972 assert(computeRegion.getRegion().hasOneBlock() &&
973 "compute region only supports one block region for now");
975 for (
auto &op : computeRegion.getRegion().getBlocks().front().getOperations())
978 for (
auto &parLoop : loopReductions)
979 postprocessLoopReduction(parLoop);
983 if (!pendingCombineReloads.empty() && launch) {
984 DominanceInfo domInfo(launch);
985 for (
auto &[slot, loadOp] : pendingCombineReloads) {
986 llvm::DenseMap<Value, Value>::iterator it =
987 reductionAccumValue.find(slot);
988 if (it == reductionAccumValue.end())
990 if (!domInfo.dominates(it->second, loadOp.getOperation()))
997 const int64_t subgroupSize =
options.subgroupSize;
998 const int64_t subgroupAlignMask = subgroupSize - 1;
1004 bool isShuffleEnabled =
false;
1006 launch.walk([&](gpu::AllReduceOp allReduce) -> WalkResult {
1007 ArrayRef<mlir::acc::GPUParallelDimAttr> parDims =
1009 for (
auto parDim : parDims) {
1010 if (parDim.isThreadX() || parDim.isThreadY()) {
1012 isShuffleEnabled =
true;
1019 if (!isShuffleEnabled) {
1020 launch.walk([&](func::CallOp callOp) -> WalkResult {
1021 if (gpu::GPUFuncOp callee =
1022 callOp->getParentOfType<ModuleOp>()
1023 .lookupSymbol<gpu::GPUFuncOp>(callOp.getCallee())) {
1024 callee.walk([&](gpu::AllReduceOp allReduce) -> WalkResult {
1025 ArrayRef<mlir::acc::GPUParallelDimAttr> parDims =
1027 for (
auto parDim : parDims) {
1028 if (parDim.isThreadX() || parDim.isThreadY()) {
1029 isShuffleEnabled =
true;
1037 : WalkResult::advance();
1041 if (isShuffleEnabled || hasThreadYBarrier) {
1044 Value curBlockDimX = launch.getBlockSizeX();
1045 Value curBlockDimY = launch.getBlockSizeY();
1049 auto getName = [&](Value val) -> std::string {
1051 return name.empty() ?
"(*)" : name;
1053 std::string blockDimXValStr = getName(curBlockDimX);
1054 std::string blockDimYValStr = getName(curBlockDimY);
1055 llvm::StringRef kind =
1056 isShuffleEnabled ?
"Shuffle reduction" :
"ThreadY barrier";
1057 return (llvm::Twine(kind) +
1058 " is generated while adjusting the number of threads into "
1060 llvm::Twine(subgroupSize) +
".\n\t" + blockDimXName +
": `" +
1061 blockDimXValStr +
"` to `((" + blockDimXValStr +
" + " +
1062 llvm::Twine(subgroupAlignMask) +
") / " +
1063 llvm::Twine(subgroupSize) +
") * " + llvm::Twine(subgroupSize) +
1064 "`\n" +
"\t" + blockDimYName +
": `" + blockDimYValStr +
1065 "` to `max(1, (new-" + blockDimXName +
" * " + blockDimYValStr +
1066 ") / new-" + blockDimXName +
")`")
1078 bool skipAlign =
false;
1079 if (constBlockDimX && constBlockDimY && *constBlockDimX > 1 &&
1080 *constBlockDimX < subgroupSize && *constBlockDimY == 1) {
1089 Value newBlockDimX, newBlockDimY;
1090 if (constBlockDimX && constBlockDimY) {
1091 int64_t bdx = *constBlockDimX;
1092 int64_t bdy = *constBlockDimY;
1093 int64_t alignedBdx =
1094 ((bdx + subgroupAlignMask) / subgroupSize) * subgroupSize;
1095 int64_t numThreads = bdx * bdy;
1096 int64_t newBdy = std::max<int64_t>(1, numThreads / alignedBdx);
1103 arith::MulIOp::create(rewriter, loc, curBlockDimX, curBlockDimY);
1107 Value cstSubgroupSize =
1110 arith::AddIOp::create(rewriter, loc, curBlockDimX, cstMask);
1111 Value subgroupsRequired =
1112 arith::DivUIOp::create(rewriter, loc, padded, cstSubgroupSize);
1113 newBlockDimX = arith::MulIOp::create(rewriter, loc, subgroupsRequired,
1117 arith::DivUIOp::create(rewriter, loc, numThreads, newBlockDimX);
1119 newBlockDimY = arith::MaxUIOp::create(rewriter, loc, cst1, quotient);
1123 launch.getBlockSizeXMutable().assign(newBlockDimX);
1124 launch.getBlockSizeYMutable().assign(newBlockDimY);
1132 if (!sharedMemPrivateVarNames.empty()) {
1134 return (llvm::Twine(
"GPU shared memory used for ") +
1135 llvm::join(sharedMemPrivateVarNames,
","))
1140 rewriter.
eraseOp(computeRegion);
1156static bool isRedundantChainAccumulate(acc::ReductionAccumulateOp op) {
1157 Value memref = op.getMemref();
1158 memref::LoadOp loadOp = op.getValue().
getDefiningOp<memref::LoadOp>();
1159 if (!loadOp || loadOp.getMemRef() != memref)
1161 for (Operation *user : memref.
getUsers()) {
1162 acc::ReductionCombineOp combineOp = dyn_cast<acc::ReductionCombineOp>(user);
1163 if (!combineOp || combineOp.getDestMemref() != memref)
1165 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
1167 if (llvm::any_of(parDims, [](mlir::acc::GPUParallelDimAttr d) {
1168 return d.isAnyBlock();
1176std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
1177 SmallVector<mlir::acc::GPUParallelDimAttr>>
1178ACCCGToGPULowering::computeActiveAndInactiveParDims(Operation *op,
1180 MLIRContext *ctx = computeRegion->getContext();
1181 SmallVector<mlir::acc::GPUParallelDimAttr> ancestorParDims =
1182 getAncestorParDims(op);
1187 bool noStructuralAncestorParDims =
1188 llvm::none_of(ancestorParDims, [](
auto pd) {
return !pd.isSeq(); });
1190 mlir::acc::GPUParallelDimAttr routineParDim;
1192 FunctionOpInterface funcOp =
1193 computeRegion->getParentOfType<FunctionOpInterface>();
1194 routineParDim = getSpecializedRoutineDim(funcOp, defaultPolicy);
1195 if (routineParDim.isThreadX()) {
1197 mlir::acc::GPUParallelDimAttr::threadYDim(ctx));
1200 mlir::acc::GPUParallelDimAttr::blockXDim(ctx));
1204 if (acc::PrivateLocalOp privateLocalOp = dyn_cast<acc::PrivateLocalOp>(op)) {
1205 for (Operation *user : privateLocalOp.getResult().getUsers()) {
1206 if (acc::ReductionAccumulateOp accumulateOp =
1207 dyn_cast<acc::ReductionAccumulateOp>(user)) {
1208 if (accumulateOp.getMemref() == privateLocalOp.getResult()) {
1209 for (mlir::acc::GPUParallelDimAttr parDim :
1210 accumulateOp.getParDims().getArray()) {
1220 if (acc::ReductionCombineOp combineOp =
1221 dyn_cast<acc::ReductionCombineOp>(user)) {
1222 if (combineOp.getSrcMemref() == privateLocalOp.getResult()) {
1223 for (mlir::acc::GPUParallelDimAttr parDim :
1229 if (
auto combineRegionOp =
1230 dyn_cast<acc::ReductionCombineRegionOp>(user)) {
1231 if (combineRegionOp.getSrcVar() == privateLocalOp.getResult()) {
1232 for (mlir::acc::GPUParallelDimAttr parDim :
1241 bool hasBlock =
false;
1242 for (mlir::acc::GPUParallelDimAttr parDim : ancestorParDims)
1243 if (parDim.isAnyBlock())
1246 mlir::acc::GPUParallelDimAttr lowestParDim =
1247 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
1249 block->
walk([&](Operation *op) {
1252 if (memref::StoreOp storeOp = dyn_cast<memref::StoreOp>(op)) {
1253 if (
auto privateLocalOp =
1254 storeOp.getMemref().getDefiningOp<acc::PrivateLocalOp>()) {
1255 acc::PrivatizeOp privatizeOp =
1257 if (mlir::acc::GPUParallelDimsAttr parDimsAttr =
1258 privatizeOp.getParDimsAttr()) {
1259 for (
auto parDim : parDimsAttr.getArray())
1266 if (CallOpInterface callOp = dyn_cast<CallOpInterface>(op)) {
1267 if (mlir::acc::GPUParallelDimAttr parDim =
1268 getAccRoutineCallParDim(callOp, defaultPolicy)) {
1269 if (parDim.isBlockZ())
1270 lowestParDim = parDim;
1272 lowestParDim = parDim.getOneHigher();
1278 if (acc::ReductionCombineOp reductionCombineOp =
1279 dyn_cast<acc::ReductionCombineOp>(op)) {
1280 for (mlir::acc::GPUParallelDimAttr parDim :
1285 if (acc::ReductionCombineRegionOp combineRegionOp =
1286 dyn_cast<acc::ReductionCombineRegionOp>(op)) {
1287 for (mlir::acc::GPUParallelDimAttr parDim :
1295 if (acc::ReductionAccumulateArrayOp accArrayOp =
1296 dyn_cast<acc::ReductionAccumulateArrayOp>(op)) {
1297 for (mlir::acc::GPUParallelDimAttr parDim :
1298 accArrayOp.getParDims().getArray()) {
1307 SmallVector<mlir::acc::GPUParallelDimAttr> launchParDims;
1308 if (routineParDim) {
1309 for (mlir::acc::GPUParallelDimAttr parDim = routineParDim;
1310 parDim.getOrder() >= lowestParDim.getOrder();
1311 parDim = parDim.getOneLower()) {
1315 launchParDims = computeRegion.getLaunchParDims();
1319 SmallVector<mlir::acc::GPUParallelDimAttr> activeParDims, inactiveParDims;
1320 for (mlir::acc::GPUParallelDimAttr launchParDim : launchParDims) {
1321 if (launchParDim.getOrder() < lowestParDim.getOrder())
1323 if (llvm::find(ancestorParDims, launchParDim) != ancestorParDims.end() ||
1324 (launchParDim.isAnyBlock() &&
1325 (noStructuralAncestorParDims || hasBlock))) {
1326 activeParDims.push_back(launchParDim);
1328 inactiveParDims.push_back(launchParDim);
1332 return std::pair{activeParDims, inactiveParDims};
1335Value ACCCGToGPULowering::emitPredicate(
1336 Location loc, SmallVector<mlir::acc::GPUParallelDimAttr> &inactiveParDims) {
1338 for (mlir::acc::GPUParallelDimAttr inactiveParDim : inactiveParDims) {
1339 Value threadId = getGPUThreadIdFor(inactiveParDim.getProcessor());
1341 Value zero = arith::ConstantOp::create(rewriter, loc, zeroAttr);
1342 Value cmp = arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq,
1345 predicate = arith::AndIOp::create(rewriter, loc, cmp, predicate);
1352void ACCCGToGPULowering::createBarrier(
1353 Location loc, mlir::acc::GPUParallelDimsAttr parDimsAttr) {
1354 bool hasAnyBlock =
false, hasThreadY =
false, hasThreadX =
false;
1355 for (
auto parDim : parDimsAttr.getArray()) {
1356 if (parDim.isAnyBlock())
1358 if (parDim.isThreadY())
1360 if (parDim.isThreadX())
1364 if (hasAnyBlock || hasThreadY)
1365 emitGPUBarrierWorkgroup(rewriter, loc);
1366 else if (hasThreadX)
1367 createPerRowBarrier(loc);
1370void ACCCGToGPULowering::createPerRowBarrier(Location loc) {
1371 hasThreadYBarrier =
true;
1373 if (staticBlockDimX <=
options.subgroupSize) {
1374 emitGPUBarrierSubgroup(rewriter, loc);
1378 if (
options.deviceType != mlir::acc::DeviceType::Nvidia) {
1381 "per-row barrier to support worker parallelism on non-NVIDIA device");
1396 Value blockDimX = gpu::BlockDimOp::create(
1397 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
1398 Value blockDimY = gpu::BlockDimOp::create(
1399 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::y);
1401 Value isSingleWorker = arith::CmpIOp::create(
1402 rewriter, loc, arith::CmpIPredicate::eq, blockDimY, cst1);
1404 auto outerIf = scf::IfOp::create(rewriter, loc, isSingleWorker,
1409 emitGPUBarrierWorkgroup(rewriter, loc);
1413 Value cstSubgroupSize =
1415 Value isSubgroupSized = arith::CmpIOp::create(
1416 rewriter, loc, arith::CmpIPredicate::ule, blockDimX, cstSubgroupSize);
1418 auto innerIf = scf::IfOp::create(rewriter, loc, isSubgroupSized,
1424 emitGPUBarrierSubgroup(rewriter, loc);
1431 Value threadYId = gpu::ThreadIdOp::create(
1432 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::y);
1433 Value barrierId = arith::AddIOp::create(rewriter, loc, threadYId, cst1);
1436 arith::IndexCastOp::create(rewriter, loc, i32Ty, barrierId);
1437 Value numberOfThreads32 =
1438 arith::IndexCastOp::create(rewriter, loc, i32Ty, blockDimX);
1442 assert(
options.deviceType == mlir::acc::DeviceType::Nvidia);
1443 NVVM::BarrierOp::create(rewriter, loc, barrierId32, numberOfThreads32);
1452static bool hasSubsequentLoopSibling(Operation *loopOp) {
1453 for (Operation *next = loopOp->getNextNode(); next;
1454 next = next->getNextNode()) {
1455 if (isa<scf::ParallelOp, scf::ForOp>(next))
1457 bool nested =
false;
1458 next->walk([&](Operation *op) {
1459 if (isa<scf::ParallelOp, scf::ForOp>(op)) {
1472static LoopLikeOpInterface findFirstSequentialLoop(Operation *op) {
1473 auto isAllSequentialParDims = [](scf::ParallelOp par) ->
bool {
1475 if (!pd || pd.getArray().empty())
1477 return llvm::all_of(pd.getArray(), [](mlir::acc::GPUParallelDimAttr d) {
1484 if (isa<scf::ForOp>(p))
1485 return cast<LoopLikeOpInterface>(p);
1486 if (scf::ParallelOp parOp = dyn_cast<scf::ParallelOp>(p)) {
1487 if (isAllSequentialParDims(parOp))
1488 return cast<LoopLikeOpInterface>(p);
1504static bool isLoopBodyClosureOp(Operation *op) {
1505 return isa<scf::ReduceOp, scf::YieldOp, acc::YieldOp>(op);
1510static bool isDeferredBarrierFlushPoint(Operation *op) {
1511 if (isLoopBodyClosureOp(op))
1515 if (isa<scf::ForOp>(op))
1517 if (scf::ParallelOp parallelOp = dyn_cast<scf::ParallelOp>(op)) {
1519 if (mlir::acc::GPUParallelDimsAttr parDims =
1521 if (parDims.getArray().size() == 1 &&
1522 parDims.getArray().front().isSeq()) {
1534static bool hasTrailingSideEffectSiblings(Operation *loopOp) {
1535 for (Operation *next = loopOp->getNextNode(); next;
1536 next = next->getNextNode()) {
1537 return !isLoopBodyClosureOp(next);
1561void ACCCGToGPULowering::createBarrierAfterSeqLoop(Operation *loopOp) {
1570 if (mayWriteSharedMemory(loopOp) && hasSubsequentLoopSibling(loopOp))
1571 emitGPUBarrierWorkgroup(rewriter, loopOp->
getLoc());
1575 bool parentIsSeq =
false;
1576 if (mlir::acc::GPUParallelDimsAttr wsParDims =
1578 if (wsParDims.getArray().size() == 1 &&
1579 wsParDims.getArray().front().isSeq()) {
1589 bool hasThreadSubLoop =
false;
1590 loopOp->
walk([&](scf::ParallelOp innerPar) -> WalkResult {
1591 if (innerPar.getOperation() == loopOp)
1593 if (mlir::acc::GPUParallelDimsAttr dims =
1595 for (
auto d : dims.getArray()) {
1596 if (d.isThreadX() || d.isThreadY()) {
1597 hasThreadSubLoop =
true;
1604 if (!hasThreadSubLoop)
1606 scf::ParallelOp threadLoop = wsLoop->getParentOfType<scf::ParallelOp>();
1609 scf::ParallelOp blockLoop = threadLoop->getParentOfType<scf::ParallelOp>();
1612 mlir::acc::GPUParallelDimsAttr parDimsAttr =
1614 if (parDimsAttr.hasOnlyBlockLevel())
1615 createBarrier(loopOp->
getLoc(), parDimsAttr);
1621 scf::ParallelOp seqLoop = wsLoop->getParentOfType<scf::ParallelOp>();
1630 if (mayWriteSharedMemory(loopOp) && hasSubsequentLoopSibling(wsLoop))
1631 emitGPUBarrierWorkgroup(rewriter, loopOp->
getLoc());
1634 if (scf::ParallelOp outerParLoop =
1635 seqLoop->getParentOfType<scf::ParallelOp>()) {
1636 mlir::acc::GPUParallelDimsAttr parDimsAttr =
1638 if (parDimsAttr.hasOnlyBlockLevel()) {
1639 createBarrier(loopOp->
getLoc(), parDimsAttr);
1640 }
else if (parDimsAttr.hasOnlyThreadYLevel()) {
1641 createPerRowBarrier(loopOp->
getLoc());
1642 }
else if (parDimsAttr && parDimsAttr.isSeq()) {
1646 for (Operation *gangLoop =
1647 outerParLoop->getParentOfType<scf::ParallelOp>();
1648 gangLoop; gangLoop = gangLoop->getParentOfType<scf::ParallelOp>()) {
1649 mlir::acc::GPUParallelDimsAttr gangDims =
1653 if (gangDims.hasOnlyBlockLevel()) {
1654 createBarrier(loopOp->
getLoc(), gangDims);
1657 if (!gangDims.isSeq())
1671 mlir::acc::GPUParallelDimsAttr parDimsAttr =
1673 if (parDimsAttr && parDimsAttr.hasOnlyBlockLevel() &&
1674 mayWriteSharedMemory(loopOp)) {
1675 createBarrier(loopOp->
getLoc(), parDimsAttr);
1679bool ACCCGToGPULowering::mayWriteSharedMemory(Operation *loopOp) {
1681 loopOp->
walk([&](memref::StoreOp storeOp) {
1684 llvm::SmallVector<Value, 8> worklist{storeOp.getMemref()};
1685 llvm::SmallPtrSet<Value, 8> seen;
1686 while (!worklist.empty()) {
1687 Value v = worklist.pop_back_val();
1688 if (!seen.insert(v).second)
1693 if (acc::PrivateLocalOp privateLocal =
1694 dyn_cast<acc::PrivateLocalOp>(def)) {
1695 acc::PrivatizeOp privatizeOp =
1703 if (mlir::acc::GPUParallelDimsAttr parDims =
1704 privatizeOp.getParDimsAttr()) {
1705 bool hasBlock =
false, hasThread =
false;
1706 for (mlir::acc::GPUParallelDimAttr d : parDims.getArray()) {
1709 if (d.isThreadX() || d.isThreadY())
1712 if (hasBlock && !hasThread) {
1727ACCCGToGPULowering::getPrivateMemScope(acc::PrivatizeOp privatizeOp) {
1728 bool hasBlock =
false;
1729 bool hasThreadX =
false;
1730 bool hasThreadY =
false;
1731 if (mlir::acc::GPUParallelDimsAttr parDims = privatizeOp.getParDimsAttr()) {
1732 for (mlir::acc::GPUParallelDimAttr d : parDims.getArray()) {
1741 for (mlir::acc::GPUParallelDimAttr d : computeRegion.getLaunchParDims())
1745 return PrivateMemScope::Gang;
1746 return PrivateMemScope::Thread;
1749 return PrivateMemScope::Thread;
1750 if (hasBlock && hasThreadY)
1751 return PrivateMemScope::Worker;
1753 return PrivateMemScope::Gang;
1754 return PrivateMemScope::Thread;
1758static acc::PrivateLocalOp getPrivateLocalForMemref(Value memref) {
1759 llvm::SmallVector<Value, 8> worklist{memref};
1760 llvm::SmallPtrSet<Value, 8> seen;
1761 while (!worklist.empty()) {
1762 Value v = worklist.pop_back_val();
1763 if (!seen.insert(v).second)
1768 if (acc::PrivateLocalOp privateLocal = dyn_cast<acc::PrivateLocalOp>(def))
1769 return privateLocal;
1775PrivateMemScope ACCCGToGPULowering::getPrivateScopeForMemref(Value memref) {
1776 if (
auto privateLocal = getPrivateLocalForMemref(memref))
1777 return getPrivateMemScope(
getPrivatizeOp(privateLocal, computeRegion));
1778 return PrivateMemScope::None;
1781acc::PrivatizeOp ACCCGToGPULowering::getPrivatizeForMemref(Value memref) {
1782 if (
auto privateLocal = getPrivateLocalForMemref(memref))
1784 return acc::PrivatizeOp();
1788ACCCGToGPULowering::needsPreStoreReuseBarrier(acc::PredicateRegionOp interOp) {
1792 LoopLikeOpInterface seqLoopOp = findFirstSequentialLoop(interOp);
1794 return PrivateMemScope::None;
1798 PrivateMemScope storeScope = PrivateMemScope::None;
1799 llvm::SmallPtrSet<Operation *, 4> storePrivatizes;
1800 interOp.getRegion().walk([&](memref::StoreOp storeOp) {
1801 PrivateMemScope scope = getPrivateScopeForMemref(storeOp.getMemref());
1802 if (scope != PrivateMemScope::Gang && scope != PrivateMemScope::Worker)
1804 if (
auto privatize = getPrivatizeForMemref(storeOp.getMemref()))
1805 storePrivatizes.insert(privatize.getOperation());
1806 if (storeScope == PrivateMemScope::None)
1810 if (storeScope == PrivateMemScope::None || storePrivatizes.empty())
1811 return PrivateMemScope::None;
1814 bool hasParallelPrivateUse =
false;
1815 seqLoopOp.getOperation()->walk([&](Operation *op) {
1817 if (interOp->isAncestor(op))
1821 if (memref::LoadOp loadOp = dyn_cast<memref::LoadOp>(op))
1822 memref = loadOp.getMemref();
1823 else if (memref::StoreOp storeOp = dyn_cast<memref::StoreOp>(op))
1824 memref = storeOp.getMemref();
1828 PrivateMemScope scope = getPrivateScopeForMemref(memref);
1829 if (scope != storeScope)
1832 acc::PrivatizeOp usePrivatize = getPrivatizeForMemref(memref);
1833 if (!usePrivatize || !storePrivatizes.contains(usePrivatize.getOperation()))
1836 bool insideNestedParallel =
false;
1837 for (Operation *p = op->
getParentOp(); p && p != seqLoopOp.getOperation();
1839 if (scf::ParallelOp par = dyn_cast<scf::ParallelOp>(p)) {
1840 if (mlir::acc::GPUParallelDimsAttr pd =
1842 if (llvm::any_of(pd.getArray(), [](mlir::acc::GPUParallelDimAttr d) {
1845 insideNestedParallel =
true;
1851 if (!insideNestedParallel)
1854 hasParallelPrivateUse =
true;
1858 if (!hasParallelPrivateUse)
1859 return PrivateMemScope::None;
1864void ACCCGToGPULowering::processPredicateRegion(
1865 acc::PredicateRegionOp interOp) {
1866 LLVM_DEBUG(llvm::dbgs() <<
"processing predicate region: ";
1867 interOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
1868 Location loc = interOp->getLoc();
1870 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
1871 SmallVector<mlir::acc::GPUParallelDimAttr>>
1872 parDimsPair = computeActiveAndInactiveParDims(
1873 interOp, &interOp.getRegion().front());
1881 if (hasThreadYReduction) {
1882 MLIRContext *ctx = computeRegion->getContext();
1883 mlir::acc::GPUParallelDimAttr threadXParDim =
1884 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
1885 bool hasThreadXInActive =
1886 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr pd) {
1887 return pd.isThreadX();
1889 bool hasThreadXInInactive =
1890 llvm::any_of(parDimsPair.second, [](mlir::acc::GPUParallelDimAttr pd) {
1891 return pd.isThreadX();
1897 bool regionHasThreadLevelRoutineCall =
false;
1898 if (hasThreadLevelRoutineCall) {
1899 interOp.getRegion().walk([&](CallOpInterface callOp) {
1900 if (mlir::acc::GPUParallelDimAttr parDim =
1901 getAccRoutineCallParDim(callOp, defaultPolicy)) {
1902 if (parDim.isThreadX() || parDim.isThreadY()) {
1903 regionHasThreadLevelRoutineCall =
true;
1911 if (!hasThreadXInActive && !hasThreadXInInactive &&
1912 !regionHasThreadLevelRoutineCall) {
1913 parDimsPair.second.push_back(threadXParDim);
1917 if (Value predicate = emitPredicate(loc, parDimsPair.second)) {
1918 LLVM_DEBUG(llvm::dbgs() <<
"predicate: " << predicate <<
"\n");
1919 bool isInsideThreadXLoop =
false;
1920 bool isInsideThreadYLoop =
false;
1921 for (
auto parDim : parDimsPair.first) {
1922 if (parDim.isThreadX())
1923 isInsideThreadXLoop =
true;
1924 if (parDim.isThreadY())
1925 isInsideThreadYLoop =
true;
1931 auto emitReconvergenceBarrier = [&]() {
1932 if (isInsideThreadXLoop) {
1934 }
else if (isInsideThreadYLoop) {
1939 bool predicatesThreadX = llvm::any_of(
1941 [](mlir::acc::GPUParallelDimAttr pd) { return pd.isThreadX(); });
1942 if (predicatesThreadX) {
1943 createBarrier(loc, mlir::acc::GPUParallelDimsAttr::get(
1944 interOp->getContext(), parDimsPair.second));
1948 }
else if (!parDimsPair.first.empty()) {
1950 createBarrier(loc, mlir::acc::GPUParallelDimsAttr::get(
1951 interOp->getContext(), parDimsPair.first));
1954 createBarrier(loc, mlir::acc::GPUParallelDimsAttr::get(
1955 interOp->getContext(), parDimsPair.second));
1967 PrivateMemScope scope = needsPreStoreReuseBarrier(interOp);
1968 if (scope == PrivateMemScope::Gang)
1969 emitGPUBarrierWorkgroup(rewriter, loc);
1970 else if (scope == PrivateMemScope::Worker)
1971 createPerRowBarrier(loc);
1973 auto ifOp = scf::IfOp::create(rewriter, loc, predicate,
1975 Region &thenRegion = ifOp.getThenRegion();
1979 for (
auto &bodyOp : interOp.getRegion().front().getOperations()) {
1983 if (memref::StoreOp storeOp = dyn_cast<memref::StoreOp>(&bodyOp)) {
1984 std::optional<arith::AtomicRMWKind> blockReduceKind;
1985 bool failedReductionKind =
false;
1986 Value storeVal = storeOp.getValueToStore();
1993 Block *epilogueBlock = interOp->getBlock();
1994 auto findBlockAccLoad =
1996 Value val) -> std::optional<arith::AtomicRMWKind> {
1997 Operation *def = val.getDefiningOp();
1998 if (!def || def->
getBlock() != epilogueBlock)
1999 return std::nullopt;
2000 if (memref::LoadOp loadOp = dyn_cast<memref::LoadOp>(def)) {
2001 for (
auto *user : loadOp.getMemRef().getUsers()) {
2002 if (acc::ReductionAccumulateOp accOp =
2003 dyn_cast<acc::ReductionAccumulateOp>(user)) {
2004 if (llvm::any_of(accOp.getParDims().getArray(),
2005 [](mlir::acc::GPUParallelDimAttr pd) {
2006 return pd.isAnyBlock();
2008 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
2009 accOp.getReductionOperator(),
2010 accOp.getValue().getType(), accOp.getLoc());
2012 failedReductionKind =
true;
2013 return std::nullopt;
2019 return std::nullopt;
2022 if (
auto kind = self(self, operand))
2024 return std::nullopt;
2026 blockReduceKind = findBlockAccLoad(findBlockAccLoad, storeVal);
2028 if (failedReductionKind)
2030 if (blockReduceKind) {
2033 bool threadIsActive = llvm::any_of(
2034 parDimsPair.first, [](mlir::acc::GPUParallelDimAttr pd) {
2035 return !pd.isAnyBlock();
2037 if (!threadIsActive &&
2038 !isa_and_nonnull<memref::AllocaOp>(
2039 unwrapMemRefConversion(memref).getDefiningOp())) {
2045 MemRefType memrefTy = cast<MemRefType>(memref.
getType());
2047 SmallVector<Value> initIndices;
2048 for (Value idx : storeOp.getIndices())
2051 OpBuilder::InsertionGuard guard(rewriter);
2052 Block &launchBody = launch.getBody().front();
2053 Operation *insertBefore =
nullptr;
2055 launchBody.
walk([&](scf::ParallelOp parOp) -> WalkResult {
2056 for (Operation *parent = parOp->getParentOp(); parent;
2057 parent = parent->getParentOp()) {
2058 if (parent == launch.getOperation())
2060 if (isa<scf::ParallelOp>(parent))
2063 insertBefore = parOp.getOperation();
2076 DominanceInfo domInfo(launch);
2077 IRMapping initMapping;
2078 std::function<Value(Value)> materialize =
2079 [&](Value val) -> Value {
2080 Operation *defOp = val.getDefiningOp();
2091 materialize(operand);
2092 Operation *cloned = rewriter.
clone(*defOp, initMapping);
2093 for (
auto [orig, clonedRes] :
2095 initMapping.
map(orig, clonedRes);
2097 return initMapping.
lookup(val);
2099 Value initMemref = materialize(memref);
2100 for (
auto &idx : initIndices)
2101 idx = materialize(idx);
2103 rewriter, loc, memrefTy.getElementType(), *blockReduceKind,
2105 Value blockId = gpu::BlockIdOp::create(
2106 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
2107 Value threadId = gpu::ThreadIdOp::create(
2108 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
2110 Value isBlock0 = arith::CmpIOp::create(
2111 rewriter, loc, arith::CmpIPredicate::eq, blockId, zero);
2112 Value isThread0 = arith::CmpIOp::create(
2113 rewriter, loc, arith::CmpIPredicate::eq, threadId, zero);
2114 Value isFirstThread =
2115 arith::AndIOp::create(rewriter, loc, isBlock0, isThread0);
2116 auto initIf = scf::IfOp::create(rewriter, loc, isFirstThread,
2119 initIf.getThenRegion().back().getTerminator());
2120 memref::StoreOp::create(rewriter, loc, identityVal, initMemref,
2123 gpu::BarrierOp::create(rewriter, loc);
2125 SmallVector<Value> atomicIndices;
2126 for (Value idx : storeOp.getIndices())
2128 constructAtomicAccumulation(loc, memref, atomicIndices, input,
2137 emitReconvergenceBarrier();
2140 for (
auto &bodyOp : interOp.getRegion().front().getOperations())
2163Value ACCCGToGPULowering::processPrivatize(acc::PrivatizeOp privatize) {
2164 LLVM_DEBUG(llvm::dbgs() <<
"processing privatize: ";
2165 privatize->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2166 Value tracked = privatize.getResult();
2167 if (acc::ComputeRegionOp insUser =
2168 dyn_cast<acc::ComputeRegionOp>(getOnlyUser(tracked))) {
2169 assert(privatize->hasOneUse() &&
2170 "expected acc.privatize op to have one use");
2171 tracked = insUser.getBody()->getArgument(
2172 privatize->use_begin()->getOperandNumber());
2174 Operation *privatizeUser = getOnlyUser(tracked);
2175 assert(privatizeUser &&
"expected PrivateLocalOp user for privatize");
2177 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
2178 SmallVector<mlir::acc::GPUParallelDimAttr>>
2179 parDimsPair = computeActiveAndInactiveParDims(privatizeUser,
nullptr);
2181 if (!privatize.getParDimsAttr()) {
2182 privatize.setParDimsAttr(mlir::acc::GPUParallelDimsAttr::get(
2186 Location loc = privatize->getLoc();
2187 acc::PrivateType privTy = cast<acc::PrivateType>(privatize.getType());
2188 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
2191 gpu::GPUFuncOp gpuFuncOp = computeRegion->getParentOfType<gpu::GPUFuncOp>();
2196 privatize->getParentOfType<acc::ComputeRegionOp>() != computeRegion) {
2197 return privatize.getResult();
2200 for (mlir::acc::GPUParallelDimAttr parDim : parDimsPair.first) {
2201 if (parDim.isThreadX() &&
2202 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
2203 auto alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
2204 mapping.
map(privatize.getResult(), alloca.getResult());
2205 return alloca.getResult();
2210 return privatize.getResult();
2218 bool threadYIsActive =
2219 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr parDim) {
2220 return parDim.isThreadY();
2233 bool needsWorkgroupBarrier =
false;
2235 FunctionOpInterface funcOp =
2236 computeRegion->getParentOfType<FunctionOpInterface>();
2237 mlir::acc::GPUParallelDimAttr routineParDim =
2238 getSpecializedRoutineDim(funcOp, defaultPolicy);
2239 if (routineParDim.isThreadX()) {
2242 threadYIsActive =
true;
2243 }
else if (routineParDim.isThreadY()) {
2246 needsWorkgroupBarrier =
true;
2247 }
else if (routineParDim.isAnyBlock()) {
2248 needsWorkgroupBarrier =
true;
2252 llvm::SmallVector<Value> mappedDynamicSizes;
2253 for (
auto dynamicSize : privatize.getDynamicSizes()) {
2255 mappedDynamicSizes.push_back(mappedDynamicSize);
2258 computeRegion.isEffectivelySerial()) {
2259 if (mappedDynamicSizes.empty()) {
2262 memref::AllocaOp::create(rewriter, privatize->getLoc(), baseTy);
2263 mapping.
map(privatize.getResult(), alloca.getResult());
2264 return alloca.getResult();
2267 auto alloc = memref::AllocOp::create(rewriter, privatize->getLoc(), baseTy,
2268 mappedDynamicSizes);
2275 memref::DeallocOp::create(rewriter, privatize->getLoc(), alloc);
2279 mapping.
map(privatize.getResult(), alloc.getResult());
2280 return alloc.getResult();
2285 SmallVector<mlir::acc::GPUParallelDimAttr> predicateDims;
2286 for (
auto parDim : parDimsPair.second) {
2288 if (threadYIsActive && parDim.isThreadY())
2290 predicateDims.push_back(parDim);
2292 Value predicate = emitPredicate(loc, predicateDims);
2294 predicate = arith::ConstantOp::create(
2297 auto ifOp = scf::IfOp::create(rewriter, loc, predicate,
2299 Region &thenRegion = ifOp.getThenRegion();
2302 auto mem = memref::AllocOp::create(rewriter, privatize->getLoc(), baseTy,
2303 mappedDynamicSizes);
2305 gpu::AddressSpaceAttr sharedMemoryAddressSpace = gpu::AddressSpaceAttr::get(
2306 computeRegion->getContext(), gpu::GPUDialect::getWorkgroupAddressSpace());
2310 constexpr int64_t kMaxThreadY = 32;
2311 MemRefType sharedMemTy =
2313 ? MemRefType::get({kMaxThreadY}, baseTy, MemRefLayoutAttrInterface{},
2314 sharedMemoryAddressSpace)
2315 : MemRefType::
get({}, baseTy, MemRefLayoutAttrInterface{},
2316 sharedMemoryAddressSpace);
2319 bool reuseBroadcast = !gpuFuncOp.isKernel();
2321 llvm::DenseMap<Type, Value>::iterator cachedSlot =
2322 reuseBroadcast ? privatizeBroadcastCache.find(sharedMemTy)
2323 : privatizeBroadcastCache.end();
2324 if (reuseBroadcast && cachedSlot != privatizeBroadcastCache.end()) {
2325 alloca = cachedSlot->second;
2328 mlir::acc::GPUParallelDimAttr dim =
2329 needsWorkgroupBarrier
2330 ? mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.
getContext())
2331 : mlir::
acc::GPUParallelDimAttr::threadXDim(rewriter.
getContext());
2333 loc, mlir::acc::GPUParallelDimsAttr::get(rewriter.
getContext(), {dim}));
2335 alloca = gpuFuncOp.addWorkgroupAttribution(sharedMemTy,
2340 unsigned index = gpuFuncOp.getNumWorkgroupAttributions() - 1;
2341 gpuFuncOp.setWorkgroupAttributionAttr(
index,
2342 LLVM::LLVMDialect::getAlignAttrName(),
2345 privatizeBroadcastCache[sharedMemTy] = alloca;
2348 if (threadYIsActive) {
2349 Value threadYId = getThreadId(loc, gpu::Dimension::y);
2350 memref::StoreOp::create(rewriter, privatize->getLoc(), mem, alloca,
2353 memref::StoreOp::create(rewriter, privatize->getLoc(), mem, alloca);
2359 if (needsWorkgroupBarrier) {
2360 mlir::acc::GPUParallelDimsAttr threadYDimsAttr =
2361 mlir::acc::GPUParallelDimsAttr::get(
2363 {mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.getContext())});
2364 createBarrier(loc, threadYDimsAttr);
2367 mlir::acc::GPUParallelDimsAttr threadXDimsAttr =
2368 mlir::acc::GPUParallelDimsAttr::get(
2370 {mlir::acc::GPUParallelDimAttr::threadXDim(rewriter.getContext())});
2371 createBarrier(loc, threadXDimsAttr);
2375 if (threadYIsActive) {
2376 Value threadYId = getThreadId(loc, gpu::Dimension::y);
2377 load = memref::LoadOp::create(rewriter, privatize->getLoc(), baseTy, alloca,
2381 memref::LoadOp::create(rewriter, privatize->getLoc(), baseTy, alloca);
2384 mapping.
map(privatize.getResult(),
load);
2388 if (!privatize->getParentOfType<acc::ComputeRegionOp>())
2392 if (needsWorkgroupBarrier) {
2393 mlir::acc::GPUParallelDimsAttr workerDimsAttr =
2394 mlir::acc::GPUParallelDimsAttr::get(
2396 {mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.getContext())});
2397 createBarrier(loc, workerDimsAttr);
2399 mlir::acc::GPUParallelDimsAttr vectorDimsAttr =
2400 mlir::acc::GPUParallelDimsAttr::get(
2402 {mlir::acc::GPUParallelDimAttr::threadXDim(rewriter.getContext())});
2403 createBarrier(loc, vectorDimsAttr);
2405 auto ifOp2 = scf::IfOp::create(rewriter, loc, predicate,
2407 Region &thenRegion2 = ifOp2.getThenRegion();
2410 memref::DeallocOp::create(rewriter, privatize->getLoc(),
load);
2421void ACCCGToGPULowering::processPrivateLocal(
2422 acc::PrivateLocalOp privateLocal, std::optional<int64_t> sharedMemCopies) {
2423 LLVM_DEBUG(llvm::dbgs() <<
"processing private local: ";
2424 privateLocal->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2425 Location loc = privateLocal.getLoc();
2426 acc::PrivateType privTy =
2427 cast<acc::PrivateType>(privateLocal.getPrivatized().getType());
2428 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
2430 MemRefType byteMemrefTy =
2431 MemRefType::get({ShapedType::kDynamic}, rewriter.
getI8Type());
2433 acc::PrivatizeOp privatizeOp =
getPrivatizeOp(privateLocal, computeRegion);
2435 if (privatizeOp->getParentOfType<acc::ComputeRegionOp>() == computeRegion) {
2438 Value
result = castPointerLikeTypeIfNeeded(rewriter, loc, inputMem,
2439 privateLocal.getType());
2440 mapping.
map(privateLocal.getResult(),
result);
2447 acc::ReductionAccumulateArrayOp arrayAccum =
2448 perThreadArrayReductionAccum(privateLocal.getResult());
2450 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
2451 Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
2453 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
2454 arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
2457 initPerThreadArrayAccum(rewriter, loc, alloca, baseTy, *kind);
2459 Value mem = castPointerLikeTypeIfNeeded(rewriter, loc, alloca,
2460 privateLocal.getType());
2461 mapping.
map(privateLocal.getResult(), mem);
2467 std::optional<int64_t>
copies =
2468 sharedMemCopies ? sharedMemCopies
2469 : isEligibleForSharedMemory(privateLocal, baseTy);
2471 int64_t numCopies = *
copies;
2472 int64_t elementSize = getElementSizeInBytes(loc, baseTy.getElementType());
2473 int64_t numElements = 1;
2474 for (int64_t dim : baseTy.getShape())
2476 int64_t upperBound = elementSize * numElements * numCopies;
2478 if (tryAllocateSharedMemory(upperBound)) {
2479 std::string varName =
2481 sharedMemPrivateVarNames.push_back(varName.empty() ?
"(*)" : varName);
2483 gpu::AddressSpaceAttr workgroupAS = gpu::AddressSpaceAttr::get(
2484 computeRegion->getContext(),
2485 gpu::GPUDialect::getWorkgroupAddressSpace());
2486 MemRefType sharedMemTy =
2487 MemRefType::get(baseTy.getShape(), baseTy.getElementType(),
2488 MemRefLayoutAttrInterface{}, workgroupAS);
2489 Value sharedMem = acc::GPUSharedMemoryOp::create(
2495 castPointerLikeTypeIfNeeded(rewriter, loc, sharedMem, baseTy);
2496 Value
result = castPointerLikeTypeIfNeeded(rewriter, loc, mem,
2497 privateLocal.getType());
2499 mapping.
map(privateLocal.getResult(),
result);
2504 OpBuilder::InsertionGuard guard(rewriter);
2506 inputMem = processPrivatize(privatizeOp);
2510 assert(inputMem &&
"expected input mem to be mapped");
2511 Value
result = castPointerLikeTypeIfNeeded(rewriter, loc, inputMem,
2512 privateLocal.getType());
2513 mapping.
map(privateLocal.getResult(),
result);
2519 SmallVector<int64_t> viewShape;
2521 SmallVector<Value> viewDynSizes;
2523 SmallVector<OpFoldResult> subviewOffset;
2526 SmallVector<OpFoldResult> subviewSizes;
2528 SmallVector<int64_t> subviewStrides;
2530 SmallVector<int64_t> subviewShape;
2532 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
2533 SmallVector<mlir::acc::GPUParallelDimAttr>>
2534 parDimsPair = computeActiveAndInactiveParDims(privateLocal,
nullptr);
2535 acc::ReductionAccumulateArrayOp arrayAccum =
2536 perThreadArrayReductionAccum(privateLocal.getResult());
2537 for (mlir::acc::GPUParallelDimAttr parDim : parDimsPair.first) {
2538 if ((parDim.isThreadX() || arrayAccum) &&
2539 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
2540 Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
2542 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
2543 arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
2546 initPerThreadArrayAccum(rewriter, loc, alloca, baseTy, *kind);
2548 Value mem = castPointerLikeTypeIfNeeded(rewriter, loc, alloca,
2549 privateLocal.getType());
2550 mapping.
map(privateLocal.getResult(), mem);
2554 if (parDimsPair.first.empty()) {
2558 mlir::acc::GPUParallelDimAttr::blockXDim(privateLocal.getContext()));
2560 for (mlir::acc::GPUParallelDimAttr parDim : parDimsPair.first) {
2561 gpu::Processor gpuProc = parDim.getProcessor();
2562 Value gpuSize = getGPUSizeFor(gpuProc);
2563 viewDynSizes.push_back(gpuSize);
2564 viewShape.push_back(ShapedType::kDynamic);
2565 subviewOffset.push_back(getGPUThreadIdFor(gpuProc));
2569 SmallVector<Value> innerDynSizes =
2570 resolvePrivateLocalDynamicExtents(privateLocal);
2572 unsigned dynIdx = 0;
2573 for (
auto innerDim : baseTy.getShape()) {
2575 viewShape.push_back(innerDim);
2576 subviewShape.push_back(innerDim);
2577 if (innerDim == ShapedType::kDynamic) {
2578 assert(dynIdx < innerDynSizes.size() &&
2579 "not enough dynamic sizes for inner dimensions");
2580 viewDynSizes.push_back(innerDynSizes[dynIdx]);
2581 subviewSizes.push_back(innerDynSizes[dynIdx]);
2584 subviewSizes.push_back(rewriter.
getIndexAttr(innerDim));
2590 for (
auto innerDimIt = baseTy.getShape().rbegin();
2591 innerDimIt != baseTy.getShape().rend(); ++innerDimIt) {
2592 int64_t innerDim = *innerDimIt;
2593 subviewStrides.insert(subviewStrides.begin(), stride);
2594 if (innerDim == ShapedType::kDynamic)
2595 stride = ShapedType::kDynamic;
2596 if (stride != ShapedType::kDynamic)
2601 castPointerLikeTypeIfNeeded(rewriter, loc, inputMem, byteMemrefTy);
2603 MemRefType viewType = MemRefType::get(viewShape, baseTy.getElementType());
2604 auto view = memref::ViewOp::create(rewriter, loc, viewType, memBuffer,
2605 c0.getResult(), viewDynSizes);
2608 StridedLayoutAttr stridedLayout = StridedLayoutAttr::get(
2609 computeRegion->getContext(), ShapedType::kDynamic, subviewStrides);
2610 MemRefType subviewType =
2611 MemRefType::get(subviewShape, baseTy.getElementType(), stridedLayout);
2612 SmallVector<OpFoldResult> ones(viewType.getRank(), rewriter.
getIndexAttr(1));
2613 Value subview = memref::SubViewOp::create(rewriter, loc, subviewType, view,
2614 subviewOffset, subviewSizes, ones);
2619 Value
result = castPointerLikeTypeIfNeeded(rewriter, loc, subview,
2620 privateLocal.getType());
2621 mapping.
map(privateLocal.getResult(),
result);
2625template <
typename LoopOp>
2626void ACCCGToGPULowering::processSeqLoop(LoopOp loopOp) {
2630 LLVM_DEBUG(llvm::dbgs() <<
"processing seq loop: ";
2631 loopOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2632 llvm::SmallPtrSet<Operation *, 4> preProcessedPrivateLocals;
2633 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
2634 for (
auto &bodyOp : loopOp.getBody()->getOperations()) {
2635 if (acc::PrivateLocalOp privateLocal =
2636 dyn_cast<acc::PrivateLocalOp>(&bodyOp)) {
2637 acc::PrivateType privTy =
2638 cast<acc::PrivateType>(privateLocal.getPrivatized().getType());
2640 if (
auto copies = isEligibleForSharedMemory(privateLocal, baseTy)) {
2641 processPrivateLocal(privateLocal,
copies);
2642 preProcessedPrivateLocals.insert(privateLocal.getOperation());
2650 &newLoop.getRegion(), newLoop.getRegion().begin(),
2651 loopOp.getBody()->getArgumentTypes(),
2652 SmallVector<Location>(loopOp.getBody()->getArgumentTypes().size(),
2658 assert(blockArgs.size() &&
"expected block arguments for loop");
2659 mapping.
map(blockArgs, newLoop.getBody()->getArguments());
2661 for (
auto &bodyOp : loopOp.getBody()->getOperations()) {
2662 if (preProcessedPrivateLocals.contains(&bodyOp))
2667 mapping.
map(loopOp.getResults(), newLoop.getResults());
2672 if (hasTrailingSideEffectSiblings(loopOp.getOperation()))
2673 deferredBarrierSeqLoops.push_back(loopOp.getOperation());
2675 createBarrierAfterSeqLoop(loopOp.getOperation());
2678void ACCCGToGPULowering::flushDeferredBarriersBefore(Operation *beforeOp) {
2680 SmallVector<Operation *, 4> toFlush;
2681 for (Operation *loopOp : deferredBarrierSeqLoops)
2682 if (loopOp->getBlock() == block && loopOp->isBeforeInBlock(beforeOp))
2683 toFlush.push_back(loopOp);
2684 if (toFlush.empty())
2688 for (Operation *loopOp : toFlush)
2689 createBarrierAfterSeqLoop(loopOp);
2690 deferredBarrierSeqLoops.erase(
2691 std::remove_if(deferredBarrierSeqLoops.begin(),
2692 deferredBarrierSeqLoops.end(),
2693 [&](Operation *loopOp) {
2694 return loopOp->getBlock() == block &&
2695 loopOp->isBeforeInBlock(beforeOp);
2697 deferredBarrierSeqLoops.end());
2702void ACCCGToGPULowering::processParallelOp(scf::ParallelOp parallelOp) {
2703 LLVM_DEBUG(llvm::dbgs() <<
"processing par loop: ";
2704 parallelOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2706 "requires parallel dimensions attribute");
2707 mlir::acc::GPUParallelDimsAttr pDimsAttr =
2711 assert(pDimsAttr.getArray().size() == 1 &&
2712 "expected a single par dim in acc-cg-to-gpu");
2713 assert(parallelOp.getInductionVars().size() == 1 &&
2714 "expected a single induction variable in acc-cg-to-gpu");
2716 mlir::acc::GPUParallelDimAttr parDim = pDimsAttr.getArray().front();
2718 bool savedGridStrideFlag = insideAccumulateGridStride;
2719 Value savedReductionBuf = reductionSharedBuf;
2720 if (parDim.isThreadX()) {
2722 parallelOp.getBody()->walk([&](acc::ReductionAccumulateOp accOp) {
2723 bool hasBlockDim =
false;
2724 bool hasThreadDim =
false;
2725 for (
auto d : accOp.getParDims().getArray()) {
2728 if (d.isThreadX() || d.isThreadY())
2729 hasThreadDim =
true;
2731 if (hasThreadDim && !hasBlockDim) {
2738 insideAccumulateGridStride =
true;
2742 auto processLoopBody = [&]() {
2744 for (
auto &bodyOp : parallelOp.getBody()->getOperations()) {
2745 if (bodyOp.hasTrait<OpTrait::IsTerminator>()) {
2747 flushDeferredBarriersBefore(&bodyOp);
2755 if (parDim.isSeq()) {
2756 LLVM_DEBUG(llvm::dbgs() <<
"loop: parDim: " << parDim <<
" as gpu seq\n");
2762 bool needsAtomicReduction =
false;
2763 bool hasAccumulateSibling =
false;
2764 if (scf::ParallelOp parentPar =
2765 parallelOp->getParentOfType<scf::ParallelOp>()) {
2766 if (mlir::acc::GPUParallelDimsAttr parentDims =
2768 parentDims && llvm::any_of(parentDims.getArray(),
2769 [](
auto d) { return d.isThreadX(); })) {
2770 for (
auto &op : parentPar.getBody()->getOperations()) {
2771 if (acc::ReductionAccumulateOp acc =
2772 dyn_cast<acc::ReductionAccumulateOp>(op)) {
2773 bool hasBlockDim =
false;
2774 bool hasThreadDim =
false;
2775 for (
auto d : acc.getParDims().getArray()) {
2778 if (d.isThreadX() || d.isThreadY())
2779 hasThreadDim =
true;
2781 if (hasThreadDim && !hasBlockDim)
2782 hasAccumulateSibling =
true;
2787 if (insideAccumulateGridStride || hasAccumulateSibling) {
2788 for (
auto launchArg : computeRegion.getLaunchArgs()) {
2789 if (acc::ParWidthOp pw = launchArg.getDefiningOp<acc::ParWidthOp>()) {
2790 if (pw.getParDim().isThreadX()) {
2792 needsAtomicReduction = (*cval >=
options.subgroupSize);
2794 needsAtomicReduction =
true;
2800 if (needsAtomicReduction && !reductionSharedBuf) {
2802 parallelOp.getBody()->walk([&](acc::ReductionAccumulateOp accOp) {
2803 Type t = accOp.getValue().getType();
2804 if (isa<FloatType, IntegerType>(t))
2809 needsAtomicReduction =
false;
2811 if (needsAtomicReduction && !reductionSharedBuf) {
2812 Location seqLoc = parallelOp->getLoc();
2813 gpu::AddressSpaceAttr workgroupAS = gpu::AddressSpaceAttr::get(
2814 computeRegion->getContext(),
2815 gpu::GPUDialect::getWorkgroupAddressSpace());
2817 parallelOp.getBody()->
walk([&](acc::ReductionAccumulateOp accOp) {
2818 Type t = accOp.getValue().getType();
2819 if (isa<FloatType, IntegerType>(t))
2823 assert(elemTy &&
"expected scalar reduction element type");
2825 MemRefType bufTy = MemRefType::get({
options.subgroupSize}, elemTy,
2826 AffineMap{}, workgroupAS);
2827 reductionSharedBuf = acc::GPUSharedMemoryOp::create(
2831 Value tidY = getThreadId(seqLoc, gpu::Dimension::y);
2833 if (isa<FloatType>(elemTy)) {
2834 identity = arith::ConstantOp::create(
2835 rewriter, seqLoc, elemTy, rewriter.
getFloatAttr(elemTy, 0.0));
2839 memref::StoreOp::create(rewriter, seqLoc, identity, reductionSharedBuf,
2841 createPerRowBarrier(seqLoc);
2843 processSeqLoop(parallelOp);
2844 loopReductions.push_back(parallelOp);
2846 LLVM_DEBUG(llvm::dbgs()
2847 <<
"processing loop: parDim: " << parDim <<
" as gpu par\n");
2851 Value gpuThreadId = getGPUThreadIdFor(parDim.getProcessor());
2852 mapping.
map(parallelOp.getInductionVars()[0], gpuThreadId);
2859 llvm::for_each(parallelOp.getResults(), [&](Value v) {
2860 Type valTy = v.getType();
2861 TypedAttr zeroAttr = rewriter.getZeroAttr(valTy);
2862 auto zero = arith::ConstantOp::create(rewriter, parallelOp->getLoc(),
2864 mapping.map(v, zero);
2866 loopReductions.push_back(parallelOp);
2868 insideAccumulateGridStride = savedGridStrideFlag;
2869 if (!insideAccumulateGridStride && !savedReductionBuf)
2870 reductionSharedBuf = Value();
2874static gpu::AllReduceOperation
2875getAllReduceOperation(arith::AtomicRMWKind kind) {
2877 case arith::AtomicRMWKind::addf:
2878 case arith::AtomicRMWKind::addi:
2879 return gpu::AllReduceOperation::ADD;
2880 case arith::AtomicRMWKind::mulf:
2881 case arith::AtomicRMWKind::muli:
2882 return gpu::AllReduceOperation::MUL;
2883 case arith::AtomicRMWKind::minu:
2884 return gpu::AllReduceOperation::MINUI;
2885 case arith::AtomicRMWKind::mins:
2886 return gpu::AllReduceOperation::MINSI;
2887 case arith::AtomicRMWKind::minnumf:
2888 return gpu::AllReduceOperation::MINNUMF;
2889 case arith::AtomicRMWKind::maxu:
2890 return gpu::AllReduceOperation::MAXUI;
2891 case arith::AtomicRMWKind::maxs:
2892 return gpu::AllReduceOperation::MAXSI;
2893 case arith::AtomicRMWKind::maxnumf:
2894 return gpu::AllReduceOperation::MAXNUMF;
2895 case arith::AtomicRMWKind::ori:
2896 return gpu::AllReduceOperation::OR;
2897 case arith::AtomicRMWKind::andi:
2898 return gpu::AllReduceOperation::AND;
2899 case arith::AtomicRMWKind::xori:
2900 return gpu::AllReduceOperation::XOR;
2901 case arith::AtomicRMWKind::minimumf:
2902 return gpu::AllReduceOperation::MINIMUMF;
2903 case arith::AtomicRMWKind::maximumf:
2904 return gpu::AllReduceOperation::MAXIMUMF;
2905 case arith::AtomicRMWKind::assign:
2908 llvm_unreachable(
"unsupported atomic kind");
2911void ACCCGToGPULowering::constructAtomicAccumulation(
2913 arith::AtomicRMWKind kind) {
2915 "cannot lower atomic accumulation on an stack variable");
2927 MemRefType memrefTy = cast<MemRefType>(memref.
getType());
2928 unsigned rank = memrefTy.getRank();
2929 assert(
indices.size() == rank &&
"expected one index per memref dimension");
2930 SmallVector<OpFoldResult> offsets(
indices.begin(),
indices.end());
2931 SmallVector<OpFoldResult> sizes(rank, rewriter.
getIndexAttr(1));
2932 SmallVector<OpFoldResult> strides(rank, rewriter.
getIndexAttr(1));
2933 target = memref::SubViewOp::create(rewriter, loc, memref, offsets, sizes,
2937 auto atomicUpdateOp =
2938 acc::AtomicUpdateOp::create(rewriter, loc,
target, Value());
2939 Region ®ion = atomicUpdateOp->getRegion(0);
2943 Value reductionExpr =
2945 acc::YieldOp::create(rewriter, loc, reductionExpr);
2949void ACCCGToGPULowering::createGPUAllReduceOp(
2950 Location loc, Value input, Value memref, arith::AtomicRMWKind kind,
2952 gpu::AllReduceOperationAttr attr = gpu::AllReduceOperationAttr::get(
2953 computeRegion->getContext(), getAllReduceOperation(kind));
2954 auto allReduceOp = gpu::AllReduceOp::create(rewriter, loc, input, attr,
true);
2962 SmallVector<mlir::acc::GPUParallelDimAttr> inactiveParDims;
2963 MLIRContext *ctx = computeRegion->getContext();
2964 bool hasThreadX =
false;
2965 for (
auto parDim : parDimsAttr.getArray()) {
2966 if (parDim.isAnyBlock())
2968 if (parDim.isThreadX())
2970 if (computeRegion.getLaunchArg(parDim) ||
2972 inactiveParDims.push_back(parDim);
2978 inactiveParDims.push_back(mlir::acc::GPUParallelDimAttr::threadXDim(ctx));
2979 Value predicate = emitPredicate(loc, inactiveParDims);
2986 bool isPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
2987 unwrapMemRefConversion(memref).getDefiningOp());
2990 if (predicate && !isPerThreadPrivate) {
2992 scf::IfOp::create(rewriter, loc, predicate,
false);
2993 Region &thenRegion = ifOp.getThenRegion();
2997 memref::StoreOp::create(rewriter, loc, allReduceOp, memref,
indices);
2998 if (predicate && !isPerThreadPrivate)
3003 reductionAccumValue[memref] = allReduceOp;
3006void ACCCGToGPULowering::postprocessAccumulateOp(
3007 acc::ReductionAccumulateOp op) {
3008 Location loc = op->getLoc();
3016 bool hasThreadDim =
false;
3017 SmallVector<mlir::acc::GPUParallelDimAttr> threadParDims;
3018 for (
auto parDim : op.getParDims().getArray()) {
3019 if (!parDim.isAnyBlock()) {
3020 hasThreadDim =
true;
3021 threadParDims.push_back(parDim);
3025 std::optional<arith::AtomicRMWKind> kind;
3027 FailureOr<arith::AtomicRMWKind> kindOr = getReductionKind(
3028 op.getReductionOperator(), op.getValue().getType(), loc);
3034 if (hasThreadDim && reductionSharedBuf &&
3035 op.getValue().getType() ==
3036 cast<MemRefType>(reductionSharedBuf.getType()).getElementType()) {
3037 Value val = op.getValue();
3038 Value mem = op.getMemref();
3039 Value tidY = getThreadId(loc, gpu::Dimension::y);
3040 memref::AtomicRMWOp::create(rewriter, loc, *kind, val, reductionSharedBuf,
3042 createPerRowBarrier(loc);
3044 memref::LoadOp::create(rewriter, loc, reductionSharedBuf, tidY);
3045 memref::StoreOp::create(rewriter, loc,
result, mem);
3046 reductionAccumValue[mem] =
result;
3047 }
else if (hasThreadDim) {
3048 createGPUAllReduceOp(loc, op.getValue(), op.getMemref(), *kind,
3056 bool isPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
3057 unwrapMemRefConversion(mem).getDefiningOp());
3058 if (!isPerThreadPrivate) {
3059 SmallVector<mlir::acc::GPUParallelDimAttr> predDims;
3060 for (
auto parDim : computeRegion.getLaunchParDims())
3061 if (!parDim.isAnyBlock())
3062 predDims.push_back(parDim);
3063 if (predDims.empty()) {
3064 predDims.push_back(mlir::acc::GPUParallelDimAttr::threadXDim(
3065 computeRegion->getContext()));
3067 Value predicate = emitPredicate(loc, predDims);
3069 scf::IfOp::create(rewriter, loc, predicate,
false);
3071 memref::StoreOp::create(rewriter, loc, val, mem);
3074 memref::StoreOp::create(rewriter, loc, val, mem);
3082void ACCCGToGPULowering::postprocessLoopReduction(scf::ParallelOp parLoop) {
3083 if (parLoop.getNumReductions() == 0)
3086 for (
unsigned i = 0; i < parLoop.getNumResults(); ++i) {
3087 for (Operation *user :
3089 if (acc::ReductionAccumulateOp accumulateOp =
3090 dyn_cast<acc::ReductionAccumulateOp>(user)) {
3091 postprocessAccumulateOp(accumulateOp);
3097void ACCCGToGPULowering::processExecuteRegion(scf::ExecuteRegionOp op) {
3098 LLVM_DEBUG(llvm::dbgs() <<
"processing execute region op: ";
3099 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3100 Location loc = op->getLoc();
3101 auto types = op.getResultTypes();
3102 Region &oldRegion = op.getRegion();
3104 auto executeRegionOp = scf::ExecuteRegionOp::create(rewriter, loc, types);
3105 Region ®ion = executeRegionOp.getRegion();
3109 llvm::DenseMap<Block *, Block *> blockMap;
3110 blockMap[&oldRegion.
front()] = ®ion.
front();
3114 for (
auto &oldBlock : llvm::drop_begin(oldRegion.
getBlocks())) {
3115 TypeRange argTypes = oldBlock.getArgumentTypes();
3116 size_t numArgs = argTypes.size();
3119 SmallVector<Location>(numArgs, loc));
3120 blockMap[&oldBlock] = newBlock;
3127 for (
auto [oldBlock, newBlock] :
3129 OpBuilder::InsertionGuard blockGuard(rewriter);
3131 for (
auto &bodyOp : oldBlock.getOperations()) {
3133 if (bodyOp.hasTrait<OpTrait::IsTerminator>())
3139 Operation *oldTerminator = oldBlock.getTerminator();
3141 Operation *newTerminator = rewriter.
clone(*oldTerminator, mapping);
3146 Block *newDest = blockMap.lookup(oldDest);
3147 assert(newDest &&
"Successor block must be in blockMap");
3151 mapping.
map(op->getResults(), executeRegionOp->getResults());
3155void ACCCGToGPULowering::processAccumulateOp(acc::ReductionAccumulateOp op) {
3156 LLVM_DEBUG(llvm::dbgs() <<
"processing accumulate op: " << *op <<
"\n");
3157 Value accumulateValue = op.getValue();
3158 if (reductionSharedBuf &&
3160 cast<MemRefType>(reductionSharedBuf.getType()).getElementType()) {
3161 Location loc = op->getLoc();
3162 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
3163 op.getReductionOperator(), accumulateValue.
getType(), loc);
3168 Value tidY = getThreadId(loc, gpu::Dimension::y);
3169 memref::AtomicRMWOp::create(rewriter, loc, *kind, mappedValue,
3171 createPerRowBarrier(loc);
3173 memref::LoadOp::create(rewriter, loc, reductionSharedBuf, tidY);
3174 memref::StoreOp::create(rewriter, loc,
result, memref);
3175 reductionAccumValue[memref] =
result;
3179 Operation *newOp = rewriter.
clone(*op, mapping);
3181 }
else if (isRedundantChainAccumulate(op)) {
3187 LLVM_DEBUG(llvm::dbgs() <<
" skipped: redundant chain accumulate\n");
3188 gpu::BarrierOp::create(rewriter, op->getLoc());
3192 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
3193 op.getReductionOperator(), accumulateValue.
getType(), op.getLoc());
3196 createGPUAllReduceOp(op->getLoc(), mappedValue, memref, *kind,
3201void ACCCGToGPULowering::processAccumulateArrayOp(
3202 acc::ReductionAccumulateArrayOp op) {
3203 LLVM_DEBUG(llvm::dbgs() <<
"processing accumulate array op: " << *op <<
"\n");
3204 Location loc = op.getLoc();
3207 MemRefType memrefTy = dyn_cast<MemRefType>(memref.
getType());
3208 assert(memrefTy && memrefTy.getRank() == 1 &&
3209 "array reduction accumulate expects a rank-1 memref");
3211 FailureOr<arith::AtomicRMWKind> kindOr = getReductionKind(
3212 op.getReductionOperator(), memrefTy.getElementType(), loc);
3215 arith::AtomicRMWKind kind = *kindOr;
3220 .getDefiningOp<acc::DataBoundsOp>();
3221 assert(boundsOp &&
"expected acc.bounds defining op for array accumulate");
3222 auto eraseDeadBounds = [&] {
3223 if (boundsOp->use_empty())
3227 bool hasThreadDim =
false;
3228 bool hasBlockDim =
false;
3229 for (
auto pd : op.getParDims().getArray()) {
3230 hasThreadDim |= pd.isAnyThread();
3231 hasBlockDim |= pd.isAnyBlock();
3236 if (hasBlockDim && !hasThreadDim) {
3243 if (!reductionHasBlockContext(op)) {
3245 loc,
"reduction: thread-only array reduction accumulate");
3251 bool isPerThreadPrivate =
3252 canUseStackAlloca(memrefTy, loc,
options.maxThreadPrivateStack);
3253 if (!isPerThreadPrivate) {
3263 loc,
"reduction: shared-memory array reduction accumulate");
3269 auto toIndex = [&](Value v) -> Value {
3272 return arith::IndexCastOp::create(rewriter, loc, rewriter.
getIndexType(),
3276 Value lb = boundsOp.getLowerbound()
3277 ? toIndex(boundsOp.getLowerbound())
3278 : arith::ConstantIndexOp::create(rewriter, loc, 0);
3279 Value step = boundsOp.getStride()
3280 ? toIndex(boundsOp.getStride())
3286 if (boundsOp.getExtent()) {
3288 arith::AddIOp::create(rewriter, loc, lb, toIndex(boundsOp.getExtent()));
3290 assert(boundsOp.getUpperbound() &&
3291 "acc.bounds must specify an extent or upperbound");
3292 ub = arith::AddIOp::create(rewriter, loc, toIndex(boundsOp.getUpperbound()),
3297 auto forOp = scf::ForOp::create(rewriter, loc, lb, ub, step);
3299 OpBuilder::InsertionGuard guard(rewriter);
3300 rewriter.setInsertionPoint(forOp.getBody()->getTerminator());
3301 Value iv = forOp.getInductionVar();
3302 Value elem = memref::LoadOp::create(rewriter, loc, memref, ValueRange{iv});
3303 createGPUAllReduceOp(loc, elem, memref, kind, op.getParDims(),
3310void ACCCGToGPULowering::processReductionOp(acc::ReductionInitOp op) {
3312 op.getRegion().walk<WalkOrder::PreOrder>([&](Operation *innerOp) {
3313 if (acc::YieldOp yieldOp = dyn_cast<acc::YieldOp>(innerOp)) {
3314 op.getResult().replaceAllUsesWith(mapping.
lookup(yieldOp.getOperand(0)));
3317 if (innerOp->getNumRegions() > 0) {
3321 rewriter.
clone(*innerOp, mapping);
3326void ACCCGToGPULowering::processReductionCombineOp(acc::ReductionCombineOp op) {
3327 LLVM_DEBUG(llvm::dbgs() <<
"processing reduction combine op: ";
3328 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3329 Location loc = op.getLoc();
3330 MemRefType memrefType = dyn_cast<MemRefType>(op.getSrcMemref().getType());
3331 assert(memrefType &&
"expected memref type for reduction combine op");
3332 assert(memrefType.getRank() == 0 &&
3333 "expected scalar memref type for reduction combine op");
3334 Type elTy = memrefType.getElementType();
3335 FailureOr<arith::AtomicRMWKind> kindOr =
3336 getReductionKind(op.getReductionOperator(), elTy, loc);
3339 arith::AtomicRMWKind kind = *kindOr;
3350 bool destIsPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
3351 unwrapMemRefConversion(destMemref).getDefiningOp());
3353 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
3355 for (
auto parDim : parDims) {
3356 if (parDim.isAnyBlock() && !destIsPerThreadPrivate) {
3362 auto srcLoad = memref::LoadOp::create(rewriter, loc, srcMemref);
3363 pendingCombineReloads.push_back({srcMemref, srcLoad});
3364 constructAtomicAccumulation(loc, destMemref, {}, srcLoad,
3372 auto srcLoad = memref::LoadOp::create(rewriter, loc, srcMemref,
ValueRange{});
3374 memref::LoadOp::create(rewriter, loc, destMemref,
ValueRange{});
3376 memref::StoreOp::create(rewriter, loc, combine, destMemref,
ValueRange{});
3379void ACCCGToGPULowering::processCombineRegionOp(
3380 acc::ReductionCombineRegionOp op) {
3381 LLVM_DEBUG(llvm::dbgs() <<
"processing combine region op: ";
3382 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3386 bool destIsPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
3389 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
3391 for (
auto parDim : parDims) {
3392 if (parDim.isAnyBlock() && !destIsPerThreadPrivate) {
3396 for (Operation *user : op.getSrcVar().getUsers()) {
3397 if (acc::ReductionAccumulateOp accumulateOp =
3398 dyn_cast<acc::ReductionAccumulateOp>(user)) {
3399 Location loc = accumulateOp.getLoc();
3400 FailureOr<arith::AtomicRMWKind> kind =
3401 getReductionKind(accumulateOp.getReductionOperator(),
3402 accumulateOp.getValue().getType(), loc);
3407 auto reductionLoad = memref::LoadOp::create(rewriter, loc, srcMemref);
3408 pendingCombineReloads.push_back({srcMemref, reductionLoad});
3409 constructAtomicAccumulation(loc,
3411 {}, reductionLoad, *kind);
3419 MemRefType memrefTy = cast<MemRefType>(privateMemref.
getType());
3420 if (isa<ComplexType>(memrefTy.getElementType())) {
3421 Location loc = op.getLoc();
3422 Value reductionResult =
3423 memref::LoadOp::create(rewriter, loc, privateMemref);
3424 arith::AtomicRMWKind kind = arith::AtomicRMWKind::addf;
3425 op.getRegion().walk([&](Operation *innerOp) {
3426 if (isa<complex::MulOp>(innerOp))
3427 kind = arith::AtomicRMWKind::mulf;
3429 constructAtomicAccumulation(loc,
3431 {}, reductionResult, kind);
3436 op.getRegion().walk<WalkOrder::PreOrder>([&](Operation *innerOp) {
3437 if (acc::YieldOp yieldOp = dyn_cast<acc::YieldOp>(innerOp))
3439 if (innerOp->getNumRegions() > 0) {
3443 rewriter.
clone(*innerOp, mapping);
3448void ACCCGToGPULowering::processGenericOp(Operation *op) {
3451 LLVM_DEBUG(llvm::dbgs() <<
"processing generic op, cloning: ";
3452 op->
print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3453 Operation *newOp = rewriter.
clone(*op, mapping);
3458void ACCCGToGPULowering::processGenericOpWithRegions(Operation *op) {
3460 LLVM_DEBUG(llvm::dbgs() <<
"processing generic op with regions: ";
3461 op->
print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3467 for (
auto [oldRegion, newRegion] :
3468 llvm::zip(op->
getRegions(), newOp->getRegions())) {
3470 for (
auto &oldBlock : oldRegion.
getBlocks()) {
3471 TypeRange argTypes = oldBlock.getArgumentTypes();
3472 size_t numArgs = argTypes.size();
3475 rewriter.
createBlock(&newRegion, newRegion.end(), argTypes,
3476 SmallVector<Location>(numArgs, op->
getLoc()));
3482 for (
auto &innerOp : oldBlock.getOperations()) {
3483 OpBuilder::InsertionGuard guard(rewriter);
3485 processOp(&innerOp);
3496void ACCCGToGPULowering::processOp(Operation *op) {
3497 if (isDeferredBarrierFlushPoint(op))
3498 flushDeferredBarriersBefore(op);
3502 scf::ParallelOp parallelOp = cast<scf::ParallelOp>(op);
3503 processParallelOp(parallelOp);
3504 }
else if (scf::ForOp seqLoop = dyn_cast<scf::ForOp>(op)) {
3505 processSeqLoop(seqLoop);
3506 }
else if (acc::PrivatizeOp privatize = dyn_cast<acc::PrivatizeOp>(op)) {
3507 processPrivatize(privatize);
3508 }
else if (acc::PrivateLocalOp privateLocal =
3509 dyn_cast<acc::PrivateLocalOp>(op)) {
3510 processPrivateLocal(privateLocal);
3511 }
else if (acc::PredicateRegionOp predicateRegionOp =
3512 dyn_cast<acc::PredicateRegionOp>(op)) {
3513 processPredicateRegion(predicateRegionOp);
3514 }
else if (acc::ReductionAccumulateOp accumulateOp =
3515 dyn_cast<acc::ReductionAccumulateOp>(op)) {
3516 processAccumulateOp(accumulateOp);
3517 }
else if (
auto accumulateArrayOp =
3518 dyn_cast<acc::ReductionAccumulateArrayOp>(op)) {
3519 processAccumulateArrayOp(accumulateArrayOp);
3520 }
else if (acc::ReductionInitOp reductionInitOp =
3521 dyn_cast<acc::ReductionInitOp>(op)) {
3522 processReductionOp(reductionInitOp);
3523 }
else if (acc::ReductionCombineOp reductionCombineOp =
3524 dyn_cast<acc::ReductionCombineOp>(op)) {
3525 processReductionCombineOp(reductionCombineOp);
3526 }
else if (
auto combineRegionOp =
3527 dyn_cast<acc::ReductionCombineRegionOp>(op)) {
3528 processCombineRegionOp(combineRegionOp);
3529 }
else if (acc::ReductionOp accReductionOp = dyn_cast<acc::ReductionOp>(op)) {
3530 mapping.
map(accReductionOp->getResult(0), accReductionOp.getVarPtr());
3533 LLVM_DEBUG(llvm::dbgs() <<
"skipping mapped op: " << *op <<
"\n");
3534 }
else if (isa<acc::YieldOp>(op)) {
3535 for (
auto [operand,
result] :
3539 }
else if (isa<scf::ExecuteRegionOp>(op)) {
3540 processExecuteRegion(cast<scf::ExecuteRegionOp>(op));
3542 isa<acc::OpenACCDialect>(op->
getDialect())) {
3543 processGenericOp(op);
3545 processGenericOpWithRegions(op);
3550class RemoveParWidth :
public OpRewritePattern<acc::ParWidthOp> {
3551 using OpRewritePattern<acc::ParWidthOp>::OpRewritePattern;
3552 LogicalResult matchAndRewrite(acc::ParWidthOp op,
3553 PatternRewriter &rewriter)
const override {
3554 if (Value launchArg = op.getLaunchArg()) {
3565class ACCComputeRegionToGPUPattern
3566 :
public OpRewritePattern<acc::ComputeRegionOp> {
3568 ACCComputeRegionToGPUPattern(MLIRContext *context,
3569 acc::OpenACCSupport &accSupport,
3570 const ACCCGToGPUOptions &
options)
3571 : OpRewritePattern<acc::ComputeRegionOp>(context), accSupport(accSupport),
3574 LogicalResult matchAndRewrite(acc::ComputeRegionOp op,
3575 PatternRewriter &rewriter)
const override {
3576 ACCCGToGPULowering kernelOpRewriter(op, rewriter, accSupport,
options);
3577 return kernelOpRewriter.rewrite();
3581 acc::OpenACCSupport &accSupport;
3582 const ACCCGToGPUOptions &
options;
3585class ACCCGToGPU :
public acc::impl::ACCCGToGPUBase<ACCCGToGPU> {
3587 using acc::impl::ACCCGToGPUBase<ACCCGToGPU>::ACCCGToGPUBase;
3589 void runOnOperation()
override {
3590 FunctionOpInterface funcOp = getOperation();
3591 MLIRContext *context = funcOp->getContext();
3593 assert(deviceType != mlir::acc::DeviceType::Host &&
3594 deviceType != mlir::acc::DeviceType::Multicore &&
3595 "ACCCGToGPU only supports GPU device types");
3597 options.deviceType = deviceType;
3598 options.maxWorkgroupSharedMemory = maxWorkgroupSharedMemory;
3599 options.maxThreadPrivateStack = maxThreadPrivateStack;
3600 options.subgroupSize = subgroupSize;
3603 std::optional<std::reference_wrapper<acc::OpenACCSupport>> cachedAnalysis =
3604 getCachedParentAnalysis<acc::OpenACCSupport>(funcOp->getParentOp());
3605 acc::OpenACCSupport &accSupport = cachedAnalysis
3606 ? cachedAnalysis->get()
3607 : getAnalysis<acc::OpenACCSupport>();
3609 RewritePatternSet patterns(context);
3610 patterns.insert<ACCComputeRegionToGPUPattern>(context, accSupport,
options);
3611 patterns.insert<RemoveParWidth>(context);
3613 target.markUnknownOpDynamicallyLegal([](Operation *) {
return true; });
3614 target.addIllegalOp<acc::ComputeRegionOp, acc::ParWidthOp>();
3615 if (
failed(applyPartialConversion(getOperation(),
target,
3616 std::move(patterns)))) {
3617 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)
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.
::mlir::Pass::Option< mlir::acc::DeviceType > deviceType
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)
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.
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.
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...