mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-27 10:20:14 +00:00
opt: add capability trimming pass (not default). (#5278)
This commit adds a new optimization which tries to remove unnecessary capabilities from a SPIR-V module. When compiling a SPIR-V module, you may have some dead-code using features gated by a capability. DCE will remove this code, but the capability will remain. This means your module would still require some capability, even if it doesn't require it. Calling this pass on your module would remove obsolete capabilities. This pass wouldn't be enabled by default, and would only be usable from the API (at least for now). NOTE: this commit only adds the basic skeleton/structure, and doesn't mark as supported many capabilities it could support. I'll add them as supported as I write tests. Signed-off-by: Nathan Gauër <brioche@google.com>
This commit is contained in:
parent
ec90d2872a
commit
35d8b05de4
@ -981,6 +981,17 @@ Optimizer::PassToken CreateRemoveDontInlinePass();
|
||||
// object, currently the pass would remove accesschain pointer argument passed
|
||||
// to the function
|
||||
Optimizer::PassToken CreateFixFuncCallArgumentsPass();
|
||||
|
||||
// Creates a trim-capabilities pass.
|
||||
// This pass removes unused capabilities for a given module, and if possible,
|
||||
// associated extensions.
|
||||
// See `trim_capabilities.h` for the list of supported capabilities.
|
||||
//
|
||||
// If the module contains unsupported capabilities, this pass will ignore them.
|
||||
// This should be fine in most cases, but could yield to incorrect results if
|
||||
// the unknown capability interacts with one of the trimmed capabilities.
|
||||
Optimizer::PassToken CreateTrimCapabilitiesPass();
|
||||
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
|
||||
|
@ -122,6 +122,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
|
||||
strip_nonsemantic_info_pass.h
|
||||
struct_cfg_analysis.h
|
||||
tree_iterator.h
|
||||
trim_capabilities_pass.h
|
||||
type_manager.h
|
||||
types.h
|
||||
unify_const_pass.h
|
||||
@ -236,6 +237,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
|
||||
strip_debug_info_pass.cpp
|
||||
strip_nonsemantic_info_pass.cpp
|
||||
struct_cfg_analysis.cpp
|
||||
trim_capabilities_pass.cpp
|
||||
type_manager.cpp
|
||||
types.cpp
|
||||
unify_const_pass.cpp
|
||||
|
@ -154,6 +154,12 @@ class IRContext {
|
||||
inline IteratorRange<Module::inst_iterator> capabilities();
|
||||
inline IteratorRange<Module::const_inst_iterator> capabilities() const;
|
||||
|
||||
// Iterators for extensions instructions contained in this module.
|
||||
inline Module::inst_iterator extension_begin();
|
||||
inline Module::inst_iterator extension_end();
|
||||
inline IteratorRange<Module::inst_iterator> extensions();
|
||||
inline IteratorRange<Module::const_inst_iterator> extensions() const;
|
||||
|
||||
// Iterators for types, constants and global variables instructions.
|
||||
inline Module::inst_iterator types_values_begin();
|
||||
inline Module::inst_iterator types_values_end();
|
||||
@ -982,6 +988,22 @@ IteratorRange<Module::const_inst_iterator> IRContext::capabilities() const {
|
||||
return ((const Module*)module())->capabilities();
|
||||
}
|
||||
|
||||
Module::inst_iterator IRContext::extension_begin() {
|
||||
return module()->extension_begin();
|
||||
}
|
||||
|
||||
Module::inst_iterator IRContext::extension_end() {
|
||||
return module()->extension_end();
|
||||
}
|
||||
|
||||
IteratorRange<Module::inst_iterator> IRContext::extensions() {
|
||||
return module()->extensions();
|
||||
}
|
||||
|
||||
IteratorRange<Module::const_inst_iterator> IRContext::extensions() const {
|
||||
return ((const Module*)module())->extensions();
|
||||
}
|
||||
|
||||
Module::inst_iterator IRContext::types_values_begin() {
|
||||
return module()->types_values_begin();
|
||||
}
|
||||
|
@ -82,6 +82,7 @@
|
||||
#include "source/opt/strength_reduction_pass.h"
|
||||
#include "source/opt/strip_debug_info_pass.h"
|
||||
#include "source/opt/strip_nonsemantic_info_pass.h"
|
||||
#include "source/opt/trim_capabilities_pass.h"
|
||||
#include "source/opt/unify_const_pass.h"
|
||||
#include "source/opt/upgrade_memory_model.h"
|
||||
#include "source/opt/vector_dce.h"
|
||||
|
320
source/opt/trim_capabilities_pass.cpp
Normal file
320
source/opt/trim_capabilities_pass.cpp
Normal file
@ -0,0 +1,320 @@
|
||||
// Copyright (c) 2023 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "source/opt/trim_capabilities_pass.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "source/enum_set.h"
|
||||
#include "source/enum_string_mapping.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/spirv_target_env.h"
|
||||
#include "source/util/string_utils.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
namespace {
|
||||
constexpr uint32_t kVariableStorageClassIndex = 0;
|
||||
constexpr uint32_t kTypeArrayTypeIndex = 0;
|
||||
constexpr uint32_t kOpTypeScalarBitWidthIndex = 0;
|
||||
constexpr uint32_t kTypePointerTypeIdInIdx = 1;
|
||||
} // namespace
|
||||
|
||||
// ============== Begin opcode handler implementations. =======================
|
||||
//
|
||||
// Adding support for a new capability should only require adding a new handler,
|
||||
// and updating the
|
||||
// kSupportedCapabilities/kUntouchableCapabilities/kFordiddenCapabilities lists.
|
||||
//
|
||||
// Handler names follow the following convention:
|
||||
// Handler_<Opcode>_<Capability>()
|
||||
|
||||
static std::optional<spv::Capability> Handler_OpVariable_StorageInputOutput16(
|
||||
const Instruction* instruction) {
|
||||
assert(instruction->opcode() == spv::Op::OpVariable &&
|
||||
"This handler only support OpVariable opcodes.");
|
||||
|
||||
// This capability is only required if the variable as an Input/Output storage
|
||||
// class.
|
||||
spv::StorageClass storage_class = spv::StorageClass(
|
||||
instruction->GetSingleWordInOperand(kVariableStorageClassIndex));
|
||||
if (storage_class != spv::StorageClass::Input &&
|
||||
storage_class != spv::StorageClass::Output) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// This capability is only required if the type involves a 16-bit component.
|
||||
// Quick check: are 16-bit types allowed?
|
||||
const CapabilitySet& capabilities =
|
||||
instruction->context()->get_feature_mgr()->GetCapabilities();
|
||||
if (!capabilities.contains(spv::Capability::Float16) &&
|
||||
!capabilities.contains(spv::Capability::Int16)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// We need to walk the type definition.
|
||||
std::queue<uint32_t> instructions_to_visit;
|
||||
instructions_to_visit.push(instruction->type_id());
|
||||
const auto* def_use_mgr = instruction->context()->get_def_use_mgr();
|
||||
while (!instructions_to_visit.empty()) {
|
||||
const Instruction* item =
|
||||
def_use_mgr->GetDef(instructions_to_visit.front());
|
||||
instructions_to_visit.pop();
|
||||
|
||||
if (item->opcode() == spv::Op::OpTypePointer) {
|
||||
instructions_to_visit.push(
|
||||
item->GetSingleWordInOperand(kTypePointerTypeIdInIdx));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item->opcode() == spv::Op::OpTypeMatrix ||
|
||||
item->opcode() == spv::Op::OpTypeVector ||
|
||||
item->opcode() == spv::Op::OpTypeArray ||
|
||||
item->opcode() == spv::Op::OpTypeRuntimeArray) {
|
||||
instructions_to_visit.push(
|
||||
item->GetSingleWordInOperand(kTypeArrayTypeIndex));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item->opcode() == spv::Op::OpTypeStruct) {
|
||||
item->ForEachInOperand([&instructions_to_visit](const uint32_t* op_id) {
|
||||
instructions_to_visit.push(*op_id);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item->opcode() != spv::Op::OpTypeInt &&
|
||||
item->opcode() != spv::Op::OpTypeFloat) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item->GetSingleWordInOperand(kOpTypeScalarBitWidthIndex) == 16) {
|
||||
return spv::Capability::StorageInputOutput16;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Opcode of interest to determine capabilities requirements.
|
||||
constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 1> kOpcodeHandlers{{
|
||||
{spv::Op::OpVariable, Handler_OpVariable_StorageInputOutput16},
|
||||
}};
|
||||
|
||||
// ============== End opcode handler implementations. =======================
|
||||
|
||||
namespace {
|
||||
ExtensionSet getExtensionsRelatedTo(const CapabilitySet& capabilities,
|
||||
const AssemblyGrammar& grammar) {
|
||||
ExtensionSet output;
|
||||
const spv_operand_desc_t* desc = nullptr;
|
||||
for (auto capability : capabilities) {
|
||||
if (SPV_SUCCESS != grammar.lookupOperand(SPV_OPERAND_TYPE_CAPABILITY,
|
||||
static_cast<uint32_t>(capability),
|
||||
&desc)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < desc->numExtensions; ++i) {
|
||||
output.insert(desc->extensions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TrimCapabilitiesPass::TrimCapabilitiesPass()
|
||||
: supportedCapabilities_(
|
||||
TrimCapabilitiesPass::kSupportedCapabilities.cbegin(),
|
||||
TrimCapabilitiesPass::kSupportedCapabilities.cend()),
|
||||
forbiddenCapabilities_(
|
||||
TrimCapabilitiesPass::kForbiddenCapabilities.cbegin(),
|
||||
TrimCapabilitiesPass::kForbiddenCapabilities.cend()),
|
||||
untouchableCapabilities_(
|
||||
TrimCapabilitiesPass::kUntouchableCapabilities.cbegin(),
|
||||
TrimCapabilitiesPass::kUntouchableCapabilities.cend()),
|
||||
opcodeHandlers_(kOpcodeHandlers.cbegin(), kOpcodeHandlers.cend()) {}
|
||||
|
||||
void TrimCapabilitiesPass::addInstructionRequirements(
|
||||
Instruction* instruction, CapabilitySet* capabilities,
|
||||
ExtensionSet* extensions) const {
|
||||
// Ignoring OpCapability instructions.
|
||||
if (instruction->opcode() == spv::Op::OpCapability) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First case: the opcode is itself gated by a capability.
|
||||
{
|
||||
const spv_opcode_desc_t* desc = {};
|
||||
auto result =
|
||||
context()->grammar().lookupOpcode(instruction->opcode(), &desc);
|
||||
if (result == SPV_SUCCESS) {
|
||||
addSupportedCapabilitiesToSet(desc->numCapabilities, desc->capabilities,
|
||||
capabilities);
|
||||
if (desc->minVersion <=
|
||||
spvVersionForTargetEnv(context()->GetTargetEnv())) {
|
||||
extensions->insert(desc->extensions,
|
||||
desc->extensions + desc->numExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second case: one of the opcode operand is gated by a capability.
|
||||
const uint32_t operandCount = instruction->NumOperands();
|
||||
for (uint32_t i = 0; i < operandCount; i++) {
|
||||
const auto& operand = instruction->GetOperand(i);
|
||||
// No supported capability relies on a 2+-word operand.
|
||||
if (operand.words.size() != 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// No supported capability relies on a literal string operand.
|
||||
if (operand.type == SPV_OPERAND_TYPE_LITERAL_STRING) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const spv_operand_desc_t* desc = {};
|
||||
auto result = context()->grammar().lookupOperand(operand.type,
|
||||
operand.words[0], &desc);
|
||||
if (result != SPV_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addSupportedCapabilitiesToSet(desc->numCapabilities, desc->capabilities,
|
||||
capabilities);
|
||||
if (desc->minVersion <= spvVersionForTargetEnv(context()->GetTargetEnv())) {
|
||||
extensions->insert(desc->extensions,
|
||||
desc->extensions + desc->numExtensions);
|
||||
}
|
||||
}
|
||||
|
||||
// Last case: some complex logic needs to be run to determine capabilities.
|
||||
auto[begin, end] = opcodeHandlers_.equal_range(instruction->opcode());
|
||||
for (auto it = begin; it != end; it++) {
|
||||
const OpcodeHandler handler = it->second;
|
||||
auto result = handler(instruction);
|
||||
if (result.has_value()) {
|
||||
capabilities->insert(*result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<CapabilitySet, ExtensionSet>
|
||||
TrimCapabilitiesPass::DetermineRequiredCapabilitiesAndExtensions() const {
|
||||
CapabilitySet required_capabilities;
|
||||
ExtensionSet required_extensions;
|
||||
|
||||
get_module()->ForEachInst([&](Instruction* instruction) {
|
||||
addInstructionRequirements(instruction, &required_capabilities,
|
||||
&required_extensions);
|
||||
});
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
// Debug only. We check the outputted required capabilities against the
|
||||
// supported capabilities list. The supported capabilities list is useful for
|
||||
// API users to quickly determine if they can use the pass or not. But this
|
||||
// list has to remain up-to-date with the pass code. If we can detect a
|
||||
// capability as required, but it's not listed, it means the list is
|
||||
// out-of-sync. This method is not ideal, but should cover most cases.
|
||||
{
|
||||
for (auto capability : required_capabilities) {
|
||||
assert(supportedCapabilities_.contains(capability) &&
|
||||
"Module is using a capability that is not listed as supported.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return std::make_pair(std::move(required_capabilities),
|
||||
std::move(required_extensions));
|
||||
}
|
||||
|
||||
Pass::Status TrimCapabilitiesPass::TrimUnrequiredCapabilities(
|
||||
const CapabilitySet& required_capabilities) const {
|
||||
const FeatureManager* feature_manager = context()->get_feature_mgr();
|
||||
CapabilitySet capabilities_to_trim;
|
||||
for (auto capability : feature_manager->GetCapabilities()) {
|
||||
// Forbidden capability completely prevents trimming. Early exit.
|
||||
if (forbiddenCapabilities_.contains(capability)) {
|
||||
return Pass::Status::SuccessWithoutChange;
|
||||
}
|
||||
|
||||
// Some capabilities cannot be safely removed. Leaving them untouched.
|
||||
if (untouchableCapabilities_.contains(capability)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the capability is unsupported, don't trim it.
|
||||
if (!supportedCapabilities_.contains(capability)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (required_capabilities.contains(capability)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
capabilities_to_trim.insert(capability);
|
||||
}
|
||||
|
||||
for (auto capability : capabilities_to_trim) {
|
||||
context()->RemoveCapability(capability);
|
||||
}
|
||||
|
||||
return capabilities_to_trim.size() == 0 ? Pass::Status::SuccessWithoutChange
|
||||
: Pass::Status::SuccessWithChange;
|
||||
}
|
||||
|
||||
Pass::Status TrimCapabilitiesPass::TrimUnrequiredExtensions(
|
||||
const ExtensionSet& required_extensions) const {
|
||||
const auto supported_extensions =
|
||||
getExtensionsRelatedTo(supportedCapabilities_, context()->grammar());
|
||||
|
||||
bool modified_module = false;
|
||||
for (auto extension : supported_extensions) {
|
||||
if (!required_extensions.contains(extension)) {
|
||||
modified_module = true;
|
||||
context()->RemoveExtension(extension);
|
||||
}
|
||||
}
|
||||
|
||||
return modified_module ? Pass::Status::SuccessWithChange
|
||||
: Pass::Status::SuccessWithoutChange;
|
||||
}
|
||||
|
||||
Pass::Status TrimCapabilitiesPass::Process() {
|
||||
auto[required_capabilities, required_extensions] =
|
||||
DetermineRequiredCapabilitiesAndExtensions();
|
||||
|
||||
Pass::Status status = TrimUnrequiredCapabilities(required_capabilities);
|
||||
// If no capabilities were removed, we have no extension to trim.
|
||||
// Note: this is true because this pass only removes unused extensions caused
|
||||
// by unused capabilities.
|
||||
// This is not an extension trimming pass.
|
||||
if (status == Pass::Status::SuccessWithoutChange) {
|
||||
return status;
|
||||
}
|
||||
return TrimUnrequiredExtensions(required_extensions);
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
151
source/opt/trim_capabilities_pass.h
Normal file
151
source/opt/trim_capabilities_pass.h
Normal file
@ -0,0 +1,151 @@
|
||||
// Copyright (c) 2023 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SOURCE_OPT_TRIM_CAPABILITIES_PASS_H_
|
||||
#define SOURCE_OPT_TRIM_CAPABILITIES_PASS_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "source/enum_set.h"
|
||||
#include "source/extensions.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/opt/module.h"
|
||||
#include "source/opt/pass.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
// This is required for NDK build. The unordered_set/unordered_map
|
||||
// implementation don't work with class enums.
|
||||
struct ClassEnumHash {
|
||||
std::size_t operator()(spv::Capability value) const {
|
||||
using StoringType = typename std::underlying_type_t<spv::Capability>;
|
||||
return std::hash<StoringType>{}(static_cast<StoringType>(value));
|
||||
}
|
||||
|
||||
std::size_t operator()(spv::Op value) const {
|
||||
using StoringType = typename std::underlying_type_t<spv::Op>;
|
||||
return std::hash<StoringType>{}(static_cast<StoringType>(value));
|
||||
}
|
||||
};
|
||||
|
||||
// An opcode handler is a function which, given an instruction, returns either
|
||||
// the required capability, or nothing.
|
||||
// Each handler checks one case for a capability requirement.
|
||||
//
|
||||
// Example:
|
||||
// - `OpTypeImage` can have operand `A` operand which requires capability 1
|
||||
// - `OpTypeImage` can also have operand `B` which requires capability 2.
|
||||
// -> We have 2 handlers: `Handler_OpTypeImage_1` and
|
||||
// `Handler_OpTypeImage_2`.
|
||||
using OpcodeHandler =
|
||||
std::optional<spv::Capability> (*)(const Instruction* instruction);
|
||||
|
||||
// This pass tried to remove superfluous capabilities declared in the module.
|
||||
// - If all the capabilities listed by an extension are removed, the extension
|
||||
// is also trimmed.
|
||||
// - If the module countains any capability listed in `kForbiddenCapabilities`,
|
||||
// the module is left untouched.
|
||||
// - No capabilities listed in `kUntouchableCapabilities` are trimmed, even when
|
||||
// not used.
|
||||
// - Only capabilitied listed in `kSupportedCapabilities` are supported.
|
||||
// - If the module contains unsupported capabilities, results might be
|
||||
// incorrect.
|
||||
class TrimCapabilitiesPass : public Pass {
|
||||
private:
|
||||
// All the capabilities supported by this optimization pass. If your module
|
||||
// contains unsupported instruction, the pass could yield bad results.
|
||||
static constexpr std::array kSupportedCapabilities{
|
||||
// clang-format off
|
||||
spv::Capability::Groups,
|
||||
spv::Capability::Linkage,
|
||||
spv::Capability::MinLod,
|
||||
spv::Capability::Shader,
|
||||
spv::Capability::ShaderClockKHR,
|
||||
spv::Capability::StorageInputOutput16
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
// Those capabilities disable all transformation of the module.
|
||||
static constexpr std::array kForbiddenCapabilities{
|
||||
spv::Capability::Linkage,
|
||||
};
|
||||
|
||||
// Those capabilities are never removed from a module because we cannot
|
||||
// guess from the SPIR-V only if they are required or not.
|
||||
static constexpr std::array kUntouchableCapabilities{
|
||||
spv::Capability::Shader,
|
||||
};
|
||||
|
||||
public:
|
||||
TrimCapabilitiesPass();
|
||||
TrimCapabilitiesPass(const TrimCapabilitiesPass&) = delete;
|
||||
TrimCapabilitiesPass(TrimCapabilitiesPass&&) = delete;
|
||||
|
||||
private:
|
||||
// Inserts every capability in `capabilities[capabilityCount]` supported by
|
||||
// this pass into `output`.
|
||||
inline void addSupportedCapabilitiesToSet(
|
||||
uint32_t capabilityCount, const spv::Capability* const capabilities,
|
||||
CapabilitySet* output) const {
|
||||
for (uint32_t i = 0; i < capabilityCount; ++i) {
|
||||
if (supportedCapabilities_.contains(capabilities[i])) {
|
||||
output->insert(capabilities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Given an `instruction`, determines the capabilities and extension it
|
||||
// requires, and output them in `capabilities` and `extensions`. The returned
|
||||
// capabilities form a subset of kSupportedCapabilities.
|
||||
void addInstructionRequirements(Instruction* instruction,
|
||||
CapabilitySet* capabilities,
|
||||
ExtensionSet* extensions) const;
|
||||
|
||||
// Returns the list of required capabilities and extensions for the module.
|
||||
// The returned capabilities form a subset of kSupportedCapabilities.
|
||||
std::pair<CapabilitySet, ExtensionSet>
|
||||
DetermineRequiredCapabilitiesAndExtensions() const;
|
||||
|
||||
// Trims capabilities not listed in `required_capabilities` if possible.
|
||||
// Returns whether or not the module was modified.
|
||||
Pass::Status TrimUnrequiredCapabilities(
|
||||
const CapabilitySet& required_capabilities) const;
|
||||
|
||||
// Trims extensions not listed in `required_extensions` if supported by this
|
||||
// pass. An extensions is considered supported as soon as one capability this
|
||||
// pass support requires it.
|
||||
Pass::Status TrimUnrequiredExtensions(
|
||||
const ExtensionSet& required_extensions) const;
|
||||
|
||||
public:
|
||||
const char* name() const override { return "trim-capabilities"; }
|
||||
Status Process() override;
|
||||
|
||||
private:
|
||||
const CapabilitySet supportedCapabilities_;
|
||||
const CapabilitySet forbiddenCapabilities_;
|
||||
const CapabilitySet untouchableCapabilities_;
|
||||
const std::unordered_multimap<spv::Op, OpcodeHandler, ClassEnumHash>
|
||||
opcodeHandlers_;
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
#endif // SOURCE_OPT_TRIM_CAPABILITIES_H_
|
@ -103,6 +103,7 @@ add_spvtools_unittest(TARGET opt
|
||||
strip_debug_info_test.cpp
|
||||
strip_nonsemantic_info_test.cpp
|
||||
struct_cfg_analysis_test.cpp
|
||||
trim_capabilities_pass_test.cpp
|
||||
type_manager_test.cpp
|
||||
types_test.cpp
|
||||
unify_const_test.cpp
|
||||
|
746
test/opt/trim_capabilities_pass_test.cpp
Normal file
746
test/opt/trim_capabilities_pass_test.cpp
Normal file
@ -0,0 +1,746 @@
|
||||
// Copyright (c) 2023 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "spirv-tools/optimizer.hpp"
|
||||
#include "test/opt/pass_fixture.h"
|
||||
#include "test/opt/pass_utils.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
namespace {
|
||||
|
||||
using TrimCapabilitiesPassTest = PassTest<::testing::Test>;
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest, CheckKnownAliasTransformations) {
|
||||
// Those are expected changes caused by the test process:
|
||||
// - SPV is assembled. -> capability goes from text to number.
|
||||
// - SPV is optimized.
|
||||
// - SPV is disassembled -> capability goes from number to text.
|
||||
// - CHECK rule compares both text versions.
|
||||
// Because some capabilities share the same number (aliases), the text
|
||||
// compared with the CHECK rules depends on which alias is the first on the
|
||||
// SPIRV-Headers enum. This could change, and we want to easily distinguish
|
||||
// real failure from alias order change. This test is only here to list known
|
||||
// alias transformations. If this test breaks, it's not a bug in the
|
||||
// optimization pass, but just the SPIRV-Headers enum order that has changed.
|
||||
// If that happens, tests needs to be updated to the correct alias is used in
|
||||
// the CHECK rule.
|
||||
const std::string kTest = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability StorageUniform16
|
||||
OpCapability StorageUniformBufferBlock16
|
||||
OpCapability ShaderViewportIndexLayerNV
|
||||
OpCapability FragmentBarycentricNV
|
||||
OpCapability ShadingRateNV
|
||||
OpCapability ShaderNonUniformEXT
|
||||
OpCapability RuntimeDescriptorArrayEXT
|
||||
OpCapability InputAttachmentArrayDynamicIndexingEXT
|
||||
OpCapability UniformTexelBufferArrayDynamicIndexingEXT
|
||||
OpCapability StorageTexelBufferArrayDynamicIndexingEXT
|
||||
OpCapability UniformBufferArrayNonUniformIndexingEXT
|
||||
OpCapability SampledImageArrayNonUniformIndexingEXT
|
||||
OpCapability StorageBufferArrayNonUniformIndexingEXT
|
||||
OpCapability StorageImageArrayNonUniformIndexingEXT
|
||||
OpCapability InputAttachmentArrayNonUniformIndexingEXT
|
||||
OpCapability UniformTexelBufferArrayNonUniformIndexingEXT
|
||||
OpCapability StorageTexelBufferArrayNonUniformIndexingEXT
|
||||
OpCapability VulkanMemoryModelKHR
|
||||
OpCapability VulkanMemoryModelDeviceScopeKHR
|
||||
OpCapability PhysicalStorageBufferAddressesEXT
|
||||
OpCapability DemoteToHelperInvocationEXT
|
||||
OpCapability DotProductInputAllKHR
|
||||
OpCapability DotProductInput4x8BitKHR
|
||||
OpCapability DotProductInput4x8BitPackedKHR
|
||||
OpCapability DotProductKHR
|
||||
; CHECK: OpCapability Linkage
|
||||
; CHECK-NOT: OpCapability StorageUniform16
|
||||
; CHECK-NOT: OpCapability StorageUniformBufferBlock16
|
||||
; CHECK-NOT: OpCapability ShaderViewportIndexLayerNV
|
||||
; CHECK-NOT: OpCapability FragmentBarycentricNV
|
||||
; CHECK-NOT: OpCapability ShadingRateNV
|
||||
; CHECK-NOT: OpCapability ShaderNonUniformEXT
|
||||
; CHECK-NOT: OpCapability RuntimeDescriptorArrayEXT
|
||||
; CHECK-NOT: OpCapability InputAttachmentArrayDynamicIndexingEXT
|
||||
; CHECK-NOT: OpCapability UniformTexelBufferArrayDynamicIndexingEXT
|
||||
; CHECK-NOT: OpCapability StorageTexelBufferArrayDynamicIndexingEXT
|
||||
; CHECK-NOT: OpCapability UniformBufferArrayNonUniformIndexingEXT
|
||||
; CHECK-NOT: OpCapability SampledImageArrayNonUniformIndexingEXT
|
||||
; CHECK-NOT: OpCapability StorageBufferArrayNonUniformIndexingEXT
|
||||
; CHECK-NOT: OpCapability StorageImageArrayNonUniformIndexingEXT
|
||||
; CHECK-NOT: OpCapability InputAttachmentArrayNonUniformIndexingEXT
|
||||
; CHECK-NOT: OpCapability UniformTexelBufferArrayNonUniformIndexingEXT
|
||||
; CHECK-NOT: OpCapability StorageTexelBufferArrayNonUniformIndexingEXT
|
||||
; CHECK-NOT: OpCapability VulkanMemoryModelKHR
|
||||
; CHECK-NOT: OpCapability VulkanMemoryModelDeviceScopeKHR
|
||||
; CHECK-NOT: OpCapability PhysicalStorageBufferAddressesEXT
|
||||
; CHECK-NOT: OpCapability DemoteToHelperInvocationEXT
|
||||
; CHECK-NOT: OpCapability DotProductInputAllKHR
|
||||
; CHECK-NOT: OpCapability DotProductInput4x8BitKHR
|
||||
; CHECK-NOT: OpCapability DotProductInput4x8BitPackedKHR
|
||||
; CHECK-NOT: OpCapability DotProductKHR
|
||||
; CHECK: OpCapability UniformAndStorageBuffer16BitAccess
|
||||
; CHECK: OpCapability StorageBuffer16BitAccess
|
||||
; CHECK: OpCapability ShaderViewportIndexLayerEXT
|
||||
; CHECK: OpCapability FragmentBarycentricKHR
|
||||
; CHECK: OpCapability FragmentDensityEXT
|
||||
; CHECK: OpCapability ShaderNonUniform
|
||||
; CHECK: OpCapability RuntimeDescriptorArray
|
||||
; CHECK: OpCapability InputAttachmentArrayDynamicIndexing
|
||||
; CHECK: OpCapability UniformTexelBufferArrayDynamicIndexing
|
||||
; CHECK: OpCapability StorageTexelBufferArrayDynamicIndexing
|
||||
; CHECK: OpCapability UniformBufferArrayNonUniformIndexing
|
||||
; CHECK: OpCapability SampledImageArrayNonUniformIndexing
|
||||
; CHECK: OpCapability StorageBufferArrayNonUniformIndexing
|
||||
; CHECK: OpCapability StorageImageArrayNonUniformIndexing
|
||||
; CHECK: OpCapability InputAttachmentArrayNonUniformIndexing
|
||||
; CHECK: OpCapability UniformTexelBufferArrayNonUniformIndexing
|
||||
; CHECK: OpCapability StorageTexelBufferArrayNonUniformIndexing
|
||||
; CHECK: OpCapability VulkanMemoryModel
|
||||
; CHECK: OpCapability VulkanMemoryModelDeviceScope
|
||||
; CHECK: OpCapability PhysicalStorageBufferAddresses
|
||||
; CHECK: OpCapability DemoteToHelperInvocation
|
||||
; CHECK: OpCapability DotProductInputAll
|
||||
; CHECK: OpCapability DotProductInput4x8Bit
|
||||
; CHECK: OpCapability DotProductInput4x8BitPacked
|
||||
; CHECK: OpCapability DotProduct
|
||||
OpMemoryModel Logical Vulkan
|
||||
OpEntryPoint GLCompute %1 "main"
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%1 = OpFunction %void None %3
|
||||
%6 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd;
|
||||
)";
|
||||
SetTargetEnv(SPV_ENV_VULKAN_1_3);
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<EmptyPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest, LinkagePreventsChanges) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Linkage
|
||||
OpCapability ClipDistance
|
||||
OpCapability CullDistance
|
||||
OpCapability DemoteToHelperInvocation
|
||||
OpCapability DeviceGroup
|
||||
OpCapability DrawParameters
|
||||
OpCapability Float16
|
||||
OpCapability Float64
|
||||
OpCapability FragmentBarycentricKHR
|
||||
OpCapability FragmentFullyCoveredEXT
|
||||
OpCapability FragmentShadingRateKHR
|
||||
OpCapability GroupNonUniform
|
||||
OpCapability GroupNonUniformArithmetic
|
||||
OpCapability GroupNonUniformBallot
|
||||
OpCapability GroupNonUniformQuad
|
||||
OpCapability GroupNonUniformShuffle
|
||||
OpCapability Image1D
|
||||
OpCapability ImageBuffer
|
||||
OpCapability ImageGatherExtended
|
||||
OpCapability ImageMSArray
|
||||
OpCapability ImageQuery
|
||||
OpCapability InputAttachment
|
||||
OpCapability InputAttachmentArrayNonUniformIndexing
|
||||
OpCapability Int16
|
||||
OpCapability Int64
|
||||
OpCapability Int64Atomics
|
||||
OpCapability Int64ImageEXT
|
||||
OpCapability MeshShadingNV
|
||||
OpCapability MinLod
|
||||
OpCapability MultiView
|
||||
OpCapability MultiViewport
|
||||
OpCapability PhysicalStorageBufferAddresses
|
||||
OpCapability RayQueryKHR
|
||||
OpCapability RayTracingKHR
|
||||
OpCapability RayTracingNV
|
||||
OpCapability RayTraversalPrimitiveCullingKHR
|
||||
OpCapability RuntimeDescriptorArray
|
||||
OpCapability SampleMaskPostDepthCoverage
|
||||
OpCapability SampleRateShading
|
||||
OpCapability Sampled1D
|
||||
OpCapability SampledBuffer
|
||||
OpCapability SampledImageArrayNonUniformIndexing
|
||||
OpCapability Shader
|
||||
OpCapability ShaderClockKHR
|
||||
OpCapability ShaderLayer
|
||||
OpCapability ShaderNonUniform
|
||||
OpCapability ShaderViewportIndex
|
||||
OpCapability ShaderViewportIndexLayerEXT
|
||||
OpCapability SparseResidency
|
||||
OpCapability StencilExportEXT
|
||||
OpCapability StorageImageArrayNonUniformIndexingEXT
|
||||
OpCapability StorageImageExtendedFormats
|
||||
OpCapability StorageImageReadWithoutFormat
|
||||
OpCapability StorageImageWriteWithoutFormat
|
||||
OpCapability StorageInputOutput16
|
||||
OpCapability StoragePushConstant16
|
||||
OpCapability StorageTexelBufferArrayNonUniformIndexing
|
||||
OpCapability StorageUniform16
|
||||
OpCapability StorageUniformBufferBlock16
|
||||
OpCapability Tessellation
|
||||
OpCapability UniformTexelBufferArrayNonUniformIndexing
|
||||
OpCapability VulkanMemoryModel
|
||||
OpExtension "SPV_EXT_fragment_fully_covered"
|
||||
OpExtension "SPV_EXT_shader_image_int64"
|
||||
OpExtension "SPV_EXT_shader_stencil_export"
|
||||
OpExtension "SPV_EXT_shader_viewport_index_layer"
|
||||
OpExtension "SPV_KHR_fragment_shader_barycentric"
|
||||
OpExtension "SPV_KHR_fragment_shading_rate"
|
||||
OpExtension "SPV_KHR_post_depth_coverage"
|
||||
OpExtension "SPV_KHR_ray_query"
|
||||
OpExtension "SPV_KHR_ray_tracing"
|
||||
OpExtension "SPV_KHR_shader_clock"
|
||||
OpExtension "SPV_NV_mesh_shader"
|
||||
OpExtension "SPV_NV_ray_tracing"
|
||||
OpExtension "SPV_NV_viewport_array2"
|
||||
; CHECK: OpCapability Linkage
|
||||
; CHECK: OpCapability ClipDistance
|
||||
; CHECK: OpCapability CullDistance
|
||||
; CHECK: OpCapability DemoteToHelperInvocation
|
||||
; CHECK: OpCapability DeviceGroup
|
||||
; CHECK: OpCapability DrawParameters
|
||||
; CHECK: OpCapability Float16
|
||||
; CHECK: OpCapability Float64
|
||||
; CHECK: OpCapability FragmentBarycentricKHR
|
||||
; CHECK: OpCapability FragmentFullyCoveredEXT
|
||||
; CHECK: OpCapability FragmentShadingRateKHR
|
||||
; CHECK: OpCapability GroupNonUniform
|
||||
; CHECK: OpCapability GroupNonUniformArithmetic
|
||||
; CHECK: OpCapability GroupNonUniformBallot
|
||||
; CHECK: OpCapability GroupNonUniformQuad
|
||||
; CHECK: OpCapability GroupNonUniformShuffle
|
||||
; CHECK: OpCapability Image1D
|
||||
; CHECK: OpCapability ImageBuffer
|
||||
; CHECK: OpCapability ImageGatherExtended
|
||||
; CHECK: OpCapability ImageMSArray
|
||||
; CHECK: OpCapability ImageQuery
|
||||
; CHECK: OpCapability InputAttachment
|
||||
; CHECK: OpCapability InputAttachmentArrayNonUniformIndexing
|
||||
; CHECK: OpCapability Int16
|
||||
; CHECK: OpCapability Int64
|
||||
; CHECK: OpCapability Int64Atomics
|
||||
; CHECK: OpCapability Int64ImageEXT
|
||||
; CHECK: OpCapability MeshShadingNV
|
||||
; CHECK: OpCapability MinLod
|
||||
; CHECK: OpCapability MultiView
|
||||
; CHECK: OpCapability MultiViewport
|
||||
; CHECK: OpCapability PhysicalStorageBufferAddresses
|
||||
; CHECK: OpCapability RayQueryKHR
|
||||
; CHECK: OpCapability RayTracingKHR
|
||||
; CHECK: OpCapability RayTracingNV
|
||||
; CHECK: OpCapability RayTraversalPrimitiveCullingKHR
|
||||
; CHECK: OpCapability RuntimeDescriptorArray
|
||||
; CHECK: OpCapability SampleMaskPostDepthCoverage
|
||||
; CHECK: OpCapability SampleRateShading
|
||||
; CHECK: OpCapability Sampled1D
|
||||
; CHECK: OpCapability SampledBuffer
|
||||
; CHECK: OpCapability SampledImageArrayNonUniformIndexing
|
||||
; CHECK: OpCapability Shader
|
||||
; CHECK: OpCapability ShaderClockKHR
|
||||
; CHECK: OpCapability ShaderLayer
|
||||
; CHECK: OpCapability ShaderNonUniform
|
||||
; CHECK: OpCapability ShaderViewportIndex
|
||||
; CHECK: OpCapability ShaderViewportIndexLayerEXT
|
||||
; CHECK: OpCapability SparseResidency
|
||||
; CHECK: OpCapability StencilExportEXT
|
||||
; CHECK: OpCapability StorageImageArrayNonUniformIndexing
|
||||
; CHECK: OpCapability StorageImageExtendedFormats
|
||||
; CHECK: OpCapability StorageImageReadWithoutFormat
|
||||
; CHECK: OpCapability StorageImageWriteWithoutFormat
|
||||
; CHECK: OpCapability StorageInputOutput16
|
||||
; CHECK: OpCapability StoragePushConstant16
|
||||
; CHECK: OpCapability StorageTexelBufferArrayNonUniformIndexing
|
||||
; CHECK: OpCapability Tessellation
|
||||
; CHECK: OpCapability UniformTexelBufferArrayNonUniformIndex
|
||||
; CHECK: OpCapability VulkanMemoryModel
|
||||
; CHECK: OpExtension "SPV_EXT_fragment_fully_covered"
|
||||
; CHECK: OpExtension "SPV_EXT_shader_image_int64"
|
||||
; CHECK: OpExtension "SPV_EXT_shader_stencil_export"
|
||||
; CHECK: OpExtension "SPV_EXT_shader_viewport_index_layer"
|
||||
; CHECK: OpExtension "SPV_KHR_fragment_shader_barycentric"
|
||||
; CHECK: OpExtension "SPV_KHR_fragment_shading_rate"
|
||||
; CHECK: OpExtension "SPV_KHR_post_depth_coverage"
|
||||
; CHECK: OpExtension "SPV_KHR_ray_query"
|
||||
; CHECK: OpExtension "SPV_KHR_ray_tracing"
|
||||
; CHECK: OpExtension "SPV_KHR_shader_clock"
|
||||
; CHECK: OpExtension "SPV_NV_mesh_shader"
|
||||
; CHECK: OpExtension "SPV_NV_ray_tracing"
|
||||
; CHECK: OpExtension "SPV_NV_viewport_array2"
|
||||
OpMemoryModel Logical Vulkan
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%1 = OpFunction %void None %3
|
||||
%6 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd;
|
||||
)";
|
||||
SetTargetEnv(SPV_ENV_VULKAN_1_3);
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest, KeepShader) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
; CHECK: OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "main"
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%1 = OpFunction %void None %3
|
||||
%6 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd;
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest, KeepShaderClockWhenInUse) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Int64
|
||||
OpCapability ShaderClockKHR
|
||||
OpExtension "SPV_KHR_shader_clock"
|
||||
; CHECK: OpCapability ShaderClockKHR
|
||||
; CHECK: OpExtension "SPV_KHR_shader_clock"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "main"
|
||||
%void = OpTypeVoid
|
||||
%uint = OpTypeInt 32 0
|
||||
%ulong = OpTypeInt 64 0
|
||||
%scope = OpConstant %uint 1
|
||||
%3 = OpTypeFunction %void
|
||||
%1 = OpFunction %void None %3
|
||||
%6 = OpLabel
|
||||
%7 = OpReadClockKHR %ulong %scope
|
||||
OpReturn
|
||||
OpFunctionEnd;
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest, TrimShaderClockWhenUnused) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Int64
|
||||
OpCapability ShaderClockKHR
|
||||
OpExtension "SPV_KHR_shader_clock"
|
||||
; CHECK-NOT: OpCapability ShaderClockKHR
|
||||
; CHECK-NOT: OpExtension "SPV_KHR_shader_clock"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "main"
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%1 = OpFunction %void None %3
|
||||
%6 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd;
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest, AMDShaderBallotExtensionRemains) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Groups
|
||||
OpExtension "SPV_AMD_shader_ballot"
|
||||
; CHECK: OpCapability Groups
|
||||
; CHECK: OpExtension "SPV_AMD_shader_ballot"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "main"
|
||||
%void = OpTypeVoid
|
||||
%uint = OpTypeInt 32 0
|
||||
%1 = OpTypeFunction %void
|
||||
%uint_0 = OpConstant %uint 0
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
%4 = OpGroupIAddNonUniformAMD %uint %uint_0 ExclusiveScan %uint_0
|
||||
OpReturn
|
||||
OpFunctionEnd;
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest, AMDShaderBallotExtensionRemoved) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Groups
|
||||
OpExtension "SPV_AMD_shader_ballot"
|
||||
; CHECK-NOT: OpCapability Groups
|
||||
; CHECK-NOT: OpExtension "SPV_AMD_shader_ballot"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "main"
|
||||
%void = OpTypeVoid
|
||||
%1 = OpTypeFunction %void
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd;
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest, MinLodCapabilityRemoved) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Sampled1D
|
||||
OpCapability MinLod
|
||||
; CHECK-NOT: OpCapability MinLod
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "main"
|
||||
%void = OpTypeVoid
|
||||
%float = OpTypeFloat 32
|
||||
%v3float = OpTypeVector %float 3
|
||||
%v4float = OpTypeVector %float 4
|
||||
%type_image = OpTypeImage %float Cube 2 0 0 1 Rgba32f
|
||||
%ptr_type_image = OpTypePointer UniformConstant %type_image
|
||||
%type_sampler = OpTypeSampler
|
||||
%ptr_type_sampler = OpTypePointer UniformConstant %type_sampler
|
||||
%float_0 = OpConstant %float 0
|
||||
%float_000 = OpConstantComposite %v3float %float_0 %float_0 %float_0
|
||||
%image = OpVariable %ptr_type_image UniformConstant
|
||||
%sampler = OpVariable %ptr_type_sampler UniformConstant
|
||||
%1 = OpTypeFunction %void
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
%21 = OpLoad %type_image %image
|
||||
%22 = OpLoad %type_sampler %sampler
|
||||
%24 = OpSampledImage %type_sampled_image %21 %22
|
||||
%25 = OpImageSampleImplicitLod %v4float %24 %float_000
|
||||
OpReturn
|
||||
OpFunctionEnd;
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest, MinLodCapabilityRemains) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Sampled1D
|
||||
OpCapability MinLod
|
||||
; CHECK: OpCapability MinLod
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "main"
|
||||
%void = OpTypeVoid
|
||||
%float = OpTypeFloat 32
|
||||
%v3float = OpTypeVector %float 3
|
||||
%v4float = OpTypeVector %float 4
|
||||
%type_image = OpTypeImage %float Cube 2 0 0 1 Rgba32f
|
||||
%ptr_type_image = OpTypePointer UniformConstant %type_image
|
||||
%type_sampler = OpTypeSampler
|
||||
%ptr_type_sampler = OpTypePointer UniformConstant %type_sampler
|
||||
%float_0 = OpConstant %float 0
|
||||
%float_000 = OpConstantComposite %v3float %float_0 %float_0 %float_0
|
||||
%image = OpVariable %ptr_type_image UniformConstant
|
||||
%sampler = OpVariable %ptr_type_sampler UniformConstant
|
||||
%1 = OpTypeFunction %void
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
%21 = OpLoad %type_image %image
|
||||
%22 = OpLoad %type_sampler %sampler
|
||||
%24 = OpSampledImage %type_sampled_image %21 %22
|
||||
%25 = OpImageSampleImplicitLod %v4float %24 %float_000 MinLod %float_0
|
||||
OpReturn
|
||||
OpFunctionEnd;
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest, StorageInputOutput16RemainsWithInputVariable) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Float16
|
||||
OpCapability StorageInputOutput16
|
||||
; CHECK: OpCapability StorageInputOutput16
|
||||
OpExtension "SPV_KHR_16bit_storage"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %2 "main" %input_var
|
||||
OpDecorate %input_var BuiltIn LocalInvocationIndex
|
||||
%void = OpTypeVoid
|
||||
%half = OpTypeFloat 16
|
||||
%ptr = OpTypePointer Input %half
|
||||
%1 = OpTypeFunction %void
|
||||
%input_var = OpVariable %ptr Input
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest,
|
||||
StorageInputOutput16RemainsWithInputVariableArray) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Float16
|
||||
OpCapability StorageInputOutput16
|
||||
; CHECK: OpCapability StorageInputOutput16
|
||||
OpExtension "SPV_KHR_16bit_storage"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %2 "main" %input_var
|
||||
OpDecorate %input_var BuiltIn LocalInvocationIndex
|
||||
%void = OpTypeVoid
|
||||
%half = OpTypeFloat 16
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_1 = OpConstant %uint 1
|
||||
%array = OpTypeArray %half %uint_1
|
||||
%ptr = OpTypePointer Input %array
|
||||
%1 = OpTypeFunction %void
|
||||
%input_var = OpVariable %ptr Input
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest,
|
||||
StorageInputOutput16RemainsWithInputVariableStruct) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Float16
|
||||
OpCapability StorageInputOutput16
|
||||
; CHECK: OpCapability StorageInputOutput16
|
||||
OpExtension "SPV_KHR_16bit_storage"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %2 "main" %input_var
|
||||
OpDecorate %input_var BuiltIn LocalInvocationIndex
|
||||
%void = OpTypeVoid
|
||||
%half = OpTypeFloat 16
|
||||
%struct = OpTypeStruct %half
|
||||
%ptr = OpTypePointer Input %struct
|
||||
%1 = OpTypeFunction %void
|
||||
%input_var = OpVariable %ptr Input
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest,
|
||||
StorageInputOutput16RemainsWithInputVariableStructOfStruct) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Float16
|
||||
OpCapability StorageInputOutput16
|
||||
; CHECK: OpCapability StorageInputOutput16
|
||||
OpExtension "SPV_KHR_16bit_storage"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %2 "main" %input_var
|
||||
OpDecorate %input_var BuiltIn LocalInvocationIndex
|
||||
%void = OpTypeVoid
|
||||
%half = OpTypeFloat 16
|
||||
%float = OpTypeFloat 32
|
||||
%struct = OpTypeStruct %float %half
|
||||
%parent = OpTypeStruct %float %struct
|
||||
%ptr = OpTypePointer Input %parent
|
||||
%1 = OpTypeFunction %void
|
||||
%input_var = OpVariable %ptr Input
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest,
|
||||
StorageInputOutput16RemainsWithInputVariableArrayOfStruct) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Float16
|
||||
OpCapability StorageInputOutput16
|
||||
; CHECK: OpCapability StorageInputOutput16
|
||||
OpExtension "SPV_KHR_16bit_storage"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %2 "main" %input_var
|
||||
OpDecorate %input_var BuiltIn LocalInvocationIndex
|
||||
%void = OpTypeVoid
|
||||
%half = OpTypeFloat 16
|
||||
%struct = OpTypeStruct %half
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_1 = OpConstant %uint 1
|
||||
%array = OpTypeArray %struct %uint_1
|
||||
%ptr = OpTypePointer Input %array
|
||||
%1 = OpTypeFunction %void
|
||||
%input_var = OpVariable %ptr Input
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest,
|
||||
StorageInputOutput16RemainsWithInputVariableVector) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Float16
|
||||
OpCapability StorageInputOutput16
|
||||
; CHECK: OpCapability StorageInputOutput16
|
||||
OpExtension "SPV_KHR_16bit_storage"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %2 "main" %input_var
|
||||
OpDecorate %input_var BuiltIn LocalInvocationIndex
|
||||
%void = OpTypeVoid
|
||||
%half = OpTypeFloat 16
|
||||
%vector = OpTypeVector %half 4
|
||||
%ptr = OpTypePointer Input %vector
|
||||
%1 = OpTypeFunction %void
|
||||
%input_var = OpVariable %ptr Input
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest,
|
||||
StorageInputOutput16RemainsWithInputVariableMatrix) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Float16
|
||||
OpCapability StorageInputOutput16
|
||||
; CHECK: OpCapability StorageInputOutput16
|
||||
OpExtension "SPV_KHR_16bit_storage"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %2 "main" %input_var
|
||||
OpDecorate %input_var BuiltIn LocalInvocationIndex
|
||||
%void = OpTypeVoid
|
||||
%half = OpTypeFloat 16
|
||||
%vector = OpTypeVector %half 4
|
||||
%matrix = OpTypeMatrix %vector 4
|
||||
%ptr = OpTypePointer Input %matrix
|
||||
%1 = OpTypeFunction %void
|
||||
%input_var = OpVariable %ptr Input
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest,
|
||||
StorageInputOutput16IsRemovedWithoutInputVariable) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Float16
|
||||
OpCapability StorageInputOutput16
|
||||
; CHECK-NOT: OpCapability StorageInputOutput16
|
||||
OpExtension "SPV_KHR_16bit_storage"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %2 "main" %input_var
|
||||
OpDecorate %input_var BuiltIn LocalInvocationIndex
|
||||
%void = OpTypeVoid
|
||||
%1 = OpTypeFunction %void
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest,
|
||||
StorageInputOutput16RemainsWithOutputVariable) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Float16
|
||||
OpCapability StorageInputOutput16
|
||||
; CHECK: OpCapability StorageInputOutput16
|
||||
OpExtension "SPV_KHR_16bit_storage"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %2 "main" %input_var
|
||||
OpDecorate %input_var BuiltIn LocalInvocationIndex
|
||||
%void = OpTypeVoid
|
||||
%half = OpTypeFloat 16
|
||||
%ptr = OpTypePointer Output %half
|
||||
%1 = OpTypeFunction %void
|
||||
%input_var = OpVariable %ptr Output
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
TEST_F(TrimCapabilitiesPassTest,
|
||||
StorageInputOutput16IsRemovedWithoutOutputVariable) {
|
||||
const std::string kTest = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Float16
|
||||
OpCapability StorageInputOutput16
|
||||
; CHECK-NOT: OpCapability StorageInputOutput16
|
||||
OpExtension "SPV_KHR_16bit_storage"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %2 "main" %input_var
|
||||
OpDecorate %input_var BuiltIn LocalInvocationIndex
|
||||
%void = OpTypeVoid
|
||||
%1 = OpTypeFunction %void
|
||||
%2 = OpFunction %void None %1
|
||||
%3 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
const auto result =
|
||||
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
|
||||
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
Loading…
Reference in New Issue
Block a user