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
30 : public amdgpu::impl::AmdgpuEmulateAtomicsPassBase<
31 AmdgpuEmulateAtomicsPass> {
32 using AmdgpuEmulateAtomicsPassBase<
33 AmdgpuEmulateAtomicsPass>::AmdgpuEmulateAtomicsPassBase;
34 void runOnOperation() override;
35};
36
37template <typename AtomicOp, typename ArithOp>
38struct RawBufferAtomicByCasPattern : public OpConversionPattern<AtomicOp> {
39 using OpConversionPattern<AtomicOp>::OpConversionPattern;
40 using Adaptor = typename AtomicOp::Adaptor;
41
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 }
88}
89
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");
102 (void)result;
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
198 PatternBenefit benefit) {
199 // gfx10 has no atomic adds.
200 if (chipset.majorVersion == 10 || chipset < Chipset(9, 0, 8)) {
201 target.addIllegalOp<RawBufferAtomicFaddOp>();
202 }
203 // gfx11 has no fp16 atomics
204 if (chipset.majorVersion == 11) {
205 target.addDynamicallyLegalOp<RawBufferAtomicFaddOp>(
206 [](RawBufferAtomicFaddOp op) -> bool {
207 Type elemType = getElementTypeOrSelf(op.getValue().getType());
208 return !isa<Float16Type, BFloat16Type>(elemType);
209 });
210 }
211 // gfx9 has no to a very limited support for floating-point min and max.
212 if (chipset.majorVersion == 9) {
213 if (chipset >= Chipset(9, 0, 0xa)) {
214 // gfx90a supports f64 max (and min, but we don't have a min wrapper right
215 // now) but all other types need to be emulated.
216 target.addDynamicallyLegalOp<RawBufferAtomicFmaxOp>(
217 [](RawBufferAtomicFmaxOp op) -> bool {
218 return op.getValue().getType().isF64();
219 });
220 } else {
221 target.addIllegalOp<RawBufferAtomicFmaxOp>();
222 }
223 // TODO(https://github.com/llvm/llvm-project/issues/129206): Refactor
224 // this to avoid hardcoding ISA version: gfx950 has bf16 atomics.
225 if (chipset < Chipset(9, 5, 0)) {
226 target.addDynamicallyLegalOp<RawBufferAtomicFaddOp>(
227 [](RawBufferAtomicFaddOp op) -> bool {
228 Type elemType = getElementTypeOrSelf(op.getValue().getType());
229 return !isa<BFloat16Type>(elemType);
230 });
231 }
232 }
233 patterns.add<
234 RawBufferAtomicByCasPattern<RawBufferAtomicFaddOp, arith::AddFOp>,
235 RawBufferAtomicByCasPattern<RawBufferAtomicFmaxOp, arith::MaximumFOp>,
236 RawBufferAtomicByCasPattern<RawBufferAtomicSmaxOp, arith::MaxSIOp>,
237 RawBufferAtomicByCasPattern<RawBufferAtomicUminOp, arith::MinUIOp>>(
238 patterns.getContext(), benefit);
239}
240
241void AmdgpuEmulateAtomicsPass::runOnOperation() {
242 Operation *op = getOperation();
243 FailureOr<Chipset> maybeChipset = Chipset::parse(chipset);
244 if (failed(maybeChipset)) {
245 emitError(op->getLoc(), "Invalid chipset name: " + chipset);
246 return signalPassFailure();
247 }
248
249 MLIRContext &ctx = getContext();
251 RewritePatternSet patterns(&ctx);
252 target.markUnknownOpDynamicallyLegal(
253 [](Operation *op) -> bool { return true; });
254
255 populateAmdgpuEmulateAtomicsPatterns(target, patterns, *maybeChipset);
256 if (failed(applyPartialConversion(op, target, std::move(patterns))))
257 return signalPassFailure();
258}
return success()
static Value flattenVecToBits(ConversionPatternRewriter &rewriter, Location loc, Value val)
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:63
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
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
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)
void populateAmdgpuEmulateAtomicsPatterns(ConversionTarget &target, RewritePatternSet &patterns, Chipset chipset, PatternBenefit benefit=1)
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:733
Include the generated interface declarations.
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
detail::DenseArrayAttrImpl< int32_t > DenseI32ArrayAttr
Represents the amdgpu gfx chipset version, e.g., gfx90a, gfx942, gfx1103.
Definition Chipset.h:22
unsigned majorVersion
Definition Chipset.h:23
static FailureOr< Chipset > parse(StringRef name)
Parses the chipset version string and returns the chipset on success, and failure otherwise.
Definition Chipset.cpp:14