#include <utility>
#include "mlir/Dialect/SPIRV/IR/SPIRVOps.h"
#include "mlir/Dialect/CommonFolders.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVDialect.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVTypes.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/PatternMatch.h"
using namespace mlir;
static Optional<bool> getScalarOrSplatBoolAttr(Attribute boolAttr) {
if (!boolAttr)
return llvm::None;
auto type = boolAttr.getType();
if (type.isInteger(1)) {
auto attr = boolAttr.cast<BoolAttr>();
return attr.getValue();
}
if (auto vecType = type.cast<VectorType>()) {
if (vecType.getElementType().isInteger(1))
if (auto attr = boolAttr.dyn_cast<SplatElementsAttr>())
return attr.getSplatValue<bool>();
}
return llvm::None;
}
static Attribute extractCompositeElement(Attribute composite,
ArrayRef<unsigned> indices) {
if (!composite)
return {};
if (indices.empty())
return composite;
if (auto vector = composite.dyn_cast<ElementsAttr>()) {
assert(indices.size() == 1 && "must have exactly one index for a vector");
return vector.getValues<Attribute>()[indices[0]];
}
if (auto array = composite.dyn_cast<ArrayAttr>()) {
assert(!indices.empty() && "must have at least one index for an array");
return extractCompositeElement(array.getValue()[indices[0]],
indices.drop_front());
}
return {};
}
namespace {
#include "SPIRVCanonicalization.inc"
}
namespace {
struct CombineChainedAccessChain
: public OpRewritePattern<spirv::AccessChainOp> {
using OpRewritePattern<spirv::AccessChainOp>::OpRewritePattern;
LogicalResult matchAndRewrite(spirv::AccessChainOp accessChainOp,
PatternRewriter &rewriter) const override {
auto parentAccessChainOp = dyn_cast_or_null<spirv::AccessChainOp>(
accessChainOp.base_ptr().getDefiningOp());
if (!parentAccessChainOp) {
return failure();
}
SmallVector<Value, 4> indices(parentAccessChainOp.indices());
indices.append(accessChainOp.indices().begin(),
accessChainOp.indices().end());
rewriter.replaceOpWithNewOp<spirv::AccessChainOp>(
accessChainOp, parentAccessChainOp.base_ptr(), indices);
return success();
}
};
}
void spirv::AccessChainOp::getCanonicalizationPatterns(
RewritePatternSet &results, MLIRContext *context) {
results.add<CombineChainedAccessChain>(context);
}
void spirv::BitcastOp::getCanonicalizationPatterns(RewritePatternSet &results,
MLIRContext *context) {
results.add<ConvertChainedBitcast>(context);
}
OpFoldResult spirv::CompositeExtractOp::fold(ArrayRef<Attribute> operands) {
assert(operands.size() == 1 && "spv.CompositeExtract expects one operand");
auto indexVector =
llvm::to_vector<8>(llvm::map_range(indices(), [](Attribute attr) {
return static_cast<unsigned>(attr.cast<IntegerAttr>().getInt());
}));
return extractCompositeElement(operands[0], indexVector);
}
OpFoldResult spirv::ConstantOp::fold(ArrayRef<Attribute> operands) {
assert(operands.empty() && "spv.Constant has no operands");
return value();
}
OpFoldResult spirv::IAddOp::fold(ArrayRef<Attribute> operands) {
assert(operands.size() == 2 && "spv.IAdd expects two operands");
if (matchPattern(operand2(), m_Zero()))
return operand1();
return constFoldBinaryOp<IntegerAttr>(
operands, [](APInt a, const APInt &b) { return std::move(a) + b; });
}
OpFoldResult spirv::IMulOp::fold(ArrayRef<Attribute> operands) {
assert(operands.size() == 2 && "spv.IMul expects two operands");
if (matchPattern(operand2(), m_Zero()))
return operand2();
if (matchPattern(operand2(), m_One()))
return operand1();
return constFoldBinaryOp<IntegerAttr>(
operands, [](const APInt &a, const APInt &b) { return a * b; });
}
OpFoldResult spirv::ISubOp::fold(ArrayRef<Attribute> operands) {
if (operand1() == operand2())
return Builder(getContext()).getIntegerAttr(getType(), 0);
return constFoldBinaryOp<IntegerAttr>(
operands, [](APInt a, const APInt &b) { return std::move(a) - b; });
}
OpFoldResult spirv::LogicalAndOp::fold(ArrayRef<Attribute> operands) {
assert(operands.size() == 2 && "spv.LogicalAnd should take two operands");
if (Optional<bool> rhs = getScalarOrSplatBoolAttr(operands.back())) {
if (rhs.value())
return operand1();
if (!rhs.value())
return operands.back();
}
return Attribute();
}
void spirv::LogicalNotOp::getCanonicalizationPatterns(
RewritePatternSet &results, MLIRContext *context) {
results
.add<ConvertLogicalNotOfIEqual, ConvertLogicalNotOfINotEqual,
ConvertLogicalNotOfLogicalEqual, ConvertLogicalNotOfLogicalNotEqual>(
context);
}
OpFoldResult spirv::LogicalOrOp::fold(ArrayRef<Attribute> operands) {
assert(operands.size() == 2 && "spv.LogicalOr should take two operands");
if (auto rhs = getScalarOrSplatBoolAttr(operands.back())) {
if (rhs.value())
return operands.back();
if (!rhs.value())
return operand1();
}
return Attribute();
}
namespace {
// ...
struct ConvertSelectionOpToSelect
: public OpRewritePattern<spirv::SelectionOp> {
using OpRewritePattern<spirv::SelectionOp>::OpRewritePattern;
LogicalResult matchAndRewrite(spirv::SelectionOp selectionOp,
PatternRewriter &rewriter) const override {
auto *op = selectionOp.getOperation();
auto &body = op->getRegion(0);
if (body.empty()) {
return failure();
}
if (std::distance(body.begin(), body.end()) != 4) {
return failure();
}
auto *headerBlock = selectionOp.getHeaderBlock();
if (!onlyContainsBranchConditionalOp(headerBlock)) {
return failure();
}
auto brConditionalOp =
cast<spirv::BranchConditionalOp>(headerBlock->front());
auto *trueBlock = brConditionalOp.getSuccessor(0);
auto *falseBlock = brConditionalOp.getSuccessor(1);
auto *mergeBlock = selectionOp.getMergeBlock();
if (failed(canCanonicalizeSelection(trueBlock, falseBlock, mergeBlock)))
return failure();
auto trueValue = getSrcValue(trueBlock);
auto falseValue = getSrcValue(falseBlock);
auto ptrValue = getDstPtr(trueBlock);
auto storeOpAttributes =
cast<spirv::StoreOp>(trueBlock->front())->getAttrs();
auto selectOp = rewriter.create<spirv::SelectOp>(
selectionOp.getLoc(), trueValue.getType(), brConditionalOp.condition(),
trueValue, falseValue);
rewriter.create<spirv::StoreOp>(selectOp.getLoc(), ptrValue,
selectOp.getResult(), storeOpAttributes);
rewriter.eraseOp(op);
return success();
}
private:
LogicalResult canCanonicalizeSelection(Block *trueBlock, Block *falseBlock,
Block *mergeBlock) const;
bool onlyContainsBranchConditionalOp(Block *block) const {
return std::next(block->begin()) == block->end() &&
isa<spirv::BranchConditionalOp>(block->front());
}
bool isSameAttrList(spirv::StoreOp lhs, spirv::StoreOp rhs) const {
return lhs->getAttrDictionary() == rhs->getAttrDictionary();
}
Value getSrcValue(Block *block) const {
auto storeOp = cast<spirv::StoreOp>(block->front());
return storeOp.value();
}
Value getDstPtr(Block *block) const {
auto storeOp = cast<spirv::StoreOp>(block->front());
return storeOp.ptr();
}
};
LogicalResult ConvertSelectionOpToSelect::canCanonicalizeSelection(
Block *trueBlock, Block *falseBlock, Block *mergeBlock) const {
if ((std::distance(trueBlock->begin(), trueBlock->end()) != 2) ||
(std::distance(falseBlock->begin(), falseBlock->end()) != 2)) {
return failure();
}
auto trueBrStoreOp = dyn_cast<spirv::StoreOp>(trueBlock->front());
auto trueBrBranchOp =
dyn_cast<spirv::BranchOp>(*std::next(trueBlock->begin()));
auto falseBrStoreOp = dyn_cast<spirv::StoreOp>(falseBlock->front());
auto falseBrBranchOp =
dyn_cast<spirv::BranchOp>(*std::next(falseBlock->begin()));
if (!trueBrStoreOp || !trueBrBranchOp || !falseBrStoreOp ||
!falseBrBranchOp) {
return failure();
}
bool isScalarOrVector = trueBrStoreOp.value()
.getType()
.cast<spirv::SPIRVType>()
.isScalarOrVector();
if ((trueBrStoreOp.ptr() != falseBrStoreOp.ptr()) ||
!isSameAttrList(trueBrStoreOp, falseBrStoreOp) || !isScalarOrVector) {
return failure();
}
if ((trueBrBranchOp->getSuccessor(0) != mergeBlock) ||
(falseBrBranchOp->getSuccessor(0) != mergeBlock)) {
return failure();
}
return success();
}
}
void spirv::SelectionOp::getCanonicalizationPatterns(RewritePatternSet &results,
MLIRContext *context) {
results.add<ConvertSelectionOpToSelect>(context);
}