#include "mlir/Dialect/SPIRV/Transforms/Passes.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVDialect.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVOps.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVTypes.h"
#include "mlir/Dialect/SPIRV/IR/TargetAndABI.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/Visitors.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringExtras.h"
#include <optional>
namespace mlir {
namespace spirv {
#define GEN_PASS_DEF_SPIRVUPDATEVCEPASS
#include "mlir/Dialect/SPIRV/Transforms/Passes.h.inc"
}
}
using namespace mlir;
namespace {
class UpdateVCEPass final
: public spirv::impl::SPIRVUpdateVCEPassBase<UpdateVCEPass> {
void runOnOperation() override;
};
}
static LogicalResult checkAndUpdateExtensionRequirements(
Operation *op, const spirv::TargetEnv &targetEnv,
const spirv::SPIRVType::ExtensionArrayRefVector &candidates,
SetVector<spirv::Extension> &deducedExtensions) {
for (const auto &ors : candidates) {
if (std::optional<spirv::Extension> chosen = targetEnv.allows(ors)) {
deducedExtensions.insert(*chosen);
} else {
SmallVector<StringRef, 4> extStrings;
for (spirv::Extension ext : ors)
extStrings.push_back(spirv::stringifyExtension(ext));
return op->emitError("'")
<< op->getName() << "' requires at least one extension in ["
<< llvm::join(extStrings, ", ")
<< "] but none allowed in target environment";
}
}
return success();
}
static LogicalResult checkAndUpdateCapabilityRequirements(
Operation *op, const spirv::TargetEnv &targetEnv,
const spirv::SPIRVType::CapabilityArrayRefVector &candidates,
SetVector<spirv::Capability> &deducedCapabilities) {
for (const auto &ors : candidates) {
if (std::optional<spirv::Capability> chosen = targetEnv.allows(ors)) {
deducedCapabilities.insert(*chosen);
} else {
SmallVector<StringRef, 4> capStrings;
for (spirv::Capability cap : ors)
capStrings.push_back(spirv::stringifyCapability(cap));
return op->emitError("'")
<< op->getName() << "' requires at least one capability in ["
<< llvm::join(capStrings, ", ")
<< "] but none allowed in target environment";
}
}
return success();
}
void UpdateVCEPass::runOnOperation() {
spirv::ModuleOp module = getOperation();
spirv::TargetEnvAttr targetAttr = spirv::lookupTargetEnv(module);
if (!targetAttr) {
module.emitError("missing 'spirv.target_env' attribute");
return signalPassFailure();
}
spirv::TargetEnv targetEnv(targetAttr);
spirv::Version allowedVersion = targetAttr.getVersion();
spirv::Version deducedVersion = spirv::Version::V_1_0;
SetVector<spirv::Extension> deducedExtensions;
SetVector<spirv::Capability> deducedCapabilities;
WalkResult walkResult = module.walk([&](Operation *op) -> WalkResult {
if (auto minVersionIfx = dyn_cast<spirv::QueryMinVersionInterface>(op)) {
std::optional<spirv::Version> minVersion = minVersionIfx.getMinVersion();
if (minVersion) {
deducedVersion = std::max(deducedVersion, *minVersion);
if (deducedVersion > allowedVersion) {
return op->emitError("'")
<< op->getName() << "' requires min version "
<< spirv::stringifyVersion(deducedVersion)
<< " but target environment allows up to "
<< spirv::stringifyVersion(allowedVersion);
}
}
}
if (auto extensions = dyn_cast<spirv::QueryExtensionInterface>(op))
if (failed(checkAndUpdateExtensionRequirements(
op, targetEnv, extensions.getExtensions(), deducedExtensions)))
return WalkResult::interrupt();
if (auto capabilities = dyn_cast<spirv::QueryCapabilityInterface>(op))
if (failed(checkAndUpdateCapabilityRequirements(
op, targetEnv, capabilities.getCapabilities(),
deducedCapabilities)))
return WalkResult::interrupt();
SmallVector<Type, 4> valueTypes;
valueTypes.append(op->operand_type_begin(), op->operand_type_end());
valueTypes.append(op->result_type_begin(), op->result_type_end());
if (auto globalVar = dyn_cast<spirv::GlobalVariableOp>(op))
valueTypes.push_back(globalVar.getType());
SmallVector<ArrayRef<spirv::Extension>, 4> typeExtensions;
SmallVector<ArrayRef<spirv::Capability>, 8> typeCapabilities;
for (Type valueType : valueTypes) {
typeExtensions.clear();
cast<spirv::SPIRVType>(valueType).getExtensions(typeExtensions);
if (failed(checkAndUpdateExtensionRequirements(
op, targetEnv, typeExtensions, deducedExtensions)))
return WalkResult::interrupt();
typeCapabilities.clear();
cast<spirv::SPIRVType>(valueType).getCapabilities(typeCapabilities);
if (failed(checkAndUpdateCapabilityRequirements(
op, targetEnv, typeCapabilities, deducedCapabilities)))
return WalkResult::interrupt();
}
return WalkResult::advance();
});
if (walkResult.wasInterrupted())
return signalPassFailure();
auto triple = spirv::VerCapExtAttr::get(
deducedVersion, deducedCapabilities.getArrayRef(),
deducedExtensions.getArrayRef(), &getContext());
module->setAttr(spirv::ModuleOp::getVCETripleAttrName(), triple);
}