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