MLIR 24.0.0git
EmulateAtomics.cpp
Go to the documentation of this file.
1//===- EmulateAtomics.cpp - Emulate unsupported AMDGPU atomics ------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10
19
20namespace mlir::amdgpu {
21#define GEN_PASS_DEF_AMDGPUEMULATEATOMICSPASS
22#include "mlir/Dialect/AMDGPU/Transforms/Passes.h.inc"
23} // namespace mlir::amdgpu
24
25using namespace mlir;
26using namespace mlir::amdgpu;
27
28namespace {
29struct AmdgpuEmulateAtomicsPass
31 AmdgpuEmulateAtomicsPass> {
33 AmdgpuEmulateAtomicsPass>::AmdgpuEmulateAtomicsPassBase;
34 void runOnOperation() override;
35};
37template <typename AtomicOp, typename ArithOp>
38struct RawBufferAtomicByCasPattern : public OpConversionPattern<AtomicOp> {
39 using OpConversionPattern<AtomicOp>::OpConversionPattern;
40 using Adaptor = typename AtomicOp::Adaptor;
42 LogicalResult
43 matchAndRewrite(AtomicOp atomicOp, Adaptor adaptor,
44 ConversionPatternRewriter &rewriter) const override;
45};
46} // namespace
47
48namespace {
49enum class DataArgAction : unsigned char {
50 Duplicate,
51 Drop,
52};
53} // namespace
54
55// Fix up the fact that, when we're migrating from a general bugffer atomic
56// to a load or to a CAS, the number of openrands, and thus the number of
57// entries needed in operandSegmentSizes, needs to change. We use this method
58// because we'd like to preserve unknown attributes on the atomic instead of
59// discarding them.
62 DataArgAction action) {
63 newAttrs.reserve(attrs.size());
64 for (NamedAttribute attr : attrs) {
65 if (attr.getName().getValue() != "operandSegmentSizes") {
66 newAttrs.push_back(attr);
67 continue;
68 }
69 auto segmentAttr = cast<DenseI32ArrayAttr>(attr.getValue());
70 MLIRContext *context = segmentAttr.getContext();
71 DenseI32ArrayAttr newSegments;
72 switch (action) {
73 case DataArgAction::Drop:
74 newSegments = DenseI32ArrayAttr::get(
75 context, segmentAttr.asArrayRef().drop_front());
76 break;
77 case DataArgAction::Duplicate: {
79 ArrayRef<int32_t> oldVals = segmentAttr.asArrayRef();
80 newVals.push_back(oldVals[0]);
81 newVals.append(oldVals.begin(), oldVals.end());
82 newSegments = DenseI32ArrayAttr::get(context, newVals);
83 break;
84 }
85 }
86 newAttrs.push_back(NamedAttribute(attr.getName(), newSegments));
87 }
90template <typename OpTy>
91static typename OpTy::Properties
94 typename OpTy::Properties properties{};
95 OpTy::populateDefaultProperties(
96 OperationName(OpTy::getOperationName(), builder.getContext()),
97 properties);
98 LogicalResult result =
99 OpTy::setPropertiesFromAttr(properties, builder.getDictionaryAttr(attrs),
100 [&]() { return emitError(loc); });
101 assert(succeeded(result) && "failed to convert operation properties");
103 return properties;
104}
105
106// A helper function to flatten a vector value to a scalar containing its bits,
107// returning the value itself if othetwise.
108static Value flattenVecToBits(ConversionPatternRewriter &rewriter, Location loc,
109 Value val) {
110 auto vectorType = dyn_cast<VectorType>(val.getType());
111 if (!vectorType)
112 return val;
113
114 int64_t bitwidth =
115 vectorType.getElementTypeBitWidth() * vectorType.getNumElements();
116 Type allBitsType = rewriter.getIntegerType(bitwidth);
117 auto allBitsVecType = VectorType::get({1}, allBitsType);
118 Value bitcast = vector::BitCastOp::create(rewriter, loc, allBitsVecType, val);
119 Value scalar = vector::ExtractOp::create(rewriter, loc, bitcast, 0);
120 return scalar;
121}
122
123template <typename AtomicOp, typename ArithOp>
124LogicalResult RawBufferAtomicByCasPattern<AtomicOp, ArithOp>::matchAndRewrite(
125 AtomicOp atomicOp, Adaptor adaptor,
126 ConversionPatternRewriter &rewriter) const {
127 Location loc = atomicOp.getLoc();
128
129 NamedAttrList origProperties;
130 atomicOp->getName().walkInherentAttrs(atomicOp,
131 [&](StringRef name, Attribute &attr) {
132 origProperties.append(name, attr);
133 });
134 ArrayRef<NamedAttribute> discardableAttrs =
135 atomicOp->getDiscardableAttrDictionary().getValue();
136 ValueRange operands = adaptor.getOperands();
137 Value data = operands.take_front()[0];
138 ValueRange invariantArgs = operands.drop_front();
139 Type dataType = data.getType();
140
142 patchOperandSegmentSizes(origProperties, loadAttrs, DataArgAction::Drop);
143 auto loadProperties =
144 getPropertiesFromAttrs<RawBufferLoadOp>(rewriter, loc, loadAttrs);
145 Value initialLoad =
146 RawBufferLoadOp::create(rewriter, loc, TypeRange{dataType}, invariantArgs,
147 loadProperties, discardableAttrs);
148 Block *currentBlock = rewriter.getInsertionBlock();
149 Block *afterAtomic =
150 rewriter.splitBlock(currentBlock, rewriter.getInsertionPoint());
151 afterAtomic->addArgument(dataType, loc);
152 Block *loopBlock = rewriter.createBlock(afterAtomic, {dataType}, {loc});
153
154 rewriter.setInsertionPointToEnd(currentBlock);
155 cf::BranchOp::create(rewriter, loc, loopBlock, initialLoad);
156
157 rewriter.setInsertionPointToEnd(loopBlock);
158 Value prevLoad = loopBlock->getArgument(0);
159 Value operated = ArithOp::create(rewriter, loc, data, prevLoad);
160 dataType = operated.getType();
161
162 SmallVector<NamedAttribute> cmpswapAttrs;
163 patchOperandSegmentSizes(origProperties, cmpswapAttrs,
164 DataArgAction::Duplicate);
165 SmallVector<Value> cmpswapArgs = {operated, prevLoad};
166 cmpswapArgs.append(invariantArgs.begin(), invariantArgs.end());
168 rewriter, loc, cmpswapAttrs);
169 Value atomicRes = RawBufferAtomicCmpswapOp::create(
170 rewriter, loc, TypeRange{dataType}, cmpswapArgs, cmpswapProperties,
171 discardableAttrs);
172
173 // We care about exact bitwise equality here, so do some bitcasts.
174 // These will fold away during lowering to the ROCDL dialect, where
175 // an int->float bitcast is introduced to account for the fact that cmpswap
176 // only takes integer arguments.
177
178 Value prevLoadForCompare = flattenVecToBits(rewriter, loc, prevLoad);
179 Value atomicResForCompare = flattenVecToBits(rewriter, loc, atomicRes);
180 if (auto floatDataTy = dyn_cast<FloatType>(dataType)) {
181 Type equivInt = rewriter.getIntegerType(floatDataTy.getWidth());
182 prevLoadForCompare =
183 arith::BitcastOp::create(rewriter, loc, equivInt, prevLoad);
184 atomicResForCompare =
185 arith::BitcastOp::create(rewriter, loc, equivInt, atomicRes);
186 }
187 Value canLeave =
188 arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq,
189 atomicResForCompare, prevLoadForCompare);
190 cf::CondBranchOp::create(rewriter, loc, canLeave, afterAtomic,
191 ValueRange{prevLoad}, loopBlock, atomicRes);
192 rewriter.replaceOp(atomicOp, ValueRange{afterAtomic->getArgument(0)});
193 return success();
194}
195
196/// Returns whether \p op can be lowered to a native buffer atomic fadd on
197/// \p target.
199 RawBufferAtomicFaddOp op) {
200 namespace AMDGPU = ::llvm::AMDGPU;
201
202 // A target with only the no-return form can still perform the atomic, it
203 // just cannot report the old value, so it suffices when the result is dead.
204 bool hasFadd = target.has(AMDGPU::FEAT_ATOMIC_FADD_RTN_INSTS) ||
205 (target.has(AMDGPU::FEAT_ATOMIC_FADD_NO_RTN_INSTS) &&
206 op.getOldValue().use_empty());
207 if (!hasFadd)
208 return false;
209
210 // The packed 16-bit forms are separate instructions with their own features.
211 Type elemType = getElementTypeOrSelf(op.getValue().getType());
212 if (isa<Float16Type>(elemType))
213 return target.has(AMDGPU::FEAT_ATOMIC_BUFFER_GLOBAL_PK_ADD_F16_INSTS);
214 if (isa<BFloat16Type>(elemType))
215 return target.has(AMDGPU::FEAT_ATOMIC_BUFFER_PK_ADD_BF16_INST);
216 return true;
217}
218
221 const ROCDL::TargetInfo &targetInfo, PatternBenefit benefit) {
222 namespace AMDGPU = ::llvm::AMDGPU;
223
224 target.addDynamicallyLegalOp<RawBufferAtomicFaddOp>(
225 [targetInfo](RawBufferAtomicFaddOp op) -> bool {
226 return isFaddNativelySupported(targetInfo, op);
227 });
228
229 // Floating-point min and max are only emulated on gfx9; later generations
230 // have them for every type this op accepts. f64 is the one gfx9 case that
231 // may be native.
232 if (targetInfo.isGeneration(9)) {
233 target.addDynamicallyLegalOp<RawBufferAtomicFmaxOp>(
234 [targetInfo](RawBufferAtomicFmaxOp op) -> bool {
235 return op.getValue().getType().isF64() &&
236 targetInfo.has(AMDGPU::FEAT_ATOMIC_FMIN_FMAX_GLOBAL_F64);
237 });
238 }
239 patterns.add<
240 RawBufferAtomicByCasPattern<RawBufferAtomicFaddOp, arith::AddFOp>,
241 RawBufferAtomicByCasPattern<RawBufferAtomicFmaxOp, arith::MaximumFOp>,
242 RawBufferAtomicByCasPattern<RawBufferAtomicSmaxOp, arith::MaxSIOp>,
243 RawBufferAtomicByCasPattern<RawBufferAtomicUminOp, arith::MinUIOp>>(
244 patterns.getContext(), benefit);
245}
246
247void AmdgpuEmulateAtomicsPass::runOnOperation() {
248 Operation *op = getOperation();
249 FailureOr<ROCDL::TargetInfo> targetInfo =
251 /*waveSize=*/0, [&] { return op->emitError(); });
252 if (failed(targetInfo))
253 return signalPassFailure();
254
255 MLIRContext &ctx = getContext();
257 RewritePatternSet patterns(&ctx);
258 target.markUnknownOpDynamicallyLegal(
259 [](Operation *op) -> bool { return true; });
260
261 populateAmdgpuEmulateAtomicsPatterns(target, patterns, *targetInfo);
262 if (failed(applyPartialConversion(op, target, std::move(patterns))))
263 return signalPassFailure();
264}
return success()
static Value flattenVecToBits(ConversionPatternRewriter &rewriter, Location loc, Value val)
static bool isFaddNativelySupported(const ROCDL::TargetInfo &target, RawBufferAtomicFaddOp op)
Returns whether op can be lowered to a native buffer atomic fadd on target.
static OpTy::Properties getPropertiesFromAttrs(OpBuilder &builder, Location loc, ArrayRef< NamedAttribute > attrs)
static void patchOperandSegmentSizes(ArrayRef< NamedAttribute > attrs, SmallVectorImpl< NamedAttribute > &newAttrs, DataArgAction action)
b getContext())
Attributes are known-constant values of operations.
Definition Attributes.h:25
Block represents an ordered list of Operations.
Definition Block.h:34
BlockArgument getArgument(unsigned i)
Definition Block.h:154
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
Definition Block.cpp:158
MLIRContext * getContext() const
Definition Builders.h:56
DictionaryAttr getDictionaryAttr(ArrayRef< NamedAttribute > value)
Definition Builders.cpp:112
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:64
NamedAttrList is array of NamedAttributes that tracks whether it is sorted and does some basic work t...
void append(StringRef name, Attribute attr)
Add an attribute with the specified name.
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
This class helps build Operations.
Definition Builders.h:210
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
Describes the AMDGPU target a lowering is producing code for: the triple's subarch (which identifies ...
bool has(Feature feature) const
Returns whether the target has feature.
bool isGeneration(unsigned major) const
Returns whether the target belongs to gfx generation major (9 for any gfx9xx, 12 for any gfx12xx,...
static FailureOr< TargetInfo > get(StringRef arch, unsigned waveSize=0, function_ref< InFlightDiagnostic()> emitError=nullptr)
Resolves a target description.
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
static DenseArrayAttrImpl get(MLIRContext *context, ArrayRef< int32_t > content)
StringRef resolveArchOption(StringRef arch, StringRef deprecatedAlias)
Returns the target architecture that a pass should parse, given its arch option and the value of the ...
void populateAmdgpuEmulateAtomicsPatterns(ConversionTarget &target, RewritePatternSet &patterns, const ROCDL::TargetInfo &targetInfo, PatternBenefit benefit=1)
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:734
Include the generated interface declarations.
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
detail::DenseArrayAttrImpl< int32_t > DenseI32ArrayAttr