25#include "llvm/ADT/DenseMap.h"
26#include "llvm/ADT/STLExtras.h"
27#include "llvm/Support/Alignment.h"
32#define GEN_PASS_DEF_SPIRVUNIFYALIASEDRESOURCEPASS
33#include "mlir/Dialect/SPIRV/Transforms/Passes.h.inc"
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();
55 aliasedResources[{*set, *binding}].push_back(varOp);
58 return aliasedResources;
65 auto ptrType = dyn_cast<spirv::PointerType>(type);
69 auto structType = dyn_cast<spirv::StructType>(ptrType.getPointeeType());
70 if (!structType || structType.getNumElements() != 1)
74 dyn_cast<spirv::RuntimeArrayType>(structType.getElementType(0));
78 return rtArrayType.getElementType();
84static std::optional<int>
90 scalarNumBits.reserve(types.size());
91 vectorNumBits.reserve(types.size());
92 vectorIndices.reserve(types.size());
94 for (
const auto &indexedTypes : llvm::enumerate(types)) {
97 if (
auto vectorType = dyn_cast<VectorType>(type)) {
98 if (vectorType.getNumElements() % 2 != 0)
106 scalarNumBits.push_back(
107 vectorType.getElementType().getIntOrFloatBitWidth());
108 vectorNumBits.push_back(*numBytes * 8);
109 vectorIndices.push_back(indexedTypes.index());
115 if (!vectorNumBits.empty()) {
119 auto *minVal = llvm::min_element(vectorNumBits);
122 if (llvm::any_of(vectorNumBits,
123 [&](
int bits) {
return bits % *minVal != 0; }))
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; }))
139 auto *minVal = llvm::min_element(scalarNumBits);
140 if (llvm::any_of(scalarNumBits,
141 [minVal](
int64_t bit) {
return bit % *minVal != 0; }))
143 return std::distance(scalarNumBits.begin(), minVal);
164class ResourceAliasAnalysis {
168 explicit ResourceAliasAnalysis(Operation *);
172 bool shouldUnify(Operation *op)
const;
178 spirv::GlobalVariableOp
179 getCanonicalResource(
const Descriptor &descriptor)
const;
180 spirv::GlobalVariableOp
181 getCanonicalResource(spirv::GlobalVariableOp varOp)
const;
184 spirv::SPIRVType
getElementType(spirv::GlobalVariableOp varOp)
const;
189 void recordIfUnifiable(
const Descriptor &descriptor,
190 ArrayRef<spirv::GlobalVariableOp> resources);
206ResourceAliasAnalysis::ResourceAliasAnalysis(
Operation *root) {
214 for (
const auto &descriptorResource : aliasedResources) {
215 recordIfUnifiable(descriptorResource.first, descriptorResource.second);
219bool ResourceAliasAnalysis::shouldUnify(Operation *op)
const {
223 if (
auto varOp = dyn_cast<spirv::GlobalVariableOp>(op)) {
224 auto canonicalOp = getCanonicalResource(varOp);
225 return canonicalOp && varOp != canonicalOp;
227 if (
auto addressOp = dyn_cast<spirv::AddressOfOp>(op)) {
228 auto moduleOp = addressOp->getParentOfType<spirv::ModuleOp>();
231 return shouldUnify(varOp);
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());
244spirv::GlobalVariableOp ResourceAliasAnalysis::getCanonicalResource(
246 auto varIt = canonicalResourceMap.find(descriptor);
247 if (varIt == canonicalResourceMap.end())
249 return varIt->second;
252spirv::GlobalVariableOp ResourceAliasAnalysis::getCanonicalResource(
253 spirv::GlobalVariableOp varOp)
const {
254 auto descriptorIt = descriptorMap.find(varOp);
255 if (descriptorIt == descriptorMap.end())
257 return getCanonicalResource(descriptorIt->second);
261ResourceAliasAnalysis::getElementType(spirv::GlobalVariableOp varOp)
const {
262 auto it = elementTypeMap.find(varOp);
263 if (it == elementTypeMap.end())
268void ResourceAliasAnalysis::recordIfUnifiable(
269 const Descriptor &descriptor, ArrayRef<spirv::GlobalVariableOp> resources) {
271 SmallVector<spirv::SPIRVType> elementTypes;
272 for (spirv::GlobalVariableOp resource : resources) {
277 auto type = cast<spirv::SPIRVType>(elementType);
278 if (!type.isScalarOrVector())
281 elementTypes.push_back(type);
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()];
301template <
typename OpTy>
317 ConversionPatternRewriter &rewriter)
const override {
320 rewriter.eraseOp(varOp);
330 ConversionPatternRewriter &rewriter)
const override {
332 auto moduleOp = addressOp->getParentOfType<spirv::ModuleOp>();
333 auto srcVarOp = cast<spirv::GlobalVariableOp>(
335 auto dstVarOp =
analysis.getCanonicalResource(srcVarOp);
336 rewriter.replaceOpWithNewOp<spirv::AddressOfOp>(addressOp, dstVarOp);
346 ConversionPatternRewriter &rewriter)
const override {
347 auto addressOp = acOp.getBasePtr().getDefiningOp<spirv::AddressOfOp>();
349 return rewriter.notifyMatchFailure(acOp,
"base ptr not addressof op");
351 auto moduleOp = acOp->getParentOfType<spirv::ModuleOp>();
352 auto srcVarOp = cast<spirv::GlobalVariableOp>(
354 auto dstVarOp =
analysis.getCanonicalResource(srcVarOp);
359 if (srcElemType == dstElemType ||
363 rewriter.replaceOpWithNewOp<spirv::AccessChainOp>(
364 acOp, adaptor.getBasePtr(), adaptor.getIndices());
370 if (srcElemType.
isIntOrFloat() && isa<VectorType>(dstElemType)) {
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);
383 auto indices = llvm::to_vector<4>(acOp.getIndices());
387 int ratio = dstNumBytes / srcNumBytes;
388 auto ratioValue = spirv::ConstantOp::create(
389 rewriter, loc, indexType, rewriter.getIntegerAttr(indexType, ratio));
392 spirv::SDivOp::create(rewriter, loc, indexType, oldIndex, ratioValue);
393 indices.push_back(spirv::SModOp::create(rewriter, loc, indexType,
394 oldIndex, ratioValue));
396 rewriter.replaceOpWithNewOp<spirv::AccessChainOp>(
397 acOp, adaptor.getBasePtr(),
indices);
402 (isa<VectorType>(srcElemType) && isa<VectorType>(dstElemType))) {
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);
414 auto indices = llvm::to_vector<4>(acOp.getIndices());
418 int ratio = srcNumBytes / dstNumBytes;
419 auto ratioValue = spirv::ConstantOp::create(
420 rewriter, loc, indexType, rewriter.getIntegerAttr(indexType, ratio));
423 spirv::IMulOp::create(rewriter, loc, indexType, oldIndex, ratioValue);
425 rewriter.replaceOpWithNewOp<spirv::AccessChainOp>(
426 acOp, adaptor.getBasePtr(),
indices);
430 return rewriter.notifyMatchFailure(
431 acOp,
"unsupported src/dst types for spirv.AccessChain");
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());
447 auto newLoadOp = spirv::LoadOp::create(rewriter, loc, adaptor.getPtr());
448 if (srcElemType == dstElemType) {
449 rewriter.replaceOp(loadOp, newLoadOp->getResults());
454 auto castOp = spirv::BitcastOp::create(rewriter, loc, srcElemType,
455 newLoadOp.getValue());
456 rewriter.replaceOp(loadOp, castOp->getResults());
462 (isa<VectorType>(srcElemType) && isa<VectorType>(dstElemType))) {
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;
476 return rewriter.notifyMatchFailure(loadOp,
"more than 4 components");
479 components.reserve(ratio);
480 components.push_back(newLoadOp);
482 auto acOp = adaptor.getPtr().getDefiningOp<spirv::AccessChainOp>();
484 return rewriter.notifyMatchFailure(loadOp,
"ptr not spirv.AccessChain");
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) {
491 indices.back() = spirv::IAddOp::create(rewriter, loc, i32Type,
493 auto componentAcOp = spirv::AccessChainOp::create(
494 rewriter, loc, acOp.getBasePtr(),
indices);
497 components.push_back(
498 spirv::LoadOp::create(rewriter, loc, componentAcOp));
506 Type vectorType = srcElemType;
507 if (!isa<VectorType>(srcElemType))
508 vectorType = VectorType::get({ratio}, dstElemType);
512 if (
auto srcElemVecType = dyn_cast<VectorType>(srcElemType))
513 if (
auto dstElemVecType = dyn_cast<VectorType>(dstElemType)) {
514 if (srcElemVecType.getElementType() !=
515 dstElemVecType.getElementType()) {
517 dstNumBytes / (srcElemVecType.getElementTypeBitWidth() / 8);
521 Type castType = srcElemVecType.getElementType();
523 castType = VectorType::get({count}, castType);
525 for (
Value &c : components)
526 c = spirv::BitcastOp::create(rewriter, loc, castType, c);
529 Value vectorValue = spirv::CompositeConstructOp::create(
530 rewriter, loc, vectorType, components);
532 if (!isa<VectorType>(srcElemType))
534 spirv::BitcastOp::create(rewriter, loc, srcElemType, vectorValue);
535 rewriter.replaceOp(loadOp, vectorValue);
539 return rewriter.notifyMatchFailure(
540 loadOp,
"unsupported src/dst types for spirv.Load");
549 ConversionPatternRewriter &rewriter)
const override {
551 cast<spirv::PointerType>(storeOp.getPtr().getType()).getPointeeType();
553 cast<spirv::PointerType>(adaptor.getPtr().getType()).getPointeeType();
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>();
566 return rewriter.notifyMatchFailure(storeOp,
567 "ptr not spirv.AccessChain");
568 if (srcVector.getElementType() != dstVector.getElementType())
569 value = spirv::BitcastOp::create(
571 VectorType::get(srcVector.getShape(), dstVector.getElementType()),
573 unsigned count = dstVector.getNumElements();
574 unsigned ratio = srcVector.getNumElements() / count;
575 unsigned bytes = count * dstVector.getElementTypeBitWidth() / 8;
580 auto getPart = [&](
unsigned i) ->
Value {
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));
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);
598 auto offset = spirv::ConstantOp::create(
599 rewriter, loc, indexType, rewriter.getIntegerAttr(indexType, i));
600 indices.back() = spirv::IAddOp::create(rewriter, loc, indexType,
602 Value ptr = spirv::AccessChainOp::create(rewriter, loc,
604 IntegerAttr alignment = storeOp.getAlignmentAttr();
606 alignment = rewriter.getI32IntegerAttr(
607 llvm::commonAlignment(llvm::Align(alignment.getInt()), i * bytes)
609 auto store = spirv::StoreOp::create(
610 rewriter, loc,
ptr, part, storeOp.getMemoryAccessAttr(), alignment);
611 store->setDiscardableAttrs(storeOp->getDiscardableAttrDictionary());
613 rewriter.eraseOp(storeOp);
616 if (srcElemType != dstElemType) {
618 return rewriter.notifyMatchFailure(storeOp,
"not scalar type");
620 return rewriter.notifyMatchFailure(storeOp,
"different bitwidth");
621 value = spirv::BitcastOp::create(rewriter, loc, dstElemType, value);
623 auto newStore = rewriter.replaceOpWithNewOp<spirv::StoreOp>(
624 storeOp, adaptor.getPtr(), value, storeOp.getMemoryAccessAttr(),
625 storeOp.getAlignmentAttr());
626 newStore->setDiscardableAttrs(storeOp->getDiscardableAttrDictionary());
636class UnifyAliasedResourcePass final
637 :
public spirv::impl::SPIRVUnifyAliasedResourcePassBase<
638 UnifyAliasedResourcePass> {
641 : getTargetEnvFn(std::move(getTargetEnv)) {}
643 void runOnOperation()
override;
649void UnifyAliasedResourcePass::runOnOperation() {
650 spirv::ModuleOp moduleOp = getOperation();
653 if (getTargetEnvFn) {
657 spirv::TargetEnvAttr targetEnv = getTargetEnvFn(moduleOp);
659 bool isVulkanOnAppleDevices =
660 clientAPI == spirv::ClientAPI::Vulkan &&
662 if (clientAPI != spirv::ClientAPI::WebGPU &&
663 clientAPI != spirv::ClientAPI::Metal && !isVulkanOnAppleDevices)
668 ResourceAliasAnalysis &
analysis = getAnalysis<ResourceAliasAnalysis>();
670 ConversionTarget
target(*context);
671 target.addDynamicallyLegalOp<spirv::GlobalVariableOp, spirv::AddressOfOp,
672 spirv::AccessChainOp, spirv::LoadOp,
675 target.addLegalDialect<spirv::SPIRVDialect>();
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();
689 for (
const auto &dr : resourceMap) {
690 const auto &resources = dr.second;
691 if (resources.size() == 1)
692 resources.front()->removeDiscardableAttr(
"aliased");
697std::unique_ptr<mlir::OperationPass<spirv::ModuleOp>>
699 return std::make_unique<UnifyAliasedResourcePass>(std::move(getTargetEnv));
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,...
#define MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CLASS_NAME)
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...
MLIRContext is the top-level object for a collection of MLIR operations.
Operation is the basic unit of execution within MLIR.
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...
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
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.
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...
Include the generated interface declarations.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
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.