215 lines · cpp
1//===- DeduceVersionExtensionCapabilityPass.cpp ---------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file implements a pass to deduce minimal version/extension/capability10// requirements for a spirv::ModuleOp.11//12//===----------------------------------------------------------------------===//13 14#include "mlir/Dialect/SPIRV/Transforms/Passes.h"15 16#include "mlir/Dialect/SPIRV/IR/SPIRVOps.h"17#include "mlir/Dialect/SPIRV/IR/SPIRVTypes.h"18#include "mlir/Dialect/SPIRV/IR/TargetAndABI.h"19#include "mlir/IR/Builders.h"20#include "mlir/IR/Visitors.h"21#include "llvm/ADT/StringExtras.h"22#include <optional>23 24namespace mlir {25namespace spirv {26#define GEN_PASS_DEF_SPIRVUPDATEVCEPASS27#include "mlir/Dialect/SPIRV/Transforms/Passes.h.inc"28} // namespace spirv29} // namespace mlir30 31using namespace mlir;32 33namespace {34/// Pass to deduce minimal version/extension/capability requirements for a35/// spirv::ModuleOp.36class UpdateVCEPass final37 : public spirv::impl::SPIRVUpdateVCEPassBase<UpdateVCEPass> {38 void runOnOperation() override;39};40} // namespace41 42/// Checks that `candidates` extension requirements are possible to be satisfied43/// with the given `targetEnv` and updates `deducedExtensions` if so. Emits44/// errors attaching to the given `op` on failures.45///46/// `candidates` is a vector of vector for extension requirements following47/// ((Extension::A OR Extension::B) AND (Extension::C OR Extension::D))48/// convention.49static LogicalResult checkAndUpdateExtensionRequirements(50 Operation *op, const spirv::TargetEnv &targetEnv,51 const spirv::SPIRVType::ExtensionArrayRefVector &candidates,52 SetVector<spirv::Extension> &deducedExtensions) {53 for (const auto &ors : candidates) {54 if (std::optional<spirv::Extension> chosen = targetEnv.allows(ors)) {55 deducedExtensions.insert(*chosen);56 } else {57 SmallVector<StringRef, 4> extStrings;58 for (spirv::Extension ext : ors)59 extStrings.push_back(spirv::stringifyExtension(ext));60 61 return op->emitError("'")62 << op->getName() << "' requires at least one extension in ["63 << llvm::join(extStrings, ", ")64 << "] but none allowed in target environment";65 }66 }67 return success();68}69 70/// Checks that `candidates`capability requirements are possible to be satisfied71/// with the given `targetEnv` and updates `deducedCapabilities` if so. Emits72/// errors attaching to the given `op` on failures.73///74/// `candidates` is a vector of vector for capability requirements following75/// ((Capability::A OR Capability::B) AND (Capability::C OR Capability::D))76/// convention.77static LogicalResult checkAndUpdateCapabilityRequirements(78 Operation *op, const spirv::TargetEnv &targetEnv,79 const spirv::SPIRVType::CapabilityArrayRefVector &candidates,80 SetVector<spirv::Capability> &deducedCapabilities) {81 for (const auto &ors : candidates) {82 if (std::optional<spirv::Capability> chosen = targetEnv.allows(ors)) {83 deducedCapabilities.insert(*chosen);84 } else {85 SmallVector<StringRef, 4> capStrings;86 for (spirv::Capability cap : ors)87 capStrings.push_back(spirv::stringifyCapability(cap));88 89 return op->emitError("'")90 << op->getName() << "' requires at least one capability in ["91 << llvm::join(capStrings, ", ")92 << "] but none allowed in target environment";93 }94 }95 return success();96}97 98static void addAllImpliedCapabilities(SetVector<spirv::Capability> &caps) {99 SetVector<spirv::Capability> tmp;100 for (spirv::Capability cap : caps)101 tmp.insert_range(getRecursiveImpliedCapabilities(cap));102 caps.insert_range(std::move(tmp));103}104 105void UpdateVCEPass::runOnOperation() {106 spirv::ModuleOp module = getOperation();107 108 spirv::TargetEnvAttr targetAttr = spirv::lookupTargetEnv(module);109 if (!targetAttr) {110 module.emitError("missing 'spirv.target_env' attribute");111 return signalPassFailure();112 }113 114 spirv::TargetEnv targetEnv(targetAttr);115 spirv::Version allowedVersion = targetAttr.getVersion();116 117 spirv::Version deducedVersion = spirv::Version::V_1_0;118 SetVector<spirv::Extension> deducedExtensions;119 SetVector<spirv::Capability> deducedCapabilities;120 121 // Walk each SPIR-V op to deduce the minimal version/extension/capability122 // requirements.123 WalkResult walkResult = module.walk([&](Operation *op) -> WalkResult {124 // Op min version requirements125 if (auto minVersionIfx = dyn_cast<spirv::QueryMinVersionInterface>(op)) {126 std::optional<spirv::Version> minVersion = minVersionIfx.getMinVersion();127 if (minVersion) {128 deducedVersion = std::max(deducedVersion, *minVersion);129 if (deducedVersion > allowedVersion) {130 return op->emitError("'")131 << op->getName() << "' requires min version "132 << spirv::stringifyVersion(deducedVersion)133 << " but target environment allows up to "134 << spirv::stringifyVersion(allowedVersion);135 }136 }137 }138 139 // Op extension requirements140 if (auto extensions = dyn_cast<spirv::QueryExtensionInterface>(op))141 if (failed(checkAndUpdateExtensionRequirements(142 op, targetEnv, extensions.getExtensions(), deducedExtensions)))143 return WalkResult::interrupt();144 145 // Op capability requirements146 if (auto capabilities = dyn_cast<spirv::QueryCapabilityInterface>(op))147 if (failed(checkAndUpdateCapabilityRequirements(148 op, targetEnv, capabilities.getCapabilities(),149 deducedCapabilities)))150 return WalkResult::interrupt();151 152 SmallVector<Type, 4> valueTypes;153 valueTypes.append(op->operand_type_begin(), op->operand_type_end());154 valueTypes.append(op->result_type_begin(), op->result_type_end());155 156 // Special treatment for global variables, whose type requirements are157 // conveyed by type attributes.158 if (auto globalVar = dyn_cast<spirv::GlobalVariableOp>(op))159 valueTypes.push_back(globalVar.getType());160 161 // If the op is FunctionLike make sure to process input and result types.162 if (auto funcOpInterface = dyn_cast<FunctionOpInterface>(op)) {163 llvm::append_range(valueTypes, funcOpInterface.getArgumentTypes());164 llvm::append_range(valueTypes, funcOpInterface.getResultTypes());165 }166 167 // Requirements from values' types168 SmallVector<ArrayRef<spirv::Extension>, 4> typeExtensions;169 SmallVector<ArrayRef<spirv::Capability>, 8> typeCapabilities;170 for (Type valueType : valueTypes) {171 typeExtensions.clear();172 cast<spirv::SPIRVType>(valueType).getExtensions(typeExtensions);173 if (failed(checkAndUpdateExtensionRequirements(174 op, targetEnv, typeExtensions, deducedExtensions)))175 return WalkResult::interrupt();176 177 typeCapabilities.clear();178 cast<spirv::SPIRVType>(valueType).getCapabilities(typeCapabilities);179 if (failed(checkAndUpdateCapabilityRequirements(180 op, targetEnv, typeCapabilities, deducedCapabilities)))181 return WalkResult::interrupt();182 }183 184 return WalkResult::advance();185 });186 187 if (walkResult.wasInterrupted())188 return signalPassFailure();189 190 addAllImpliedCapabilities(deducedCapabilities);191 192 // Update min version requirement for capabilities after deducing them.193 for (spirv::Capability cap : deducedCapabilities) {194 if (std::optional<spirv::Version> minVersion = spirv::getMinVersion(cap)) {195 deducedVersion = std::max(deducedVersion, *minVersion);196 if (deducedVersion > allowedVersion) {197 module.emitError("Capability '")198 << spirv::stringifyCapability(cap) << "' requires min version "199 << spirv::stringifyVersion(deducedVersion)200 << " but target environment allows up to "201 << spirv::stringifyVersion(allowedVersion);202 return signalPassFailure();203 }204 }205 }206 207 // TODO: verify that the deduced version is consistent with208 // SPIR-V ops' maximal version requirements.209 210 auto triple = spirv::VerCapExtAttr::get(211 deducedVersion, deducedCapabilities.getArrayRef(),212 deducedExtensions.getArrayRef(), &getContext());213 module->setAttr(spirv::ModuleOp::getVCETripleAttrName(), triple);214}215