626 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// This file implements a pass that unifies access of multiple aliased resources10// into access of one single resource.11//12//===----------------------------------------------------------------------===//13 14#include "mlir/Dialect/SPIRV/Transforms/Passes.h"15 16#include "mlir/Dialect/SPIRV/IR/SPIRVDialect.h"17#include "mlir/Dialect/SPIRV/IR/SPIRVOps.h"18#include "mlir/Dialect/SPIRV/IR/SPIRVTypes.h"19#include "mlir/Dialect/SPIRV/IR/TargetAndABI.h"20#include "mlir/IR/Builders.h"21#include "mlir/IR/BuiltinAttributes.h"22#include "mlir/IR/BuiltinTypes.h"23#include "mlir/IR/SymbolTable.h"24#include "mlir/Transforms/DialectConversion.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_SPIRVUNIFYALIASEDRESOURCEPASS32#include "mlir/Dialect/SPIRV/Transforms/Passes.h.inc"33} // namespace spirv34} // namespace mlir35 36using namespace mlir;37 38//===----------------------------------------------------------------------===//39// Utility functions40//===----------------------------------------------------------------------===//41 42using Descriptor = std::pair<uint32_t, uint32_t>; // (set #, binding #)43using AliasedResourceMap =44 DenseMap<Descriptor, SmallVector<spirv::GlobalVariableOp>>;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 type62/// otherwise.63static Type getRuntimeArrayElementType(Type type) {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 canonical81/// resource that all resources should be unified into. Returns std::nullopt if82/// unable to unify.83static std::optional<int>84deduceCanonicalResource(ArrayRef<spirv::SPIRVType> types) {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 layout99 // 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 updates117 // 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 chosen126 // 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 the137 // 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 145static bool areSameBitwidthScalarType(Type a, Type b) {146 return a.isIntOrFloat() && b.isIntOrFloat() &&147 a.getIntOrFloatBitWidth() == b.getIntOrFloatBitWidth();148}149 150//===----------------------------------------------------------------------===//151// Analysis152//===----------------------------------------------------------------------===//153 154namespace {155/// A class for analyzing aliased resources.156///157/// Resources are expected to be spirv.GlobalVarible that has a descriptor set158/// and binding number. Such resources are of the type159/// `!spirv.ptr<!spirv.struct<...>>` per Vulkan requirements.160///161/// Right now, we only support the case that there is a single runtime array162/// inside the struct.163class ResourceAliasAnalysis {164public:165 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ResourceAliasAnalysis)166 167 explicit ResourceAliasAnalysis(Operation *);168 169 /// Returns true if the given `op` can be rewritten to use a canonical170 /// 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::GlobalVariableOp178 getCanonicalResource(const Descriptor &descriptor) const;179 spirv::GlobalVariableOp180 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 we187 /// 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.195 DenseMap<Descriptor, spirv::GlobalVariableOp> canonicalResourceMap;196 197 /// Mapping from an aliased resource to its descriptor.198 DenseMap<spirv::GlobalVariableOp, Descriptor> descriptorMap;199 200 /// Mapping from an aliased resource to its element (scalar/vector) type.201 DenseMap<spirv::GlobalVariableOp, spirv::SPIRVType> elementTypeMap;202};203} // namespace204 205ResourceAliasAnalysis::ResourceAliasAnalysis(Operation *root) {206 // Collect all aliased resources first and put them into different sets207 // 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 identify212 // 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::SPIRVType260ResourceAliasAnalysis::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// Patterns298//===----------------------------------------------------------------------===//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> {312 using ConvertAliasResource::ConvertAliasResource;313 314 LogicalResult315 matchAndRewrite(spirv::GlobalVariableOp varOp, OpAdaptor adaptor,316 ConversionPatternRewriter &rewriter) const override {317 // Just remove the aliased resource. Users will be rewritten to use the318 // canonical one.319 rewriter.eraseOp(varOp);320 return success();321 }322};323 324struct ConvertAddressOf : public ConvertAliasResource<spirv::AddressOfOp> {325 using ConvertAliasResource::ConvertAliasResource;326 327 LogicalResult328 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> {341 using ConvertAliasResource::ConvertAliasResource;342 343 LogicalResult344 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. Rewrite371 // them into a buffer with vector element types. We need to scale the last372 // index for the vector as a whole, then add one level of index for inside373 // the vector.374 int srcNumBytes = *srcElemType.getSizeInBytes();375 int dstNumBytes = *dstElemType.getSizeInBytes();376 assert(dstNumBytes >= srcNumBytes && dstNumBytes % srcNumBytes == 0);377 378 auto indices = llvm::to_vector<4>(acOp.getIndices());379 Value oldIndex = indices.back();380 Type indexType = oldIndex.getType();381 382 int ratio = dstNumBytes / srcNumBytes;383 auto ratioValue = spirv::ConstantOp::create(384 rewriter, loc, indexType, rewriter.getIntegerAttr(indexType, ratio));385 386 indices.back() =387 spirv::SDivOp::create(rewriter, loc, indexType, oldIndex, ratioValue);388 indices.push_back(spirv::SModOp::create(rewriter, loc, indexType,389 oldIndex, ratioValue));390 391 rewriter.replaceOpWithNewOp<spirv::AccessChainOp>(392 acOp, adaptor.getBasePtr(), indices);393 return success();394 }395 396 if ((srcElemType.isIntOrFloat() && dstElemType.isIntOrFloat()) ||397 (isa<VectorType>(srcElemType) && isa<VectorType>(dstElemType))) {398 // The source indices are for a buffer with larger bitwidth scalar/vector399 // element types. Rewrite them into a buffer with smaller bitwidth element400 // types. We only need to scale the last index.401 int srcNumBytes = *srcElemType.getSizeInBytes();402 int dstNumBytes = *dstElemType.getSizeInBytes();403 assert(srcNumBytes >= dstNumBytes && srcNumBytes % dstNumBytes == 0);404 405 auto indices = llvm::to_vector<4>(acOp.getIndices());406 Value oldIndex = indices.back();407 Type indexType = oldIndex.getType();408 409 int ratio = srcNumBytes / dstNumBytes;410 auto ratioValue = spirv::ConstantOp::create(411 rewriter, loc, indexType, rewriter.getIntegerAttr(indexType, ratio));412 413 indices.back() =414 spirv::IMulOp::create(rewriter, loc, indexType, oldIndex, ratioValue);415 416 rewriter.replaceOpWithNewOp<spirv::AccessChainOp>(417 acOp, adaptor.getBasePtr(), indices);418 return success();419 }420 421 return rewriter.notifyMatchFailure(422 acOp, "unsupported src/dst types for spirv.AccessChain");423 }424};425 426struct ConvertLoad : public ConvertAliasResource<spirv::LoadOp> {427 using ConvertAliasResource::ConvertAliasResource;428 429 LogicalResult430 matchAndRewrite(spirv::LoadOp loadOp, OpAdaptor adaptor,431 ConversionPatternRewriter &rewriter) const override {432 auto srcPtrType = cast<spirv::PointerType>(loadOp.getPtr().getType());433 auto srcElemType = cast<spirv::SPIRVType>(srcPtrType.getPointeeType());434 auto dstPtrType = cast<spirv::PointerType>(adaptor.getPtr().getType());435 auto dstElemType = cast<spirv::SPIRVType>(dstPtrType.getPointeeType());436 437 Location loc = loadOp.getLoc();438 auto newLoadOp = spirv::LoadOp::create(rewriter, loc, adaptor.getPtr());439 if (srcElemType == dstElemType) {440 rewriter.replaceOp(loadOp, newLoadOp->getResults());441 return success();442 }443 444 if (areSameBitwidthScalarType(srcElemType, dstElemType)) {445 auto castOp = spirv::BitcastOp::create(rewriter, loc, srcElemType,446 newLoadOp.getValue());447 rewriter.replaceOp(loadOp, castOp->getResults());448 449 return success();450 }451 452 if ((srcElemType.isIntOrFloat() && dstElemType.isIntOrFloat()) ||453 (isa<VectorType>(srcElemType) && isa<VectorType>(dstElemType))) {454 // The source and destination have scalar types of different bitwidths, or455 // vector types of different component counts. For such cases, we load456 // multiple smaller bitwidth values and construct a larger bitwidth one.457 458 int srcNumBytes = *srcElemType.getSizeInBytes();459 int dstNumBytes = *dstElemType.getSizeInBytes();460 assert(srcNumBytes > dstNumBytes && srcNumBytes % dstNumBytes == 0);461 int ratio = srcNumBytes / dstNumBytes;462 if (ratio > 4)463 return rewriter.notifyMatchFailure(loadOp, "more than 4 components");464 465 SmallVector<Value> components;466 components.reserve(ratio);467 components.push_back(newLoadOp);468 469 auto acOp = adaptor.getPtr().getDefiningOp<spirv::AccessChainOp>();470 if (!acOp)471 return rewriter.notifyMatchFailure(loadOp, "ptr not spirv.AccessChain");472 473 auto i32Type = rewriter.getI32Type();474 Value oneValue = spirv::ConstantOp::getOne(i32Type, loc, rewriter);475 auto indices = llvm::to_vector<4>(acOp.getIndices());476 for (int i = 1; i < ratio; ++i) {477 // Load all subsequent components belonging to this element.478 indices.back() = spirv::IAddOp::create(rewriter, loc, i32Type,479 indices.back(), oneValue);480 auto componentAcOp = spirv::AccessChainOp::create(481 rewriter, loc, acOp.getBasePtr(), indices);482 // Assuming little endian, this reads lower-ordered bits of the number483 // to lower-numbered components of the vector.484 components.push_back(485 spirv::LoadOp::create(rewriter, loc, componentAcOp));486 }487 488 // Create a vector of the components and then cast back to the larger489 // bitwidth element type. For spirv.bitcast, the lower-numbered components490 // of the vector map to lower-ordered bits of the larger bitwidth element491 // type.492 493 Type vectorType = srcElemType;494 if (!isa<VectorType>(srcElemType))495 vectorType = VectorType::get({ratio}, dstElemType);496 497 // If both the source and destination are vector types, we need to make498 // sure the scalar type is the same for composite construction later.499 if (auto srcElemVecType = dyn_cast<VectorType>(srcElemType))500 if (auto dstElemVecType = dyn_cast<VectorType>(dstElemType)) {501 if (srcElemVecType.getElementType() !=502 dstElemVecType.getElementType()) {503 int64_t count =504 dstNumBytes / (srcElemVecType.getElementTypeBitWidth() / 8);505 506 // Make sure not to create 1-element vectors, which are illegal in507 // SPIR-V.508 Type castType = srcElemVecType.getElementType();509 if (count > 1)510 castType = VectorType::get({count}, castType);511 512 for (Value &c : components)513 c = spirv::BitcastOp::create(rewriter, loc, castType, c);514 }515 }516 Value vectorValue = spirv::CompositeConstructOp::create(517 rewriter, loc, vectorType, components);518 519 if (!isa<VectorType>(srcElemType))520 vectorValue =521 spirv::BitcastOp::create(rewriter, loc, srcElemType, vectorValue);522 rewriter.replaceOp(loadOp, vectorValue);523 return success();524 }525 526 return rewriter.notifyMatchFailure(527 loadOp, "unsupported src/dst types for spirv.Load");528 }529};530 531struct ConvertStore : public ConvertAliasResource<spirv::StoreOp> {532 using ConvertAliasResource::ConvertAliasResource;533 534 LogicalResult535 matchAndRewrite(spirv::StoreOp storeOp, OpAdaptor adaptor,536 ConversionPatternRewriter &rewriter) const override {537 auto srcElemType =538 cast<spirv::PointerType>(storeOp.getPtr().getType()).getPointeeType();539 auto dstElemType =540 cast<spirv::PointerType>(adaptor.getPtr().getType()).getPointeeType();541 if (!srcElemType.isIntOrFloat() || !dstElemType.isIntOrFloat())542 return rewriter.notifyMatchFailure(storeOp, "not scalar type");543 if (!areSameBitwidthScalarType(srcElemType, dstElemType))544 return rewriter.notifyMatchFailure(storeOp, "different bitwidth");545 546 Location loc = storeOp.getLoc();547 Value value = adaptor.getValue();548 if (srcElemType != dstElemType)549 value = spirv::BitcastOp::create(rewriter, loc, dstElemType, value);550 rewriter.replaceOpWithNewOp<spirv::StoreOp>(storeOp, adaptor.getPtr(),551 value, storeOp->getAttrs());552 return success();553 }554};555 556//===----------------------------------------------------------------------===//557// Pass558//===----------------------------------------------------------------------===//559 560namespace {561class UnifyAliasedResourcePass final562 : public spirv::impl::SPIRVUnifyAliasedResourcePassBase<563 UnifyAliasedResourcePass> {564public:565 explicit UnifyAliasedResourcePass(spirv::GetTargetEnvFn getTargetEnv)566 : getTargetEnvFn(std::move(getTargetEnv)) {}567 568 void runOnOperation() override;569 570private:571 spirv::GetTargetEnvFn getTargetEnvFn;572};573 574void UnifyAliasedResourcePass::runOnOperation() {575 spirv::ModuleOp moduleOp = getOperation();576 MLIRContext *context = &getContext();577 578 if (getTargetEnvFn) {579 // This pass is only needed for targeting WebGPU, Metal, or layering580 // Vulkan on Metal via MoltenVK, where we need to translate SPIR-V into581 // WGSL or MSL. The translation has limitations.582 spirv::TargetEnvAttr targetEnv = getTargetEnvFn(moduleOp);583 spirv::ClientAPI clientAPI = targetEnv.getClientAPI();584 bool isVulkanOnAppleDevices =585 clientAPI == spirv::ClientAPI::Vulkan &&586 targetEnv.getVendorID() == spirv::Vendor::Apple;587 if (clientAPI != spirv::ClientAPI::WebGPU &&588 clientAPI != spirv::ClientAPI::Metal && !isVulkanOnAppleDevices)589 return;590 }591 592 // Analyze aliased resources first.593 ResourceAliasAnalysis &analysis = getAnalysis<ResourceAliasAnalysis>();594 595 ConversionTarget target(*context);596 target.addDynamicallyLegalOp<spirv::GlobalVariableOp, spirv::AddressOfOp,597 spirv::AccessChainOp, spirv::LoadOp,598 spirv::StoreOp>(599 [&analysis](Operation *op) { return !analysis.shouldUnify(op); });600 target.addLegalDialect<spirv::SPIRVDialect>();601 602 // Run patterns to rewrite usages of non-canonical resources.603 RewritePatternSet patterns(context);604 patterns.add<ConvertVariable, ConvertAddressOf, ConvertAccessChain,605 ConvertLoad, ConvertStore>(analysis, context);606 if (failed(applyPartialConversion(moduleOp, target, std::move(patterns))))607 return signalPassFailure();608 609 // Drop aliased attribute if we only have one single bound resource for a610 // descriptor. We need to re-collect the map here given in the above the611 // conversion is best effort; certain sets may not be converted.612 AliasedResourceMap resourceMap =613 collectAliasedResources(cast<spirv::ModuleOp>(moduleOp));614 for (const auto &dr : resourceMap) {615 const auto &resources = dr.second;616 if (resources.size() == 1)617 resources.front()->removeAttr("aliased");618 }619}620} // namespace621 622std::unique_ptr<mlir::OperationPass<spirv::ModuleOp>>623spirv::createUnifyAliasedResourcePass(spirv::GetTargetEnvFn getTargetEnv) {624 return std::make_unique<UnifyAliasedResourcePass>(std::move(getTargetEnv));625}626