#include "mlir/Dialect/SPIRV/Transforms/Passes.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVAttributes.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVDialect.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVEnums.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVOps.h"
#include "mlir/Dialect/SPIRV/IR/TargetAndABI.h"
#include "mlir/Dialect/SPIRV/Transforms/SPIRVConversion.h"
#include "mlir/Dialect/SPIRV/Utils/LayoutUtils.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/ADT/SetVector.h"
namespace mlir {
namespace spirv {
#define GEN_PASS_DEF_SPIRVLOWERABIATTRIBUTESPASS
#include "mlir/Dialect/SPIRV/Transforms/Passes.h.inc"
}
}
using namespace mlir;
static spirv::GlobalVariableOp
createGlobalVarForEntryPointArgument(OpBuilder &builder, spirv::FuncOp funcOp,
unsigned argIndex,
spirv::InterfaceVarABIAttr abiInfo) {
auto spirvModule = funcOp->getParentOfType<spirv::ModuleOp>();
if (!spirvModule)
return nullptr;
OpBuilder::InsertionGuard moduleInsertionGuard(builder);
builder.setInsertionPoint(funcOp.getOperation());
std::string varName =
funcOp.getName().str() + "_arg_" + std::to_string(argIndex);
auto varType = funcOp.getFunctionType().getInput(argIndex);
if (cast<spirv::SPIRVType>(varType).isScalarOrVector()) {
auto storageClass = abiInfo.getStorageClass();
if (!storageClass)
return nullptr;
varType =
spirv::PointerType::get(spirv::StructType::get(varType), *storageClass);
}
auto varPtrType = cast<spirv::PointerType>(varType);
auto varPointeeType = cast<spirv::StructType>(varPtrType.getPointeeType());
varPointeeType =
cast<spirv::StructType>(VulkanLayoutUtils::decorateType(varPointeeType));
if (!varPointeeType)
return nullptr;
varType =
spirv::PointerType::get(varPointeeType, varPtrType.getStorageClass());
return builder.create<spirv::GlobalVariableOp>(
funcOp.getLoc(), varType, varName, abiInfo.getDescriptorSet(),
abiInfo.getBinding());
}
static LogicalResult
getInterfaceVariables(spirv::FuncOp funcOp,
SmallVectorImpl<Attribute> &interfaceVars) {
auto module = funcOp->getParentOfType<spirv::ModuleOp>();
if (!module) {
return failure();
}
SetVector<Operation *> interfaceVarSet;
funcOp.walk([&](spirv::AddressOfOp addressOfOp) {
auto var =
module.lookupSymbol<spirv::GlobalVariableOp>(addressOfOp.getVariable());
switch (cast<spirv::PointerType>(var.getType()).getStorageClass()) {
case spirv::StorageClass::Input:
case spirv::StorageClass::Output:
interfaceVarSet.insert(var.getOperation());
break;
default:
break;
}
});
for (auto &var : interfaceVarSet) {
interfaceVars.push_back(SymbolRefAttr::get(
funcOp.getContext(), cast<spirv::GlobalVariableOp>(var).getSymName()));
}
return success();
}
static LogicalResult lowerEntryPointABIAttr(spirv::FuncOp funcOp,
OpBuilder &builder) {
auto entryPointAttrName = spirv::getEntryPointABIAttrName();
auto entryPointAttr =
funcOp->getAttrOfType<spirv::EntryPointABIAttr>(entryPointAttrName);
if (!entryPointAttr) {
return failure();
}
OpBuilder::InsertionGuard moduleInsertionGuard(builder);
auto spirvModule = funcOp->getParentOfType<spirv::ModuleOp>();
builder.setInsertionPointToEnd(spirvModule.getBody());
SmallVector<Attribute, 1> interfaceVars;
if (failed(getInterfaceVariables(funcOp, interfaceVars))) {
return failure();
}
spirv::TargetEnvAttr targetEnvAttr = spirv::lookupTargetEnv(funcOp);
spirv::TargetEnv targetEnv(targetEnvAttr);
FailureOr<spirv::ExecutionModel> executionModel =
spirv::getExecutionModel(targetEnvAttr);
if (failed(executionModel))
return funcOp.emitRemark("lower entry point failure: could not select "
"execution model based on 'spirv.target_env'");
builder.create<spirv::EntryPointOp>(funcOp.getLoc(), *executionModel, funcOp,
interfaceVars);
if (DenseI32ArrayAttr workgroupSizeAttr = entryPointAttr.getWorkgroupSize()) {
std::optional<ArrayRef<spirv::Capability>> caps =
spirv::getCapabilities(spirv::ExecutionMode::LocalSize);
if (!caps || targetEnv.allows(*caps)) {
builder.create<spirv::ExecutionModeOp>(funcOp.getLoc(), funcOp,
spirv::ExecutionMode::LocalSize,
workgroupSizeAttr.asArrayRef());
entryPointAttr = spirv::EntryPointABIAttr::get(
entryPointAttr.getContext(), DenseI32ArrayAttr(),
entryPointAttr.getSubgroupSize(), entryPointAttr.getTargetWidth());
}
}
if (std::optional<int> subgroupSize = entryPointAttr.getSubgroupSize()) {
std::optional<ArrayRef<spirv::Capability>> caps =
spirv::getCapabilities(spirv::ExecutionMode::SubgroupSize);
if (!caps || targetEnv.allows(*caps)) {
builder.create<spirv::ExecutionModeOp>(funcOp.getLoc(), funcOp,
spirv::ExecutionMode::SubgroupSize,
*subgroupSize);
entryPointAttr = spirv::EntryPointABIAttr::get(
entryPointAttr.getContext(), entryPointAttr.getWorkgroupSize(),
std::nullopt, entryPointAttr.getTargetWidth());
}
}
if (std::optional<int> targetWidth = entryPointAttr.getTargetWidth()) {
std::optional<ArrayRef<spirv::Capability>> caps =
spirv::getCapabilities(spirv::ExecutionMode::SignedZeroInfNanPreserve);
if (!caps || targetEnv.allows(*caps)) {
builder.create<spirv::ExecutionModeOp>(
funcOp.getLoc(), funcOp,
spirv::ExecutionMode::SignedZeroInfNanPreserve, *targetWidth);
entryPointAttr = spirv::EntryPointABIAttr::get(
entryPointAttr.getContext(), entryPointAttr.getWorkgroupSize(),
entryPointAttr.getSubgroupSize(), std::nullopt);
}
}
if (entryPointAttr.getWorkgroupSize() || entryPointAttr.getSubgroupSize() ||
entryPointAttr.getTargetWidth())
funcOp->setAttr(entryPointAttrName, entryPointAttr);
else
funcOp->removeAttr(entryPointAttrName);
return success();
}
namespace {
class ProcessInterfaceVarABI final : public OpConversionPattern<spirv::FuncOp> {
public:
using OpConversionPattern<spirv::FuncOp>::OpConversionPattern;
LogicalResult
matchAndRewrite(spirv::FuncOp funcOp, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override;
};
class LowerABIAttributesPass final
: public spirv::impl::SPIRVLowerABIAttributesPassBase<
LowerABIAttributesPass> {
void runOnOperation() override;
};
}
LogicalResult ProcessInterfaceVarABI::matchAndRewrite(
spirv::FuncOp funcOp, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const {
if (!funcOp->getAttrOfType<spirv::EntryPointABIAttr>(
spirv::getEntryPointABIAttrName())) {
return failure();
}
TypeConverter::SignatureConversion signatureConverter(
funcOp.getFunctionType().getNumInputs());
auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
auto indexType = typeConverter.getIndexType();
auto attrName = spirv::getInterfaceVarABIAttrName();
for (const auto &argType :
llvm::enumerate(funcOp.getFunctionType().getInputs())) {
auto abiInfo = funcOp.getArgAttrOfType<spirv::InterfaceVarABIAttr>(
argType.index(), attrName);
if (!abiInfo) {
return failure();
}
spirv::GlobalVariableOp var = createGlobalVarForEntryPointArgument(
rewriter, funcOp, argType.index(), abiInfo);
if (!var)
return failure();
OpBuilder::InsertionGuard funcInsertionGuard(rewriter);
rewriter.setInsertionPointToStart(&funcOp.front());
Value replacement =
rewriter.create<spirv::AddressOfOp>(funcOp.getLoc(), var);
if (cast<spirv::SPIRVType>(argType.value()).isScalarOrVector()) {
auto zero =
spirv::ConstantOp::getZero(indexType, funcOp.getLoc(), rewriter);
auto loadPtr = rewriter.create<spirv::AccessChainOp>(
funcOp.getLoc(), replacement, zero.getConstant());
replacement = rewriter.create<spirv::LoadOp>(funcOp.getLoc(), loadPtr);
}
signatureConverter.remapInput(argType.index(), replacement);
}
if (failed(rewriter.convertRegionTypes(&funcOp.getBody(), *getTypeConverter(),
&signatureConverter)))
return failure();
rewriter.modifyOpInPlace(funcOp, [&] {
funcOp.setType(rewriter.getFunctionType(
signatureConverter.getConvertedTypes(), std::nullopt));
});
return success();
}
void LowerABIAttributesPass::runOnOperation() {
spirv::ModuleOp module = getOperation();
MLIRContext *context = &getContext();
spirv::TargetEnvAttr targetEnvAttr = spirv::lookupTargetEnv(module);
if (!targetEnvAttr) {
module->emitOpError("missing SPIR-V target env attribute");
return signalPassFailure();
}
spirv::TargetEnv targetEnv(targetEnvAttr);
SPIRVTypeConverter typeConverter(targetEnv);
typeConverter.addSourceMaterialization([](OpBuilder &builder,
spirv::PointerType type,
ValueRange inputs, Location loc) {
if (inputs.size() != 1 || !isa<spirv::PointerType>(inputs[0].getType()))
return Value();
return builder.create<spirv::BitcastOp>(loc, type, inputs[0]).getResult();
});
RewritePatternSet patterns(context);
patterns.add<ProcessInterfaceVarABI>(typeConverter, context);
ConversionTarget target(*context);
target.addDynamicallyLegalOp<spirv::FuncOp>([&](spirv::FuncOp op) {
StringRef attrName = spirv::getInterfaceVarABIAttrName();
for (unsigned i = 0, e = op.getNumArguments(); i < e; ++i)
if (op.getArgAttr(i, attrName))
return false;
return true;
});
target.markUnknownOpDynamicallyLegal([](Operation *op) {
return op->getDialect()->getNamespace() ==
spirv::SPIRVDialect::getDialectNamespace();
});
if (failed(applyPartialConversion(module, target, std::move(patterns))))
return signalPassFailure();
OpBuilder builder(context);
SmallVector<spirv::FuncOp, 1> entryPointFns;
auto entryPointAttrName = spirv::getEntryPointABIAttrName();
module.walk([&](spirv::FuncOp funcOp) {
if (funcOp->getAttrOfType<spirv::EntryPointABIAttr>(entryPointAttrName)) {
entryPointFns.push_back(funcOp);
}
});
for (auto fn : entryPointFns) {
if (failed(lowerEntryPointABIAttr(fn, builder))) {
return signalPassFailure();
}
}
}