llvm-project/mlir/lib/Dialect/SPIRV/Transforms/LowerABIAttributesPass.cpp
Lei Zhang 47c6ab2b97 [mlir][spirv] Properly support SPIR-V conversion target
This commit defines a new SPIR-V dialect attribute for specifying
a SPIR-V target environment. It is a dictionary attribute containing
the SPIR-V version, supported extension list, and allowed capability
list. A SPIRVConversionTarget subclass is created to take in the
target environment and sets proper dynmaically legal ops by querying
the op availability interface of SPIR-V ops to make sure they are
available in the specified target environment. All existing conversions
targeting SPIR-V is changed to use this SPIRVConversionTarget. It
probes whether the input IR has a `spv.target_env` attribute,
otherwise, it uses the default target environment: SPIR-V 1.0 with
Shader capability and no extra extensions.

Differential Revision: https://reviews.llvm.org/D72256
2020-01-14 19:18:42 -05:00

256 lines
10 KiB
C++

//===- LowerABIAttributesPass.cpp - Decorate composite type ---------------===//
//
// Part of the MLIR Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements a pass to lower attributes that specify the shader ABI
// for the functions in the generated SPIR-V module.
//
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/SPIRV/LayoutUtils.h"
#include "mlir/Dialect/SPIRV/Passes.h"
#include "mlir/Dialect/SPIRV/SPIRVDialect.h"
#include "mlir/Dialect/SPIRV/SPIRVLowering.h"
#include "mlir/Dialect/SPIRV/SPIRVOps.h"
#include "mlir/Dialect/StandardOps/Ops.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/ADT/SetVector.h"
using namespace mlir;
/// Checks if the `type` is a scalar or vector type. It is assumed that they are
/// valid for SPIR-V dialect already.
static bool isScalarOrVectorType(Type type) {
return spirv::SPIRVDialect::isValidScalarType(type) || type.isa<VectorType>();
}
/// Creates a global variable for an argument based on the ABI info.
static spirv::GlobalVariableOp
createGlobalVariableForArg(FuncOp funcOp, OpBuilder &builder, unsigned argNum,
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(argNum);
// Get the type of variable. If this is a scalar/vector type and has an ABI
// info create a variable of type !spv.ptr<!spv.struct<elementTYpe>>. If not
// it must already be a !spv.ptr<!spv.struct<...>>.
auto varType = funcOp.getType().getInput(argNum);
auto storageClass =
static_cast<spirv::StorageClass>(abiInfo.storage_class().getInt());
if (isScalarOrVectorType(varType)) {
varType =
spirv::PointerType::get(spirv::StructType::get(varType), storageClass);
}
auto varPtrType = varType.cast<spirv::PointerType>();
auto varPointeeType = varPtrType.getPointeeType().cast<spirv::StructType>();
// Set the offset information.
VulkanLayoutUtils::Size size = 0, alignment = 0;
varPointeeType =
VulkanLayoutUtils::decorateType(varPointeeType, size, alignment)
.cast<spirv::StructType>();
varType =
spirv::PointerType::get(varPointeeType, varPtrType.getStorageClass());
return builder.create<spirv::GlobalVariableOp>(
funcOp.getLoc(), varType, varName, abiInfo.descriptor_set().getInt(),
abiInfo.binding().getInt());
}
/// Gets the global variables that need to be specified as interface variable
/// with an spv.EntryPointOp. Traverses the body of a entry function to do so.
static LogicalResult
getInterfaceVariables(FuncOp funcOp,
SmallVectorImpl<Attribute> &interfaceVars) {
auto module = funcOp.getParentOfType<spirv::ModuleOp>();
if (!module) {
return failure();
}
llvm::SetVector<Operation *> interfaceVarSet;
// TODO(ravishankarm) : This should in reality traverse the entry function
// call graph and collect all the interfaces. For now, just traverse the
// instructions in this function.
funcOp.walk([&](spirv::AddressOfOp addressOfOp) {
auto var =
module.lookupSymbol<spirv::GlobalVariableOp>(addressOfOp.variable());
if (var.type().cast<spirv::PointerType>().getStorageClass() !=
spirv::StorageClass::StorageBuffer) {
interfaceVarSet.insert(var.getOperation());
}
});
for (auto &var : interfaceVarSet) {
interfaceVars.push_back(SymbolRefAttr::get(
cast<spirv::GlobalVariableOp>(var).sym_name(), funcOp.getContext()));
}
return success();
}
/// Lowers the entry point attribute.
static LogicalResult lowerEntryPointABIAttr(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.setInsertionPoint(spirvModule.body().front().getTerminator());
// Adds the spv.EntryPointOp after collecting all the interface variables
// needed.
SmallVector<Attribute, 1> interfaceVars;
if (failed(getInterfaceVariables(funcOp, interfaceVars))) {
return failure();
}
builder.create<spirv::EntryPointOp>(
funcOp.getLoc(), spirv::ExecutionModel::GLCompute, funcOp, interfaceVars);
// Specifies the spv.ExecutionModeOp.
auto localSizeAttr = entryPointAttr.local_size();
SmallVector<int32_t, 3> localSize(localSizeAttr.getValues<int32_t>());
builder.create<spirv::ExecutionModeOp>(
funcOp.getLoc(), funcOp, spirv::ExecutionMode::LocalSize, localSize);
funcOp.removeAttr(entryPointAttrName);
return success();
}
namespace {
/// Pattern rewriter for changing function signature to match the ABI specified
/// in attributes.
class FuncOpLowering final : public SPIRVOpLowering<FuncOp> {
public:
using SPIRVOpLowering<FuncOp>::SPIRVOpLowering;
PatternMatchResult
matchAndRewrite(FuncOp funcOp, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const override;
};
/// Pass to implement the ABI information specified as attributes.
class LowerABIAttributesPass final
: public OperationPass<LowerABIAttributesPass, spirv::ModuleOp> {
private:
void runOnOperation() override;
};
} // namespace
PatternMatchResult
FuncOpLowering::matchAndRewrite(FuncOp funcOp, ArrayRef<Value> operands,
ConversionPatternRewriter &rewriter) const {
if (!funcOp.getAttrOfType<spirv::EntryPointABIAttr>(
spirv::getEntryPointABIAttrName())) {
// TODO(ravishankarm) : Non-entry point functions are not handled.
return matchFailure();
}
TypeConverter::SignatureConversion signatureConverter(
funcOp.getType().getNumInputs());
auto attrName = spirv::getInterfaceVarABIAttrName();
for (auto argType : llvm::enumerate(funcOp.getType().getInputs())) {
auto abiInfo = funcOp.getArgAttrOfType<spirv::InterfaceVarABIAttr>(
argType.index(), attrName);
if (!abiInfo) {
// TODO(ravishankarm) : For non-entry point functions, it should be legal
// to pass around scalar/vector values and return a scalar/vector. For now
// non-entry point functions are not handled in this ABI lowering and will
// produce an error.
return matchFailure();
}
auto var =
createGlobalVariableForArg(funcOp, rewriter, argType.index(), abiInfo);
if (!var) {
return matchFailure();
}
OpBuilder::InsertionGuard funcInsertionGuard(rewriter);
rewriter.setInsertionPointToStart(&funcOp.front());
// Insert spirv::AddressOf and spirv::AccessChain operations.
Value replacement =
rewriter.create<spirv::AddressOfOp>(funcOp.getLoc(), var);
// Check if the arg is a scalar or vector type. In that case, the value
// needs to be loaded into registers.
// TODO(ravishankarm) : This is loading value of the scalar into registers
// at the start of the function. It is probably better to do the load just
// before the use. There might be multiple loads and currently there is no
// easy way to replace all uses with a sequence of operations.
if (isScalarOrVectorType(argType.value())) {
auto indexType =
typeConverter.convertType(IndexType::get(funcOp.getContext()));
auto zero =
spirv::ConstantOp::getZero(indexType, funcOp.getLoc(), &rewriter);
auto loadPtr = rewriter.create<spirv::AccessChainOp>(
funcOp.getLoc(), replacement, zero.constant());
replacement = rewriter.create<spirv::LoadOp>(funcOp.getLoc(), loadPtr,
/*memory_access=*/nullptr,
/*alignment=*/nullptr);
}
signatureConverter.remapInput(argType.index(), replacement);
}
// Creates a new function with the update signature.
rewriter.updateRootInPlace(funcOp, [&] {
funcOp.setType(rewriter.getFunctionType(
signatureConverter.getConvertedTypes(), llvm::None));
rewriter.applySignatureConversion(&funcOp.getBody(), signatureConverter);
});
return matchSuccess();
}
void LowerABIAttributesPass::runOnOperation() {
// Uses the signature conversion methodology of the dialect conversion
// framework to implement the conversion.
spirv::ModuleOp module = getOperation();
MLIRContext *context = &getContext();
SPIRVTypeConverter typeConverter;
OwningRewritePatternList patterns;
patterns.insert<FuncOpLowering>(context, typeConverter);
std::unique_ptr<ConversionTarget> target = spirv::SPIRVConversionTarget::get(
spirv::lookupTargetEnvOrDefault(module), context);
auto entryPointAttrName = spirv::getEntryPointABIAttrName();
target->addDynamicallyLegalOp<FuncOp>([&](FuncOp op) {
return op.getAttrOfType<spirv::EntryPointABIAttr>(entryPointAttrName) &&
op.getNumResults() == 0 && op.getNumArguments() == 0;
});
target->addLegalOp<ReturnOp>();
if (failed(
applyPartialConversion(module, *target, patterns, &typeConverter))) {
return signalPassFailure();
}
// Walks over all the FuncOps in spirv::ModuleOp to lower the entry point
// attributes.
OpBuilder builder(context);
SmallVector<FuncOp, 1> entryPointFns;
module.walk([&](FuncOp funcOp) {
if (funcOp.getAttrOfType<spirv::EntryPointABIAttr>(entryPointAttrName)) {
entryPointFns.push_back(funcOp);
}
});
for (auto fn : entryPointFns) {
if (failed(lowerEntryPointABIAttr(fn, builder))) {
return signalPassFailure();
}
}
}
std::unique_ptr<OpPassBase<spirv::ModuleOp>>
mlir::spirv::createLowerABIAttributesPass() {
return std::make_unique<LowerABIAttributesPass>();
}
static PassRegistration<LowerABIAttributesPass>
pass("spirv-lower-abi-attrs", "Lower SPIR-V ABI Attributes");