MLIR 24.0.0git
UnifyAliasedResourcePass.cpp
Go to the documentation of this file.
1//===- UnifyAliasedResourcePass.cpp - Pass to Unify Aliased Resources -----===//
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//
9// This file implements a pass that unifies access of multiple aliased resources
10// into access of one single resource.
11//
12//===----------------------------------------------------------------------===//
13
15
20#include "mlir/IR/Builders.h"
23#include "mlir/IR/SymbolTable.h"
25#include "llvm/ADT/DenseMap.h"
26#include "llvm/ADT/STLExtras.h"
27#include "llvm/Support/Alignment.h"
28#include <iterator>
29
30namespace mlir {
31namespace spirv {
32#define GEN_PASS_DEF_SPIRVUNIFYALIASEDRESOURCEPASS
33#include "mlir/Dialect/SPIRV/Transforms/Passes.h.inc"
34} // namespace spirv
35} // namespace mlir
36
37using namespace mlir;
38
39//===----------------------------------------------------------------------===//
40// Utility functions
41//===----------------------------------------------------------------------===//
42
43using Descriptor = std::pair<uint32_t, uint32_t>; // (set #, binding #)
46
47/// Collects all aliased resources in the given SPIR-V `moduleOp`.
48static AliasedResourceMap collectAliasedResources(spirv::ModuleOp moduleOp) {
49 AliasedResourceMap aliasedResources;
50 moduleOp->walk([&aliasedResources](spirv::GlobalVariableOp varOp) {
51 if (varOp->getDiscardableAttrOfType<UnitAttr>("aliased")) {
52 std::optional<uint32_t> set = varOp.getDescriptorSet();
53 std::optional<uint32_t> binding = varOp.getBinding();
54 if (set && binding)
55 aliasedResources[{*set, *binding}].push_back(varOp);
56 }
57 });
58 return aliasedResources;
59}
60
61/// Returns the element type if the given `type` is a runtime array resource:
62/// `!spirv.ptr<!spirv.struct<!spirv.rtarray<...>>>`. Returns null type
63/// otherwise.
65 auto ptrType = dyn_cast<spirv::PointerType>(type);
66 if (!ptrType)
67 return {};
68
69 auto structType = dyn_cast<spirv::StructType>(ptrType.getPointeeType());
70 if (!structType || structType.getNumElements() != 1)
71 return {};
72
73 auto rtArrayType =
74 dyn_cast<spirv::RuntimeArrayType>(structType.getElementType(0));
75 if (!rtArrayType)
76 return {};
77
78 return rtArrayType.getElementType();
79}
80
81/// Given a list of resource element `types`, returns the index of the canonical
82/// resource that all resources should be unified into. Returns std::nullopt if
83/// unable to unify.
84static std::optional<int>
86 // scalarNumBits: contains all resources' scalar types' bit counts.
87 // vectorNumBits: only contains resources whose element types are vectors.
88 // vectorIndices: each vector's original index in `types`.
89 SmallVector<int> scalarNumBits, vectorNumBits, vectorIndices;
90 scalarNumBits.reserve(types.size());
91 vectorNumBits.reserve(types.size());
92 vectorIndices.reserve(types.size());
93
94 for (const auto &indexedTypes : llvm::enumerate(types)) {
95 spirv::SPIRVType type = indexedTypes.value();
96 assert(type.isScalarOrVector());
97 if (auto vectorType = dyn_cast<VectorType>(type)) {
98 if (vectorType.getNumElements() % 2 != 0)
99 return std::nullopt; // Odd-sized vector has special layout
100 // requirements.
101
102 std::optional<int64_t> numBytes = type.getSizeInBytes();
103 if (!numBytes)
104 return std::nullopt;
105
106 scalarNumBits.push_back(
107 vectorType.getElementType().getIntOrFloatBitWidth());
108 vectorNumBits.push_back(*numBytes * 8);
109 vectorIndices.push_back(indexedTypes.index());
110 } else {
111 scalarNumBits.push_back(type.getIntOrFloatBitWidth());
112 }
113 }
114
115 if (!vectorNumBits.empty()) {
116 // Choose the *vector* with the smallest bitwidth as the canonical resource,
117 // so that we can still keep vectorized load/store and avoid partial updates
118 // to large vectors.
119 auto *minVal = llvm::min_element(vectorNumBits);
120 // Make sure that the canonical resource's bitwidth is divisible by others.
121 // With out this, we cannot properly adjust the index later.
122 if (llvm::any_of(vectorNumBits,
123 [&](int bits) { return bits % *minVal != 0; }))
124 return std::nullopt;
125
126 // Require all scalar type bit counts to be a multiple of the chosen
127 // vector's primitive type to avoid reading/writing subcomponents.
128 int index = vectorIndices[std::distance(vectorNumBits.begin(), minVal)];
129 int baseNumBits = scalarNumBits[index];
130 if (llvm::any_of(scalarNumBits,
131 [&](int bits) { return bits % baseNumBits != 0; }))
132 return std::nullopt;
133
134 return index;
135 }
136
137 // All element types are scalars. Then choose the smallest bitwidth as the
138 // cannonical resource to avoid subcomponent load/store.
139 auto *minVal = llvm::min_element(scalarNumBits);
140 if (llvm::any_of(scalarNumBits,
141 [minVal](int64_t bit) { return bit % *minVal != 0; }))
142 return std::nullopt;
143 return std::distance(scalarNumBits.begin(), minVal);
144}
145
147 return a.isIntOrFloat() && b.isIntOrFloat() &&
148 a.getIntOrFloatBitWidth() == b.getIntOrFloatBitWidth();
149}
150
151//===----------------------------------------------------------------------===//
152// Analysis
153//===----------------------------------------------------------------------===//
154
155namespace {
156/// A class for analyzing aliased resources.
157///
158/// Resources are expected to be spirv.GlobalVarible that has a descriptor set
159/// and binding number. Such resources are of the type
160/// `!spirv.ptr<!spirv.struct<...>>` per Vulkan requirements.
161///
162/// Right now, we only support the case that there is a single runtime array
163/// inside the struct.
164class ResourceAliasAnalysis {
165public:
167
168 explicit ResourceAliasAnalysis(Operation *);
169
170 /// Returns true if the given `op` can be rewritten to use a canonical
171 /// resource.
172 bool shouldUnify(Operation *op) const;
173
174 /// Returns all descriptors and their corresponding aliased resources.
175 const AliasedResourceMap &getResourceMap() const { return resourceMap; }
176
177 /// Returns the canonical resource for the given descriptor/variable.
178 spirv::GlobalVariableOp
179 getCanonicalResource(const Descriptor &descriptor) const;
180 spirv::GlobalVariableOp
181 getCanonicalResource(spirv::GlobalVariableOp varOp) const;
182
183 /// Returns the element type for the given variable.
184 spirv::SPIRVType getElementType(spirv::GlobalVariableOp varOp) const;
185
186private:
187 /// Given the descriptor and aliased resources bound to it, analyze whether we
188 /// can unify them and record if so.
189 void recordIfUnifiable(const Descriptor &descriptor,
190 ArrayRef<spirv::GlobalVariableOp> resources);
191
192 /// Mapping from a descriptor to all aliased resources bound to it.
193 AliasedResourceMap resourceMap;
194
195 /// Mapping from a descriptor to the chosen canonical resource.
197
198 /// Mapping from an aliased resource to its descriptor.
200
201 /// Mapping from an aliased resource to its element (scalar/vector) type.
203};
204} // namespace
205
206ResourceAliasAnalysis::ResourceAliasAnalysis(Operation *root) {
207 // Collect all aliased resources first and put them into different sets
208 // according to the descriptor.
209 AliasedResourceMap aliasedResources =
210 collectAliasedResources(cast<spirv::ModuleOp>(root));
211
212 // For each resource set, analyze whether we can unify; if so, try to identify
213 // a canonical resource, whose element type has the largest bitwidth.
214 for (const auto &descriptorResource : aliasedResources) {
215 recordIfUnifiable(descriptorResource.first, descriptorResource.second);
216 }
217}
218
219bool ResourceAliasAnalysis::shouldUnify(Operation *op) const {
220 if (!op)
221 return false;
222
223 if (auto varOp = dyn_cast<spirv::GlobalVariableOp>(op)) {
224 auto canonicalOp = getCanonicalResource(varOp);
225 return canonicalOp && varOp != canonicalOp;
226 }
227 if (auto addressOp = dyn_cast<spirv::AddressOfOp>(op)) {
228 auto moduleOp = addressOp->getParentOfType<spirv::ModuleOp>();
229 auto *varOp =
230 SymbolTable::lookupSymbolIn(moduleOp, addressOp.getVariable());
231 return shouldUnify(varOp);
232 }
233
234 if (auto acOp = dyn_cast<spirv::AccessChainOp>(op))
235 return shouldUnify(acOp.getBasePtr().getDefiningOp());
236 if (auto loadOp = dyn_cast<spirv::LoadOp>(op))
237 return shouldUnify(loadOp.getPtr().getDefiningOp());
238 if (auto storeOp = dyn_cast<spirv::StoreOp>(op))
239 return shouldUnify(storeOp.getPtr().getDefiningOp());
240
241 return false;
242}
243
244spirv::GlobalVariableOp ResourceAliasAnalysis::getCanonicalResource(
245 const Descriptor &descriptor) const {
246 auto varIt = canonicalResourceMap.find(descriptor);
247 if (varIt == canonicalResourceMap.end())
248 return {};
249 return varIt->second;
250}
251
252spirv::GlobalVariableOp ResourceAliasAnalysis::getCanonicalResource(
253 spirv::GlobalVariableOp varOp) const {
254 auto descriptorIt = descriptorMap.find(varOp);
255 if (descriptorIt == descriptorMap.end())
256 return {};
257 return getCanonicalResource(descriptorIt->second);
258}
259
260spirv::SPIRVType
261ResourceAliasAnalysis::getElementType(spirv::GlobalVariableOp varOp) const {
262 auto it = elementTypeMap.find(varOp);
263 if (it == elementTypeMap.end())
264 return {};
265 return it->second;
266}
267
268void ResourceAliasAnalysis::recordIfUnifiable(
269 const Descriptor &descriptor, ArrayRef<spirv::GlobalVariableOp> resources) {
270 // Collect the element types for all resources in the current set.
271 SmallVector<spirv::SPIRVType> elementTypes;
272 for (spirv::GlobalVariableOp resource : resources) {
273 Type elementType = getRuntimeArrayElementType(resource.getType());
274 if (!elementType)
275 return; // Unexpected resource variable type.
276
277 auto type = cast<spirv::SPIRVType>(elementType);
278 if (!type.isScalarOrVector())
279 return; // Unexpected resource element type.
280
281 elementTypes.push_back(type);
282 }
283
284 std::optional<int> index = deduceCanonicalResource(elementTypes);
285 if (!index)
286 return;
287
288 // Update internal data structures for later use.
289 resourceMap[descriptor].assign(resources.begin(), resources.end());
290 canonicalResourceMap[descriptor] = resources[*index];
291 for (const auto &resource : llvm::enumerate(resources)) {
292 descriptorMap[resource.value()] = descriptor;
293 elementTypeMap[resource.value()] = elementTypes[resource.index()];
294 }
295}
296
297//===----------------------------------------------------------------------===//
298// Patterns
299//===----------------------------------------------------------------------===//
300
301template <typename OpTy>
302class ConvertAliasResource : public OpConversionPattern<OpTy> {
303public:
304 ConvertAliasResource(const ResourceAliasAnalysis &analysis,
305 MLIRContext *context, PatternBenefit benefit = 1)
306 : OpConversionPattern<OpTy>(context, benefit), analysis(analysis) {}
307
308protected:
309 const ResourceAliasAnalysis &analysis;
310};
311
312struct ConvertVariable : public ConvertAliasResource<spirv::GlobalVariableOp> {
314
315 LogicalResult
316 matchAndRewrite(spirv::GlobalVariableOp varOp, OpAdaptor adaptor,
317 ConversionPatternRewriter &rewriter) const override {
318 // Just remove the aliased resource. Users will be rewritten to use the
319 // canonical one.
320 rewriter.eraseOp(varOp);
321 return success();
322 }
323};
324
325struct ConvertAddressOf : public ConvertAliasResource<spirv::AddressOfOp> {
327
328 LogicalResult
329 matchAndRewrite(spirv::AddressOfOp addressOp, OpAdaptor adaptor,
330 ConversionPatternRewriter &rewriter) const override {
331 // Rewrite the AddressOf op to get the address of the canoncical resource.
332 auto moduleOp = addressOp->getParentOfType<spirv::ModuleOp>();
333 auto srcVarOp = cast<spirv::GlobalVariableOp>(
334 SymbolTable::lookupSymbolIn(moduleOp, addressOp.getVariable()));
335 auto dstVarOp = analysis.getCanonicalResource(srcVarOp);
336 rewriter.replaceOpWithNewOp<spirv::AddressOfOp>(addressOp, dstVarOp);
337 return success();
338 }
339};
340
341struct ConvertAccessChain : public ConvertAliasResource<spirv::AccessChainOp> {
343
344 LogicalResult
345 matchAndRewrite(spirv::AccessChainOp acOp, OpAdaptor adaptor,
346 ConversionPatternRewriter &rewriter) const override {
347 auto addressOp = acOp.getBasePtr().getDefiningOp<spirv::AddressOfOp>();
348 if (!addressOp)
349 return rewriter.notifyMatchFailure(acOp, "base ptr not addressof op");
350
351 auto moduleOp = acOp->getParentOfType<spirv::ModuleOp>();
352 auto srcVarOp = cast<spirv::GlobalVariableOp>(
353 SymbolTable::lookupSymbolIn(moduleOp, addressOp.getVariable()));
354 auto dstVarOp = analysis.getCanonicalResource(srcVarOp);
355
356 spirv::SPIRVType srcElemType = analysis.getElementType(srcVarOp);
357 spirv::SPIRVType dstElemType = analysis.getElementType(dstVarOp);
358
359 if (srcElemType == dstElemType ||
360 areSameBitwidthScalarType(srcElemType, dstElemType)) {
361 // We have the same bitwidth for source and destination element types.
362 // Thie indices keep the same.
363 rewriter.replaceOpWithNewOp<spirv::AccessChainOp>(
364 acOp, adaptor.getBasePtr(), adaptor.getIndices());
365 return success();
366 }
367
368 Location loc = acOp.getLoc();
369
370 if (srcElemType.isIntOrFloat() && isa<VectorType>(dstElemType)) {
371 // The source indices are for a buffer with scalar element types. Rewrite
372 // them into a buffer with vector element types. We need to scale the last
373 // index for the vector as a whole, then add one level of index for inside
374 // the vector.
375 std::optional<int64_t> srcBytes = srcElemType.getSizeInBytes();
376 std::optional<int64_t> dstBytes = dstElemType.getSizeInBytes();
377 if (!srcBytes || !dstBytes)
378 return rewriter.notifyMatchFailure(acOp, "unknown element byte size");
379 int srcNumBytes = *srcBytes;
380 int dstNumBytes = *dstBytes;
381 assert(dstNumBytes >= srcNumBytes && dstNumBytes % srcNumBytes == 0);
382
383 auto indices = llvm::to_vector<4>(acOp.getIndices());
384 Value oldIndex = indices.back();
385 Type indexType = oldIndex.getType();
386
387 int ratio = dstNumBytes / srcNumBytes;
388 auto ratioValue = spirv::ConstantOp::create(
389 rewriter, loc, indexType, rewriter.getIntegerAttr(indexType, ratio));
390
391 indices.back() =
392 spirv::SDivOp::create(rewriter, loc, indexType, oldIndex, ratioValue);
393 indices.push_back(spirv::SModOp::create(rewriter, loc, indexType,
394 oldIndex, ratioValue));
395
396 rewriter.replaceOpWithNewOp<spirv::AccessChainOp>(
397 acOp, adaptor.getBasePtr(), indices);
398 return success();
399 }
400
401 if ((srcElemType.isIntOrFloat() && dstElemType.isIntOrFloat()) ||
402 (isa<VectorType>(srcElemType) && isa<VectorType>(dstElemType))) {
403 // The source indices are for a buffer with larger bitwidth scalar/vector
404 // element types. Rewrite them into a buffer with smaller bitwidth element
405 // types. We only need to scale the last index.
406 std::optional<int64_t> srcBytes = srcElemType.getSizeInBytes();
407 std::optional<int64_t> dstBytes = dstElemType.getSizeInBytes();
408 if (!srcBytes || !dstBytes)
409 return rewriter.notifyMatchFailure(acOp, "unknown element byte size");
410 int srcNumBytes = *srcBytes;
411 int dstNumBytes = *dstBytes;
412 assert(srcNumBytes >= dstNumBytes && srcNumBytes % dstNumBytes == 0);
413
414 auto indices = llvm::to_vector<4>(acOp.getIndices());
415 Value oldIndex = indices.back();
416 Type indexType = oldIndex.getType();
418 int ratio = srcNumBytes / dstNumBytes;
419 auto ratioValue = spirv::ConstantOp::create(
420 rewriter, loc, indexType, rewriter.getIntegerAttr(indexType, ratio));
421
422 indices.back() =
423 spirv::IMulOp::create(rewriter, loc, indexType, oldIndex, ratioValue);
424
425 rewriter.replaceOpWithNewOp<spirv::AccessChainOp>(
426 acOp, adaptor.getBasePtr(), indices);
427 return success();
429
430 return rewriter.notifyMatchFailure(
431 acOp, "unsupported src/dst types for spirv.AccessChain");
432 }
434
435struct ConvertLoad : public ConvertAliasResource<spirv::LoadOp> {
437
438 LogicalResult
439 matchAndRewrite(spirv::LoadOp loadOp, OpAdaptor adaptor,
440 ConversionPatternRewriter &rewriter) const override {
441 auto srcPtrType = cast<spirv::PointerType>(loadOp.getPtr().getType());
442 auto srcElemType = cast<spirv::SPIRVType>(srcPtrType.getPointeeType());
443 auto dstPtrType = cast<spirv::PointerType>(adaptor.getPtr().getType());
444 auto dstElemType = cast<spirv::SPIRVType>(dstPtrType.getPointeeType());
445
446 Location loc = loadOp.getLoc();
447 auto newLoadOp = spirv::LoadOp::create(rewriter, loc, adaptor.getPtr());
448 if (srcElemType == dstElemType) {
449 rewriter.replaceOp(loadOp, newLoadOp->getResults());
450 return success();
451 }
452
453 if (areSameBitwidthScalarType(srcElemType, dstElemType)) {
454 auto castOp = spirv::BitcastOp::create(rewriter, loc, srcElemType,
455 newLoadOp.getValue());
456 rewriter.replaceOp(loadOp, castOp->getResults());
457
458 return success();
459 }
460
461 if ((srcElemType.isIntOrFloat() && dstElemType.isIntOrFloat()) ||
462 (isa<VectorType>(srcElemType) && isa<VectorType>(dstElemType))) {
463 // The source and destination have scalar types of different bitwidths, or
464 // vector types of different component counts. For such cases, we load
465 // multiple smaller bitwidth values and construct a larger bitwidth one.
466
467 std::optional<int64_t> srcBytes = srcElemType.getSizeInBytes();
468 std::optional<int64_t> dstBytes = dstElemType.getSizeInBytes();
469 if (!srcBytes || !dstBytes)
470 return rewriter.notifyMatchFailure(loadOp, "unknown element byte size");
471 int srcNumBytes = *srcBytes;
472 int dstNumBytes = *dstBytes;
473 assert(srcNumBytes > dstNumBytes && srcNumBytes % dstNumBytes == 0);
474 int ratio = srcNumBytes / dstNumBytes;
475 if (ratio > 4)
476 return rewriter.notifyMatchFailure(loadOp, "more than 4 components");
477
478 SmallVector<Value> components;
479 components.reserve(ratio);
480 components.push_back(newLoadOp);
481
482 auto acOp = adaptor.getPtr().getDefiningOp<spirv::AccessChainOp>();
483 if (!acOp)
484 return rewriter.notifyMatchFailure(loadOp, "ptr not spirv.AccessChain");
485
486 auto i32Type = rewriter.getI32Type();
487 Value oneValue = spirv::ConstantOp::getOne(i32Type, loc, rewriter);
488 auto indices = llvm::to_vector<4>(acOp.getIndices());
489 for (int i = 1; i < ratio; ++i) {
490 // Load all subsequent components belonging to this element.
491 indices.back() = spirv::IAddOp::create(rewriter, loc, i32Type,
492 indices.back(), oneValue);
493 auto componentAcOp = spirv::AccessChainOp::create(
494 rewriter, loc, acOp.getBasePtr(), indices);
495 // Assuming little endian, this reads lower-ordered bits of the number
496 // to lower-numbered components of the vector.
497 components.push_back(
498 spirv::LoadOp::create(rewriter, loc, componentAcOp));
499 }
500
501 // Create a vector of the components and then cast back to the larger
502 // bitwidth element type. For spirv.bitcast, the lower-numbered components
503 // of the vector map to lower-ordered bits of the larger bitwidth element
504 // type.
505
506 Type vectorType = srcElemType;
507 if (!isa<VectorType>(srcElemType))
508 vectorType = VectorType::get({ratio}, dstElemType);
509
510 // If both the source and destination are vector types, we need to make
511 // sure the scalar type is the same for composite construction later.
512 if (auto srcElemVecType = dyn_cast<VectorType>(srcElemType))
513 if (auto dstElemVecType = dyn_cast<VectorType>(dstElemType)) {
514 if (srcElemVecType.getElementType() !=
515 dstElemVecType.getElementType()) {
516 int64_t count =
517 dstNumBytes / (srcElemVecType.getElementTypeBitWidth() / 8);
518
519 // Make sure not to create 1-element vectors, which are illegal in
520 // SPIR-V.
521 Type castType = srcElemVecType.getElementType();
522 if (count > 1)
523 castType = VectorType::get({count}, castType);
524
525 for (Value &c : components)
526 c = spirv::BitcastOp::create(rewriter, loc, castType, c);
527 }
528 }
529 Value vectorValue = spirv::CompositeConstructOp::create(
530 rewriter, loc, vectorType, components);
531
532 if (!isa<VectorType>(srcElemType))
533 vectorValue =
534 spirv::BitcastOp::create(rewriter, loc, srcElemType, vectorValue);
535 rewriter.replaceOp(loadOp, vectorValue);
536 return success();
537 }
538
539 return rewriter.notifyMatchFailure(
540 loadOp, "unsupported src/dst types for spirv.Load");
541 }
542};
543
544struct ConvertStore : public ConvertAliasResource<spirv::StoreOp> {
546
547 LogicalResult
548 matchAndRewrite(spirv::StoreOp storeOp, OpAdaptor adaptor,
549 ConversionPatternRewriter &rewriter) const override {
550 auto srcElemType =
551 cast<spirv::PointerType>(storeOp.getPtr().getType()).getPointeeType();
552 auto dstElemType =
553 cast<spirv::PointerType>(adaptor.getPtr().getType()).getPointeeType();
554 Location loc = storeOp.getLoc();
555 Value value = adaptor.getValue();
556 auto srcVector = dyn_cast<VectorType>(srcElemType);
557 auto dstVector = dyn_cast<VectorType>(dstElemType);
558 if (srcVector && dstVector && srcVector != dstVector) {
559 if (srcVector.getElementTypeBitWidth() !=
560 dstVector.getElementTypeBitWidth() ||
561 srcVector.getNumElements() % dstVector.getNumElements() != 0)
562 return rewriter.notifyMatchFailure(storeOp,
563 "incompatible vector types");
564 auto acOp = adaptor.getPtr().getDefiningOp<spirv::AccessChainOp>();
565 if (!acOp)
566 return rewriter.notifyMatchFailure(storeOp,
567 "ptr not spirv.AccessChain");
568 if (srcVector.getElementType() != dstVector.getElementType())
569 value = spirv::BitcastOp::create(
570 rewriter, loc,
571 VectorType::get(srcVector.getShape(), dstVector.getElementType()),
572 value);
573 unsigned count = dstVector.getNumElements();
574 unsigned ratio = srcVector.getNumElements() / count;
575 unsigned bytes = count * dstVector.getElementTypeBitWidth() / 8;
576
577 // Split the value into vectors matching the canonical resource and store
578 // them at consecutive vector indices. Adjust each store's alignment for
579 // its byte offset from the original pointer.
580 auto getPart = [&](unsigned i) -> Value {
581 if (ratio == 1)
582 return value;
583 SmallVector<int32_t> components;
584 for (unsigned j = 0; j < count; ++j)
585 components.push_back(i * count + j);
586 return spirv::VectorShuffleOp::create(
587 rewriter, loc, dstVector, value, value,
588 rewriter.getI32ArrayAttr(components));
589 };
590 auto firstStore = spirv::StoreOp::create(
591 rewriter, loc, adaptor.getPtr(), getPart(0),
592 storeOp.getMemoryAccessAttr(), storeOp.getAlignmentAttr());
593 firstStore->setDiscardableAttrs(storeOp->getDiscardableAttrDictionary());
594 for (unsigned i = 1; i < ratio; ++i) {
595 Value part = getPart(i);
596 SmallVector<Value> indices = llvm::to_vector(acOp.getIndices());
597 Type indexType = indices.back().getType();
598 auto offset = spirv::ConstantOp::create(
599 rewriter, loc, indexType, rewriter.getIntegerAttr(indexType, i));
600 indices.back() = spirv::IAddOp::create(rewriter, loc, indexType,
601 indices.back(), offset);
602 Value ptr = spirv::AccessChainOp::create(rewriter, loc,
603 acOp.getBasePtr(), indices);
604 IntegerAttr alignment = storeOp.getAlignmentAttr();
605 if (alignment)
606 alignment = rewriter.getI32IntegerAttr(
607 llvm::commonAlignment(llvm::Align(alignment.getInt()), i * bytes)
608 .value());
609 auto store = spirv::StoreOp::create(
610 rewriter, loc, ptr, part, storeOp.getMemoryAccessAttr(), alignment);
611 store->setDiscardableAttrs(storeOp->getDiscardableAttrDictionary());
612 }
613 rewriter.eraseOp(storeOp);
614 return success();
615 }
616 if (srcElemType != dstElemType) {
617 if (!srcElemType.isIntOrFloat() || !dstElemType.isIntOrFloat())
618 return rewriter.notifyMatchFailure(storeOp, "not scalar type");
619 if (!areSameBitwidthScalarType(srcElemType, dstElemType))
620 return rewriter.notifyMatchFailure(storeOp, "different bitwidth");
621 value = spirv::BitcastOp::create(rewriter, loc, dstElemType, value);
622 }
623 auto newStore = rewriter.replaceOpWithNewOp<spirv::StoreOp>(
624 storeOp, adaptor.getPtr(), value, storeOp.getMemoryAccessAttr(),
625 storeOp.getAlignmentAttr());
626 newStore->setDiscardableAttrs(storeOp->getDiscardableAttrDictionary());
627 return success();
628 }
629};
630
631//===----------------------------------------------------------------------===//
632// Pass
633//===----------------------------------------------------------------------===//
634
635namespace {
636class UnifyAliasedResourcePass final
637 : public spirv::impl::SPIRVUnifyAliasedResourcePassBase<
638 UnifyAliasedResourcePass> {
639public:
640 explicit UnifyAliasedResourcePass(spirv::GetTargetEnvFn getTargetEnv)
641 : getTargetEnvFn(std::move(getTargetEnv)) {}
642
643 void runOnOperation() override;
644
645private:
646 spirv::GetTargetEnvFn getTargetEnvFn;
647};
648
649void UnifyAliasedResourcePass::runOnOperation() {
650 spirv::ModuleOp moduleOp = getOperation();
651 MLIRContext *context = &getContext();
652
653 if (getTargetEnvFn) {
654 // This pass is only needed for targeting WebGPU, Metal, or layering
655 // Vulkan on Metal via MoltenVK, where we need to translate SPIR-V into
656 // WGSL or MSL. The translation has limitations.
657 spirv::TargetEnvAttr targetEnv = getTargetEnvFn(moduleOp);
658 spirv::ClientAPI clientAPI = targetEnv.getClientAPI();
659 bool isVulkanOnAppleDevices =
660 clientAPI == spirv::ClientAPI::Vulkan &&
661 targetEnv.getVendorID() == spirv::Vendor::Apple;
662 if (clientAPI != spirv::ClientAPI::WebGPU &&
663 clientAPI != spirv::ClientAPI::Metal && !isVulkanOnAppleDevices)
664 return;
665 }
666
667 // Analyze aliased resources first.
668 ResourceAliasAnalysis &analysis = getAnalysis<ResourceAliasAnalysis>();
669
670 ConversionTarget target(*context);
671 target.addDynamicallyLegalOp<spirv::GlobalVariableOp, spirv::AddressOfOp,
672 spirv::AccessChainOp, spirv::LoadOp,
673 spirv::StoreOp>(
674 [&analysis](Operation *op) { return !analysis.shouldUnify(op); });
675 target.addLegalDialect<spirv::SPIRVDialect>();
676
677 // Run patterns to rewrite usages of non-canonical resources.
678 RewritePatternSet patterns(context);
679 patterns.add<ConvertVariable, ConvertAddressOf, ConvertAccessChain,
680 ConvertLoad, ConvertStore>(analysis, context);
681 if (failed(applyPartialConversion(moduleOp, target, std::move(patterns))))
682 return signalPassFailure();
683
684 // Drop aliased attribute if we only have one single bound resource for a
685 // descriptor. We need to re-collect the map here given in the above the
686 // conversion is best effort; certain sets may not be converted.
687 AliasedResourceMap resourceMap =
688 collectAliasedResources(cast<spirv::ModuleOp>(moduleOp));
689 for (const auto &dr : resourceMap) {
690 const auto &resources = dr.second;
691 if (resources.size() == 1)
692 resources.front()->removeDiscardableAttr("aliased");
693 }
694}
695} // namespace
696
697std::unique_ptr<mlir::OperationPass<spirv::ModuleOp>>
699 return std::make_unique<UnifyAliasedResourcePass>(std::move(getTargetEnv));
700}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
b getContext())
static Type getElementType(Type type, ArrayRef< int32_t > indices, function_ref< InFlightDiagnostic(StringRef)> emitErrorFn)
Walks the given type hierarchy with the given indices, potentially down to component granularity,...
Definition SPIRVOps.cpp:229
#define MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CLASS_NAME)
Definition TypeID.h:331
static std::optional< int > deduceCanonicalResource(ArrayRef< spirv::SPIRVType > types)
Given a list of resource element types, returns the index of the canonical resource that all resource...
DenseMap< Descriptor, SmallVector< spirv::GlobalVariableOp > > AliasedResourceMap
static AliasedResourceMap collectAliasedResources(spirv::ModuleOp moduleOp)
Collects all aliased resources in the given SPIR-V moduleOp.
static Type getRuntimeArrayElementType(Type type)
Returns the element type if the given type is a runtime array resource: !spirv.ptr<!...
static bool areSameBitwidthScalarType(Type a, Type b)
std::pair< uint32_t, uint32_t > Descriptor
ConvertAliasResource(const ResourceAliasAnalysis &analysis, MLIRContext *context, PatternBenefit benefit=1)
const ResourceAliasAnalysis & analysis
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:71
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
static Operation * lookupSymbolIn(Operation *op, StringAttr symbol)
Returns the operation registered with the given symbol name with the regions of 'symbolTableOp'.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
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
std::optional< int64_t > getSizeInBytes()
Returns the size in bytes for each type.
Vendor getVendorID() const
Returns the vendor ID.
ClientAPI getClientAPI() const
Returns the client API.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:853
detail::InFlightRemark analysis(Location loc, RemarkOpts opts)
Report an optimization analysis remark.
Definition Remarks.h:859
std::unique_ptr< OperationPass< spirv::ModuleOp > > createUnifyAliasedResourcePass(GetTargetEnvFn getTargetEnv=nullptr)
std::function< spirv::TargetEnvAttr(spirv::ModuleOp)> GetTargetEnvFn
Creates an operation pass that unifies access of multiple aliased resources into access of one single...
Definition Passes.h:36
Include the generated interface declarations.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:123
ConvertAliasResource(const ResourceAliasAnalysis &analysis, MLIRContext *context, PatternBenefit benefit=1)
LogicalResult matchAndRewrite(spirv::AccessChainOp acOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ConvertAliasResource(const ResourceAliasAnalysis &analysis, MLIRContext *context, PatternBenefit benefit=1)
LogicalResult matchAndRewrite(spirv::AddressOfOp addressOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ConvertAliasResource(const ResourceAliasAnalysis &analysis, MLIRContext *context, PatternBenefit benefit=1)
LogicalResult matchAndRewrite(spirv::LoadOp loadOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ConvertAliasResource(const ResourceAliasAnalysis &analysis, MLIRContext *context, PatternBenefit benefit=1)
LogicalResult matchAndRewrite(spirv::StoreOp storeOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ConvertAliasResource(const ResourceAliasAnalysis &analysis, MLIRContext *context, PatternBenefit benefit=1)
LogicalResult matchAndRewrite(spirv::GlobalVariableOp varOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.