Updated spirv-tools.
This commit is contained in:
parent
4386b7770e
commit
30a86bf538
2
3rdparty/spirv-tools/Android.mk
vendored
2
3rdparty/spirv-tools/Android.mk
vendored
@ -95,6 +95,7 @@ SPVTOOLS_OPT_SRC_FILES := \
|
||||
source/opt/decompose_initialized_variables_pass.cpp \
|
||||
source/opt/decoration_manager.cpp \
|
||||
source/opt/def_use_manager.cpp \
|
||||
source/opt/desc_sroa.cpp \
|
||||
source/opt/dominator_analysis.cpp \
|
||||
source/opt/dominator_tree.cpp \
|
||||
source/opt/eliminate_dead_constant_pass.cpp \
|
||||
@ -110,6 +111,7 @@ SPVTOOLS_OPT_SRC_FILES := \
|
||||
source/opt/freeze_spec_constant_value_pass.cpp \
|
||||
source/opt/function.cpp \
|
||||
source/opt/generate_webgpu_initializers_pass.cpp \
|
||||
source/opt/graphics_robust_access_pass.cpp \
|
||||
source/opt/if_conversion.cpp \
|
||||
source/opt/inline_pass.cpp \
|
||||
source/opt/inline_exhaustive_pass.cpp \
|
||||
|
4
3rdparty/spirv-tools/BUILD.gn
vendored
4
3rdparty/spirv-tools/BUILD.gn
vendored
@ -491,6 +491,8 @@ static_library("spvtools_opt") {
|
||||
"source/opt/decoration_manager.h",
|
||||
"source/opt/def_use_manager.cpp",
|
||||
"source/opt/def_use_manager.h",
|
||||
"source/opt/desc_sroa.cpp",
|
||||
"source/opt/desc_sroa.h",
|
||||
"source/opt/dominator_analysis.cpp",
|
||||
"source/opt/dominator_analysis.h",
|
||||
"source/opt/dominator_tree.cpp",
|
||||
@ -521,6 +523,8 @@ static_library("spvtools_opt") {
|
||||
"source/opt/function.h",
|
||||
"source/opt/generate_webgpu_initializers_pass.cpp",
|
||||
"source/opt/generate_webgpu_initializers_pass.h",
|
||||
"source/opt/graphics_robust_access_pass.cpp",
|
||||
"source/opt/graphics_robust_access_pass.h",
|
||||
"source/opt/if_conversion.cpp",
|
||||
"source/opt/if_conversion.h",
|
||||
"source/opt/inline_exhaustive_pass.cpp",
|
||||
|
71
3rdparty/spirv-tools/CHANGES
vendored
71
3rdparty/spirv-tools/CHANGES
vendored
@ -1,7 +1,74 @@
|
||||
Revision history for SPIRV-Tools
|
||||
|
||||
v2019.4-dev 2019-05-15
|
||||
- Start v2019.4-dev
|
||||
v2019.5-dev 2019-08-08
|
||||
- Start v2019.5-dev
|
||||
|
||||
v2019.4 2019-08-08
|
||||
- General:
|
||||
- Memory model support for SPIR-V 1.4
|
||||
- Add new spirv-fuzz tool
|
||||
- Add option for base branch in check_code_format.sh
|
||||
- Removed MarkV and Stats code. (#2576)
|
||||
- Instrument: Add version 2 of record formats (#2630)
|
||||
- Linker: Better type comparison for OpTypeArray and OpTypeForwardPointer (#2580)
|
||||
- Optimizer
|
||||
- Bindless Validation: Instrument descriptor-based loads and stores (#2583)
|
||||
- Better folding for OpSpecConstantOp (#2585, #2614)
|
||||
- Add in individual flags for Vulkan <-> WebGPU passes (#2615)
|
||||
- Handle nested breaks from switches. (#2624)
|
||||
- Optimizer: Handle array type with OpSpecConstantOp length (#2652)
|
||||
- Perform merge return with single return in loop. (#2714)
|
||||
- Add —preserve-bindings and —preserve-spec-constants (#2693)
|
||||
- Remove Common Uniform Elimination Pass (#2731)
|
||||
- Allow ray tracing shaders in inst bindle check pass. (#2733)
|
||||
- Add pass to inject code for robust-buffer-access semantics (#2771)
|
||||
- Treat access chain indexes as signed in SROA (#2776)
|
||||
- Handle RelaxedPrecision in SROA (#2788)
|
||||
- Add descriptor array scalar replacement (#2742)
|
||||
Fixes:
|
||||
- Handle decorations better in some optimizations (#2716)
|
||||
- Change the order branches are simplified in dead branch elim (#2728)
|
||||
- Fix bug in merge return (#2734)
|
||||
- SSA rewriter: Don't use trivial phis (#2757)
|
||||
- Record correct dominators in merge return (#2760)
|
||||
- Process OpDecorateId in ADCE (#2761)
|
||||
- Fix check for unreachable blocks in merge-return (#2762)
|
||||
- Handle out-of-bounds scalar replacements. (#2767)
|
||||
- Don't move debug or decorations when folding (#2772)
|
||||
- Protect against out-of-bounds references when folding OpCompositeExtract (#2774)
|
||||
- Validator
|
||||
- Validate loop merge (#2579)
|
||||
- Validate construct exits (#2459)
|
||||
- Validate OpenCL memory and addressing model environment rules (#2589)
|
||||
- Validate OpenCL environment rules for OpTypeImage (#2606)
|
||||
- Allow breaks to switch merge from nested construct (#2604)
|
||||
- Validate OpenCL environment rules for OpImageWrite (#2619)
|
||||
- Allow arrays of out per-primitive builtins for mesh shaders (#2617)
|
||||
- Validate OpenCL rules for ImageRead and OpImageSampleExplicitLod (#2643)
|
||||
- Add validation for SPV_EXT_fragment_shader_interlock (#2650)
|
||||
- Add builtin validation for SPV_NV_shader_sm_builtins (#2656)
|
||||
- Add validation for Subgroup builtins (#2637)
|
||||
- Validate variable initializer type (#2668)
|
||||
- Disallow stores to UBOs (#2651)A
|
||||
- Validate Volatile memory semantics bit (#2672)
|
||||
- Basic validation for Component decorations (#2679)
|
||||
- Validate that in OpenGL env block variables have Binding (#2685)
|
||||
- Validate usage of 8- and 16-bit types with only storage capabilities (#2704)
|
||||
- Add validation for SPV_EXT_demote_to_helper_invocation (#2707)
|
||||
- Extra small storage validation (#2732)
|
||||
- For Vulkan, disallow structures containing opaque types (#2546)
|
||||
- Validate storage class OpenCL environment rules for atomics (#2750)
|
||||
- Update OpControlBarriers rules for WebGPU (#2769)
|
||||
- Update OpMemoryBarriers rules for WebGPU (#2775)
|
||||
- Update WebGPU validation rules of OpAtomic*s (#2777)
|
||||
Fixes:
|
||||
- Disallow merge targeting block with OpLoopMerge (#2610)
|
||||
- Update vloadn and vstoren validation to match the OpenCL Extended
|
||||
Instruction Set Specification (#2599)
|
||||
- Update memory scope rules for WebGPU (#2725)
|
||||
- Allow LOD ops in compute shaders with derivative group execution modes (#2752)
|
||||
- Reduce
|
||||
Fixes:
|
||||
|
||||
v2019.3 2019-05-14
|
||||
- General:
|
||||
|
15
3rdparty/spirv-tools/CMakeLists.txt
vendored
15
3rdparty/spirv-tools/CMakeLists.txt
vendored
@ -235,6 +235,21 @@ endmacro(spvtools_pch)
|
||||
|
||||
add_subdirectory(external)
|
||||
|
||||
# Warning about extra semi-colons.
|
||||
#
|
||||
# This is not supported on all compilers/versions. so enabling only
|
||||
# for clang, since that works for all versions that our bots run.
|
||||
#
|
||||
# This is intentionally done after adding the external subdirectory,
|
||||
# so we don't enforce this flag on our dependencies, some of which do
|
||||
# not pass it.
|
||||
#
|
||||
# If the minimum version of CMake supported is updated to 3.0 or
|
||||
# later, then check_cxx_compiler_flag could be used instead.
|
||||
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
||||
add_compile_options("-Wextra-semi")
|
||||
endif()
|
||||
|
||||
add_subdirectory(source)
|
||||
add_subdirectory(tools)
|
||||
|
||||
|
@ -1 +1 @@
|
||||
"v2019.4-dev", "SPIRV-Tools v2019.4-dev v2019.3-99-g65f49dfc"
|
||||
"v2019.5-dev", "SPIRV-Tools v2019.5-dev v2019.4-2-g95386f9e"
|
||||
|
@ -763,6 +763,38 @@ Optimizer::PassToken CreateDecomposeInitializedVariablesPass();
|
||||
// continue-targets to legalize for WebGPU.
|
||||
Optimizer::PassToken CreateSplitInvalidUnreachablePass();
|
||||
|
||||
// Creates a graphics robust access pass.
|
||||
//
|
||||
// This pass injects code to clamp indexed accesses to buffers and internal
|
||||
// arrays, providing guarantees satisfying Vulkan's robustBufferAccess rules.
|
||||
//
|
||||
// TODO(dneto): Clamps coordinates and sample index for pointer calculations
|
||||
// into storage images (OpImageTexelPointer). For an cube array image, it
|
||||
// assumes the maximum layer count times 6 is at most 0xffffffff.
|
||||
//
|
||||
// NOTE: This pass will fail with a message if:
|
||||
// - The module is not a Shader module.
|
||||
// - The module declares VariablePointers, VariablePointersStorageBuffer, or
|
||||
// RuntimeDescriptorArrayEXT capabilities.
|
||||
// - The module uses an addressing model other than Logical
|
||||
// - Access chain indices are wider than 64 bits.
|
||||
// - Access chain index for a struct is not an OpConstant integer or is out
|
||||
// of range. (The module is already invalid if that is the case.)
|
||||
// - TODO(dneto): The OpImageTexelPointer coordinate component is not 32-bits
|
||||
// wide.
|
||||
Optimizer::PassToken CreateGraphicsRobustAccessPass();
|
||||
|
||||
// Create descriptor scalar replacement pass.
|
||||
// This pass replaces every array variable |desc| that has a DescriptorSet and
|
||||
// Binding decorations with a new variable for each element of the array.
|
||||
// Suppose |desc| was bound at binding |b|. Then the variable corresponding to
|
||||
// |desc[i]| will have binding |b+i|. The descriptor set will be the same. It
|
||||
// is assumed that no other variable already has a binding that will used by one
|
||||
// of the new variables. If not, the pass will generate invalid Spir-V. All
|
||||
// accesses to |desc| must be OpAccessChain instructions with a literal index
|
||||
// for the first index.
|
||||
Optimizer::PassToken CreateDescriptorScalarReplacementPass();
|
||||
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
|
||||
|
@ -26,11 +26,13 @@ if(SPIRV_BUILD_FUZZER)
|
||||
)
|
||||
|
||||
set(SPIRV_TOOLS_FUZZ_SOURCES
|
||||
data_descriptor.h
|
||||
fact_manager.h
|
||||
fuzzer.h
|
||||
fuzzer_context.h
|
||||
fuzzer_pass.h
|
||||
fuzzer_pass_add_dead_breaks.h
|
||||
fuzzer_pass_add_dead_continues.h
|
||||
fuzzer_pass_add_useful_constructs.h
|
||||
fuzzer_pass_obfuscate_constants.h
|
||||
fuzzer_pass_permute_blocks.h
|
||||
@ -46,10 +48,12 @@ if(SPIRV_BUILD_FUZZER)
|
||||
transformation_add_constant_boolean.h
|
||||
transformation_add_constant_scalar.h
|
||||
transformation_add_dead_break.h
|
||||
transformation_add_dead_continue.h
|
||||
transformation_add_type_boolean.h
|
||||
transformation_add_type_float.h
|
||||
transformation_add_type_int.h
|
||||
transformation_add_type_pointer.h
|
||||
transformation_copy_object.h
|
||||
transformation_move_block_down.h
|
||||
transformation_replace_boolean_constant_with_constant_binary.h
|
||||
transformation_replace_constant_with_uniform.h
|
||||
@ -57,11 +61,13 @@ if(SPIRV_BUILD_FUZZER)
|
||||
uniform_buffer_element_descriptor.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
|
||||
|
||||
data_descriptor.cpp
|
||||
fact_manager.cpp
|
||||
fuzzer.cpp
|
||||
fuzzer_context.cpp
|
||||
fuzzer_pass.cpp
|
||||
fuzzer_pass_add_dead_breaks.cpp
|
||||
fuzzer_pass_add_dead_continues.cpp
|
||||
fuzzer_pass_add_useful_constructs.cpp
|
||||
fuzzer_pass_obfuscate_constants.cpp
|
||||
fuzzer_pass_permute_blocks.cpp
|
||||
@ -76,10 +82,12 @@ if(SPIRV_BUILD_FUZZER)
|
||||
transformation_add_constant_boolean.cpp
|
||||
transformation_add_constant_scalar.cpp
|
||||
transformation_add_dead_break.cpp
|
||||
transformation_add_dead_continue.cpp
|
||||
transformation_add_type_boolean.cpp
|
||||
transformation_add_type_float.cpp
|
||||
transformation_add_type_int.cpp
|
||||
transformation_add_type_pointer.cpp
|
||||
transformation_copy_object.cpp
|
||||
transformation_move_block_down.cpp
|
||||
transformation_replace_boolean_constant_with_constant_binary.cpp
|
||||
transformation_replace_constant_with_uniform.cpp
|
||||
|
42
3rdparty/spirv-tools/source/fuzz/data_descriptor.cpp
vendored
Normal file
42
3rdparty/spirv-tools/source/fuzz/data_descriptor.cpp
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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/fuzz/data_descriptor.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
protobufs::DataDescriptor MakeDataDescriptor(uint32_t object,
|
||||
std::vector<uint32_t>&& indices) {
|
||||
protobufs::DataDescriptor result;
|
||||
result.set_object(object);
|
||||
for (auto index : indices) {
|
||||
result.add_index(index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DataDescriptorEquals::operator()(
|
||||
const protobufs::DataDescriptor* first,
|
||||
const protobufs::DataDescriptor* second) const {
|
||||
return first->object() == second->object() &&
|
||||
first->index().size() == second->index().size() &&
|
||||
std::equal(first->index().begin(), first->index().end(),
|
||||
second->index().begin());
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
39
3rdparty/spirv-tools/source/fuzz/data_descriptor.h
vendored
Normal file
39
3rdparty/spirv-tools/source/fuzz/data_descriptor.h
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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_FUZZ_DATA_DESCRIPTOR_H_
|
||||
#define SOURCE_FUZZ_DATA_DESCRIPTOR_H_
|
||||
|
||||
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
// Factory method to create a data descriptor message from an object id and a
|
||||
// list of indices.
|
||||
protobufs::DataDescriptor MakeDataDescriptor(uint32_t object,
|
||||
std::vector<uint32_t>&& indices);
|
||||
|
||||
// Equality function for data descriptors.
|
||||
struct DataDescriptorEquals {
|
||||
bool operator()(const protobufs::DataDescriptor* first,
|
||||
const protobufs::DataDescriptor* second) const;
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // #define SOURCE_FUZZ_DATA_DESCRIPTOR_H_
|
@ -68,6 +68,9 @@ std::string ToString(const protobufs::Fact& fact) {
|
||||
|
||||
} // namespace
|
||||
|
||||
//=======================
|
||||
// Constant uniform facts
|
||||
|
||||
// The purpose of this struct is to group the fields and data used to represent
|
||||
// facts about uniform constants.
|
||||
struct FactManager::ConstantUniformFacts {
|
||||
@ -330,10 +333,44 @@ bool FactManager::ConstantUniformFacts::AddFact(
|
||||
return true;
|
||||
}
|
||||
|
||||
FactManager::FactManager() {
|
||||
uniform_constant_facts_ = MakeUnique<ConstantUniformFacts>();
|
||||
// End of uniform constant facts
|
||||
//==============================
|
||||
|
||||
//==============================
|
||||
// Id synonym facts
|
||||
|
||||
// The purpose of this struct is to group the fields and data used to represent
|
||||
// facts about id synonyms.
|
||||
struct FactManager::IdSynonymFacts {
|
||||
// See method in FactManager which delegates to this method.
|
||||
void AddFact(const protobufs::FactIdSynonym& fact);
|
||||
|
||||
// A record of all the synonyms that are available.
|
||||
std::map<uint32_t, std::vector<protobufs::DataDescriptor>> synonyms;
|
||||
|
||||
// The set of keys to the above map; useful if you just want to know which ids
|
||||
// have synonyms.
|
||||
std::set<uint32_t> ids_with_synonyms;
|
||||
};
|
||||
|
||||
void FactManager::IdSynonymFacts::AddFact(
|
||||
const protobufs::FactIdSynonym& fact) {
|
||||
if (synonyms.count(fact.id()) == 0) {
|
||||
assert(ids_with_synonyms.count(fact.id()) == 0);
|
||||
ids_with_synonyms.insert(fact.id());
|
||||
synonyms[fact.id()] = std::vector<protobufs::DataDescriptor>();
|
||||
}
|
||||
assert(ids_with_synonyms.count(fact.id()) == 1);
|
||||
synonyms[fact.id()].push_back(fact.data_descriptor());
|
||||
}
|
||||
|
||||
// End of id synonym facts
|
||||
//==============================
|
||||
|
||||
FactManager::FactManager()
|
||||
: uniform_constant_facts_(MakeUnique<ConstantUniformFacts>()),
|
||||
id_synonym_facts_(MakeUnique<IdSynonymFacts>()) {}
|
||||
|
||||
FactManager::~FactManager() = default;
|
||||
|
||||
void FactManager::AddFacts(const MessageConsumer& message_consumer,
|
||||
@ -350,13 +387,17 @@ void FactManager::AddFacts(const MessageConsumer& message_consumer,
|
||||
|
||||
bool FactManager::AddFact(const spvtools::fuzz::protobufs::Fact& fact,
|
||||
spvtools::opt::IRContext* context) {
|
||||
assert(fact.fact_case() == protobufs::Fact::kConstantUniformFact &&
|
||||
"Right now this is the only fact.");
|
||||
if (!uniform_constant_facts_->AddFact(fact.constant_uniform_fact(),
|
||||
context)) {
|
||||
return false;
|
||||
switch (fact.fact_case()) {
|
||||
case protobufs::Fact::kConstantUniformFact:
|
||||
return uniform_constant_facts_->AddFact(fact.constant_uniform_fact(),
|
||||
context);
|
||||
case protobufs::Fact::kIdSynonymFact:
|
||||
id_synonym_facts_->AddFact(fact.id_synonym_fact());
|
||||
return true;
|
||||
default:
|
||||
assert(false && "Unknown fact type.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType(
|
||||
@ -389,5 +430,14 @@ FactManager::GetConstantUniformFactsAndTypes() const {
|
||||
return uniform_constant_facts_->facts_and_type_ids;
|
||||
}
|
||||
|
||||
const std::set<uint32_t>& FactManager::GetIdsForWhichSynonymsAreKnown() const {
|
||||
return id_synonym_facts_->ids_with_synonyms;
|
||||
}
|
||||
|
||||
const std::vector<protobufs::DataDescriptor>& FactManager::GetSynonymsForId(
|
||||
uint32_t id) const {
|
||||
return id_synonym_facts_->synonyms.at(id);
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
33
3rdparty/spirv-tools/source/fuzz/fact_manager.h
vendored
33
3rdparty/spirv-tools/source/fuzz/fact_manager.h
vendored
@ -16,6 +16,7 @@
|
||||
#define SOURCE_FUZZ_FACT_MANAGER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@ -51,13 +52,12 @@ class FactManager {
|
||||
// fact manager.
|
||||
bool AddFact(const protobufs::Fact& fact, opt::IRContext* context);
|
||||
|
||||
// The fact manager will ultimately be responsible for managing a few distinct
|
||||
// categories of facts. In principle there could be different fact managers
|
||||
// for each kind of fact, but in practice providing one 'go to' place for
|
||||
// facts will be convenient. To keep some separation, the public methods of
|
||||
// the fact manager should be grouped according to the kind of fact to which
|
||||
// they relate. At present we only have one kind of fact: facts about
|
||||
// uniform variables.
|
||||
// The fact manager is responsible for managing a few distinct categories of
|
||||
// facts. In principle there could be different fact managers for each kind
|
||||
// of fact, but in practice providing one 'go to' place for facts is
|
||||
// convenient. To keep some separation, the public methods of the fact
|
||||
// manager should be grouped according to the kind of fact to which they
|
||||
// relate.
|
||||
|
||||
//==============================
|
||||
// Querying facts about uniform constants
|
||||
@ -96,6 +96,21 @@ class FactManager {
|
||||
// End of uniform constant facts
|
||||
//==============================
|
||||
|
||||
//==============================
|
||||
// Querying facts about id synonyms
|
||||
|
||||
// Returns every id for which a fact of the form "this id is synonymous
|
||||
// with this piece of data" is known.
|
||||
const std::set<uint32_t>& GetIdsForWhichSynonymsAreKnown() const;
|
||||
|
||||
// Requires that at least one synonym for |id| is known, and returns the
|
||||
// sequence of all known synonyms.
|
||||
const std::vector<protobufs::DataDescriptor>& GetSynonymsForId(
|
||||
uint32_t id) const;
|
||||
|
||||
// End of id synonym facts
|
||||
//==============================
|
||||
|
||||
private:
|
||||
// For each distinct kind of fact to be managed, we use a separate opaque
|
||||
// struct type.
|
||||
@ -104,6 +119,10 @@ class FactManager {
|
||||
// buffer elements.
|
||||
std::unique_ptr<ConstantUniformFacts>
|
||||
uniform_constant_facts_; // Unique pointer to internal data.
|
||||
|
||||
struct IdSynonymFacts; // Opaque struct for holding data about id synonyms.
|
||||
std::unique_ptr<IdSynonymFacts>
|
||||
id_synonym_facts_; // Unique pointer to internal data.
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
|
4
3rdparty/spirv-tools/source/fuzz/fuzzer.cpp
vendored
4
3rdparty/spirv-tools/source/fuzz/fuzzer.cpp
vendored
@ -20,6 +20,7 @@
|
||||
#include "source/fuzz/fact_manager.h"
|
||||
#include "source/fuzz/fuzzer_context.h"
|
||||
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
|
||||
#include "source/fuzz/fuzzer_pass_add_dead_continues.h"
|
||||
#include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
|
||||
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
|
||||
#include "source/fuzz/fuzzer_pass_permute_blocks.h"
|
||||
@ -112,6 +113,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
|
||||
FuzzerPassAddDeadBreaks(ir_context.get(), &fact_manager, &fuzzer_context,
|
||||
transformation_sequence_out)
|
||||
.Apply();
|
||||
FuzzerPassAddDeadContinues(ir_context.get(), &fact_manager, &fuzzer_context,
|
||||
transformation_sequence_out)
|
||||
.Apply();
|
||||
FuzzerPassObfuscateConstants(ir_context.get(), &fact_manager, &fuzzer_context,
|
||||
transformation_sequence_out)
|
||||
.Apply();
|
||||
|
@ -25,6 +25,7 @@ namespace {
|
||||
// Keep them in alphabetical order.
|
||||
|
||||
const uint32_t kDefaultChanceOfAddingDeadBreak = 20;
|
||||
const uint32_t kDefaultChanceOfAddingDeadContinue = 20;
|
||||
const uint32_t kDefaultChanceOfMovingBlockDown = 25;
|
||||
const uint32_t kDefaultChanceOfObfuscatingConstant = 20;
|
||||
const uint32_t kDefaultChanceOfSplittingBlock = 20;
|
||||
@ -46,6 +47,7 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
|
||||
: random_generator_(random_generator),
|
||||
next_fresh_id_(min_fresh_id),
|
||||
chance_of_adding_dead_break_(kDefaultChanceOfAddingDeadBreak),
|
||||
chance_of_adding_dead_continue_(kDefaultChanceOfAddingDeadContinue),
|
||||
chance_of_moving_block_down_(kDefaultChanceOfMovingBlockDown),
|
||||
chance_of_obfuscating_constant_(kDefaultChanceOfObfuscatingConstant),
|
||||
chance_of_splitting_block_(kDefaultChanceOfSplittingBlock),
|
||||
|
@ -44,6 +44,9 @@ class FuzzerContext {
|
||||
// Probabilities associated with applying various transformations.
|
||||
// Keep them in alphabetical order.
|
||||
uint32_t GetChanceOfAddingDeadBreak() { return chance_of_adding_dead_break_; }
|
||||
uint32_t GetChanceOfAddingDeadContinue() {
|
||||
return chance_of_adding_dead_continue_;
|
||||
}
|
||||
uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
|
||||
uint32_t GetChanceOfObfuscatingConstant() {
|
||||
return chance_of_obfuscating_constant_;
|
||||
@ -66,6 +69,7 @@ class FuzzerContext {
|
||||
// Probabilities associated with applying various transformations.
|
||||
// Keep them in alphabetical order.
|
||||
uint32_t chance_of_adding_dead_break_;
|
||||
uint32_t chance_of_adding_dead_continue_;
|
||||
uint32_t chance_of_moving_block_down_;
|
||||
uint32_t chance_of_obfuscating_constant_;
|
||||
uint32_t chance_of_splitting_block_;
|
||||
|
@ -50,11 +50,9 @@ void FuzzerPassAddDeadBreaks::Apply() {
|
||||
// TODO(afd): right now we completely ignore OpPhi instructions at
|
||||
// merge blocks. This will lead to interesting opportunities being
|
||||
// missed.
|
||||
std::vector<uint32_t> phi_ids;
|
||||
auto candidate_transformation = TransformationAddDeadBreak(
|
||||
block.id(), merge_block_id,
|
||||
GetFuzzerContext()->GetRandomGenerator()->RandomBool(),
|
||||
std::move(phi_ids));
|
||||
GetFuzzerContext()->GetRandomGenerator()->RandomBool(), {});
|
||||
if (candidate_transformation.IsApplicable(GetIRContext(),
|
||||
*GetFactManager())) {
|
||||
// Only consider a transformation as a candidate if it is applicable.
|
||||
@ -84,14 +82,11 @@ void FuzzerPassAddDeadBreaks::Apply() {
|
||||
// Remove the transformation at the chosen index from the sequence.
|
||||
auto transformation = std::move(candidate_transformations[index]);
|
||||
candidate_transformations.erase(candidate_transformations.begin() + index);
|
||||
// Probabilistically decide whether to try to apply it vs. ignore it.
|
||||
if (GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
|
||||
GetFuzzerContext()->GetChanceOfAddingDeadBreak()) {
|
||||
continue;
|
||||
}
|
||||
// If the transformation can be applied, apply it and add it to the
|
||||
// sequence of transformations that have been applied.
|
||||
if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) {
|
||||
// Probabilistically decide whether to try to apply it vs. ignore it, in the
|
||||
// case that it is applicable.
|
||||
if (transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
|
||||
GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
|
||||
GetFuzzerContext()->GetChanceOfAddingDeadBreak()) {
|
||||
transformation.Apply(GetIRContext(), GetFactManager());
|
||||
*GetTransformations()->add_transformation() = transformation.ToMessage();
|
||||
}
|
||||
|
60
3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.cpp
vendored
Normal file
60
3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.cpp
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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/fuzz/fuzzer_pass_add_dead_continues.h"
|
||||
|
||||
#include "source/fuzz/transformation_add_dead_continue.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
FuzzerPassAddDeadContinues::FuzzerPassAddDeadContinues(
|
||||
opt::IRContext* ir_context, FactManager* fact_manager,
|
||||
FuzzerContext* fuzzer_context,
|
||||
protobufs::TransformationSequence* transformations)
|
||||
: FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
|
||||
|
||||
FuzzerPassAddDeadContinues::~FuzzerPassAddDeadContinues() = default;
|
||||
|
||||
void FuzzerPassAddDeadContinues::Apply() {
|
||||
// Consider every block in every function.
|
||||
for (auto& function : *GetIRContext()->module()) {
|
||||
for (auto& block : function) {
|
||||
// Make a transformation to add a dead continue from this node; if the
|
||||
// node turns out to be inappropriate (e.g. by not being in a loop) the
|
||||
// precondition for the transformation will fail and it will be ignored.
|
||||
//
|
||||
// TODO(afd): right now we completely ignore OpPhi instructions at
|
||||
// merge blocks. This will lead to interesting opportunities being
|
||||
// missed.
|
||||
auto candidate_transformation = TransformationAddDeadContinue(
|
||||
block.id(), GetFuzzerContext()->GetRandomGenerator()->RandomBool(),
|
||||
{});
|
||||
// Probabilistically decide whether to apply the transformation in the
|
||||
// case that it is applicable.
|
||||
if (candidate_transformation.IsApplicable(GetIRContext(),
|
||||
*GetFactManager()) &&
|
||||
GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
|
||||
GetFuzzerContext()->GetChanceOfAddingDeadContinue()) {
|
||||
candidate_transformation.Apply(GetIRContext(), GetFactManager());
|
||||
*GetTransformations()->add_transformation() =
|
||||
candidate_transformation.ToMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
39
3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.h
vendored
Normal file
39
3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.h
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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_FUZZ_FUZZER_PASS_ADD_DEAD_CONTINUES_H_
|
||||
#define SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_CONTINUES_H_
|
||||
|
||||
#include "source/fuzz/fuzzer_pass.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
// A fuzzer pass for adding dead continue edges to the module.
|
||||
class FuzzerPassAddDeadContinues : public FuzzerPass {
|
||||
public:
|
||||
FuzzerPassAddDeadContinues(
|
||||
opt::IRContext* ir_context, FactManager* fact_manager,
|
||||
FuzzerContext* fuzzer_context,
|
||||
protobufs::TransformationSequence* transformations);
|
||||
|
||||
~FuzzerPassAddDeadContinues();
|
||||
|
||||
void Apply() override;
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // #define SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_CONTINUES_H_
|
175
3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp
vendored
175
3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp
vendored
@ -30,6 +30,181 @@ void UpdateModuleIdBound(opt::IRContext* context, uint32_t id) {
|
||||
std::max(context->module()->id_bound(), id + 1));
|
||||
}
|
||||
|
||||
opt::BasicBlock* MaybeFindBlock(opt::IRContext* context,
|
||||
uint32_t maybe_block_id) {
|
||||
auto inst = context->get_def_use_mgr()->GetDef(maybe_block_id);
|
||||
if (inst == nullptr) {
|
||||
// No instruction defining this id was found.
|
||||
return nullptr;
|
||||
}
|
||||
if (inst->opcode() != SpvOpLabel) {
|
||||
// The instruction defining the id is not a label, so it cannot be a block
|
||||
// id.
|
||||
return nullptr;
|
||||
}
|
||||
return context->cfg()->block(maybe_block_id);
|
||||
}
|
||||
|
||||
bool PhiIdsOkForNewEdge(
|
||||
opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
|
||||
const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids) {
|
||||
if (bb_from->IsSuccessor(bb_to)) {
|
||||
// There is already an edge from |from_block| to |to_block|, so there is
|
||||
// no need to extend OpPhi instructions. Do not allow phi ids to be
|
||||
// present. This might turn out to be too strict; perhaps it would be OK
|
||||
// just to ignore the ids in this case.
|
||||
return phi_ids.empty();
|
||||
}
|
||||
// The edge would add a previously non-existent edge from |from_block| to
|
||||
// |to_block|, so we go through the given phi ids and check that they exactly
|
||||
// match the OpPhi instructions in |to_block|.
|
||||
uint32_t phi_index = 0;
|
||||
// An explicit loop, rather than applying a lambda to each OpPhi in |bb_to|,
|
||||
// makes sense here because we need to increment |phi_index| for each OpPhi
|
||||
// instruction.
|
||||
for (auto& inst : *bb_to) {
|
||||
if (inst.opcode() != SpvOpPhi) {
|
||||
// The OpPhi instructions all occur at the start of the block; if we find
|
||||
// a non-OpPhi then we have seen them all.
|
||||
break;
|
||||
}
|
||||
if (phi_index == static_cast<uint32_t>(phi_ids.size())) {
|
||||
// Not enough phi ids have been provided to account for the OpPhi
|
||||
// instructions.
|
||||
return false;
|
||||
}
|
||||
// Look for an instruction defining the next phi id.
|
||||
opt::Instruction* phi_extension =
|
||||
context->get_def_use_mgr()->GetDef(phi_ids[phi_index]);
|
||||
if (!phi_extension) {
|
||||
// The id given to extend this OpPhi does not exist.
|
||||
return false;
|
||||
}
|
||||
if (phi_extension->type_id() != inst.type_id()) {
|
||||
// The instruction given to extend this OpPhi either does not have a type
|
||||
// or its type does not match that of the OpPhi.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context->get_instr_block(phi_extension)) {
|
||||
// The instruction defining the phi id has an associated block (i.e., it
|
||||
// is not a global value). Check whether its definition dominates the
|
||||
// exit of |from_block|.
|
||||
auto dominator_analysis =
|
||||
context->GetDominatorAnalysis(bb_from->GetParent());
|
||||
if (!dominator_analysis->Dominates(phi_extension,
|
||||
bb_from->terminator())) {
|
||||
// The given id is no good as its definition does not dominate the exit
|
||||
// of |from_block|
|
||||
return false;
|
||||
}
|
||||
}
|
||||
phi_index++;
|
||||
}
|
||||
// Return false if not all of the ids for extending OpPhi instructions are
|
||||
// needed. This might turn out to be stricter than necessary; perhaps it would
|
||||
// be OK just to not use the ids in this case.
|
||||
return phi_index == static_cast<uint32_t>(phi_ids.size());
|
||||
}
|
||||
|
||||
void AddUnreachableEdgeAndUpdateOpPhis(
|
||||
opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
|
||||
bool condition_value,
|
||||
const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids) {
|
||||
assert(PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids) &&
|
||||
"Precondition on phi_ids is not satisfied");
|
||||
assert(bb_from->terminator()->opcode() == SpvOpBranch &&
|
||||
"Precondition on terminator of bb_from is not satisfied");
|
||||
|
||||
// Get the id of the boolean constant to be used as the condition.
|
||||
opt::analysis::Bool bool_type;
|
||||
opt::analysis::BoolConstant bool_constant(
|
||||
context->get_type_mgr()->GetRegisteredType(&bool_type)->AsBool(),
|
||||
condition_value);
|
||||
uint32_t bool_id = context->get_constant_mgr()->FindDeclaredConstant(
|
||||
&bool_constant, context->get_type_mgr()->GetId(&bool_type));
|
||||
|
||||
const bool from_to_edge_already_exists = bb_from->IsSuccessor(bb_to);
|
||||
auto successor = bb_from->terminator()->GetSingleWordInOperand(0);
|
||||
|
||||
// Add the dead branch, by turning OpBranch into OpBranchConditional, and
|
||||
// ordering the targets depending on whether the given boolean corresponds to
|
||||
// true or false.
|
||||
bb_from->terminator()->SetOpcode(SpvOpBranchConditional);
|
||||
bb_from->terminator()->SetInOperands(
|
||||
{{SPV_OPERAND_TYPE_ID, {bool_id}},
|
||||
{SPV_OPERAND_TYPE_ID, {condition_value ? successor : bb_to->id()}},
|
||||
{SPV_OPERAND_TYPE_ID, {condition_value ? bb_to->id() : successor}}});
|
||||
|
||||
// Update OpPhi instructions in the target block if this branch adds a
|
||||
// previously non-existent edge from source to target.
|
||||
if (!from_to_edge_already_exists) {
|
||||
uint32_t phi_index = 0;
|
||||
for (auto& inst : *bb_to) {
|
||||
if (inst.opcode() != SpvOpPhi) {
|
||||
break;
|
||||
}
|
||||
assert(phi_index < static_cast<uint32_t>(phi_ids.size()) &&
|
||||
"There should be exactly one phi id per OpPhi instruction.");
|
||||
inst.AddOperand({SPV_OPERAND_TYPE_ID, {phi_ids[phi_index]}});
|
||||
inst.AddOperand({SPV_OPERAND_TYPE_ID, {bb_from->id()}});
|
||||
phi_index++;
|
||||
}
|
||||
assert(phi_index == static_cast<uint32_t>(phi_ids.size()) &&
|
||||
"There should be exactly one phi id per OpPhi instruction.");
|
||||
}
|
||||
}
|
||||
|
||||
bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id,
|
||||
uint32_t maybe_loop_header_id) {
|
||||
// We deem a block to be part of a loop's continue construct if the loop's
|
||||
// continue target dominates the block.
|
||||
auto containing_construct_block = context->cfg()->block(maybe_loop_header_id);
|
||||
if (containing_construct_block->IsLoopHeader()) {
|
||||
auto continue_target = containing_construct_block->ContinueBlockId();
|
||||
if (context->GetDominatorAnalysis(containing_construct_block->GetParent())
|
||||
->Dominates(continue_target, block_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
opt::BasicBlock::iterator GetIteratorForBaseInstructionAndOffset(
|
||||
opt::BasicBlock* block, const opt::Instruction* base_inst,
|
||||
uint32_t offset) {
|
||||
// The cases where |base_inst| is the block's label, vs. inside the block,
|
||||
// are dealt with separately.
|
||||
if (base_inst == block->GetLabelInst()) {
|
||||
// |base_inst| is the block's label.
|
||||
if (offset == 0) {
|
||||
// We cannot return an iterator to the block's label.
|
||||
return block->end();
|
||||
}
|
||||
// Conceptually, the first instruction in the block is [label + 1].
|
||||
// We thus start from 1 when applying the offset.
|
||||
auto inst_it = block->begin();
|
||||
for (uint32_t i = 1; i < offset && inst_it != block->end(); i++) {
|
||||
++inst_it;
|
||||
}
|
||||
// This is either the desired instruction, or the end of the block.
|
||||
return inst_it;
|
||||
}
|
||||
// |base_inst| is inside the block.
|
||||
for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) {
|
||||
if (base_inst == &*inst_it) {
|
||||
// We have found the base instruction; we now apply the offset.
|
||||
for (uint32_t i = 0; i < offset && inst_it != block->end(); i++) {
|
||||
++inst_it;
|
||||
}
|
||||
// This is either the desired instruction, or the end of the block.
|
||||
return inst_it;
|
||||
}
|
||||
}
|
||||
assert(false && "The base instruction was not found.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace fuzzerutil
|
||||
|
||||
} // namespace fuzz
|
||||
|
44
3rdparty/spirv-tools/source/fuzz/fuzzer_util.h
vendored
44
3rdparty/spirv-tools/source/fuzz/fuzzer_util.h
vendored
@ -15,6 +15,11 @@
|
||||
#ifndef SOURCE_FUZZ_FUZZER_UTIL_H_
|
||||
#define SOURCE_FUZZ_FUZZER_UTIL_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
|
||||
#include "source/opt/basic_block.h"
|
||||
#include "source/opt/instruction.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
|
||||
namespace spvtools {
|
||||
@ -30,6 +35,45 @@ bool IsFreshId(opt::IRContext* context, uint32_t id);
|
||||
// account for the given id.
|
||||
void UpdateModuleIdBound(opt::IRContext* context, uint32_t id);
|
||||
|
||||
// Return the block with id |maybe_block_id| if it exists, and nullptr
|
||||
// otherwise.
|
||||
opt::BasicBlock* MaybeFindBlock(opt::IRContext* context,
|
||||
uint32_t maybe_block_id);
|
||||
|
||||
// When adding an edge from |bb_from| to |bb_to| (which are assumed to be blocks
|
||||
// in the same function), it is important to supply |bb_to| with ids that can be
|
||||
// used to augment OpPhi instructions in the case that there is not already such
|
||||
// an edge. This function returns true if and only if the ids provided in
|
||||
// |phi_ids| suffice for this purpose,
|
||||
bool PhiIdsOkForNewEdge(
|
||||
opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
|
||||
const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids);
|
||||
|
||||
// Requires that PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids) holds,
|
||||
// and that bb_from ends with "OpBranch %some_block". Turns OpBranch into
|
||||
// "OpBranchConditional |condition_value| ...", such that control will branch
|
||||
// to %some_block, with |bb_to| being the unreachable alternative. Updates
|
||||
// OpPhi instructions in |bb_to| using |phi_ids| so that the new edge is valid.
|
||||
void AddUnreachableEdgeAndUpdateOpPhis(
|
||||
opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
|
||||
bool condition_value,
|
||||
const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids);
|
||||
|
||||
// Returns true if and only if |maybe_loop_header_id| is a loop header and
|
||||
// |block_id| is in the continue construct of the associated loop.
|
||||
bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id,
|
||||
uint32_t maybe_loop_header_id);
|
||||
|
||||
// Requires that |base_inst| is either the label instruction of |block| or an
|
||||
// instruction inside |block|.
|
||||
//
|
||||
// If the block contains a (non-label, non-terminator) instruction |offset|
|
||||
// instructions after |base_inst|, an iterator to this instruction is returned.
|
||||
//
|
||||
// Otherwise |block|->end() is returned.
|
||||
opt::BasicBlock::iterator GetIteratorForBaseInstructionAndOffset(
|
||||
opt::BasicBlock* block, const opt::Instruction* base_inst, uint32_t offset);
|
||||
|
||||
} // namespace fuzzerutil
|
||||
|
||||
} // namespace fuzz
|
||||
|
@ -57,6 +57,22 @@ message IdUseDescriptor {
|
||||
|
||||
}
|
||||
|
||||
message DataDescriptor {
|
||||
|
||||
// Represents a data element that can be accessed from an id, by walking the
|
||||
// type hierarchy via a sequence of 0 or more indices.
|
||||
//
|
||||
// Very similar to a UniformBufferElementDescriptor, except that a
|
||||
// DataDescriptor is rooted at the id of a scalar or composite.
|
||||
|
||||
// The object being accessed - a scalar or composite
|
||||
uint32 object = 1;
|
||||
|
||||
// 0 or more indices, used to index into a composite object
|
||||
repeated uint32 index = 2;
|
||||
|
||||
}
|
||||
|
||||
message UniformBufferElementDescriptor {
|
||||
|
||||
// Represents a data element inside a uniform buffer. The element is
|
||||
@ -97,6 +113,7 @@ message Fact {
|
||||
oneof fact {
|
||||
// Order the fact options by numeric id (rather than alphabetically).
|
||||
FactConstantUniform constant_uniform_fact = 1;
|
||||
FactIdSynonym id_synonym_fact = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,6 +135,22 @@ message FactConstantUniform {
|
||||
|
||||
}
|
||||
|
||||
message FactIdSynonym {
|
||||
|
||||
// Records the fact that the data held in an id is guaranteed to be equal to
|
||||
// the data held in a data descriptor. spirv-fuzz can use this to replace
|
||||
// uses of the id with references to the data described by the data
|
||||
// descriptor.
|
||||
|
||||
// An id
|
||||
uint32 id = 1;
|
||||
|
||||
// A data descriptor guaranteed to hold a value identical to that held by the
|
||||
// id
|
||||
DataDescriptor data_descriptor = 2;
|
||||
|
||||
}
|
||||
|
||||
message TransformationSequence {
|
||||
repeated Transformation transformation = 1;
|
||||
}
|
||||
@ -137,6 +170,8 @@ message Transformation {
|
||||
TransformationReplaceBooleanConstantWithConstantBinary replace_boolean_constant_with_constant_binary = 9;
|
||||
TransformationAddTypePointer add_type_pointer = 10;
|
||||
TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11;
|
||||
TransformationAddDeadContinue add_dead_continue = 12;
|
||||
TransformationCopyObject copy_object = 13;
|
||||
// Add additional option using the next available number.
|
||||
}
|
||||
}
|
||||
@ -190,6 +225,25 @@ message TransformationAddDeadBreak {
|
||||
|
||||
}
|
||||
|
||||
message TransformationAddDeadContinue {
|
||||
|
||||
// A transformation that turns a basic block appearing in a loop and that
|
||||
// unconditionally branches to its successor into a block that potentially
|
||||
// branches to the continue target of the loop, but in such a manner that the
|
||||
// continue branch cannot actually be taken.
|
||||
|
||||
// The block to continue from
|
||||
uint32 from_block = 1;
|
||||
|
||||
// Determines whether the continue condition is true or false
|
||||
bool continue_condition_value = 2;
|
||||
|
||||
// A sequence of ids suitable for extending OpPhi instructions as a result of
|
||||
// the new break edge
|
||||
repeated uint32 phi_id = 3;
|
||||
|
||||
}
|
||||
|
||||
message TransformationAddTypeBoolean {
|
||||
|
||||
// Adds OpTypeBool to the module
|
||||
@ -242,6 +296,26 @@ message TransformationAddTypePointer {
|
||||
|
||||
}
|
||||
|
||||
message TransformationCopyObject {
|
||||
|
||||
// A transformation that introduces an OpCopyObject instruction to make a
|
||||
// copy of an object.
|
||||
|
||||
// Id of the object to be copied
|
||||
uint32 object = 1;
|
||||
|
||||
// The id of an instruction in a block
|
||||
uint32 base_instruction_id = 2;
|
||||
|
||||
// An offset, such that OpCopyObject instruction should be inserted right
|
||||
// before the instruction |offset| instructions after |base_instruction_id|
|
||||
uint32 offset = 3;
|
||||
|
||||
// A fresh id for the copied object
|
||||
uint32 fresh_id = 4;
|
||||
|
||||
}
|
||||
|
||||
message TransformationMoveBlockDown {
|
||||
|
||||
// A transformation that moves a basic block to be one position lower in
|
||||
@ -271,6 +345,7 @@ message TransformationReplaceConstantWithUniform {
|
||||
}
|
||||
|
||||
message TransformationReplaceBooleanConstantWithConstantBinary {
|
||||
|
||||
// A transformation to capture replacing a use of a boolean constant with
|
||||
// binary operation on two constant values
|
||||
|
||||
@ -293,13 +368,14 @@ message TransformationReplaceBooleanConstantWithConstantBinary {
|
||||
|
||||
message TransformationSplitBlock {
|
||||
|
||||
// A transformation that splits a basic block into two basic blocks.
|
||||
// A transformation that splits a basic block into two basic blocks
|
||||
|
||||
// The result id of an instruction.
|
||||
uint32 result_id = 1;
|
||||
// The result id of an instruction
|
||||
uint32 base_instruction_id = 1;
|
||||
|
||||
// An offset, such that the block containing |result_id_| should be split
|
||||
// right before the instruction |offset_| instructions after |result_id_|.
|
||||
// An offset, such that the block containing |base_instruction_id| should be
|
||||
// split right before the instruction |offset| instructions after
|
||||
// |base_instruction_id|
|
||||
uint32 offset = 2;
|
||||
|
||||
// An id that must not yet be used by the module to which this transformation
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "transformation_add_constant_boolean.h"
|
||||
#include "transformation_add_constant_scalar.h"
|
||||
#include "transformation_add_dead_break.h"
|
||||
#include "transformation_add_dead_continue.h"
|
||||
#include "transformation_add_type_boolean.h"
|
||||
#include "transformation_add_type_float.h"
|
||||
#include "transformation_add_type_int.h"
|
||||
@ -45,6 +46,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
|
||||
message.add_constant_scalar());
|
||||
case protobufs::Transformation::TransformationCase::kAddDeadBreak:
|
||||
return MakeUnique<TransformationAddDeadBreak>(message.add_dead_break());
|
||||
case protobufs::Transformation::TransformationCase::kAddDeadContinue:
|
||||
return MakeUnique<TransformationAddDeadContinue>(
|
||||
message.add_dead_continue());
|
||||
case protobufs::Transformation::TransformationCase::kAddTypeBoolean:
|
||||
return MakeUnique<TransformationAddTypeBoolean>(
|
||||
message.add_type_boolean());
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "source/fuzz/transformation_add_dead_break.h"
|
||||
|
||||
#include "source/fuzz/fact_manager.h"
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
#include "source/opt/basic_block.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/opt/struct_cfg_analysis.h"
|
||||
@ -37,98 +38,6 @@ TransformationAddDeadBreak::TransformationAddDeadBreak(
|
||||
}
|
||||
}
|
||||
|
||||
opt::BasicBlock* TransformationAddDeadBreak::MaybeFindBlock(
|
||||
opt::IRContext* context, uint32_t maybe_block_id) const {
|
||||
auto inst = context->get_def_use_mgr()->GetDef(maybe_block_id);
|
||||
if (inst == nullptr) {
|
||||
// No instruction defining this id was found.
|
||||
return nullptr;
|
||||
}
|
||||
if (inst->opcode() != SpvOpLabel) {
|
||||
// The instruction defining the id is not a label, so it cannot be a block
|
||||
// id.
|
||||
return nullptr;
|
||||
}
|
||||
return context->cfg()->block(maybe_block_id);
|
||||
}
|
||||
|
||||
bool TransformationAddDeadBreak::PhiIdsOk(opt::IRContext* context,
|
||||
opt::BasicBlock* bb_from,
|
||||
opt::BasicBlock* bb_to) const {
|
||||
if (bb_from->IsSuccessor(bb_to)) {
|
||||
// There is already an edge from |from_block| to |to_block|, so there is
|
||||
// no need to extend OpPhi instructions. Do not allow phi ids to be
|
||||
// present. This might turn out to be too strict; perhaps it would be OK
|
||||
// just to ignore the ids in this case.
|
||||
return message_.phi_id().empty();
|
||||
}
|
||||
// The break would add a previously non-existent edge from |from_block| to
|
||||
// |to_block|, so we go through the given phi ids and check that they exactly
|
||||
// match the OpPhi instructions in |to_block|.
|
||||
uint32_t phi_index = 0;
|
||||
// An explicit loop, rather than applying a lambda to each OpPhi in |bb_to|,
|
||||
// makes sense here because we need to increment |phi_index| for each OpPhi
|
||||
// instruction.
|
||||
for (auto& inst : *bb_to) {
|
||||
if (inst.opcode() != SpvOpPhi) {
|
||||
// The OpPhi instructions all occur at the start of the block; if we find
|
||||
// a non-OpPhi then we have seen them all.
|
||||
break;
|
||||
}
|
||||
if (phi_index == static_cast<uint32_t>(message_.phi_id().size())) {
|
||||
// Not enough phi ids have been provided to account for the OpPhi
|
||||
// instructions.
|
||||
return false;
|
||||
}
|
||||
// Look for an instruction defining the next phi id.
|
||||
opt::Instruction* phi_extension =
|
||||
context->get_def_use_mgr()->GetDef(message_.phi_id()[phi_index]);
|
||||
if (!phi_extension) {
|
||||
// The id given to extend this OpPhi does not exist.
|
||||
return false;
|
||||
}
|
||||
if (phi_extension->type_id() != inst.type_id()) {
|
||||
// The instruction given to extend this OpPhi either does not have a type
|
||||
// or its type does not match that of the OpPhi.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context->get_instr_block(phi_extension)) {
|
||||
// The instruction defining the phi id has an associated block (i.e., it
|
||||
// is not a global value). Check whether its definition dominates the
|
||||
// exit of |from_block|.
|
||||
auto dominator_analysis =
|
||||
context->GetDominatorAnalysis(bb_from->GetParent());
|
||||
if (!dominator_analysis->Dominates(phi_extension,
|
||||
bb_from->terminator())) {
|
||||
// The given id is no good as its definition does not dominate the exit
|
||||
// of |from_block|
|
||||
return false;
|
||||
}
|
||||
}
|
||||
phi_index++;
|
||||
}
|
||||
// Reject the transformation if not all of the ids for extending OpPhi
|
||||
// instructions are needed. This might turn out to be stricter than necessary;
|
||||
// perhaps it would be OK just to not use the ids in this case.
|
||||
return phi_index == static_cast<uint32_t>(message_.phi_id().size());
|
||||
}
|
||||
|
||||
bool TransformationAddDeadBreak::FromBlockIsInLoopContinueConstruct(
|
||||
opt::IRContext* context, uint32_t maybe_loop_header) const {
|
||||
// We deem a block to be part of a loop's continue construct if the loop's
|
||||
// continue target dominates the block.
|
||||
auto containing_construct_block = context->cfg()->block(maybe_loop_header);
|
||||
if (containing_construct_block->IsLoopHeader()) {
|
||||
auto continue_target = containing_construct_block->ContinueBlockId();
|
||||
if (context->GetDominatorAnalysis(containing_construct_block->GetParent())
|
||||
->Dominates(continue_target, message_.from_block())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow(
|
||||
opt::IRContext* context, opt::BasicBlock* bb_from) const {
|
||||
// Look at the structured control flow associated with |from_block| and
|
||||
@ -180,7 +89,8 @@ bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow(
|
||||
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2577): We do not
|
||||
// currently allow a dead break from a back edge block, but we could and
|
||||
// ultimately should.
|
||||
return !FromBlockIsInLoopContinueConstruct(context, containing_construct);
|
||||
return !fuzzerutil::BlockIsInLoopContinueConstruct(
|
||||
context, message_.from_block(), containing_construct);
|
||||
}
|
||||
|
||||
// Case (3) holds if and only if |to_block| is the merge block for this
|
||||
@ -191,7 +101,8 @@ bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow(
|
||||
if (containing_loop_header &&
|
||||
message_.to_block() ==
|
||||
context->cfg()->block(containing_loop_header)->MergeBlockId()) {
|
||||
return !FromBlockIsInLoopContinueConstruct(context, containing_loop_header);
|
||||
return !fuzzerutil::BlockIsInLoopContinueConstruct(
|
||||
context, message_.from_block(), containing_loop_header);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -199,7 +110,7 @@ bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow(
|
||||
bool TransformationAddDeadBreak::IsApplicable(
|
||||
opt::IRContext* context, const FactManager& /*unused*/) const {
|
||||
// First, we check that a constant with the same value as
|
||||
// |break_condition_value| is present.
|
||||
// |message_.break_condition_value| is present.
|
||||
opt::analysis::Bool bool_type;
|
||||
auto registered_bool_type =
|
||||
context->get_type_mgr()->GetRegisteredType(&bool_type);
|
||||
@ -214,17 +125,20 @@ bool TransformationAddDeadBreak::IsApplicable(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that |from_block| and |to_block| really are block ids
|
||||
opt::BasicBlock* bb_from = MaybeFindBlock(context, message_.from_block());
|
||||
// Check that |message_.from_block| and |message_.to_block| really are block
|
||||
// ids
|
||||
opt::BasicBlock* bb_from =
|
||||
fuzzerutil::MaybeFindBlock(context, message_.from_block());
|
||||
if (bb_from == nullptr) {
|
||||
return false;
|
||||
}
|
||||
opt::BasicBlock* bb_to = MaybeFindBlock(context, message_.to_block());
|
||||
opt::BasicBlock* bb_to =
|
||||
fuzzerutil::MaybeFindBlock(context, message_.to_block());
|
||||
if (bb_to == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that |from_block| ends with an unconditional branch.
|
||||
// Check that |message_.from_block| ends with an unconditional branch.
|
||||
if (bb_from->terminator()->opcode() != SpvOpBranch) {
|
||||
// The block associated with the id does not end with an unconditional
|
||||
// branch.
|
||||
@ -243,7 +157,8 @@ bool TransformationAddDeadBreak::IsApplicable(
|
||||
"The id of the block we found should match the target id for the break.");
|
||||
|
||||
// Check whether the data passed to extend OpPhi instructions is appropriate.
|
||||
if (!PhiIdsOk(context, bb_from, bb_to)) {
|
||||
if (!fuzzerutil::PhiIdsOkForNewEdge(context, bb_from, bb_to,
|
||||
message_.phi_id())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -254,50 +169,10 @@ bool TransformationAddDeadBreak::IsApplicable(
|
||||
|
||||
void TransformationAddDeadBreak::Apply(opt::IRContext* context,
|
||||
FactManager* /*unused*/) const {
|
||||
// Get the id of the boolean constant to be used as the break condition.
|
||||
opt::analysis::Bool bool_type;
|
||||
opt::analysis::BoolConstant bool_constant(
|
||||
context->get_type_mgr()->GetRegisteredType(&bool_type)->AsBool(),
|
||||
message_.break_condition_value());
|
||||
uint32_t bool_id = context->get_constant_mgr()->FindDeclaredConstant(
|
||||
&bool_constant, context->get_type_mgr()->GetId(&bool_type));
|
||||
|
||||
auto bb_from = context->cfg()->block(message_.from_block());
|
||||
auto bb_to = context->cfg()->block(message_.to_block());
|
||||
const bool from_to_edge_already_exists = bb_from->IsSuccessor(bb_to);
|
||||
auto successor = bb_from->terminator()->GetSingleWordInOperand(0);
|
||||
assert(bb_from->terminator()->opcode() == SpvOpBranch &&
|
||||
"Precondition for the transformation requires that the source block "
|
||||
"ends with OpBranch");
|
||||
|
||||
// Add the dead break, by turning OpBranch into OpBranchConditional, and
|
||||
// ordering the targets depending on whether the given boolean corresponds to
|
||||
// true or false.
|
||||
bb_from->terminator()->SetOpcode(SpvOpBranchConditional);
|
||||
bb_from->terminator()->SetInOperands(
|
||||
{{SPV_OPERAND_TYPE_ID, {bool_id}},
|
||||
{SPV_OPERAND_TYPE_ID,
|
||||
{message_.break_condition_value() ? successor : message_.to_block()}},
|
||||
{SPV_OPERAND_TYPE_ID,
|
||||
{message_.break_condition_value() ? message_.to_block() : successor}}});
|
||||
|
||||
// Update OpPhi instructions in the target block if this break adds a
|
||||
// previously non-existent edge from source to target.
|
||||
if (!from_to_edge_already_exists) {
|
||||
uint32_t phi_index = 0;
|
||||
for (auto& inst : *bb_to) {
|
||||
if (inst.opcode() != SpvOpPhi) {
|
||||
break;
|
||||
}
|
||||
assert(phi_index < static_cast<uint32_t>(message_.phi_id().size()) &&
|
||||
"There should be exactly one phi id per OpPhi instruction.");
|
||||
inst.AddOperand({SPV_OPERAND_TYPE_ID, {message_.phi_id()[phi_index]}});
|
||||
inst.AddOperand({SPV_OPERAND_TYPE_ID, {message_.from_block()}});
|
||||
phi_index++;
|
||||
}
|
||||
assert(phi_index == static_cast<uint32_t>(message_.phi_id().size()) &&
|
||||
"There should be exactly one phi id per OpPhi instruction.");
|
||||
}
|
||||
fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
|
||||
context, context->cfg()->block(message_.from_block()),
|
||||
context->cfg()->block(message_.to_block()),
|
||||
message_.break_condition_value(), message_.phi_id());
|
||||
|
||||
// Invalidate all analyses
|
||||
context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
|
||||
|
@ -34,25 +34,25 @@ class TransformationAddDeadBreak : public Transformation {
|
||||
bool break_condition_value,
|
||||
std::vector<uint32_t> phi_id);
|
||||
|
||||
// - |message.from_block| must be the id of a block a in the given module.
|
||||
// - |message.to_block| must be the id of a block b in the given module.
|
||||
// - if |message.break_condition_value| holds (does not hold) then
|
||||
// - |message_.from_block| must be the id of a block a in the given module.
|
||||
// - |message_.to_block| must be the id of a block b in the given module.
|
||||
// - if |message_.break_condition_value| holds (does not hold) then
|
||||
// OpConstantTrue (OpConstantFalse) must be present in the module
|
||||
// - |message.phi_ids| must be a list of ids that are all available at
|
||||
// |message.from_block|
|
||||
// - |message_.phi_ids| must be a list of ids that are all available at
|
||||
// |message_.from_block|
|
||||
// - a and b must be in the same function.
|
||||
// - b must be a merge block.
|
||||
// - a must end with an unconditional branch to some block c.
|
||||
// - replacing this branch with a conditional branch to b or c, with
|
||||
// the boolean constant associated with |message.break_condition_value| as
|
||||
// the condition, and the ids in |message.phi_ids| used to extend
|
||||
// the boolean constant associated with |message_.break_condition_value| as
|
||||
// the condition, and the ids in |message_.phi_ids| used to extend
|
||||
// any OpPhi instructions at b as a result of the edge from a, must
|
||||
// maintain validity of the module.
|
||||
bool IsApplicable(opt::IRContext* context,
|
||||
const FactManager& fact_manager) const override;
|
||||
|
||||
// Replaces the terminator of a with a conditional branch to b or c.
|
||||
// The boolean constant associated with |message.break_condition_value| is
|
||||
// The boolean constant associated with |message_.break_condition_value| is
|
||||
// used as the condition, and the order of b and c is arranged such that
|
||||
// control is guaranteed to jump to c.
|
||||
void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
|
||||
@ -60,21 +60,6 @@ class TransformationAddDeadBreak : public Transformation {
|
||||
protobufs::Transformation ToMessage() const override;
|
||||
|
||||
private:
|
||||
// Return the block with id |maybe_block_id| if it exists, and nullptr
|
||||
// otherwise.
|
||||
opt::BasicBlock* MaybeFindBlock(opt::IRContext* context,
|
||||
uint32_t maybe_block_id) const;
|
||||
|
||||
// Returns true if and only if the phi ids associated with |message_| are
|
||||
// sufficient to allow an edge from |bb_from| to |bb_to| to be added.
|
||||
bool PhiIdsOk(opt::IRContext* context, opt::BasicBlock* bb_from,
|
||||
opt::BasicBlock* bb_to) const;
|
||||
|
||||
// Returns true if and only if |message_.from_block| is in the continue
|
||||
// construct of a loop headed at |maybe_loop_header|.
|
||||
bool FromBlockIsInLoopContinueConstruct(opt::IRContext* context,
|
||||
uint32_t maybe_loop_header) const;
|
||||
|
||||
// Returns true if and only if adding an edge from |bb_from| to
|
||||
// |message_.to_block| respects structured control flow.
|
||||
bool AddingBreakRespectsStructuredControlFlow(opt::IRContext* context,
|
||||
|
133
3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp
vendored
Normal file
133
3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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/fuzz/transformation_add_dead_continue.h"
|
||||
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
TransformationAddDeadContinue::TransformationAddDeadContinue(
|
||||
const spvtools::fuzz::protobufs::TransformationAddDeadContinue& message)
|
||||
: message_(message) {}
|
||||
|
||||
TransformationAddDeadContinue::TransformationAddDeadContinue(
|
||||
uint32_t from_block, bool continue_condition_value,
|
||||
std::vector<uint32_t> phi_id) {
|
||||
message_.set_from_block(from_block);
|
||||
message_.set_continue_condition_value(continue_condition_value);
|
||||
for (auto id : phi_id) {
|
||||
message_.add_phi_id(id);
|
||||
}
|
||||
}
|
||||
|
||||
bool TransformationAddDeadContinue::IsApplicable(
|
||||
opt::IRContext* context, const FactManager& /*unused*/) const {
|
||||
// First, we check that a constant with the same value as
|
||||
// |message_.continue_condition_value| is present.
|
||||
opt::analysis::Bool bool_type;
|
||||
auto registered_bool_type =
|
||||
context->get_type_mgr()->GetRegisteredType(&bool_type);
|
||||
if (!registered_bool_type) {
|
||||
return false;
|
||||
}
|
||||
opt::analysis::BoolConstant bool_constant(
|
||||
registered_bool_type->AsBool(), message_.continue_condition_value());
|
||||
if (!context->get_constant_mgr()->FindConstant(&bool_constant)) {
|
||||
// The required constant is not present, so the transformation cannot be
|
||||
// applied.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that |message_.from_block| really is a block id.
|
||||
opt::BasicBlock* bb_from =
|
||||
fuzzerutil::MaybeFindBlock(context, message_.from_block());
|
||||
if (bb_from == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that |message_.from_block| ends with an unconditional branch.
|
||||
if (bb_from->terminator()->opcode() != SpvOpBranch) {
|
||||
// The block associated with the id does not end with an unconditional
|
||||
// branch.
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(bb_from != nullptr &&
|
||||
"We should have found a block if this line of code is reached.");
|
||||
assert(
|
||||
bb_from->id() == message_.from_block() &&
|
||||
"The id of the block we found should match the source id for the break.");
|
||||
|
||||
// Get the header for the innermost loop containing |message_.from_block|.
|
||||
// Because the structured CFG analysis does not regard a loop header as part
|
||||
// of the loop it heads, we check first whether bb_from is a loop header
|
||||
// before using the structured CFG analysis.
|
||||
auto loop_header = bb_from->IsLoopHeader()
|
||||
? message_.from_block()
|
||||
: context->GetStructuredCFGAnalysis()->ContainingLoop(
|
||||
message_.from_block());
|
||||
if (!loop_header) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fuzzerutil::BlockIsInLoopContinueConstruct(context, message_.from_block(),
|
||||
loop_header)) {
|
||||
// We cannot jump to the continue target from the continue construct.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto continue_block = context->cfg()->block(loop_header)->ContinueBlockId();
|
||||
|
||||
if (context->GetStructuredCFGAnalysis()->IsMergeBlock(continue_block)) {
|
||||
// A branch straight to the continue target that is also a merge block might
|
||||
// break the property that a construct header must dominate its merge block
|
||||
// (if the merge block is reachable).
|
||||
return false;
|
||||
}
|
||||
|
||||
// The transformation is good if and only if the given phi ids are sufficient
|
||||
// to extend relevant OpPhi instructions in the continue block.
|
||||
return fuzzerutil::PhiIdsOkForNewEdge(context, bb_from,
|
||||
context->cfg()->block(continue_block),
|
||||
message_.phi_id());
|
||||
}
|
||||
|
||||
void TransformationAddDeadContinue::Apply(opt::IRContext* context,
|
||||
FactManager* /*unused*/) const {
|
||||
auto bb_from = context->cfg()->block(message_.from_block());
|
||||
auto continue_block =
|
||||
bb_from->IsLoopHeader()
|
||||
? bb_from->ContinueBlockId()
|
||||
: context->GetStructuredCFGAnalysis()->LoopContinueBlock(
|
||||
message_.from_block());
|
||||
assert(continue_block &&
|
||||
"Precondition for this transformation requires that "
|
||||
"message_.from_block must be in a loop.");
|
||||
fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
|
||||
context, bb_from, context->cfg()->block(continue_block),
|
||||
message_.continue_condition_value(), message_.phi_id());
|
||||
// Invalidate all analyses
|
||||
context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
|
||||
}
|
||||
|
||||
protobufs::Transformation TransformationAddDeadContinue::ToMessage() const {
|
||||
protobufs::Transformation result;
|
||||
*result.mutable_add_dead_continue() = message_;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
68
3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.h
vendored
Normal file
68
3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.h
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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_FUZZ_TRANSFORMATION_ADD_DEAD_CONTINUE_H_
|
||||
#define SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_CONTINUE_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "source/fuzz/fact_manager.h"
|
||||
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
|
||||
#include "source/fuzz/transformation.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
class TransformationAddDeadContinue : public Transformation {
|
||||
public:
|
||||
explicit TransformationAddDeadContinue(
|
||||
const protobufs::TransformationAddDeadContinue& message);
|
||||
|
||||
TransformationAddDeadContinue(uint32_t from_block,
|
||||
bool continue_condition_value,
|
||||
std::vector<uint32_t> phi_id);
|
||||
|
||||
// - |message_.from_block| must be the id of a block a in the given module.
|
||||
// - a must be contained in a loop with continue target b
|
||||
// - b must not be the merge block of a selection construct
|
||||
// - if |message_.continue_condition_value| holds (does not hold) then
|
||||
// OpConstantTrue (OpConstantFalse) must be present in the module
|
||||
// - |message_.phi_ids| must be a list of ids that are all available at
|
||||
// |message_.from_block|
|
||||
// - a must end with an unconditional branch to some block c.
|
||||
// - replacing this branch with a conditional branch to b or c, with
|
||||
// the boolean constant associated with |message_.continue_condition_value|
|
||||
// as the condition, and the ids in |message_.phi_ids| used to extend any
|
||||
// OpPhi instructions at b as a result of the edge from a, must maintain
|
||||
// validity of the module.
|
||||
bool IsApplicable(opt::IRContext* context,
|
||||
const FactManager& fact_manager) const override;
|
||||
|
||||
// Replaces the terminator of a with a conditional branch to b or c.
|
||||
// The boolean constant associated with |message_.continue_condition_value| is
|
||||
// used as the condition, and the order of b and c is arranged such that
|
||||
// control is guaranteed to jump to c.
|
||||
void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
|
||||
|
||||
protobufs::Transformation ToMessage() const override;
|
||||
|
||||
private:
|
||||
protobufs::TransformationAddDeadContinue message_;
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_CONTINUE_H_
|
158
3rdparty/spirv-tools/source/fuzz/transformation_copy_object.cpp
vendored
Normal file
158
3rdparty/spirv-tools/source/fuzz/transformation_copy_object.cpp
vendored
Normal file
@ -0,0 +1,158 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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/fuzz/transformation_copy_object.h"
|
||||
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
#include "source/opt/instruction.h"
|
||||
#include "source/util/make_unique.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
TransformationCopyObject::TransformationCopyObject(
|
||||
const protobufs::TransformationCopyObject& message)
|
||||
: message_(message) {}
|
||||
|
||||
TransformationCopyObject::TransformationCopyObject(uint32_t object,
|
||||
uint32_t base_instruction_id,
|
||||
uint32_t offset,
|
||||
uint32_t fresh_id) {
|
||||
message_.set_object(object);
|
||||
message_.set_base_instruction_id(base_instruction_id);
|
||||
message_.set_offset(offset);
|
||||
message_.set_fresh_id(fresh_id);
|
||||
}
|
||||
|
||||
bool TransformationCopyObject::IsApplicable(
|
||||
opt::IRContext* context, const FactManager& /*fact_manager*/) const {
|
||||
if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
|
||||
// We require the id for the object copy to be unused.
|
||||
return false;
|
||||
}
|
||||
// The id of the object to be copied must exist
|
||||
auto object_inst = context->get_def_use_mgr()->GetDef(message_.object());
|
||||
if (!object_inst) {
|
||||
return false;
|
||||
}
|
||||
if (!object_inst->type_id()) {
|
||||
// We can only apply OpCopyObject to instructions that have types.
|
||||
return false;
|
||||
}
|
||||
if (!context->get_decoration_mgr()
|
||||
->GetDecorationsFor(message_.object(), true)
|
||||
.empty()) {
|
||||
// We do not copy objects that have decorations: if the copy is not
|
||||
// decorated analogously, using the original object vs. its copy may not be
|
||||
// equivalent.
|
||||
// TODO(afd): it would be possible to make the copy but not add an id
|
||||
// synonym.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto base_instruction =
|
||||
context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
|
||||
if (!base_instruction) {
|
||||
// The given id to insert after is not defined.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto destination_block = context->get_instr_block(base_instruction);
|
||||
if (!destination_block) {
|
||||
// The given id to insert after is not in a block.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto insert_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
|
||||
destination_block, base_instruction, message_.offset());
|
||||
|
||||
if (insert_before == destination_block->end()) {
|
||||
// The offset was inappropriate.
|
||||
return false;
|
||||
}
|
||||
if (insert_before->PreviousNode() &&
|
||||
(insert_before->PreviousNode()->opcode() == SpvOpLoopMerge ||
|
||||
insert_before->PreviousNode()->opcode() == SpvOpSelectionMerge)) {
|
||||
// We cannot insert a copy directly after a merge instruction.
|
||||
return false;
|
||||
}
|
||||
if (insert_before->opcode() == SpvOpVariable) {
|
||||
// We cannot insert a copy directly before a variable; variables in a
|
||||
// function must be contiguous in the entry block.
|
||||
return false;
|
||||
}
|
||||
// We cannot insert a copy directly before OpPhi, because OpPhi instructions
|
||||
// need to be contiguous at the start of a block.
|
||||
if (insert_before->opcode() == SpvOpPhi) {
|
||||
return false;
|
||||
}
|
||||
// |message_object| must be available at the point where we want to add the
|
||||
// copy. It is available if it is at global scope (in which case it has no
|
||||
// block), or if it dominates the point of insertion but is different from the
|
||||
// point of insertion.
|
||||
//
|
||||
// The reason why the object needs to be different from the insertion point is
|
||||
// that the copy will be added *before* this point, and we do not want to
|
||||
// insert it before the object's defining instruction.
|
||||
return !context->get_instr_block(object_inst) ||
|
||||
(object_inst != &*insert_before &&
|
||||
context->GetDominatorAnalysis(destination_block->GetParent())
|
||||
->Dominates(object_inst, &*insert_before));
|
||||
}
|
||||
|
||||
void TransformationCopyObject::Apply(opt::IRContext* context,
|
||||
FactManager* fact_manager) const {
|
||||
// - A new instruction,
|
||||
// %|message_.fresh_id| = OpCopyObject %ty %|message_.object|
|
||||
// is added directly before the instruction at |message_.insert_after_id| +
|
||||
// |message_|.offset, where %ty is the type of |message_.object|.
|
||||
// - The fact that |message_.fresh_id| and |message_.object| are synonyms
|
||||
// is added to the fact manager.
|
||||
// The id of the object to be copied must exist
|
||||
auto object_inst = context->get_def_use_mgr()->GetDef(message_.object());
|
||||
assert(object_inst && "The object to be copied must exist.");
|
||||
auto base_instruction =
|
||||
context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
|
||||
assert(base_instruction && "The base instruction must exist.");
|
||||
auto destination_block = context->get_instr_block(base_instruction);
|
||||
assert(destination_block && "The base instruction must be in a block.");
|
||||
auto insert_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
|
||||
destination_block, base_instruction, message_.offset());
|
||||
assert(insert_before != destination_block->end() &&
|
||||
"There must be an instruction before which the copy can be inserted.");
|
||||
|
||||
opt::Instruction::OperandList operands = {
|
||||
{SPV_OPERAND_TYPE_ID, {message_.object()}}};
|
||||
insert_before->InsertBefore(MakeUnique<opt::Instruction>(
|
||||
context, SpvOp::SpvOpCopyObject, object_inst->type_id(),
|
||||
message_.fresh_id(), operands));
|
||||
|
||||
fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
|
||||
context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
|
||||
|
||||
protobufs::Fact fact;
|
||||
fact.mutable_id_synonym_fact()->set_id(message_.object());
|
||||
fact.mutable_id_synonym_fact()->mutable_data_descriptor()->set_object(
|
||||
message_.fresh_id());
|
||||
fact_manager->AddFact(fact, context);
|
||||
}
|
||||
|
||||
protobufs::Transformation TransformationCopyObject::ToMessage() const {
|
||||
protobufs::Transformation result;
|
||||
*result.mutable_copy_object() = message_;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
68
3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h
vendored
Normal file
68
3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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_FUZZ_TRANSFORMATION_COPY_OBJECT_H_
|
||||
#define SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_
|
||||
|
||||
#include "source/fuzz/fact_manager.h"
|
||||
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
|
||||
#include "source/fuzz/transformation.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
class TransformationCopyObject : public Transformation {
|
||||
public:
|
||||
explicit TransformationCopyObject(
|
||||
const protobufs::TransformationCopyObject& message);
|
||||
|
||||
TransformationCopyObject(uint32_t fresh_id, uint32_t object,
|
||||
uint32_t insert_after_id, uint32_t offset);
|
||||
|
||||
// - |message_.fresh_id| must not be used by the module.
|
||||
// - |message_.object| must be a result id that is a legitimate operand for
|
||||
// OpCopyObject. In particular, it must be the id of an instruction that
|
||||
// has a result type
|
||||
// - |message_.object| must not be the target of any decoration.
|
||||
// TODO(afd): consider copying decorations along with objects.
|
||||
// - |message_.insert_after_id| must be the result id of an instruction
|
||||
// 'base' in some block 'blk'.
|
||||
// - 'blk' must contain an instruction 'inst' located |message_.offset|
|
||||
// instructions after 'base' (if |message_.offset| = 0 then 'inst' =
|
||||
// 'base').
|
||||
// - It must be legal to insert an OpCopyObject instruction directly
|
||||
// before 'inst'.
|
||||
// - |message_object| must be available directly before 'inst'.
|
||||
bool IsApplicable(opt::IRContext* context,
|
||||
const FactManager& fact_manager) const override;
|
||||
|
||||
// - A new instruction,
|
||||
// %|message_.fresh_id| = OpCopyObject %ty %|message_.object|
|
||||
// is added directly before the instruction at |message_.insert_after_id| +
|
||||
// |message_|.offset, where %ty is the type of |message_.object|.
|
||||
// - The fact that |message_.fresh_id| and |message_.object| are synonyms
|
||||
// is added to the fact manager.
|
||||
void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
|
||||
|
||||
protobufs::Transformation ToMessage() const override;
|
||||
|
||||
private:
|
||||
protobufs::TransformationCopyObject message_;
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_
|
@ -26,147 +26,104 @@ TransformationSplitBlock::TransformationSplitBlock(
|
||||
const spvtools::fuzz::protobufs::TransformationSplitBlock& message)
|
||||
: message_(message) {}
|
||||
|
||||
TransformationSplitBlock::TransformationSplitBlock(uint32_t result_id,
|
||||
TransformationSplitBlock::TransformationSplitBlock(uint32_t base_instruction_id,
|
||||
uint32_t offset,
|
||||
uint32_t fresh_id) {
|
||||
message_.set_result_id(result_id);
|
||||
message_.set_base_instruction_id(base_instruction_id);
|
||||
message_.set_offset(offset);
|
||||
message_.set_fresh_id(fresh_id);
|
||||
}
|
||||
|
||||
std::pair<bool, opt::BasicBlock::iterator>
|
||||
TransformationSplitBlock::FindInstToSplitBefore(opt::BasicBlock* block) const {
|
||||
// There are three possibilities:
|
||||
// (1) the transformation wants to split at some offset from the block's
|
||||
// label.
|
||||
// (2) the transformation wants to split at some offset from a
|
||||
// non-label instruction inside the block.
|
||||
// (3) the split assocaiated with this transformation has nothing to do with
|
||||
// this block
|
||||
if (message_.result_id() == block->id()) {
|
||||
// Case (1).
|
||||
if (message_.offset() == 0) {
|
||||
// The offset is not allowed to be 0: this would mean splitting before the
|
||||
// block's label.
|
||||
// By returning (true, block->end()), we indicate that we did find the
|
||||
// instruction (so that it is not worth searching further for it), but
|
||||
// that splitting will not be possible.
|
||||
return {true, block->end()};
|
||||
}
|
||||
// Conceptually, the first instruction in the block is [label + 1].
|
||||
// We thus start from 1 when applying the offset.
|
||||
auto inst_it = block->begin();
|
||||
for (uint32_t i = 1; i < message_.offset() && inst_it != block->end();
|
||||
i++) {
|
||||
++inst_it;
|
||||
}
|
||||
// This is either the desired instruction, or the end of the block.
|
||||
return {true, inst_it};
|
||||
}
|
||||
for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) {
|
||||
if (message_.result_id() == inst_it->result_id()) {
|
||||
// Case (2): we have found the base instruction; we now apply the offset.
|
||||
for (uint32_t i = 0; i < message_.offset() && inst_it != block->end();
|
||||
i++) {
|
||||
++inst_it;
|
||||
}
|
||||
// This is either the desired instruction, or the end of the block.
|
||||
return {true, inst_it};
|
||||
}
|
||||
}
|
||||
// Case (3).
|
||||
return {false, block->end()};
|
||||
}
|
||||
|
||||
bool TransformationSplitBlock::IsApplicable(
|
||||
opt::IRContext* context, const FactManager& /*unused*/) const {
|
||||
if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
|
||||
// We require the id for the new block to be unused.
|
||||
return false;
|
||||
}
|
||||
// Consider every block in every function.
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto& block : function) {
|
||||
auto maybe_split_before = FindInstToSplitBefore(&block);
|
||||
if (!maybe_split_before.first) {
|
||||
continue;
|
||||
}
|
||||
if (maybe_split_before.second == block.end()) {
|
||||
// The base instruction was found, but the offset was inappropriate.
|
||||
return false;
|
||||
}
|
||||
if (block.IsLoopHeader()) {
|
||||
// We cannot split a loop header block: back-edges would become invalid.
|
||||
return false;
|
||||
}
|
||||
auto split_before = maybe_split_before.second;
|
||||
if (split_before->PreviousNode() &&
|
||||
split_before->PreviousNode()->opcode() == SpvOpSelectionMerge) {
|
||||
// We cannot split directly after a selection merge: this would separate
|
||||
// the merge from its associated branch or switch operation.
|
||||
return false;
|
||||
}
|
||||
if (split_before->opcode() == SpvOpVariable) {
|
||||
// We cannot split directly after a variable; variables in a function
|
||||
// must be contiguous in the entry block.
|
||||
return false;
|
||||
}
|
||||
if (split_before->opcode() == SpvOpPhi &&
|
||||
split_before->NumInOperands() != 2) {
|
||||
// We cannot split before an OpPhi unless the OpPhi has exactly one
|
||||
// associated incoming edge.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
auto base_instruction =
|
||||
context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
|
||||
if (!base_instruction) {
|
||||
// The instruction describing the block we should split does not exist.
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
auto block_containing_base_instruction =
|
||||
context->get_instr_block(base_instruction);
|
||||
if (!block_containing_base_instruction) {
|
||||
// The instruction describing the block we should split is not contained in
|
||||
// a block.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (block_containing_base_instruction->IsLoopHeader()) {
|
||||
// We cannot split a loop header block: back-edges would become invalid.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto split_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
|
||||
block_containing_base_instruction, base_instruction, message_.offset());
|
||||
if (split_before == block_containing_base_instruction->end()) {
|
||||
// The offset was inappropriate.
|
||||
return false;
|
||||
}
|
||||
if (split_before->PreviousNode() &&
|
||||
split_before->PreviousNode()->opcode() == SpvOpSelectionMerge) {
|
||||
// We cannot split directly after a selection merge: this would separate
|
||||
// the merge from its associated branch or switch operation.
|
||||
return false;
|
||||
}
|
||||
if (split_before->opcode() == SpvOpVariable) {
|
||||
// We cannot split directly after a variable; variables in a function
|
||||
// must be contiguous in the entry block.
|
||||
return false;
|
||||
}
|
||||
// We cannot split before an OpPhi unless the OpPhi has exactly one
|
||||
// associated incoming edge.
|
||||
return !(split_before->opcode() == SpvOpPhi &&
|
||||
split_before->NumInOperands() != 2);
|
||||
}
|
||||
|
||||
void TransformationSplitBlock::Apply(opt::IRContext* context,
|
||||
FactManager* /*unused*/) const {
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto& block : function) {
|
||||
auto maybe_split_before = FindInstToSplitBefore(&block);
|
||||
if (!maybe_split_before.first) {
|
||||
continue;
|
||||
}
|
||||
assert(maybe_split_before.second != block.end() &&
|
||||
"If the transformation is applicable, we should have an "
|
||||
"instruction to split on.");
|
||||
// We need to make sure the module's id bound is large enough to add the
|
||||
// fresh id.
|
||||
fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
|
||||
// Split the block.
|
||||
auto new_bb = block.SplitBasicBlock(context, message_.fresh_id(),
|
||||
maybe_split_before.second);
|
||||
// The split does not automatically add a branch between the two parts of
|
||||
// the original block, so we add one.
|
||||
block.AddInstruction(MakeUnique<opt::Instruction>(
|
||||
auto base_instruction =
|
||||
context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
|
||||
assert(base_instruction && "Base instruction must exist");
|
||||
auto block_containing_base_instruction =
|
||||
context->get_instr_block(base_instruction);
|
||||
assert(block_containing_base_instruction &&
|
||||
"Base instruction must be in a block");
|
||||
auto split_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
|
||||
block_containing_base_instruction, base_instruction, message_.offset());
|
||||
assert(split_before != block_containing_base_instruction->end() &&
|
||||
"If the transformation is applicable, we should have an "
|
||||
"instruction to split on.");
|
||||
// We need to make sure the module's id bound is large enough to add the
|
||||
// fresh id.
|
||||
fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
|
||||
// Split the block.
|
||||
auto new_bb = block_containing_base_instruction->SplitBasicBlock(
|
||||
context, message_.fresh_id(), split_before);
|
||||
// The split does not automatically add a branch between the two parts of
|
||||
// the original block, so we add one.
|
||||
block_containing_base_instruction->AddInstruction(
|
||||
MakeUnique<opt::Instruction>(
|
||||
context, SpvOpBranch, 0, 0,
|
||||
std::initializer_list<opt::Operand>{
|
||||
opt::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID,
|
||||
{message_.fresh_id()})}));
|
||||
// If we split before OpPhi instructions, we need to update their
|
||||
// predecessor operand so that the block they used to be inside is now the
|
||||
// predecessor.
|
||||
new_bb->ForEachPhiInst([&block](opt::Instruction* phi_inst) {
|
||||
// If we split before OpPhi instructions, we need to update their
|
||||
// predecessor operand so that the block they used to be inside is now the
|
||||
// predecessor.
|
||||
new_bb->ForEachPhiInst(
|
||||
[block_containing_base_instruction](opt::Instruction* phi_inst) {
|
||||
// The following assertion is a sanity check. It is guaranteed to hold
|
||||
// if IsApplicable holds.
|
||||
assert(phi_inst->NumInOperands() == 2 &&
|
||||
"We can only split a block before an OpPhi if block has exactly "
|
||||
"one predecessor.");
|
||||
phi_inst->SetInOperand(1, {block.id()});
|
||||
phi_inst->SetInOperand(1, {block_containing_base_instruction->id()});
|
||||
});
|
||||
// Invalidate all analyses
|
||||
context->InvalidateAnalysesExceptFor(
|
||||
opt::IRContext::Analysis::kAnalysisNone);
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert(0 &&
|
||||
"Should be unreachable: it should have been possible to apply this "
|
||||
"transformation.");
|
||||
// Invalidate all analyses
|
||||
context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
|
||||
}
|
||||
|
||||
protobufs::Transformation TransformationSplitBlock::ToMessage() const {
|
||||
|
@ -28,13 +28,13 @@ class TransformationSplitBlock : public Transformation {
|
||||
explicit TransformationSplitBlock(
|
||||
const protobufs::TransformationSplitBlock& message);
|
||||
|
||||
TransformationSplitBlock(uint32_t result_id, uint32_t offset,
|
||||
TransformationSplitBlock(uint32_t base_instruction_id, uint32_t offset,
|
||||
uint32_t fresh_id);
|
||||
|
||||
// - |message_.result_id| must be the result id of an instruction 'base' in
|
||||
// some block 'blk'.
|
||||
// - |message_.base_instruction_id| must be the result id of an instruction
|
||||
// 'base' in some block 'blk'.
|
||||
// - 'blk' must contain an instruction 'inst' located |message_.offset|
|
||||
// instructions after 'inst' (if |message_.offset| = 0 then 'inst' =
|
||||
// instructions after 'base' (if |message_.offset| = 0 then 'inst' =
|
||||
// 'base').
|
||||
// - Splitting 'blk' at 'inst', so that all instructions from 'inst' onwards
|
||||
// appear in a new block that 'blk' directly jumps to must be valid.
|
||||
@ -52,14 +52,6 @@ class TransformationSplitBlock : public Transformation {
|
||||
protobufs::Transformation ToMessage() const override;
|
||||
|
||||
private:
|
||||
// Returns:
|
||||
// - (true, block->end()) if the relevant instruction is in this block
|
||||
// but inapplicable
|
||||
// - (true, it) if 'it' is an iterator for the relevant instruction
|
||||
// - (false, _) otherwise.
|
||||
std::pair<bool, opt::BasicBlock::iterator> FindInstToSplitBefore(
|
||||
opt::BasicBlock* block) const;
|
||||
|
||||
protobufs::TransformationSplitBlock message_;
|
||||
};
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
#include "source/fuzz/uniform_buffer_element_descriptor.h"
|
||||
|
||||
#include <source/opt/instruction.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
@ -15,7 +15,6 @@
|
||||
#ifndef SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_
|
||||
#define SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
|
||||
@ -25,8 +24,8 @@
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
// Factory method to create a uniform buffer element descriptor message from an
|
||||
// id and list of indices.
|
||||
// Factory method to create a uniform buffer element descriptor message from
|
||||
// descriptor set and binding ids and a list of indices.
|
||||
protobufs::UniformBufferElementDescriptor MakeUniformBufferElementDescriptor(
|
||||
uint32_t descriptor_set, uint32_t binding, std::vector<uint32_t>&& indices);
|
||||
|
||||
|
@ -33,6 +33,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
|
||||
decompose_initialized_variables_pass.h
|
||||
decoration_manager.h
|
||||
def_use_manager.h
|
||||
desc_sroa.h
|
||||
dominator_analysis.h
|
||||
dominator_tree.h
|
||||
eliminate_dead_constant_pass.h
|
||||
@ -48,6 +49,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
|
||||
freeze_spec_constant_value_pass.h
|
||||
function.h
|
||||
generate_webgpu_initializers_pass.h
|
||||
graphics_robust_access_pass.h
|
||||
if_conversion.h
|
||||
inline_exhaustive_pass.h
|
||||
inline_opaque_pass.h
|
||||
@ -133,6 +135,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
|
||||
decompose_initialized_variables_pass.cpp
|
||||
decoration_manager.cpp
|
||||
def_use_manager.cpp
|
||||
desc_sroa.cpp
|
||||
dominator_analysis.cpp
|
||||
dominator_tree.cpp
|
||||
eliminate_dead_constant_pass.cpp
|
||||
@ -147,6 +150,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
|
||||
fold_spec_constant_op_and_composite_pass.cpp
|
||||
freeze_spec_constant_value_pass.cpp
|
||||
function.cpp
|
||||
graphics_robust_access_pass.cpp
|
||||
generate_webgpu_initializers_pass.cpp
|
||||
if_conversion.cpp
|
||||
inline_exhaustive_pass.cpp
|
||||
|
@ -836,6 +836,15 @@ bool AggressiveDCEPass::ProcessGlobalValues() {
|
||||
// attributes here.
|
||||
for (auto& val : get_module()->types_values()) {
|
||||
if (IsDead(&val)) {
|
||||
// Save forwarded pointer if pointer is live since closure does not mark
|
||||
// this live as it does not have a result id. This is a little too
|
||||
// conservative since it is not known if the structure type that needed
|
||||
// it is still live. TODO(greg-lunarg): Only save if needed.
|
||||
if (val.opcode() == SpvOpTypeForwardPointer) {
|
||||
uint32_t ptr_ty_id = val.GetSingleWordInOperand(0);
|
||||
Instruction* ptr_ty_inst = get_def_use_mgr()->GetDef(ptr_ty_id);
|
||||
if (!IsDead(ptr_ty_inst)) continue;
|
||||
}
|
||||
to_kill_.push_back(&val);
|
||||
}
|
||||
}
|
||||
@ -918,6 +927,7 @@ void AggressiveDCEPass::InitExtensions() {
|
||||
"SPV_NV_mesh_shader",
|
||||
"SPV_NV_ray_tracing",
|
||||
"SPV_EXT_fragment_invocation_density",
|
||||
"SPV_EXT_physical_storage_buffer",
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,9 @@ ConstantFoldingRule FoldExtractWithConstants() {
|
||||
auto cc = c->AsCompositeConstant();
|
||||
assert(cc != nullptr);
|
||||
auto components = cc->GetComponents();
|
||||
// Protect against invalid IR. Refuse to fold if the index is out
|
||||
// of bounds.
|
||||
if (element_index >= components.size()) return nullptr;
|
||||
c = components[element_index];
|
||||
}
|
||||
return c;
|
||||
|
41
3rdparty/spirv-tools/source/opt/constants.cpp
vendored
41
3rdparty/spirv-tools/source/opt/constants.cpp
vendored
@ -103,6 +103,45 @@ int64_t Constant::GetS64() const {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t Constant::GetZeroExtendedValue() const {
|
||||
const auto* int_type = type()->AsInteger();
|
||||
assert(int_type != nullptr);
|
||||
const auto width = int_type->width();
|
||||
assert(width <= 64);
|
||||
|
||||
uint64_t value = 0;
|
||||
if (const IntConstant* ic = AsIntConstant()) {
|
||||
if (width <= 32) {
|
||||
value = ic->GetU32BitValue();
|
||||
} else {
|
||||
value = ic->GetU64BitValue();
|
||||
}
|
||||
} else {
|
||||
assert(AsNullConstant() && "Must be an integer constant.");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int64_t Constant::GetSignExtendedValue() const {
|
||||
const auto* int_type = type()->AsInteger();
|
||||
assert(int_type != nullptr);
|
||||
const auto width = int_type->width();
|
||||
assert(width <= 64);
|
||||
|
||||
int64_t value = 0;
|
||||
if (const IntConstant* ic = AsIntConstant()) {
|
||||
if (width <= 32) {
|
||||
// Let the C++ compiler do the sign extension.
|
||||
value = int64_t(ic->GetS32BitValue());
|
||||
} else {
|
||||
value = ic->GetS64BitValue();
|
||||
}
|
||||
} else {
|
||||
assert(AsNullConstant() && "Must be an integer constant.");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
ConstantManager::ConstantManager(IRContext* ctx) : ctx_(ctx) {
|
||||
// Populate the constant table with values from constant declarations in the
|
||||
// module. The values of each OpConstant declaration is the identity
|
||||
@ -252,7 +291,7 @@ std::unique_ptr<Constant> ConstantManager::CreateConstant(
|
||||
}
|
||||
}
|
||||
|
||||
const Constant* ConstantManager::GetConstantFromInst(Instruction* inst) {
|
||||
const Constant* ConstantManager::GetConstantFromInst(const Instruction* inst) {
|
||||
std::vector<uint32_t> literal_words_or_ids;
|
||||
|
||||
// Collect the constant defining literals or component ids.
|
||||
|
10
3rdparty/spirv-tools/source/opt/constants.h
vendored
10
3rdparty/spirv-tools/source/opt/constants.h
vendored
@ -116,6 +116,14 @@ class Constant {
|
||||
// Integer type.
|
||||
int64_t GetS64() const;
|
||||
|
||||
// Returns the zero-extended representation of an integer constant. Must
|
||||
// be an integral constant of at most 64 bits.
|
||||
uint64_t GetZeroExtendedValue() const;
|
||||
|
||||
// Returns the sign-extended representation of an integer constant. Must
|
||||
// be an integral constant of at most 64 bits.
|
||||
int64_t GetSignExtendedValue() const;
|
||||
|
||||
// Returns true if the constant is a zero or a composite containing 0s.
|
||||
virtual bool IsZero() const { return false; }
|
||||
|
||||
@ -514,7 +522,7 @@ class ConstantManager {
|
||||
// Gets or creates a Constant instance to hold the constant value of the given
|
||||
// instruction. It returns a pointer to a Constant instance or nullptr if it
|
||||
// could not create the constant.
|
||||
const Constant* GetConstantFromInst(Instruction* inst);
|
||||
const Constant* GetConstantFromInst(const Instruction* inst);
|
||||
|
||||
// Gets or creates a constant defining instruction for the given Constant |c|.
|
||||
// If |c| had already been defined, it returns a pointer to the existing
|
||||
|
255
3rdparty/spirv-tools/source/opt/desc_sroa.cpp
vendored
Normal file
255
3rdparty/spirv-tools/source/opt/desc_sroa.cpp
vendored
Normal file
@ -0,0 +1,255 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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/desc_sroa.h"
|
||||
|
||||
#include <source/util/string_utils.h>
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
Pass::Status DescriptorScalarReplacement::Process() {
|
||||
bool modified = false;
|
||||
|
||||
std::vector<Instruction*> vars_to_kill;
|
||||
|
||||
for (Instruction& var : context()->types_values()) {
|
||||
if (IsCandidate(&var)) {
|
||||
modified = true;
|
||||
if (!ReplaceCandidate(&var)) {
|
||||
return Status::Failure;
|
||||
}
|
||||
vars_to_kill.push_back(&var);
|
||||
}
|
||||
}
|
||||
|
||||
for (Instruction* var : vars_to_kill) {
|
||||
context()->KillInst(var);
|
||||
}
|
||||
|
||||
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
bool DescriptorScalarReplacement::IsCandidate(Instruction* var) {
|
||||
if (var->opcode() != SpvOpVariable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t ptr_type_id = var->type_id();
|
||||
Instruction* ptr_type_inst =
|
||||
context()->get_def_use_mgr()->GetDef(ptr_type_id);
|
||||
if (ptr_type_inst->opcode() != SpvOpTypePointer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1);
|
||||
Instruction* var_type_inst =
|
||||
context()->get_def_use_mgr()->GetDef(var_type_id);
|
||||
if (var_type_inst->opcode() != SpvOpTypeArray) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has_desc_set_decoration = false;
|
||||
context()->get_decoration_mgr()->ForEachDecoration(
|
||||
var->result_id(), SpvDecorationDescriptorSet,
|
||||
[&has_desc_set_decoration](const Instruction&) {
|
||||
has_desc_set_decoration = true;
|
||||
});
|
||||
if (!has_desc_set_decoration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has_binding_decoration = false;
|
||||
context()->get_decoration_mgr()->ForEachDecoration(
|
||||
var->result_id(), SpvDecorationBinding,
|
||||
[&has_binding_decoration](const Instruction&) {
|
||||
has_binding_decoration = true;
|
||||
});
|
||||
if (!has_binding_decoration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DescriptorScalarReplacement::ReplaceCandidate(Instruction* var) {
|
||||
std::vector<Instruction*> work_list;
|
||||
bool failed = !get_def_use_mgr()->WhileEachUser(
|
||||
var->result_id(), [this, &work_list](Instruction* use) {
|
||||
if (use->opcode() == SpvOpName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (use->IsDecoration()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (use->opcode()) {
|
||||
case SpvOpAccessChain:
|
||||
case SpvOpInBoundsAccessChain:
|
||||
work_list.push_back(use);
|
||||
return true;
|
||||
default:
|
||||
context()->EmitErrorMessage(
|
||||
"Variable cannot be replaced: invalid instruction", use);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (failed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Instruction* use : work_list) {
|
||||
if (!ReplaceAccessChain(var, use)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DescriptorScalarReplacement::ReplaceAccessChain(Instruction* var,
|
||||
Instruction* use) {
|
||||
if (use->NumInOperands() <= 1) {
|
||||
context()->EmitErrorMessage(
|
||||
"Variable cannot be replaced: invalid instruction", use);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t idx_id = use->GetSingleWordInOperand(1);
|
||||
const analysis::Constant* idx_const =
|
||||
context()->get_constant_mgr()->FindDeclaredConstant(idx_id);
|
||||
if (idx_const == nullptr) {
|
||||
context()->EmitErrorMessage("Variable cannot be replaced: invalid index",
|
||||
use);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t idx = idx_const->GetU32();
|
||||
uint32_t replacement_var = GetReplacementVariable(var, idx);
|
||||
|
||||
if (use->NumInOperands() == 2) {
|
||||
// We are not indexing into the replacement variable. We can replaces the
|
||||
// access chain with the replacement varibale itself.
|
||||
context()->ReplaceAllUsesWith(use->result_id(), replacement_var);
|
||||
context()->KillInst(use);
|
||||
return true;
|
||||
}
|
||||
|
||||
// We need to build a new access chain with the replacement variable as the
|
||||
// base address.
|
||||
Instruction::OperandList new_operands;
|
||||
|
||||
// Same result id and result type.
|
||||
new_operands.emplace_back(use->GetOperand(0));
|
||||
new_operands.emplace_back(use->GetOperand(1));
|
||||
|
||||
// Use the replacement variable as the base address.
|
||||
new_operands.push_back({SPV_OPERAND_TYPE_ID, {replacement_var}});
|
||||
|
||||
// Drop the first index because it is consumed by the replacment, and copy the
|
||||
// rest.
|
||||
for (uint32_t i = 4; i < use->NumOperands(); i++) {
|
||||
new_operands.emplace_back(use->GetOperand(i));
|
||||
}
|
||||
|
||||
use->ReplaceOperands(new_operands);
|
||||
context()->UpdateDefUse(use);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t DescriptorScalarReplacement::GetReplacementVariable(Instruction* var,
|
||||
uint32_t idx) {
|
||||
auto replacement_vars = replacement_variables_.find(var);
|
||||
if (replacement_vars == replacement_variables_.end()) {
|
||||
uint32_t ptr_type_id = var->type_id();
|
||||
Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
|
||||
assert(ptr_type_inst->opcode() == SpvOpTypePointer &&
|
||||
"Variable should be a pointer to an array.");
|
||||
uint32_t arr_type_id = ptr_type_inst->GetSingleWordInOperand(1);
|
||||
Instruction* arr_type_inst = get_def_use_mgr()->GetDef(arr_type_id);
|
||||
assert(arr_type_inst->opcode() == SpvOpTypeArray &&
|
||||
"Variable should be a pointer to an array.");
|
||||
|
||||
uint32_t array_len_id = arr_type_inst->GetSingleWordInOperand(1);
|
||||
const analysis::Constant* array_len_const =
|
||||
context()->get_constant_mgr()->FindDeclaredConstant(array_len_id);
|
||||
assert(array_len_const != nullptr && "Array length must be a constant.");
|
||||
uint32_t array_len = array_len_const->GetU32();
|
||||
|
||||
replacement_vars = replacement_variables_
|
||||
.insert({var, std::vector<uint32_t>(array_len, 0)})
|
||||
.first;
|
||||
}
|
||||
|
||||
if (replacement_vars->second[idx] == 0) {
|
||||
replacement_vars->second[idx] = CreateReplacementVariable(var, idx);
|
||||
}
|
||||
|
||||
return replacement_vars->second[idx];
|
||||
}
|
||||
|
||||
uint32_t DescriptorScalarReplacement::CreateReplacementVariable(
|
||||
Instruction* var, uint32_t idx) {
|
||||
// The storage class for the new variable is the same as the original.
|
||||
SpvStorageClass storage_class =
|
||||
static_cast<SpvStorageClass>(var->GetSingleWordInOperand(0));
|
||||
|
||||
// The type for the new variable will be a pointer to type of the elements of
|
||||
// the array.
|
||||
uint32_t ptr_type_id = var->type_id();
|
||||
Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
|
||||
assert(ptr_type_inst->opcode() == SpvOpTypePointer &&
|
||||
"Variable should be a pointer to an array.");
|
||||
uint32_t arr_type_id = ptr_type_inst->GetSingleWordInOperand(1);
|
||||
Instruction* arr_type_inst = get_def_use_mgr()->GetDef(arr_type_id);
|
||||
assert(arr_type_inst->opcode() == SpvOpTypeArray &&
|
||||
"Variable should be a pointer to an array.");
|
||||
uint32_t element_type_id = arr_type_inst->GetSingleWordInOperand(0);
|
||||
|
||||
uint32_t ptr_element_type_id = context()->get_type_mgr()->FindPointerToType(
|
||||
element_type_id, storage_class);
|
||||
|
||||
// Create the variable.
|
||||
uint32_t id = TakeNextId();
|
||||
std::unique_ptr<Instruction> variable(
|
||||
new Instruction(context(), SpvOpVariable, ptr_element_type_id, id,
|
||||
std::initializer_list<Operand>{
|
||||
{SPV_OPERAND_TYPE_STORAGE_CLASS,
|
||||
{static_cast<uint32_t>(storage_class)}}}));
|
||||
context()->AddGlobalValue(std::move(variable));
|
||||
|
||||
// Copy all of the decorations to the new variable. The only difference is
|
||||
// the Binding decoration needs to be adjusted.
|
||||
for (auto old_decoration :
|
||||
get_decoration_mgr()->GetDecorationsFor(var->result_id(), true)) {
|
||||
assert(old_decoration->opcode() == SpvOpDecorate);
|
||||
std::unique_ptr<Instruction> new_decoration(
|
||||
old_decoration->Clone(context()));
|
||||
new_decoration->SetInOperand(0, {id});
|
||||
|
||||
uint32_t decoration = new_decoration->GetSingleWordInOperand(1u);
|
||||
if (decoration == SpvDecorationBinding) {
|
||||
uint32_t new_binding = new_decoration->GetSingleWordInOperand(2) + idx;
|
||||
new_decoration->SetInOperand(2, {new_binding});
|
||||
}
|
||||
context()->AddAnnotationInst(std::move(new_decoration));
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
84
3rdparty/spirv-tools/source/opt/desc_sroa.h
vendored
Normal file
84
3rdparty/spirv-tools/source/opt/desc_sroa.h
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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_DESC_SROA_H_
|
||||
#define SOURCE_OPT_DESC_SROA_H_
|
||||
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "source/opt/function.h"
|
||||
#include "source/opt/pass.h"
|
||||
#include "source/opt/type_manager.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
// Documented in optimizer.hpp
|
||||
class DescriptorScalarReplacement : public Pass {
|
||||
public:
|
||||
DescriptorScalarReplacement() {}
|
||||
|
||||
const char* name() const override { return "descriptor-scalar-replacement"; }
|
||||
|
||||
Status Process() override;
|
||||
|
||||
IRContext::Analysis GetPreservedAnalyses() override {
|
||||
return IRContext::kAnalysisDefUse |
|
||||
IRContext::kAnalysisInstrToBlockMapping |
|
||||
IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
|
||||
IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
|
||||
}
|
||||
|
||||
private:
|
||||
// Returns true if |var| is an OpVariable instruction that represents a
|
||||
// descriptor array. These are the variables that we want to replace.
|
||||
bool IsCandidate(Instruction* var);
|
||||
|
||||
// Replaces all references to |var| by new variables, one for each element of
|
||||
// the array |var|. The binding for the new variables corresponding to
|
||||
// element i will be the binding of |var| plus i. Returns true if successful.
|
||||
bool ReplaceCandidate(Instruction* var);
|
||||
|
||||
// Replaces the base address |var| in the OpAccessChain or
|
||||
// OpInBoundsAccessChain instruction |use| by the variable that the access
|
||||
// chain accesses. The first index in |use| must be an |OpConstant|. Returns
|
||||
// |true| if successful.
|
||||
bool ReplaceAccessChain(Instruction* var, Instruction* use);
|
||||
|
||||
// Returns the id of the variable that will be used to replace the |idx|th
|
||||
// element of |var|. The variable is created if it has not already been
|
||||
// created.
|
||||
uint32_t GetReplacementVariable(Instruction* var, uint32_t idx);
|
||||
|
||||
// Returns the id of a new variable that can be used to replace the |idx|th
|
||||
// element of |var|.
|
||||
uint32_t CreateReplacementVariable(Instruction* var, uint32_t idx);
|
||||
|
||||
// A map from an OpVariable instruction to the set of variables that will be
|
||||
// used to replace it. The entry |replacement_variables_[var][i]| is the id of
|
||||
// a variable that will be used in the place of the the ith element of the
|
||||
// array |var|. If the entry is |0|, then the variable has not been
|
||||
// created yet.
|
||||
std::map<Instruction*, std::vector<uint32_t>> replacement_variables_;
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_OPT_DESC_SROA_H_
|
968
3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.cpp
vendored
Normal file
968
3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.cpp
vendored
Normal file
@ -0,0 +1,968 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// This pass injects code in a graphics shader to implement guarantees
|
||||
// satisfying Vulkan's robustBufferAcces rules. Robust access rules permit
|
||||
// an out-of-bounds access to be redirected to an access of the same type
|
||||
// (load, store, etc.) but within the same root object.
|
||||
//
|
||||
// We assume baseline functionality in Vulkan, i.e. the module uses
|
||||
// logical addressing mode, without VK_KHR_variable_pointers.
|
||||
//
|
||||
// - Logical addressing mode implies:
|
||||
// - Each root pointer (a pointer that exists other than by the
|
||||
// execution of a shader instruction) is the result of an OpVariable.
|
||||
//
|
||||
// - Instructions that result in pointers are:
|
||||
// OpVariable
|
||||
// OpAccessChain
|
||||
// OpInBoundsAccessChain
|
||||
// OpFunctionParameter
|
||||
// OpImageTexelPointer
|
||||
// OpCopyObject
|
||||
//
|
||||
// - Instructions that use a pointer are:
|
||||
// OpLoad
|
||||
// OpStore
|
||||
// OpAccessChain
|
||||
// OpInBoundsAccessChain
|
||||
// OpFunctionCall
|
||||
// OpImageTexelPointer
|
||||
// OpCopyMemory
|
||||
// OpCopyObject
|
||||
// all OpAtomic* instructions
|
||||
//
|
||||
// We classify pointer-users into:
|
||||
// - Accesses:
|
||||
// - OpLoad
|
||||
// - OpStore
|
||||
// - OpAtomic*
|
||||
// - OpCopyMemory
|
||||
//
|
||||
// - Address calculations:
|
||||
// - OpAccessChain
|
||||
// - OpInBoundsAccessChain
|
||||
//
|
||||
// - Pass-through:
|
||||
// - OpFunctionCall
|
||||
// - OpFunctionParameter
|
||||
// - OpCopyObject
|
||||
//
|
||||
// The strategy is:
|
||||
//
|
||||
// - Handle only logical addressing mode. In particular, don't handle a module
|
||||
// if it uses one of the variable-pointers capabilities.
|
||||
//
|
||||
// - Don't handle modules using capability RuntimeDescriptorArrayEXT. So the
|
||||
// only runtime arrays are those that are the last member in a
|
||||
// Block-decorated struct. This allows us to feasibly/easily compute the
|
||||
// length of the runtime array. See below.
|
||||
//
|
||||
// - The memory locations accessed by OpLoad, OpStore, OpCopyMemory, and
|
||||
// OpAtomic* are determined by their pointer parameter or parameters.
|
||||
// Pointers are always (correctly) typed and so the address and number of
|
||||
// consecutive locations are fully determined by the pointer.
|
||||
//
|
||||
// - A pointer value orginates as one of few cases:
|
||||
//
|
||||
// - OpVariable for an interface object or an array of them: image,
|
||||
// buffer (UBO or SSBO), sampler, sampled-image, push-constant, input
|
||||
// variable, output variable. The execution environment is responsible for
|
||||
// allocating the correct amount of storage for these, and for ensuring
|
||||
// each resource bound to such a variable is big enough to contain the
|
||||
// SPIR-V pointee type of the variable.
|
||||
//
|
||||
// - OpVariable for a non-interface object. These are variables in
|
||||
// Workgroup, Private, and Function storage classes. The compiler ensures
|
||||
// the underlying allocation is big enough to store the entire SPIR-V
|
||||
// pointee type of the variable.
|
||||
//
|
||||
// - An OpFunctionParameter. This always maps to a pointer parameter to an
|
||||
// OpFunctionCall.
|
||||
//
|
||||
// - In logical addressing mode, these are severely limited:
|
||||
// "Any pointer operand to an OpFunctionCall must be:
|
||||
// - a memory object declaration, or
|
||||
// - a pointer to an element in an array that is a memory object
|
||||
// declaration, where the element type is OpTypeSampler or OpTypeImage"
|
||||
//
|
||||
// - This has an important simplifying consequence:
|
||||
//
|
||||
// - When looking for a pointer to the structure containing a runtime
|
||||
// array, you begin with a pointer to the runtime array and trace
|
||||
// backward in the function. You never have to trace back beyond
|
||||
// your function call boundary. So you can't take a partial access
|
||||
// chain into an SSBO, then pass that pointer into a function. So
|
||||
// we don't resort to using fat pointers to compute array length.
|
||||
// We can trace back to a pointer to the containing structure,
|
||||
// and use that in an OpArrayLength instruction. (The structure type
|
||||
// gives us the member index of the runtime array.)
|
||||
//
|
||||
// - Otherwise, the pointer type fully encodes the range of valid
|
||||
// addresses. In particular, the type of a pointer to an aggregate
|
||||
// value fully encodes the range of indices when indexing into
|
||||
// that aggregate.
|
||||
//
|
||||
// - The pointer is the result of an access chain instruction. We clamp
|
||||
// indices contributing to address calculations. As noted above, the
|
||||
// valid ranges are either bound by the length of a runtime array, or
|
||||
// by the type of the base pointer. The length of a runtime array is
|
||||
// the result of an OpArrayLength instruction acting on the pointer of
|
||||
// the containing structure as noted above.
|
||||
//
|
||||
// - TODO(dneto): OpImageTexelPointer:
|
||||
// - Clamp coordinate to the image size returned by OpImageQuerySize
|
||||
// - If multi-sampled, clamp the sample index to the count returned by
|
||||
// OpImageQuerySamples.
|
||||
// - If not multi-sampled, set the sample index to 0.
|
||||
//
|
||||
// - Rely on the external validator to check that pointers are only
|
||||
// used by the instructions as above.
|
||||
//
|
||||
// - Handles OpTypeRuntimeArray
|
||||
// Track pointer back to original resource (pointer to struct), so we can
|
||||
// query the runtime array size.
|
||||
//
|
||||
|
||||
#include "graphics_robust_access_pass.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <utility>
|
||||
|
||||
#include "constants.h"
|
||||
#include "def_use_manager.h"
|
||||
#include "function.h"
|
||||
#include "ir_context.h"
|
||||
#include "module.h"
|
||||
#include "pass.h"
|
||||
#include "source/diagnostic.h"
|
||||
#include "source/util/make_unique.h"
|
||||
#include "spirv-tools/libspirv.h"
|
||||
#include "spirv/unified1/GLSL.std.450.h"
|
||||
#include "spirv/unified1/spirv.h"
|
||||
#include "type_manager.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
using opt::BasicBlock;
|
||||
using opt::Instruction;
|
||||
using opt::Operand;
|
||||
using spvtools::MakeUnique;
|
||||
|
||||
GraphicsRobustAccessPass::GraphicsRobustAccessPass() : module_status_() {}
|
||||
|
||||
Pass::Status GraphicsRobustAccessPass::Process() {
|
||||
module_status_ = PerModuleState();
|
||||
|
||||
ProcessCurrentModule();
|
||||
|
||||
auto result = module_status_.failed
|
||||
? Status::Failure
|
||||
: (module_status_.modified ? Status::SuccessWithChange
|
||||
: Status::SuccessWithoutChange);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
spvtools::DiagnosticStream GraphicsRobustAccessPass::Fail() {
|
||||
module_status_.failed = true;
|
||||
// We don't really have a position, and we'll ignore the result.
|
||||
return std::move(
|
||||
spvtools::DiagnosticStream({}, consumer(), "", SPV_ERROR_INVALID_BINARY)
|
||||
<< name() << ": ");
|
||||
}
|
||||
|
||||
spv_result_t GraphicsRobustAccessPass::IsCompatibleModule() {
|
||||
auto* feature_mgr = context()->get_feature_mgr();
|
||||
if (!feature_mgr->HasCapability(SpvCapabilityShader))
|
||||
return Fail() << "Can only process Shader modules";
|
||||
if (feature_mgr->HasCapability(SpvCapabilityVariablePointers))
|
||||
return Fail() << "Can't process modules with VariablePointers capability";
|
||||
if (feature_mgr->HasCapability(SpvCapabilityVariablePointersStorageBuffer))
|
||||
return Fail() << "Can't process modules with VariablePointersStorageBuffer "
|
||||
"capability";
|
||||
if (feature_mgr->HasCapability(SpvCapabilityRuntimeDescriptorArrayEXT)) {
|
||||
// These have a RuntimeArray outside of Block-decorated struct. There
|
||||
// is no way to compute the array length from within SPIR-V.
|
||||
return Fail() << "Can't process modules with RuntimeDescriptorArrayEXT "
|
||||
"capability";
|
||||
}
|
||||
|
||||
{
|
||||
auto* inst = context()->module()->GetMemoryModel();
|
||||
const auto addressing_model = inst->GetSingleWordOperand(0);
|
||||
if (addressing_model != SpvAddressingModelLogical)
|
||||
return Fail() << "Addressing model must be Logical. Found "
|
||||
<< inst->PrettyPrint();
|
||||
}
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
spv_result_t GraphicsRobustAccessPass::ProcessCurrentModule() {
|
||||
auto err = IsCompatibleModule();
|
||||
if (err != SPV_SUCCESS) return err;
|
||||
|
||||
ProcessFunction fn = [this](opt::Function* f) { return ProcessAFunction(f); };
|
||||
module_status_.modified |= context()->ProcessReachableCallTree(fn);
|
||||
|
||||
// Need something here. It's the price we pay for easier failure paths.
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
bool GraphicsRobustAccessPass::ProcessAFunction(opt::Function* function) {
|
||||
// Ensure that all pointers computed inside a function are within bounds.
|
||||
// Find the access chains in this block before trying to modify them.
|
||||
std::vector<Instruction*> access_chains;
|
||||
std::vector<Instruction*> image_texel_pointers;
|
||||
for (auto& block : *function) {
|
||||
for (auto& inst : block) {
|
||||
switch (inst.opcode()) {
|
||||
case SpvOpAccessChain:
|
||||
case SpvOpInBoundsAccessChain:
|
||||
access_chains.push_back(&inst);
|
||||
break;
|
||||
case SpvOpImageTexelPointer:
|
||||
image_texel_pointers.push_back(&inst);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto* inst : access_chains) {
|
||||
ClampIndicesForAccessChain(inst);
|
||||
}
|
||||
|
||||
for (auto* inst : image_texel_pointers) {
|
||||
if (SPV_SUCCESS != ClampCoordinateForImageTexelPointer(inst)) break;
|
||||
}
|
||||
return module_status_.modified;
|
||||
}
|
||||
|
||||
void GraphicsRobustAccessPass::ClampIndicesForAccessChain(
|
||||
Instruction* access_chain) {
|
||||
Instruction& inst = *access_chain;
|
||||
|
||||
auto* constant_mgr = context()->get_constant_mgr();
|
||||
auto* def_use_mgr = context()->get_def_use_mgr();
|
||||
auto* type_mgr = context()->get_type_mgr();
|
||||
|
||||
// Replaces one of the OpAccessChain index operands with a new value.
|
||||
// Updates def-use analysis.
|
||||
auto replace_index = [&inst, def_use_mgr](uint32_t operand_index,
|
||||
Instruction* new_value) {
|
||||
inst.SetOperand(operand_index, {new_value->result_id()});
|
||||
def_use_mgr->AnalyzeInstUse(&inst);
|
||||
};
|
||||
|
||||
// Replaces one of the OpAccesssChain index operands with a clamped value.
|
||||
// Replace the operand at |operand_index| with the value computed from
|
||||
// unsigned_clamp(%old_value, %min_value, %max_value). It also analyzes
|
||||
// the new instruction and records that them module is modified.
|
||||
auto clamp_index = [&inst, this, &replace_index](
|
||||
uint32_t operand_index, Instruction* old_value,
|
||||
Instruction* min_value, Instruction* max_value) {
|
||||
auto* clamp_inst = MakeClampInst(old_value, min_value, max_value, &inst);
|
||||
replace_index(operand_index, clamp_inst);
|
||||
};
|
||||
|
||||
// Ensures the specified index of access chain |inst| has a value that is
|
||||
// at most |count| - 1. If the index is already a constant value less than
|
||||
// |count| then no change is made.
|
||||
auto clamp_to_literal_count = [&inst, this, &constant_mgr, &type_mgr,
|
||||
&replace_index, &clamp_index](
|
||||
uint32_t operand_index, uint64_t count) {
|
||||
Instruction* index_inst =
|
||||
this->GetDef(inst.GetSingleWordOperand(operand_index));
|
||||
const auto* index_type =
|
||||
type_mgr->GetType(index_inst->type_id())->AsInteger();
|
||||
assert(index_type);
|
||||
if (count <= 1) {
|
||||
// Replace the index with 0.
|
||||
replace_index(operand_index, GetValueForType(0, index_type));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index_width = index_type->width();
|
||||
|
||||
// If the index is a constant then |index_constant| will not be a null
|
||||
// pointer. (If index is an |OpConstantNull| then it |index_constant| will
|
||||
// not be a null pointer.) Since access chain indices must be scalar
|
||||
// integers, this can't be a spec constant.
|
||||
if (auto* index_constant = constant_mgr->GetConstantFromInst(index_inst)) {
|
||||
auto* int_index_constant = index_constant->AsIntConstant();
|
||||
int64_t value = 0;
|
||||
// OpAccessChain indices are treated as signed. So get the signed
|
||||
// constant value here.
|
||||
if (index_width <= 32) {
|
||||
value = int64_t(int_index_constant->GetS32BitValue());
|
||||
} else if (index_width <= 64) {
|
||||
value = int_index_constant->GetS64BitValue();
|
||||
} else {
|
||||
this->Fail() << "Can't handle indices wider than 64 bits, found "
|
||||
"constant index with "
|
||||
<< index_type->width() << "bits";
|
||||
return;
|
||||
}
|
||||
if (value < 0) {
|
||||
replace_index(operand_index, GetValueForType(0, index_type));
|
||||
} else if (uint64_t(value) < count) {
|
||||
// Nothing to do.
|
||||
return;
|
||||
} else {
|
||||
// Replace with count - 1.
|
||||
assert(count > 0); // Already took care of this case above.
|
||||
replace_index(operand_index, GetValueForType(count - 1, index_type));
|
||||
}
|
||||
} else {
|
||||
// Generate a clamp instruction.
|
||||
|
||||
// Compute the bit width of a viable type to hold (count-1).
|
||||
const auto maxval = count - 1;
|
||||
const auto* maxval_type = index_type;
|
||||
// Look for a bit width, up to 64 bits wide, to fit maxval.
|
||||
uint32_t maxval_width = index_width;
|
||||
while ((maxval_width < 64) && (0 != (maxval >> maxval_width))) {
|
||||
maxval_width *= 2;
|
||||
}
|
||||
// Widen the index value if necessary
|
||||
if (maxval_width > index_width) {
|
||||
// Find the wider type. We only need this case if a constant (array)
|
||||
// bound is too big. This never requires us to *add* a capability
|
||||
// declaration for Int64 because the existence of the array bound would
|
||||
// already have required that declaration.
|
||||
index_inst = WidenInteger(index_type->IsSigned(), maxval_width,
|
||||
index_inst, &inst);
|
||||
maxval_type = type_mgr->GetType(index_inst->type_id())->AsInteger();
|
||||
}
|
||||
// Finally, clamp the index.
|
||||
clamp_index(operand_index, index_inst, GetValueForType(0, maxval_type),
|
||||
GetValueForType(maxval, maxval_type));
|
||||
}
|
||||
};
|
||||
|
||||
// Ensures the specified index of access chain |inst| has a value that is at
|
||||
// most the value of |count_inst| minus 1, where |count_inst| is treated as an
|
||||
// unsigned integer.
|
||||
auto clamp_to_count = [&inst, this, &constant_mgr, &clamp_to_literal_count,
|
||||
&clamp_index, &type_mgr](uint32_t operand_index,
|
||||
Instruction* count_inst) {
|
||||
Instruction* index_inst =
|
||||
this->GetDef(inst.GetSingleWordOperand(operand_index));
|
||||
const auto* index_type =
|
||||
type_mgr->GetType(index_inst->type_id())->AsInteger();
|
||||
const auto* count_type =
|
||||
type_mgr->GetType(count_inst->type_id())->AsInteger();
|
||||
assert(index_type);
|
||||
if (const auto* count_constant =
|
||||
constant_mgr->GetConstantFromInst(count_inst)) {
|
||||
uint64_t value = 0;
|
||||
const auto width = count_constant->type()->AsInteger()->width();
|
||||
if (width <= 32) {
|
||||
value = count_constant->AsIntConstant()->GetU32BitValue();
|
||||
} else if (width <= 64) {
|
||||
value = count_constant->AsIntConstant()->GetU64BitValue();
|
||||
} else {
|
||||
this->Fail() << "Can't handle indices wider than 64 bits, found "
|
||||
"constant index with "
|
||||
<< index_type->width() << "bits";
|
||||
return;
|
||||
}
|
||||
clamp_to_literal_count(operand_index, value);
|
||||
} else {
|
||||
// Widen them to the same width.
|
||||
const auto index_width = index_type->width();
|
||||
const auto count_width = count_type->width();
|
||||
const auto target_width = std::max(index_width, count_width);
|
||||
// UConvert requires the result type to have 0 signedness. So enforce
|
||||
// that here.
|
||||
auto* wider_type = index_width < count_width ? count_type : index_type;
|
||||
if (index_type->width() < target_width) {
|
||||
// Access chain indices are treated as signed integers.
|
||||
index_inst = WidenInteger(true, target_width, index_inst, &inst);
|
||||
} else if (count_type->width() < target_width) {
|
||||
// Assume type sizes are treated as unsigned.
|
||||
count_inst = WidenInteger(false, target_width, count_inst, &inst);
|
||||
}
|
||||
// Compute count - 1.
|
||||
// It doesn't matter if 1 is signed or unsigned.
|
||||
auto* one = GetValueForType(1, wider_type);
|
||||
auto* count_minus_1 = InsertInst(
|
||||
&inst, SpvOpISub, type_mgr->GetId(wider_type), TakeNextId(),
|
||||
{{SPV_OPERAND_TYPE_ID, {count_inst->result_id()}},
|
||||
{SPV_OPERAND_TYPE_ID, {one->result_id()}}});
|
||||
clamp_index(operand_index, index_inst, GetValueForType(0, wider_type),
|
||||
count_minus_1);
|
||||
}
|
||||
};
|
||||
|
||||
const Instruction* base_inst = GetDef(inst.GetSingleWordInOperand(0));
|
||||
const Instruction* base_type = GetDef(base_inst->type_id());
|
||||
Instruction* pointee_type = GetDef(base_type->GetSingleWordInOperand(1));
|
||||
|
||||
// Walk the indices from earliest to latest, replacing indices with a
|
||||
// clamped value, and updating the pointee_type. The order matters for
|
||||
// the case when we have to compute the length of a runtime array. In
|
||||
// that the algorithm relies on the fact that that the earlier indices
|
||||
// have already been clamped.
|
||||
const uint32_t num_operands = inst.NumOperands();
|
||||
for (uint32_t idx = 3; !module_status_.failed && idx < num_operands; ++idx) {
|
||||
const uint32_t index_id = inst.GetSingleWordOperand(idx);
|
||||
Instruction* index_inst = GetDef(index_id);
|
||||
|
||||
switch (pointee_type->opcode()) {
|
||||
case SpvOpTypeMatrix: // Use column count
|
||||
case SpvOpTypeVector: // Use component count
|
||||
{
|
||||
const uint32_t count = pointee_type->GetSingleWordOperand(2);
|
||||
clamp_to_literal_count(idx, count);
|
||||
pointee_type = GetDef(pointee_type->GetSingleWordOperand(1));
|
||||
} break;
|
||||
|
||||
case SpvOpTypeArray: {
|
||||
// The array length can be a spec constant, so go through the general
|
||||
// case.
|
||||
Instruction* array_len = GetDef(pointee_type->GetSingleWordOperand(2));
|
||||
clamp_to_count(idx, array_len);
|
||||
pointee_type = GetDef(pointee_type->GetSingleWordOperand(1));
|
||||
} break;
|
||||
|
||||
case SpvOpTypeStruct: {
|
||||
// SPIR-V requires the index to be an OpConstant.
|
||||
// We need to know the index literal value so we can compute the next
|
||||
// pointee type.
|
||||
if (index_inst->opcode() != SpvOpConstant ||
|
||||
!constant_mgr->GetConstantFromInst(index_inst)
|
||||
->type()
|
||||
->AsInteger()) {
|
||||
Fail() << "Member index into struct is not a constant integer: "
|
||||
<< index_inst->PrettyPrint(
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)
|
||||
<< "\nin access chain: "
|
||||
<< inst.PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
return;
|
||||
}
|
||||
const auto num_members = pointee_type->NumInOperands();
|
||||
const auto* index_constant =
|
||||
constant_mgr->GetConstantFromInst(index_inst);
|
||||
// Get the sign-extended value, since access index is always treated as
|
||||
// signed.
|
||||
const auto index_value = index_constant->GetSignExtendedValue();
|
||||
if (index_value < 0 || index_value >= num_members) {
|
||||
Fail() << "Member index " << index_value
|
||||
<< " is out of bounds for struct type: "
|
||||
<< pointee_type->PrettyPrint(
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)
|
||||
<< "\nin access chain: "
|
||||
<< inst.PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
return;
|
||||
}
|
||||
pointee_type = GetDef(pointee_type->GetSingleWordInOperand(
|
||||
static_cast<uint32_t>(index_value)));
|
||||
// No need to clamp this index. We just checked that it's valid.
|
||||
} break;
|
||||
|
||||
case SpvOpTypeRuntimeArray: {
|
||||
auto* array_len = MakeRuntimeArrayLengthInst(&inst, idx);
|
||||
if (!array_len) { // We've already signaled an error.
|
||||
return;
|
||||
}
|
||||
clamp_to_count(idx, array_len);
|
||||
pointee_type = GetDef(pointee_type->GetSingleWordOperand(1));
|
||||
} break;
|
||||
|
||||
default:
|
||||
Fail() << " Unhandled pointee type for access chain "
|
||||
<< pointee_type->PrettyPrint(
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t GraphicsRobustAccessPass::GetGlslInsts() {
|
||||
if (module_status_.glsl_insts_id == 0) {
|
||||
// This string serves double-duty as raw data for a string and for a vector
|
||||
// of 32-bit words
|
||||
const char glsl[] = "GLSL.std.450\0\0\0\0";
|
||||
const size_t glsl_str_byte_len = 16;
|
||||
// Use an existing import if we can.
|
||||
for (auto& inst : context()->module()->ext_inst_imports()) {
|
||||
const auto& name_words = inst.GetInOperand(0).words;
|
||||
if (0 == std::strncmp(reinterpret_cast<const char*>(name_words.data()),
|
||||
glsl, glsl_str_byte_len)) {
|
||||
module_status_.glsl_insts_id = inst.result_id();
|
||||
}
|
||||
}
|
||||
if (module_status_.glsl_insts_id == 0) {
|
||||
// Make a new import instruction.
|
||||
module_status_.glsl_insts_id = TakeNextId();
|
||||
std::vector<uint32_t> words(glsl_str_byte_len / sizeof(uint32_t));
|
||||
std::memcpy(words.data(), glsl, glsl_str_byte_len);
|
||||
auto import_inst = MakeUnique<Instruction>(
|
||||
context(), SpvOpExtInstImport, 0, module_status_.glsl_insts_id,
|
||||
std::initializer_list<Operand>{
|
||||
Operand{SPV_OPERAND_TYPE_LITERAL_STRING, std::move(words)}});
|
||||
Instruction* inst = import_inst.get();
|
||||
context()->module()->AddExtInstImport(std::move(import_inst));
|
||||
module_status_.modified = true;
|
||||
context()->AnalyzeDefUse(inst);
|
||||
// Reanalyze the feature list, since we added an extended instruction
|
||||
// set improt.
|
||||
context()->get_feature_mgr()->Analyze(context()->module());
|
||||
}
|
||||
}
|
||||
return module_status_.glsl_insts_id;
|
||||
}
|
||||
|
||||
opt::Instruction* opt::GraphicsRobustAccessPass::GetValueForType(
|
||||
uint64_t value, const analysis::Integer* type) {
|
||||
auto* mgr = context()->get_constant_mgr();
|
||||
assert(type->width() <= 64);
|
||||
std::vector<uint32_t> words;
|
||||
words.push_back(uint32_t(value));
|
||||
if (type->width() > 32) {
|
||||
words.push_back(uint32_t(value >> 32u));
|
||||
}
|
||||
const auto* constant = mgr->GetConstant(type, words);
|
||||
return mgr->GetDefiningInstruction(
|
||||
constant, context()->get_type_mgr()->GetTypeInstruction(type));
|
||||
}
|
||||
|
||||
opt::Instruction* opt::GraphicsRobustAccessPass::WidenInteger(
|
||||
bool sign_extend, uint32_t bit_width, Instruction* value,
|
||||
Instruction* before_inst) {
|
||||
analysis::Integer unsigned_type_for_query(bit_width, false);
|
||||
auto* type_mgr = context()->get_type_mgr();
|
||||
auto* unsigned_type = type_mgr->GetRegisteredType(&unsigned_type_for_query);
|
||||
auto type_id = context()->get_type_mgr()->GetId(unsigned_type);
|
||||
auto conversion_id = TakeNextId();
|
||||
auto* conversion = InsertInst(
|
||||
before_inst, (sign_extend ? SpvOpSConvert : SpvOpUConvert), type_id,
|
||||
conversion_id, {{SPV_OPERAND_TYPE_ID, {value->result_id()}}});
|
||||
return conversion;
|
||||
}
|
||||
|
||||
Instruction* GraphicsRobustAccessPass::MakeClampInst(Instruction* x,
|
||||
Instruction* min,
|
||||
Instruction* max,
|
||||
Instruction* where) {
|
||||
// Get IDs of instructions we'll be referencing. Evaluate them before calling
|
||||
// the function so we force a deterministic ordering in case both of them need
|
||||
// to take a new ID.
|
||||
const uint32_t glsl_insts_id = GetGlslInsts();
|
||||
uint32_t clamp_id = TakeNextId();
|
||||
assert(x->type_id() == min->type_id());
|
||||
assert(x->type_id() == max->type_id());
|
||||
auto* clamp_inst = InsertInst(
|
||||
where, SpvOpExtInst, x->type_id(), clamp_id,
|
||||
{
|
||||
{SPV_OPERAND_TYPE_ID, {glsl_insts_id}},
|
||||
{SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {GLSLstd450UClamp}},
|
||||
{SPV_OPERAND_TYPE_ID, {x->result_id()}},
|
||||
{SPV_OPERAND_TYPE_ID, {min->result_id()}},
|
||||
{SPV_OPERAND_TYPE_ID, {max->result_id()}},
|
||||
});
|
||||
return clamp_inst;
|
||||
}
|
||||
|
||||
Instruction* GraphicsRobustAccessPass::MakeRuntimeArrayLengthInst(
|
||||
Instruction* access_chain, uint32_t operand_index) {
|
||||
// The Index parameter to the access chain at |operand_index| is indexing
|
||||
// *into* the runtime-array. To get the number of elements in the runtime
|
||||
// array we need a pointer to the Block-decorated struct that contains the
|
||||
// runtime array. So conceptually we have to go 2 steps backward in the
|
||||
// access chain. The two steps backward might forces us to traverse backward
|
||||
// across multiple dominating instructions.
|
||||
auto* type_mgr = context()->get_type_mgr();
|
||||
|
||||
// How many access chain indices do we have to unwind to find the pointer
|
||||
// to the struct containing the runtime array?
|
||||
uint32_t steps_remaining = 2;
|
||||
// Find or create an instruction computing the pointer to the structure
|
||||
// containing the runtime array.
|
||||
// Walk backward through pointer address calculations until we either get
|
||||
// to exactly the right base pointer, or to an access chain instruction
|
||||
// that we can replicate but truncate to compute the address of the right
|
||||
// struct.
|
||||
Instruction* current_access_chain = access_chain;
|
||||
Instruction* pointer_to_containing_struct = nullptr;
|
||||
while (steps_remaining > 0) {
|
||||
switch (current_access_chain->opcode()) {
|
||||
case SpvOpCopyObject:
|
||||
// Whoops. Walk right through this one.
|
||||
current_access_chain =
|
||||
GetDef(current_access_chain->GetSingleWordInOperand(0));
|
||||
break;
|
||||
case SpvOpAccessChain:
|
||||
case SpvOpInBoundsAccessChain: {
|
||||
const int first_index_operand = 3;
|
||||
// How many indices in this access chain contribute to getting us
|
||||
// to an element in the runtime array?
|
||||
const auto num_contributing_indices =
|
||||
current_access_chain == access_chain
|
||||
? operand_index - (first_index_operand - 1)
|
||||
: current_access_chain->NumInOperands() - 1 /* skip the base */;
|
||||
Instruction* base =
|
||||
GetDef(current_access_chain->GetSingleWordInOperand(0));
|
||||
if (num_contributing_indices == steps_remaining) {
|
||||
// The base pointer points to the structure.
|
||||
pointer_to_containing_struct = base;
|
||||
steps_remaining = 0;
|
||||
break;
|
||||
} else if (num_contributing_indices < steps_remaining) {
|
||||
// Peel off the index and keep going backward.
|
||||
steps_remaining -= num_contributing_indices;
|
||||
current_access_chain = base;
|
||||
} else {
|
||||
// This access chain has more indices than needed. Generate a new
|
||||
// access chain instruction, but truncating the list of indices.
|
||||
const int base_operand = 2;
|
||||
// We'll use the base pointer and the indices up to but not including
|
||||
// the one indexing into the runtime array.
|
||||
Instruction::OperandList ops;
|
||||
// Use the base pointer
|
||||
ops.push_back(current_access_chain->GetOperand(base_operand));
|
||||
const uint32_t num_indices_to_keep =
|
||||
num_contributing_indices - steps_remaining - 1;
|
||||
for (uint32_t i = 0; i <= num_indices_to_keep; i++) {
|
||||
ops.push_back(
|
||||
current_access_chain->GetOperand(first_index_operand + i));
|
||||
}
|
||||
// Compute the type of the result of the new access chain. Start at
|
||||
// the base and walk the indices in a forward direction.
|
||||
auto* constant_mgr = context()->get_constant_mgr();
|
||||
std::vector<uint32_t> indices_for_type;
|
||||
for (uint32_t i = 0; i < ops.size() - 1; i++) {
|
||||
uint32_t index_for_type_calculation = 0;
|
||||
Instruction* index =
|
||||
GetDef(current_access_chain->GetSingleWordOperand(
|
||||
first_index_operand + i));
|
||||
if (auto* index_constant =
|
||||
constant_mgr->GetConstantFromInst(index)) {
|
||||
// We only need 32 bits. For the type calculation, it's sufficient
|
||||
// to take the zero-extended value. It only matters for the struct
|
||||
// case, and struct member indices are unsigned.
|
||||
index_for_type_calculation =
|
||||
uint32_t(index_constant->GetZeroExtendedValue());
|
||||
} else {
|
||||
// Indexing into a variably-sized thing like an array. Use 0.
|
||||
index_for_type_calculation = 0;
|
||||
}
|
||||
indices_for_type.push_back(index_for_type_calculation);
|
||||
}
|
||||
auto* base_ptr_type = type_mgr->GetType(base->type_id())->AsPointer();
|
||||
auto* base_pointee_type = base_ptr_type->pointee_type();
|
||||
auto* new_access_chain_result_pointee_type =
|
||||
type_mgr->GetMemberType(base_pointee_type, indices_for_type);
|
||||
const uint32_t new_access_chain_type_id = type_mgr->FindPointerToType(
|
||||
type_mgr->GetId(new_access_chain_result_pointee_type),
|
||||
base_ptr_type->storage_class());
|
||||
|
||||
// Create the instruction and insert it.
|
||||
const auto new_access_chain_id = TakeNextId();
|
||||
auto* new_access_chain =
|
||||
InsertInst(current_access_chain, current_access_chain->opcode(),
|
||||
new_access_chain_type_id, new_access_chain_id, ops);
|
||||
pointer_to_containing_struct = new_access_chain;
|
||||
steps_remaining = 0;
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
Fail() << "Unhandled access chain in logical addressing mode passes "
|
||||
"through "
|
||||
<< current_access_chain->PrettyPrint(
|
||||
SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET |
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
assert(pointer_to_containing_struct);
|
||||
auto* pointee_type =
|
||||
type_mgr->GetType(pointer_to_containing_struct->type_id())
|
||||
->AsPointer()
|
||||
->pointee_type();
|
||||
|
||||
auto* struct_type = pointee_type->AsStruct();
|
||||
const uint32_t member_index_of_runtime_array =
|
||||
uint32_t(struct_type->element_types().size() - 1);
|
||||
// Create the length-of-array instruction before the original access chain,
|
||||
// but after the generation of the pointer to the struct.
|
||||
const auto array_len_id = TakeNextId();
|
||||
analysis::Integer uint_type_for_query(32, false);
|
||||
auto* uint_type = type_mgr->GetRegisteredType(&uint_type_for_query);
|
||||
auto* array_len = InsertInst(
|
||||
access_chain, SpvOpArrayLength, type_mgr->GetId(uint_type), array_len_id,
|
||||
{{SPV_OPERAND_TYPE_ID, {pointer_to_containing_struct->result_id()}},
|
||||
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {member_index_of_runtime_array}}});
|
||||
return array_len;
|
||||
}
|
||||
|
||||
spv_result_t GraphicsRobustAccessPass::ClampCoordinateForImageTexelPointer(
|
||||
opt::Instruction* image_texel_pointer) {
|
||||
// TODO(dneto): Write tests for this code.
|
||||
return SPV_SUCCESS;
|
||||
|
||||
// Example:
|
||||
// %texel_ptr = OpImageTexelPointer %texel_ptr_type %image_ptr %coord
|
||||
// %sample
|
||||
//
|
||||
// We want to clamp %coord components between vector-0 and the result
|
||||
// of OpImageQuerySize acting on the underlying image. So insert:
|
||||
// %image = OpLoad %image_type %image_ptr
|
||||
// %query_size = OpImageQuerySize %query_size_type %image
|
||||
//
|
||||
// For a multi-sampled image, %sample is the sample index, and we need
|
||||
// to clamp it between zero and the number of samples in the image.
|
||||
// %sample_count = OpImageQuerySamples %uint %image
|
||||
// %max_sample_index = OpISub %uint %sample_count %uint_1
|
||||
// For non-multi-sampled images, the sample index must be constant zero.
|
||||
|
||||
auto* def_use_mgr = context()->get_def_use_mgr();
|
||||
auto* type_mgr = context()->get_type_mgr();
|
||||
auto* constant_mgr = context()->get_constant_mgr();
|
||||
|
||||
auto* image_ptr = GetDef(image_texel_pointer->GetSingleWordInOperand(0));
|
||||
auto* image_ptr_type = GetDef(image_ptr->type_id());
|
||||
auto image_type_id = image_ptr_type->GetSingleWordInOperand(1);
|
||||
auto* image_type = GetDef(image_type_id);
|
||||
auto* coord = GetDef(image_texel_pointer->GetSingleWordInOperand(1));
|
||||
auto* samples = GetDef(image_texel_pointer->GetSingleWordInOperand(2));
|
||||
|
||||
// We will modify the module, at least by adding image query instructions.
|
||||
module_status_.modified = true;
|
||||
|
||||
// Declare the ImageQuery capability if the module doesn't already have it.
|
||||
auto* feature_mgr = context()->get_feature_mgr();
|
||||
if (!feature_mgr->HasCapability(SpvCapabilityImageQuery)) {
|
||||
auto cap = MakeUnique<Instruction>(
|
||||
context(), SpvOpCapability, 0, 0,
|
||||
std::initializer_list<Operand>{
|
||||
{SPV_OPERAND_TYPE_CAPABILITY, {SpvCapabilityImageQuery}}});
|
||||
def_use_mgr->AnalyzeInstDefUse(cap.get());
|
||||
context()->AddCapability(std::move(cap));
|
||||
feature_mgr->Analyze(context()->module());
|
||||
}
|
||||
|
||||
// OpImageTexelPointer is used to translate a coordinate and sample index
|
||||
// into an address for use with an atomic operation. That is, it may only
|
||||
// used with what Vulkan calls a "storage image"
|
||||
// (OpTypeImage parameter Sampled=2).
|
||||
// Note: A storage image never has a level-of-detail associated with it.
|
||||
|
||||
// Constraints on the sample id:
|
||||
// - Only 2D images can be multi-sampled: OpTypeImage parameter MS=1
|
||||
// only if Dim=2D.
|
||||
// - Non-multi-sampled images (OpTypeImage parameter MS=0) must use
|
||||
// sample ID to a constant 0.
|
||||
|
||||
// The coordinate is treated as unsigned, and should be clamped against the
|
||||
// image "size", returned by OpImageQuerySize. (Note: OpImageQuerySizeLod
|
||||
// is only usable with a sampled image, i.e. its image type has Sampled=1).
|
||||
|
||||
// Determine the result type for the OpImageQuerySize.
|
||||
// For non-arrayed images:
|
||||
// non-Cube:
|
||||
// - Always the same as the coordinate type
|
||||
// Cube:
|
||||
// - Use all but the last component of the coordinate (which is the face
|
||||
// index from 0 to 5).
|
||||
// For arrayed images (in Vulkan the Dim is 1D, 2D, or Cube):
|
||||
// non-Cube:
|
||||
// - A vector with the components in the coordinate, and one more for
|
||||
// the layer index.
|
||||
// Cube:
|
||||
// - The same as the coordinate type: 3-element integer vector.
|
||||
// - The third component from the size query is the layer count.
|
||||
// - The third component in the texel pointer calculation is
|
||||
// 6 * layer + face, where 0 <= face < 6.
|
||||
// Cube: Use all but the last component of the coordinate (which is the face
|
||||
// index from 0 to 5).
|
||||
const auto dim = SpvDim(image_type->GetSingleWordInOperand(1));
|
||||
const bool arrayed = image_type->GetSingleWordInOperand(3) == 1;
|
||||
const bool multisampled = image_type->GetSingleWordInOperand(4) != 0;
|
||||
const auto query_num_components = [dim, arrayed, this]() -> int {
|
||||
const int arrayness_bonus = arrayed ? 1 : 0;
|
||||
int num_coords = 0;
|
||||
switch (dim) {
|
||||
case SpvDimBuffer:
|
||||
case SpvDim1D:
|
||||
num_coords = 1;
|
||||
break;
|
||||
case SpvDimCube:
|
||||
// For cube, we need bounds for x, y, but not face.
|
||||
case SpvDimRect:
|
||||
case SpvDim2D:
|
||||
num_coords = 2;
|
||||
break;
|
||||
case SpvDim3D:
|
||||
num_coords = 3;
|
||||
break;
|
||||
case SpvDimSubpassData:
|
||||
case SpvDimMax:
|
||||
return Fail() << "Invalid image dimension for OpImageTexelPointer: "
|
||||
<< int(dim);
|
||||
break;
|
||||
}
|
||||
return num_coords + arrayness_bonus;
|
||||
}();
|
||||
const auto* coord_component_type = [type_mgr, coord]() {
|
||||
const analysis::Type* coord_type = type_mgr->GetType(coord->type_id());
|
||||
if (auto* vector_type = coord_type->AsVector()) {
|
||||
return vector_type->element_type()->AsInteger();
|
||||
}
|
||||
return coord_type->AsInteger();
|
||||
}();
|
||||
// For now, only handle 32-bit case for coordinates.
|
||||
if (!coord_component_type) {
|
||||
return Fail() << " Coordinates for OpImageTexelPointer are not integral: "
|
||||
<< image_texel_pointer->PrettyPrint(
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
}
|
||||
if (coord_component_type->width() != 32) {
|
||||
return Fail() << " Expected OpImageTexelPointer coordinate components to "
|
||||
"be 32-bits wide. They are "
|
||||
<< coord_component_type->width() << " bits. "
|
||||
<< image_texel_pointer->PrettyPrint(
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
}
|
||||
const auto* query_size_type =
|
||||
[type_mgr, coord_component_type,
|
||||
query_num_components]() -> const analysis::Type* {
|
||||
if (query_num_components == 1) return coord_component_type;
|
||||
analysis::Vector proposed(coord_component_type, query_num_components);
|
||||
return type_mgr->GetRegisteredType(&proposed);
|
||||
}();
|
||||
|
||||
const uint32_t image_id = TakeNextId();
|
||||
auto* image =
|
||||
InsertInst(image_texel_pointer, SpvOpLoad, image_type_id, image_id,
|
||||
{{SPV_OPERAND_TYPE_ID, {image_ptr->result_id()}}});
|
||||
|
||||
const uint32_t query_size_id = TakeNextId();
|
||||
auto* query_size =
|
||||
InsertInst(image_texel_pointer, SpvOpImageQuerySize,
|
||||
type_mgr->GetTypeInstruction(query_size_type), query_size_id,
|
||||
{{SPV_OPERAND_TYPE_ID, {image->result_id()}}});
|
||||
|
||||
auto* component_1 = constant_mgr->GetConstant(coord_component_type, {1});
|
||||
const uint32_t component_1_id =
|
||||
constant_mgr->GetDefiningInstruction(component_1)->result_id();
|
||||
auto* component_0 = constant_mgr->GetConstant(coord_component_type, {0});
|
||||
const uint32_t component_0_id =
|
||||
constant_mgr->GetDefiningInstruction(component_0)->result_id();
|
||||
|
||||
// If the image is a cube array, then the last component of the queried
|
||||
// size is the layer count. In the query, we have to accomodate folding
|
||||
// in the face index ranging from 0 through 5. The inclusive upper bound
|
||||
// on the third coordinate therefore is multiplied by 6.
|
||||
auto* query_size_including_faces = query_size;
|
||||
if (arrayed && (dim == SpvDimCube)) {
|
||||
// Multiply the last coordinate by 6.
|
||||
auto* component_6 = constant_mgr->GetConstant(coord_component_type, {6});
|
||||
const uint32_t component_6_id =
|
||||
constant_mgr->GetDefiningInstruction(component_6)->result_id();
|
||||
assert(query_num_components == 3);
|
||||
auto* multiplicand = constant_mgr->GetConstant(
|
||||
query_size_type, {component_1_id, component_1_id, component_6_id});
|
||||
auto* multiplicand_inst =
|
||||
constant_mgr->GetDefiningInstruction(multiplicand);
|
||||
const auto query_size_including_faces_id = TakeNextId();
|
||||
query_size_including_faces = InsertInst(
|
||||
image_texel_pointer, SpvOpIMul,
|
||||
type_mgr->GetTypeInstruction(query_size_type),
|
||||
query_size_including_faces_id,
|
||||
{{SPV_OPERAND_TYPE_ID, {query_size_including_faces->result_id()}},
|
||||
{SPV_OPERAND_TYPE_ID, {multiplicand_inst->result_id()}}});
|
||||
}
|
||||
|
||||
// Make a coordinate-type with all 1 components.
|
||||
auto* coordinate_1 =
|
||||
query_num_components == 1
|
||||
? component_1
|
||||
: constant_mgr->GetConstant(
|
||||
query_size_type,
|
||||
std::vector<uint32_t>(query_num_components, component_1_id));
|
||||
// Make a coordinate-type with all 1 components.
|
||||
auto* coordinate_0 =
|
||||
query_num_components == 0
|
||||
? component_0
|
||||
: constant_mgr->GetConstant(
|
||||
query_size_type,
|
||||
std::vector<uint32_t>(query_num_components, component_0_id));
|
||||
|
||||
const uint32_t query_max_including_faces_id = TakeNextId();
|
||||
auto* query_max_including_faces = InsertInst(
|
||||
image_texel_pointer, SpvOpISub,
|
||||
type_mgr->GetTypeInstruction(query_size_type),
|
||||
query_max_including_faces_id,
|
||||
{{SPV_OPERAND_TYPE_ID, {query_size_including_faces->result_id()}},
|
||||
{SPV_OPERAND_TYPE_ID,
|
||||
{constant_mgr->GetDefiningInstruction(coordinate_1)->result_id()}}});
|
||||
|
||||
// Clamp the coordinate
|
||||
auto* clamp_coord =
|
||||
MakeClampInst(coord, constant_mgr->GetDefiningInstruction(coordinate_0),
|
||||
query_max_including_faces, image_texel_pointer);
|
||||
image_texel_pointer->SetInOperand(1, {clamp_coord->result_id()});
|
||||
|
||||
// Clamp the sample index
|
||||
if (multisampled) {
|
||||
// Get the sample count via OpImageQuerySamples
|
||||
const auto query_samples_id = TakeNextId();
|
||||
auto* query_samples = InsertInst(
|
||||
image_texel_pointer, SpvOpImageQuerySamples,
|
||||
constant_mgr->GetDefiningInstruction(component_0)->type_id(),
|
||||
query_samples_id, {{SPV_OPERAND_TYPE_ID, {image->result_id()}}});
|
||||
|
||||
const auto max_samples_id = TakeNextId();
|
||||
auto* max_samples = InsertInst(image_texel_pointer, SpvOpImageQuerySamples,
|
||||
query_samples->type_id(), max_samples_id,
|
||||
{{SPV_OPERAND_TYPE_ID, {query_samples_id}},
|
||||
{SPV_OPERAND_TYPE_ID, {component_1_id}}});
|
||||
|
||||
auto* clamp_samples = MakeClampInst(
|
||||
samples, constant_mgr->GetDefiningInstruction(coordinate_0),
|
||||
max_samples, image_texel_pointer);
|
||||
image_texel_pointer->SetInOperand(2, {clamp_samples->result_id()});
|
||||
|
||||
} else {
|
||||
// Just replace it with 0. Don't even check what was there before.
|
||||
image_texel_pointer->SetInOperand(2, {component_0_id});
|
||||
}
|
||||
|
||||
def_use_mgr->AnalyzeInstUse(image_texel_pointer);
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
opt::Instruction* GraphicsRobustAccessPass::InsertInst(
|
||||
opt::Instruction* where_inst, SpvOp opcode, uint32_t type_id,
|
||||
uint32_t result_id, const Instruction::OperandList& operands) {
|
||||
module_status_.modified = true;
|
||||
auto* result = where_inst->InsertBefore(
|
||||
MakeUnique<Instruction>(context(), opcode, type_id, result_id, operands));
|
||||
context()->get_def_use_mgr()->AnalyzeInstDefUse(result);
|
||||
auto* basic_block = context()->get_instr_block(where_inst);
|
||||
context()->set_instr_block(result, basic_block);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
146
3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.h
vendored
Normal file
146
3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.h
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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_GRAPHICS_ROBUST_ACCESS_PASS_H_
|
||||
#define SOURCE_OPT_GRAPHICS_ROBUST_ACCESS_PASS_H_
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "source/diagnostic.h"
|
||||
|
||||
#include "constants.h"
|
||||
#include "def_use_manager.h"
|
||||
#include "instruction.h"
|
||||
#include "module.h"
|
||||
#include "pass.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
// See optimizer.hpp for documentation.
|
||||
class GraphicsRobustAccessPass : public Pass {
|
||||
public:
|
||||
GraphicsRobustAccessPass();
|
||||
const char* name() const override { return "graphics-robust-access"; }
|
||||
Status Process() override;
|
||||
|
||||
IRContext::Analysis GetPreservedAnalyses() override {
|
||||
return IRContext::kAnalysisDefUse |
|
||||
IRContext::kAnalysisInstrToBlockMapping |
|
||||
IRContext::kAnalysisConstants | IRContext::kAnalysisTypes |
|
||||
IRContext::kAnalysisIdToFuncMapping;
|
||||
}
|
||||
|
||||
private:
|
||||
// Records failure for the current module, and returns a stream
|
||||
// that can be used to provide user error information to the message
|
||||
// consumer.
|
||||
spvtools::DiagnosticStream Fail();
|
||||
|
||||
// Returns SPV_SUCCESS if this pass can correctly process the module.
|
||||
// Otherwise logs a message and returns a failure code.
|
||||
spv_result_t IsCompatibleModule();
|
||||
|
||||
// Transform the current module, if possible. Failure and modification
|
||||
// status is recorded in the |_| member. On failure, error information is
|
||||
// posted to the message consumer. The return value has no significance.
|
||||
spv_result_t ProcessCurrentModule();
|
||||
|
||||
// Process the given function. Updates the state value |_|. Returns true
|
||||
// if the module was modified.
|
||||
bool ProcessAFunction(opt::Function*);
|
||||
|
||||
// Clamps indices in the OpAccessChain or OpInBoundsAccessChain instruction
|
||||
// |access_chain|. Inserts instructions before the given instruction. Updates
|
||||
// analyses and records that the module is modified.
|
||||
void ClampIndicesForAccessChain(Instruction* access_chain);
|
||||
|
||||
// Returns the id of the instruction importing the "GLSL.std.450" extended
|
||||
// instruction set. If it does not yet exist, the import instruction is
|
||||
// created and inserted into the module, and updates |_.modified| and
|
||||
// |_.glsl_insts_id|.
|
||||
uint32_t GetGlslInsts();
|
||||
|
||||
// Returns an instruction which is constant with the given value of the given
|
||||
// type. Ignores any value bits beyond the width of the type.
|
||||
Instruction* GetValueForType(uint64_t value, const analysis::Integer* type);
|
||||
|
||||
// Returns the unsigned value for the given constant. Assumes it's at most
|
||||
// 64 bits wide.
|
||||
uint64_t GetUnsignedValueForConstant(const analysis::Constant* c);
|
||||
|
||||
// Converts an integer value to an unsigned wider integer type, using either
|
||||
// sign extension or zero extension. The new instruction is inserted
|
||||
// immediately before |before_inst|, and is analyzed for definitions and uses.
|
||||
// Returns the newly inserted instruction. Assumes the |value| is an integer
|
||||
// scalar of a narrower type than |bitwidth| bits.
|
||||
Instruction* WidenInteger(bool sign_extend, uint32_t bitwidth,
|
||||
Instruction* value, Instruction* before_inst);
|
||||
|
||||
// Returns a new instruction that invokes the UClamp GLSL.std.450 extended
|
||||
// instruction with the three given operands. That is, the result of the
|
||||
// instruction is:
|
||||
// - |min| if |x| is unsigned-less than |min|
|
||||
// - |max| if |x| is unsigned-more than |max|
|
||||
// - |x| otherwise.
|
||||
// We assume that |min| is unsigned-less-or-equal to |max|, and that the
|
||||
// operands all have the same scalar integer type. The instruction is
|
||||
// inserted before |where|.
|
||||
opt::Instruction* MakeClampInst(Instruction* x, Instruction* min,
|
||||
Instruction* max, Instruction* where);
|
||||
|
||||
// Returns a new instruction which evaluates to the length the runtime array
|
||||
// referenced by the access chain at the specfied index. The instruction is
|
||||
// inserted before the access chain instruction. Returns a null pointer in
|
||||
// some cases if assumptions are violated (rather than asserting out).
|
||||
opt::Instruction* MakeRuntimeArrayLengthInst(Instruction* access_chain,
|
||||
uint32_t operand_index);
|
||||
|
||||
// Clamps the coordinate for an OpImageTexelPointer so it stays within
|
||||
// the bounds of the size of the image. Updates analyses and records that
|
||||
// the module is modified. Returns a status code to indicate success
|
||||
// or failure. If assumptions are not met, returns an error status code
|
||||
// and emits a diagnostic.
|
||||
spv_result_t ClampCoordinateForImageTexelPointer(opt::Instruction* itp);
|
||||
|
||||
// Gets the instruction that defines the given id.
|
||||
opt::Instruction* GetDef(uint32_t id) {
|
||||
return context()->get_def_use_mgr()->GetDef(id);
|
||||
}
|
||||
|
||||
// Returns a new instruction inserted before |where_inst|, and created from
|
||||
// the remaining arguments. Registers the definitions and uses of the new
|
||||
// instruction and also records its block.
|
||||
opt::Instruction* InsertInst(opt::Instruction* where_inst, SpvOp opcode,
|
||||
uint32_t type_id, uint32_t result_id,
|
||||
const Instruction::OperandList& operands);
|
||||
|
||||
// State required for the current module.
|
||||
struct PerModuleState {
|
||||
// This pass modified the module.
|
||||
bool modified = false;
|
||||
// True if there is an error processing the current module, e.g. if
|
||||
// preconditions are not met.
|
||||
bool failed = false;
|
||||
// The id of the GLSL.std.450 extended instruction set. Zero if it does
|
||||
// not exist.
|
||||
uint32_t glsl_insts_id = 0;
|
||||
} module_status_;
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_OPT_GRAPHICS_ROBUST_ACCESS_PASS_H_
|
@ -399,7 +399,7 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> {
|
||||
inline bool operator<(const Instruction&) const;
|
||||
|
||||
// Takes ownership of the instruction owned by |i| and inserts it immediately
|
||||
// before |this|. Returns the insterted instruction.
|
||||
// before |this|. Returns the inserted instruction.
|
||||
Instruction* InsertBefore(std::unique_ptr<Instruction>&& i);
|
||||
// Takes ownership of the instructions in |list| and inserts them in order
|
||||
// immediately before |this|. Returns the first inserted instruction.
|
||||
|
@ -243,10 +243,13 @@ void InstrumentPass::GenStageStreamWriteCode(uint32_t stage_idx,
|
||||
kInstTessEvalOutPrimitiveId, base_offset_id, builder);
|
||||
uint32_t load_id = GenVarLoad(
|
||||
context()->GetBuiltinInputVarId(SpvBuiltInTessCoord), builder);
|
||||
Instruction* uvec3_cast_inst =
|
||||
builder->AddUnaryOp(GetVec3UintId(), SpvOpBitcast, load_id);
|
||||
uint32_t uvec3_cast_id = uvec3_cast_inst->result_id();
|
||||
Instruction* u_inst = builder->AddIdLiteralOp(
|
||||
GetUintId(), SpvOpCompositeExtract, load_id, 0);
|
||||
GetUintId(), SpvOpCompositeExtract, uvec3_cast_id, 0);
|
||||
Instruction* v_inst = builder->AddIdLiteralOp(
|
||||
GetUintId(), SpvOpCompositeExtract, load_id, 1);
|
||||
GetUintId(), SpvOpCompositeExtract, uvec3_cast_id, 1);
|
||||
GenDebugOutputFieldCode(base_offset_id, kInstTessEvalOutTessCoordU,
|
||||
u_inst->result_id(), builder);
|
||||
GenDebugOutputFieldCode(base_offset_id, kInstTessEvalOutTessCoordV,
|
||||
@ -552,18 +555,26 @@ uint32_t InstrumentPass::GetUintId() {
|
||||
return uint_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetVecUintId(uint32_t len) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Integer uint_ty(32, false);
|
||||
analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
|
||||
analysis::Vector v_uint_ty(reg_uint_ty, len);
|
||||
analysis::Type* reg_v_uint_ty = type_mgr->GetRegisteredType(&v_uint_ty);
|
||||
uint32_t v_uint_id = type_mgr->GetTypeInstruction(reg_v_uint_ty);
|
||||
return v_uint_id;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetVec4UintId() {
|
||||
if (v4uint_id_ == 0) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Integer uint_ty(32, false);
|
||||
analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
|
||||
analysis::Vector v4uint_ty(reg_uint_ty, 4);
|
||||
analysis::Type* reg_v4uint_ty = type_mgr->GetRegisteredType(&v4uint_ty);
|
||||
v4uint_id_ = type_mgr->GetTypeInstruction(reg_v4uint_ty);
|
||||
}
|
||||
if (v4uint_id_ == 0) v4uint_id_ = GetVecUintId(4u);
|
||||
return v4uint_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetVec3UintId() {
|
||||
if (v3uint_id_ == 0) v3uint_id_ = GetVecUintId(3u);
|
||||
return v3uint_id_;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GetBoolId() {
|
||||
if (bool_id_ == 0) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
@ -890,6 +901,7 @@ void InstrumentPass::InitializeInstrument() {
|
||||
v4float_id_ = 0;
|
||||
uint_id_ = 0;
|
||||
v4uint_id_ = 0;
|
||||
v3uint_id_ = 0;
|
||||
bool_id_ = 0;
|
||||
void_id_ = 0;
|
||||
storage_buffer_ext_defined_ = false;
|
||||
|
@ -248,9 +248,15 @@ class InstrumentPass : public Pass {
|
||||
// Return id for v4float type
|
||||
uint32_t GetVec4FloatId();
|
||||
|
||||
// Return id for uint vector type of |length|
|
||||
uint32_t GetVecUintId(uint32_t length);
|
||||
|
||||
// Return id for v4uint type
|
||||
uint32_t GetVec4UintId();
|
||||
|
||||
// Return id for v3uint type
|
||||
uint32_t GetVec3UintId();
|
||||
|
||||
// Return id for output function. Define if it doesn't exist with
|
||||
// |val_spec_param_cnt| validation-specific uint32 parameters.
|
||||
uint32_t GetStreamWriteFunctionId(uint32_t stage_idx,
|
||||
@ -366,9 +372,12 @@ class InstrumentPass : public Pass {
|
||||
// id for v4float type
|
||||
uint32_t v4float_id_;
|
||||
|
||||
// id for v4float type
|
||||
// id for v4uint type
|
||||
uint32_t v4uint_id_;
|
||||
|
||||
// id for v3uint type
|
||||
uint32_t v3uint_id_;
|
||||
|
||||
// id for 32-bit unsigned type
|
||||
uint32_t uint_id_;
|
||||
|
||||
|
56
3rdparty/spirv-tools/source/opt/ir_context.cpp
vendored
56
3rdparty/spirv-tools/source/opt/ir_context.cpp
vendored
@ -190,6 +190,13 @@ bool IRContext::KillDef(uint32_t id) {
|
||||
}
|
||||
|
||||
bool IRContext::ReplaceAllUsesWith(uint32_t before, uint32_t after) {
|
||||
return ReplaceAllUsesWithPredicate(
|
||||
before, after, [](Instruction*, uint32_t) { return true; });
|
||||
}
|
||||
|
||||
bool IRContext::ReplaceAllUsesWithPredicate(
|
||||
uint32_t before, uint32_t after,
|
||||
const std::function<bool(Instruction*, uint32_t)>& predicate) {
|
||||
if (before == after) return false;
|
||||
|
||||
// Ensure that |after| has been registered as def.
|
||||
@ -198,8 +205,10 @@ bool IRContext::ReplaceAllUsesWith(uint32_t before, uint32_t after) {
|
||||
|
||||
std::vector<std::pair<Instruction*, uint32_t>> uses_to_update;
|
||||
get_def_use_mgr()->ForEachUse(
|
||||
before, [&uses_to_update](Instruction* user, uint32_t index) {
|
||||
uses_to_update.emplace_back(user, index);
|
||||
before, [&predicate, &uses_to_update](Instruction* user, uint32_t index) {
|
||||
if (predicate(user, index)) {
|
||||
uses_to_update.emplace_back(user, index);
|
||||
}
|
||||
});
|
||||
|
||||
Instruction* prev = nullptr;
|
||||
@ -691,6 +700,13 @@ uint32_t IRContext::GetBuiltinInputVarId(uint32_t builtin) {
|
||||
reg_type = type_mgr->GetRegisteredType(&v3uint_ty);
|
||||
break;
|
||||
}
|
||||
case SpvBuiltInTessCoord: {
|
||||
analysis::Float float_ty(32);
|
||||
analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty);
|
||||
analysis::Vector v3float_ty(reg_float_ty, 3);
|
||||
reg_type = type_mgr->GetRegisteredType(&v3float_ty);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assert(false && "unhandled builtin");
|
||||
return 0;
|
||||
@ -779,6 +795,42 @@ bool IRContext::ProcessCallTreeFromRoots(ProcessFunction& pfn,
|
||||
return modified;
|
||||
}
|
||||
|
||||
void IRContext::EmitErrorMessage(std::string message, Instruction* inst) {
|
||||
if (!consumer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Instruction* line_inst = inst;
|
||||
while (line_inst != nullptr) { // Stop at the beginning of the basic block.
|
||||
if (!line_inst->dbg_line_insts().empty()) {
|
||||
line_inst = &line_inst->dbg_line_insts().back();
|
||||
if (line_inst->opcode() == SpvOpNoLine) {
|
||||
line_inst = nullptr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
line_inst = line_inst->PreviousNode();
|
||||
}
|
||||
|
||||
uint32_t line_number = 0;
|
||||
uint32_t col_number = 0;
|
||||
char* source = nullptr;
|
||||
if (line_inst != nullptr) {
|
||||
Instruction* file_name =
|
||||
get_def_use_mgr()->GetDef(line_inst->GetSingleWordInOperand(0));
|
||||
source = reinterpret_cast<char*>(&file_name->GetInOperand(0).words[0]);
|
||||
|
||||
// Get the line number and column number.
|
||||
line_number = line_inst->GetSingleWordInOperand(1);
|
||||
col_number = line_inst->GetSingleWordInOperand(2);
|
||||
}
|
||||
|
||||
message +=
|
||||
"\n " + inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
consumer()(SPV_MSG_ERROR, source, {line_number, col_number, 0},
|
||||
message.c_str());
|
||||
}
|
||||
|
||||
// Gets the dominator analysis for function |f|.
|
||||
DominatorAnalysis* IRContext::GetDominatorAnalysis(const Function* f) {
|
||||
if (!AreAnalysesValid(kAnalysisDominatorAnalysis)) {
|
||||
|
13
3rdparty/spirv-tools/source/opt/ir_context.h
vendored
13
3rdparty/spirv-tools/source/opt/ir_context.h
vendored
@ -382,6 +382,15 @@ class IRContext {
|
||||
// |before| and |after| must be registered definitions in the DefUseManager.
|
||||
bool ReplaceAllUsesWith(uint32_t before, uint32_t after);
|
||||
|
||||
// Replace all uses of |before| id with |after| id if those uses
|
||||
// (instruction, operand pair) return true for |predicate|. Returns true if
|
||||
// any replacement happens. This method does not kill the definition of the
|
||||
// |before| id. If |after| is the same as |before|, does nothing and return
|
||||
// false.
|
||||
bool ReplaceAllUsesWithPredicate(
|
||||
uint32_t before, uint32_t after,
|
||||
const std::function<bool(Instruction*, uint32_t)>& predicate);
|
||||
|
||||
// Returns true if all of the analyses that are suppose to be valid are
|
||||
// actually valid.
|
||||
bool IsConsistent();
|
||||
@ -547,6 +556,10 @@ class IRContext {
|
||||
bool ProcessCallTreeFromRoots(ProcessFunction& pfn,
|
||||
std::queue<uint32_t>* roots);
|
||||
|
||||
// Emmits a error message to the message consumer indicating the error
|
||||
// described by |message| occurred in |inst|.
|
||||
void EmitErrorMessage(std::string message, Instruction* inst);
|
||||
|
||||
private:
|
||||
// Builds the def-use manager from scratch, even if it was already valid.
|
||||
void BuildDefUseManager() {
|
||||
|
@ -256,6 +256,7 @@ void LocalSingleBlockLoadStoreElimPass::InitExtensions() {
|
||||
"SPV_NV_mesh_shader",
|
||||
"SPV_NV_ray_tracing",
|
||||
"SPV_EXT_fragment_invocation_density",
|
||||
"SPV_EXT_physical_storage_buffer",
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -119,6 +119,7 @@ void LocalSingleStoreElimPass::InitExtensionWhiteList() {
|
||||
"SPV_NV_mesh_shader",
|
||||
"SPV_NV_ray_tracing",
|
||||
"SPV_EXT_fragment_invocation_density",
|
||||
"SPV_EXT_physical_storage_buffer",
|
||||
});
|
||||
}
|
||||
bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) {
|
||||
|
@ -105,6 +105,7 @@ void LocalMultiStoreElimPass::InitExtensions() {
|
||||
"SPV_NV_mesh_shader",
|
||||
"SPV_NV_ray_tracing",
|
||||
"SPV_EXT_fragment_invocation_density",
|
||||
"SPV_EXT_physical_storage_buffer",
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -820,12 +820,7 @@ bool MergeReturnPass::HasNontrivialUnreachableBlocks(Function* function) {
|
||||
|
||||
StructuredCFGAnalysis* struct_cfg_analysis =
|
||||
context()->GetStructuredCFGAnalysis();
|
||||
if (struct_cfg_analysis->IsMergeBlock(bb.id())) {
|
||||
// |bb| must be an empty block ending with OpUnreachable.
|
||||
if (bb.begin()->opcode() != SpvOpUnreachable) {
|
||||
return true;
|
||||
}
|
||||
} else if (struct_cfg_analysis->IsContinueBlock(bb.id())) {
|
||||
if (struct_cfg_analysis->IsContinueBlock(bb.id())) {
|
||||
// |bb| must be an empty block ending with a branch to the header.
|
||||
Instruction* inst = &*bb.begin();
|
||||
if (inst->opcode() != SpvOpBranch) {
|
||||
@ -836,6 +831,11 @@ bool MergeReturnPass::HasNontrivialUnreachableBlocks(Function* function) {
|
||||
struct_cfg_analysis->ContainingLoop(bb.id())) {
|
||||
return true;
|
||||
}
|
||||
} else if (struct_cfg_analysis->IsMergeBlock(bb.id())) {
|
||||
// |bb| must be an empty block ending with OpUnreachable.
|
||||
if (bb.begin()->opcode() != SpvOpUnreachable) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
17
3rdparty/spirv-tools/source/opt/optimizer.cpp
vendored
17
3rdparty/spirv-tools/source/opt/optimizer.cpp
vendored
@ -22,6 +22,7 @@
|
||||
|
||||
#include <source/spirv_optimizer_options.h>
|
||||
#include "source/opt/build_module.h"
|
||||
#include "source/opt/graphics_robust_access_pass.h"
|
||||
#include "source/opt/log.h"
|
||||
#include "source/opt/pass_manager.h"
|
||||
#include "source/opt/passes.h"
|
||||
@ -314,6 +315,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
|
||||
RegisterPass(CreateCombineAccessChainsPass());
|
||||
} else if (pass_name == "convert-local-access-chains") {
|
||||
RegisterPass(CreateLocalAccessChainConvertPass());
|
||||
} else if (pass_name == "descriptor-scalar-replacement") {
|
||||
RegisterPass(CreateDescriptorScalarReplacementPass());
|
||||
} else if (pass_name == "eliminate-dead-code-aggressive") {
|
||||
RegisterPass(CreateAggressiveDCEPass());
|
||||
} else if (pass_name == "propagate-line-info") {
|
||||
@ -393,7 +396,7 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
|
||||
} else if (pass_name == "replace-invalid-opcode") {
|
||||
RegisterPass(CreateReplaceInvalidOpcodePass());
|
||||
} else if (pass_name == "inst-bindless-check") {
|
||||
RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true, 1));
|
||||
RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true, 2));
|
||||
RegisterPass(CreateSimplificationPass());
|
||||
RegisterPass(CreateDeadBranchElimPass());
|
||||
RegisterPass(CreateBlockMergePass());
|
||||
@ -472,6 +475,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
|
||||
RegisterPass(CreateLegalizeVectorShufflePass());
|
||||
} else if (pass_name == "decompose-initialized-variables") {
|
||||
RegisterPass(CreateDecomposeInitializedVariablesPass());
|
||||
} else if (pass_name == "graphics-robust-access") {
|
||||
RegisterPass(CreateGraphicsRobustAccessPass());
|
||||
} else {
|
||||
Errorf(consumer(), nullptr, {},
|
||||
"Unknown flag '--%s'. Use --help for a list of valid flags",
|
||||
@ -878,4 +883,14 @@ Optimizer::PassToken CreateSplitInvalidUnreachablePass() {
|
||||
MakeUnique<opt::SplitInvalidUnreachablePass>());
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateGraphicsRobustAccessPass() {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::GraphicsRobustAccessPass>());
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateDescriptorScalarReplacementPass() {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::DescriptorScalarReplacement>());
|
||||
}
|
||||
|
||||
} // namespace spvtools
|
||||
|
2
3rdparty/spirv-tools/source/opt/passes.h
vendored
2
3rdparty/spirv-tools/source/opt/passes.h
vendored
@ -29,6 +29,7 @@
|
||||
#include "source/opt/dead_insert_elim_pass.h"
|
||||
#include "source/opt/dead_variable_elimination.h"
|
||||
#include "source/opt/decompose_initialized_variables_pass.h"
|
||||
#include "source/opt/desc_sroa.h"
|
||||
#include "source/opt/eliminate_dead_constant_pass.h"
|
||||
#include "source/opt/eliminate_dead_functions_pass.h"
|
||||
#include "source/opt/eliminate_dead_members_pass.h"
|
||||
@ -37,6 +38,7 @@
|
||||
#include "source/opt/fold_spec_constant_op_and_composite_pass.h"
|
||||
#include "source/opt/freeze_spec_constant_value_pass.h"
|
||||
#include "source/opt/generate_webgpu_initializers_pass.h"
|
||||
#include "source/opt/graphics_robust_access_pass.h"
|
||||
#include "source/opt/if_conversion.h"
|
||||
#include "source/opt/inline_exhaustive_pass.h"
|
||||
#include "source/opt/inline_opaque_pass.h"
|
||||
|
@ -60,25 +60,25 @@ Pass::Status ScalarReplacementPass::ProcessFunction(Function* function) {
|
||||
Instruction* varInst = worklist.front();
|
||||
worklist.pop();
|
||||
|
||||
if (!ReplaceVariable(varInst, &worklist))
|
||||
return Status::Failure;
|
||||
else
|
||||
status = Status::SuccessWithChange;
|
||||
Status var_status = ReplaceVariable(varInst, &worklist);
|
||||
if (var_status == Status::Failure)
|
||||
return var_status;
|
||||
else if (var_status == Status::SuccessWithChange)
|
||||
status = var_status;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool ScalarReplacementPass::ReplaceVariable(
|
||||
Pass::Status ScalarReplacementPass::ReplaceVariable(
|
||||
Instruction* inst, std::queue<Instruction*>* worklist) {
|
||||
std::vector<Instruction*> replacements;
|
||||
if (!CreateReplacementVariables(inst, &replacements)) {
|
||||
return false;
|
||||
return Status::Failure;
|
||||
}
|
||||
|
||||
std::vector<Instruction*> dead;
|
||||
dead.push_back(inst);
|
||||
if (!get_def_use_mgr()->WhileEachUser(
|
||||
if (get_def_use_mgr()->WhileEachUser(
|
||||
inst, [this, &replacements, &dead](Instruction* user) {
|
||||
if (!IsAnnotationInst(user->opcode())) {
|
||||
switch (user->opcode()) {
|
||||
@ -92,8 +92,10 @@ bool ScalarReplacementPass::ReplaceVariable(
|
||||
break;
|
||||
case SpvOpAccessChain:
|
||||
case SpvOpInBoundsAccessChain:
|
||||
if (!ReplaceAccessChain(user, replacements)) return false;
|
||||
dead.push_back(user);
|
||||
if (ReplaceAccessChain(user, replacements))
|
||||
dead.push_back(user);
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
case SpvOpName:
|
||||
case SpvOpMemberName:
|
||||
@ -105,7 +107,10 @@ bool ScalarReplacementPass::ReplaceVariable(
|
||||
}
|
||||
return true;
|
||||
}))
|
||||
return false;
|
||||
dead.push_back(inst);
|
||||
|
||||
// If there are no dead instructions to clean up, return with no changes.
|
||||
if (dead.empty()) return Status::SuccessWithoutChange;
|
||||
|
||||
// Clean up some dead code.
|
||||
while (!dead.empty()) {
|
||||
@ -125,7 +130,7 @@ bool ScalarReplacementPass::ReplaceVariable(
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return Status::SuccessWithChange;
|
||||
}
|
||||
|
||||
void ScalarReplacementPass::ReplaceWholeLoad(
|
||||
@ -227,9 +232,14 @@ bool ScalarReplacementPass::ReplaceAccessChain(
|
||||
// indexes) or a direct use of the replacement variable.
|
||||
uint32_t indexId = chain->GetSingleWordInOperand(1u);
|
||||
const Instruction* index = get_def_use_mgr()->GetDef(indexId);
|
||||
uint64_t indexValue = GetConstantInteger(index);
|
||||
if (indexValue > replacements.size()) {
|
||||
// Out of bounds access, this is illegal IR.
|
||||
int64_t indexValue = context()
|
||||
->get_constant_mgr()
|
||||
->GetConstantFromInst(index)
|
||||
->GetSignExtendedValue();
|
||||
if (indexValue < 0 ||
|
||||
indexValue >= static_cast<int64_t>(replacements.size())) {
|
||||
// Out of bounds access, this is illegal IR. Notice that OpAccessChain
|
||||
// indexing is 0-based, so we should also reject index == size-of-array.
|
||||
return false;
|
||||
} else {
|
||||
const Instruction* var = replacements[static_cast<size_t>(indexValue)];
|
||||
@ -263,7 +273,7 @@ bool ScalarReplacementPass::CreateReplacementVariables(
|
||||
Instruction* inst, std::vector<Instruction*>* replacements) {
|
||||
Instruction* type = GetStorageType(inst);
|
||||
|
||||
std::unique_ptr<std::unordered_set<uint64_t>> components_used =
|
||||
std::unique_ptr<std::unordered_set<int64_t>> components_used =
|
||||
GetUsedComponents(inst);
|
||||
|
||||
uint32_t elem = 0;
|
||||
@ -354,6 +364,35 @@ void ScalarReplacementPass::CreateVariable(
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(inst);
|
||||
context()->set_instr_block(inst, block);
|
||||
|
||||
// Copy decorations from the member to the new variable.
|
||||
Instruction* typeInst = GetStorageType(varInst);
|
||||
for (auto dec_inst :
|
||||
get_decoration_mgr()->GetDecorationsFor(typeInst->result_id(), false)) {
|
||||
uint32_t decoration;
|
||||
if (dec_inst->opcode() != SpvOpMemberDecorate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dec_inst->GetSingleWordInOperand(1) != index) {
|
||||
continue;
|
||||
}
|
||||
|
||||
decoration = dec_inst->GetSingleWordInOperand(2u);
|
||||
switch (decoration) {
|
||||
case SpvDecorationRelaxedPrecision: {
|
||||
std::unique_ptr<Instruction> new_dec_inst(
|
||||
new Instruction(context(), SpvOpDecorate, 0, 0, {}));
|
||||
new_dec_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {id}));
|
||||
for (uint32_t i = 2; i < dec_inst->NumInOperandWords(); ++i) {
|
||||
new_dec_inst->AddOperand(Operand(dec_inst->GetInOperand(i)));
|
||||
}
|
||||
context()->AddAnnotationInst(std::move(new_dec_inst));
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
replacements->push_back(inst);
|
||||
}
|
||||
|
||||
@ -461,35 +500,15 @@ void ScalarReplacementPass::GetOrCreateInitialValue(Instruction* source,
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t ScalarReplacementPass::GetIntegerLiteral(const Operand& op) const {
|
||||
assert(op.words.size() <= 2);
|
||||
uint64_t len = 0;
|
||||
for (uint32_t i = 0; i != op.words.size(); ++i) {
|
||||
len |= (op.words[i] << (32 * i));
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
uint64_t ScalarReplacementPass::GetConstantInteger(
|
||||
const Instruction* constant) const {
|
||||
assert(get_def_use_mgr()->GetDef(constant->type_id())->opcode() ==
|
||||
SpvOpTypeInt);
|
||||
assert(constant->opcode() == SpvOpConstant ||
|
||||
constant->opcode() == SpvOpConstantNull);
|
||||
if (constant->opcode() == SpvOpConstantNull) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Operand& op = constant->GetInOperand(0u);
|
||||
return GetIntegerLiteral(op);
|
||||
}
|
||||
|
||||
uint64_t ScalarReplacementPass::GetArrayLength(
|
||||
const Instruction* arrayType) const {
|
||||
assert(arrayType->opcode() == SpvOpTypeArray);
|
||||
const Instruction* length =
|
||||
get_def_use_mgr()->GetDef(arrayType->GetSingleWordInOperand(1u));
|
||||
return GetConstantInteger(length);
|
||||
return context()
|
||||
->get_constant_mgr()
|
||||
->GetConstantFromInst(length)
|
||||
->GetZeroExtendedValue();
|
||||
}
|
||||
|
||||
uint64_t ScalarReplacementPass::GetNumElements(const Instruction* type) const {
|
||||
@ -525,25 +544,42 @@ bool ScalarReplacementPass::CanReplaceVariable(
|
||||
assert(varInst->opcode() == SpvOpVariable);
|
||||
|
||||
// Can only replace function scope variables.
|
||||
if (varInst->GetSingleWordInOperand(0u) != SpvStorageClassFunction)
|
||||
if (varInst->GetSingleWordInOperand(0u) != SpvStorageClassFunction) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckTypeAnnotations(get_def_use_mgr()->GetDef(varInst->type_id())))
|
||||
if (!CheckTypeAnnotations(get_def_use_mgr()->GetDef(varInst->type_id()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Instruction* typeInst = GetStorageType(varInst);
|
||||
return CheckType(typeInst) && CheckAnnotations(varInst) && CheckUses(varInst);
|
||||
if (!CheckType(typeInst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckAnnotations(varInst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckUses(varInst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScalarReplacementPass::CheckType(const Instruction* typeInst) const {
|
||||
if (!CheckTypeAnnotations(typeInst)) return false;
|
||||
if (!CheckTypeAnnotations(typeInst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (typeInst->opcode()) {
|
||||
case SpvOpTypeStruct:
|
||||
// Don't bother with empty structs or very large structs.
|
||||
if (typeInst->NumInOperands() == 0 ||
|
||||
IsLargerThanSizeLimit(typeInst->NumInOperands()))
|
||||
IsLargerThanSizeLimit(typeInst->NumInOperands())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case SpvOpTypeArray:
|
||||
if (IsSpecConstant(typeInst->GetSingleWordInOperand(1u))) {
|
||||
@ -592,6 +628,7 @@ bool ScalarReplacementPass::CheckTypeAnnotations(
|
||||
case SpvDecorationAlignment:
|
||||
case SpvDecorationAlignmentId:
|
||||
case SpvDecorationMaxByteOffset:
|
||||
case SpvDecorationRelaxedPrecision:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
@ -728,10 +765,10 @@ bool ScalarReplacementPass::IsLargerThanSizeLimit(uint64_t length) const {
|
||||
return length > max_num_elements_;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::unordered_set<uint64_t>>
|
||||
std::unique_ptr<std::unordered_set<int64_t>>
|
||||
ScalarReplacementPass::GetUsedComponents(Instruction* inst) {
|
||||
std::unique_ptr<std::unordered_set<uint64_t>> result(
|
||||
new std::unordered_set<uint64_t>());
|
||||
std::unique_ptr<std::unordered_set<int64_t>> result(
|
||||
new std::unordered_set<int64_t>());
|
||||
|
||||
analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
|
||||
|
||||
@ -769,18 +806,8 @@ ScalarReplacementPass::GetUsedComponents(Instruction* inst) {
|
||||
const analysis::Constant* index_const =
|
||||
const_mgr->FindDeclaredConstant(index_id);
|
||||
if (index_const) {
|
||||
const analysis::Integer* index_type =
|
||||
index_const->type()->AsInteger();
|
||||
assert(index_type);
|
||||
if (index_type->width() == 32) {
|
||||
result->insert(index_const->GetU32());
|
||||
return true;
|
||||
} else if (index_type->width() == 64) {
|
||||
result->insert(index_const->GetU64());
|
||||
return true;
|
||||
}
|
||||
result.reset(nullptr);
|
||||
return false;
|
||||
result->insert(index_const->GetSignExtendedValue());
|
||||
return true;
|
||||
} else {
|
||||
// Could be any element. Assuming all are used.
|
||||
result.reset(nullptr);
|
||||
|
@ -117,9 +117,12 @@ class ScalarReplacementPass : public Pass {
|
||||
// for element of the composite type. Uses of |inst| are updated as
|
||||
// appropriate. If the replacement variables are themselves scalarizable, they
|
||||
// get added to |worklist| for further processing. If any replacement
|
||||
// variable ends up with no uses it is erased. Returns false if the variable
|
||||
// could not be replaced.
|
||||
bool ReplaceVariable(Instruction* inst, std::queue<Instruction*>* worklist);
|
||||
// variable ends up with no uses it is erased. Returns
|
||||
// - Status::SuccessWithoutChange if the variable could not be replaced.
|
||||
// - Status::SuccessWithChange if it made replacements.
|
||||
// - Status::Failure if it couldn't create replacement variables.
|
||||
Pass::Status ReplaceVariable(Instruction* inst,
|
||||
std::queue<Instruction*>* worklist);
|
||||
|
||||
// Returns the underlying storage type for |inst|.
|
||||
//
|
||||
@ -155,14 +158,6 @@ class ScalarReplacementPass : public Pass {
|
||||
bool CreateReplacementVariables(Instruction* inst,
|
||||
std::vector<Instruction*>* replacements);
|
||||
|
||||
// Returns the value of an OpConstant of integer type.
|
||||
//
|
||||
// |constant| must use two or fewer words to generate the value.
|
||||
uint64_t GetConstantInteger(const Instruction* constant) const;
|
||||
|
||||
// Returns the integer literal for |op|.
|
||||
uint64_t GetIntegerLiteral(const Operand& op) const;
|
||||
|
||||
// Returns the array length for |arrayInst|.
|
||||
uint64_t GetArrayLength(const Instruction* arrayInst) const;
|
||||
|
||||
@ -213,7 +208,7 @@ class ScalarReplacementPass : public Pass {
|
||||
// Returns a set containing the which components of the result of |inst| are
|
||||
// potentially used. If the return value is |nullptr|, then every components
|
||||
// is possibly used.
|
||||
std::unique_ptr<std::unordered_set<uint64_t>> GetUsedComponents(
|
||||
std::unique_ptr<std::unordered_set<int64_t>> GetUsedComponents(
|
||||
Instruction* inst);
|
||||
|
||||
// Returns an instruction defining a null constant with type |type_id|. If
|
||||
|
@ -71,8 +71,16 @@ bool SimplificationPass::SimplifyFunction(Function* function) {
|
||||
}
|
||||
});
|
||||
if (inst->opcode() == SpvOpCopyObject) {
|
||||
context()->ReplaceAllUsesWith(inst->result_id(),
|
||||
inst->GetSingleWordInOperand(0));
|
||||
context()->ReplaceAllUsesWithPredicate(
|
||||
inst->result_id(), inst->GetSingleWordInOperand(0),
|
||||
[](Instruction* user, uint32_t) {
|
||||
const auto opcode = user->opcode();
|
||||
if (!spvOpcodeIsDebug(opcode) &&
|
||||
!spvOpcodeIsDecoration(opcode)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
inst_to_kill.insert(inst);
|
||||
in_work_list.insert(inst);
|
||||
} else if (inst->opcode() == SpvOpNop) {
|
||||
@ -107,8 +115,15 @@ bool SimplificationPass::SimplifyFunction(Function* function) {
|
||||
});
|
||||
|
||||
if (inst->opcode() == SpvOpCopyObject) {
|
||||
context()->ReplaceAllUsesWith(inst->result_id(),
|
||||
inst->GetSingleWordInOperand(0));
|
||||
context()->ReplaceAllUsesWithPredicate(
|
||||
inst->result_id(), inst->GetSingleWordInOperand(0),
|
||||
[](Instruction* user, uint32_t) {
|
||||
const auto opcode = user->opcode();
|
||||
if (!spvOpcodeIsDebug(opcode) && !spvOpcodeIsDecoration(opcode)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
inst_to_kill.insert(inst);
|
||||
in_work_list.insert(inst);
|
||||
} else if (inst->opcode() == SpvOpNop) {
|
||||
|
6
3rdparty/spirv-tools/source/opt/types.cpp
vendored
6
3rdparty/spirv-tools/source/opt/types.cpp
vendored
@ -274,7 +274,7 @@ void Float::GetExtraHashWords(std::vector<uint32_t>* words,
|
||||
words->push_back(width_);
|
||||
}
|
||||
|
||||
Vector::Vector(Type* type, uint32_t count)
|
||||
Vector::Vector(const Type* type, uint32_t count)
|
||||
: Type(kVector), element_type_(type), count_(count) {
|
||||
assert(type->AsBool() || type->AsInteger() || type->AsFloat());
|
||||
}
|
||||
@ -299,7 +299,7 @@ void Vector::GetExtraHashWords(std::vector<uint32_t>* words,
|
||||
words->push_back(count_);
|
||||
}
|
||||
|
||||
Matrix::Matrix(Type* type, uint32_t count)
|
||||
Matrix::Matrix(const Type* type, uint32_t count)
|
||||
: Type(kMatrix), element_type_(type), count_(count) {
|
||||
assert(type->AsVector());
|
||||
}
|
||||
@ -426,7 +426,7 @@ void Array::GetExtraHashWords(std::vector<uint32_t>* words,
|
||||
|
||||
void Array::ReplaceElementType(const Type* type) { element_type_ = type; }
|
||||
|
||||
RuntimeArray::RuntimeArray(Type* type)
|
||||
RuntimeArray::RuntimeArray(const Type* type)
|
||||
: Type(kRuntimeArray), element_type_(type) {
|
||||
assert(!type->AsVoid());
|
||||
}
|
||||
|
6
3rdparty/spirv-tools/source/opt/types.h
vendored
6
3rdparty/spirv-tools/source/opt/types.h
vendored
@ -258,7 +258,7 @@ class Float : public Type {
|
||||
|
||||
class Vector : public Type {
|
||||
public:
|
||||
Vector(Type* element_type, uint32_t count);
|
||||
Vector(const Type* element_type, uint32_t count);
|
||||
Vector(const Vector&) = default;
|
||||
|
||||
std::string str() const override;
|
||||
@ -280,7 +280,7 @@ class Vector : public Type {
|
||||
|
||||
class Matrix : public Type {
|
||||
public:
|
||||
Matrix(Type* element_type, uint32_t count);
|
||||
Matrix(const Type* element_type, uint32_t count);
|
||||
Matrix(const Matrix&) = default;
|
||||
|
||||
std::string str() const override;
|
||||
@ -407,7 +407,7 @@ class Array : public Type {
|
||||
|
||||
class RuntimeArray : public Type {
|
||||
public:
|
||||
RuntimeArray(Type* element_type);
|
||||
RuntimeArray(const Type* element_type);
|
||||
RuntimeArray(const RuntimeArray&) = default;
|
||||
|
||||
std::string str() const override;
|
||||
|
44
3rdparty/spirv-tools/source/util/string_utils.h
vendored
44
3rdparty/spirv-tools/source/util/string_utils.h
vendored
@ -15,8 +15,10 @@
|
||||
#ifndef SOURCE_UTIL_STRING_UTILS_H_
|
||||
#define SOURCE_UTIL_STRING_UTILS_H_
|
||||
|
||||
#include <assert.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "source/util/string_utils.h"
|
||||
|
||||
@ -42,6 +44,48 @@ std::string CardinalToOrdinal(size_t cardinal);
|
||||
// string will be empty.
|
||||
std::pair<std::string, std::string> SplitFlagArgs(const std::string& flag);
|
||||
|
||||
// Encodes a string as a sequence of words, using the SPIR-V encoding.
|
||||
inline std::vector<uint32_t> MakeVector(std::string input) {
|
||||
std::vector<uint32_t> result;
|
||||
uint32_t word = 0;
|
||||
size_t num_bytes = input.size();
|
||||
// SPIR-V strings are null-terminated. The byte_index == num_bytes
|
||||
// case is used to push the terminating null byte.
|
||||
for (size_t byte_index = 0; byte_index <= num_bytes; byte_index++) {
|
||||
const auto new_byte =
|
||||
(byte_index < num_bytes ? uint8_t(input[byte_index]) : uint8_t(0));
|
||||
word |= (new_byte << (8 * (byte_index % sizeof(uint32_t))));
|
||||
if (3 == (byte_index % sizeof(uint32_t))) {
|
||||
result.push_back(word);
|
||||
word = 0;
|
||||
}
|
||||
}
|
||||
// Emit a trailing partial word.
|
||||
if ((num_bytes + 1) % sizeof(uint32_t)) {
|
||||
result.push_back(word);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Decode a string from a sequence of words, using the SPIR-V encoding.
|
||||
template <class VectorType>
|
||||
inline std::string MakeString(const VectorType& words) {
|
||||
std::string result;
|
||||
|
||||
for (uint32_t word : words) {
|
||||
for (int byte_index = 0; byte_index < 4; byte_index++) {
|
||||
uint32_t extracted_word = (word >> (8 * byte_index)) & 0xFF;
|
||||
char c = static_cast<char>(extracted_word);
|
||||
if (c == 0) {
|
||||
return result;
|
||||
}
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
assert(false && "Did not find terminating null for the string.");
|
||||
return result;
|
||||
} // namespace utils
|
||||
|
||||
} // namespace utils
|
||||
} // namespace spvtools
|
||||
|
||||
|
@ -57,36 +57,51 @@ spv_result_t ValidateMemorySemantics(ValidationState_t& _,
|
||||
}
|
||||
|
||||
if (spvIsWebGPUEnv(_.context()->target_env)) {
|
||||
uint32_t valid_bits = SpvMemorySemanticsUniformMemoryMask |
|
||||
SpvMemorySemanticsWorkgroupMemoryMask |
|
||||
SpvMemorySemanticsImageMemoryMask |
|
||||
SpvMemorySemanticsOutputMemoryKHRMask |
|
||||
SpvMemorySemanticsMakeAvailableKHRMask |
|
||||
SpvMemorySemanticsMakeVisibleKHRMask;
|
||||
if (!spvOpcodeIsAtomicOp(inst->opcode())) {
|
||||
valid_bits |= SpvMemorySemanticsAcquireReleaseMask;
|
||||
}
|
||||
uint32_t valid_bits;
|
||||
switch (inst->opcode()) {
|
||||
case SpvOpControlBarrier:
|
||||
if (!(value & SpvMemorySemanticsAcquireReleaseMask)) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< "For WebGPU, AcquireRelease must be set for Memory "
|
||||
"Semantics of OpControlBarrier.";
|
||||
}
|
||||
|
||||
if (value & ~valid_bits) {
|
||||
if (spvOpcodeIsAtomicOp(inst->opcode())) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< "WebGPU spec disallows, for OpAtomic*, any bit masks in "
|
||||
"Memory Semantics that are not UniformMemory, "
|
||||
"WorkgroupMemory, ImageMemory, or OutputMemoryKHR";
|
||||
} else {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< "WebGPU spec disallows any bit masks in Memory Semantics "
|
||||
"that are not AcquireRelease, UniformMemory, "
|
||||
"WorkgroupMemory, ImageMemory, OutputMemoryKHR, "
|
||||
"MakeAvailableKHR, or MakeVisibleKHR";
|
||||
}
|
||||
}
|
||||
if (!(value & SpvMemorySemanticsWorkgroupMemoryMask)) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< "For WebGPU, WorkgroupMemory must be set for Memory "
|
||||
"Semantics of OpControlBarrier.";
|
||||
}
|
||||
|
||||
if (!spvOpcodeIsAtomicOp(inst->opcode()) &&
|
||||
!(value & SpvMemorySemanticsAcquireReleaseMask)) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< "WebGPU spec requires AcquireRelease to set in Memory "
|
||||
"Semantics.";
|
||||
valid_bits = SpvMemorySemanticsAcquireReleaseMask |
|
||||
SpvMemorySemanticsWorkgroupMemoryMask;
|
||||
if (value & ~valid_bits) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< "For WebGPU only WorkgroupMemory and AcquireRelease may be "
|
||||
"set for Memory Semantics of OpControlBarrier.";
|
||||
}
|
||||
break;
|
||||
case SpvOpMemoryBarrier:
|
||||
if (!(value & SpvMemorySemanticsImageMemoryMask)) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< "For WebGPU, ImageMemory must be set for Memory Semantics "
|
||||
"of OpMemoryBarrier.";
|
||||
}
|
||||
valid_bits = SpvMemorySemanticsImageMemoryMask;
|
||||
if (value & ~valid_bits) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< "For WebGPU only ImageMemory may be set for Memory "
|
||||
"Semantics of OpMemoryBarrier.";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (spvOpcodeIsAtomicOp(inst->opcode())) {
|
||||
if (value != 0) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< "For WebGPU Memory no bits may be set for Memory "
|
||||
"Semantics of OpAtomic* instructions.";
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,6 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _,
|
||||
|
||||
// WebGPU Specific rules
|
||||
if (spvIsWebGPUEnv(_.context()->target_env)) {
|
||||
// Scope for execution must be limited to Workgroup or Subgroup
|
||||
if (value != SpvScopeWorkgroup) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< spvOpcodeString(opcode)
|
||||
@ -229,12 +228,41 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
|
||||
|
||||
// WebGPU specific rules
|
||||
if (spvIsWebGPUEnv(_.context()->target_env)) {
|
||||
if (value != SpvScopeWorkgroup && value != SpvScopeInvocation &&
|
||||
value != SpvScopeQueueFamilyKHR) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< spvOpcodeString(opcode)
|
||||
<< ": in WebGPU environment Memory Scope is limited to "
|
||||
<< "Workgroup, Invocation, and QueueFamilyKHR";
|
||||
switch (inst->opcode()) {
|
||||
case SpvOpControlBarrier:
|
||||
if (value != SpvScopeWorkgroup) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< spvOpcodeString(opcode)
|
||||
<< ": in WebGPU environment Memory Scope is limited to "
|
||||
<< "Workgroup for OpControlBarrier";
|
||||
}
|
||||
break;
|
||||
case SpvOpMemoryBarrier:
|
||||
if (value != SpvScopeWorkgroup) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< spvOpcodeString(opcode)
|
||||
<< ": in WebGPU environment Memory Scope is limited to "
|
||||
<< "Workgroup for OpMemoryBarrier";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (spvOpcodeIsAtomicOp(inst->opcode())) {
|
||||
if (value != SpvScopeQueueFamilyKHR) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< spvOpcodeString(opcode)
|
||||
<< ": in WebGPU environment Memory Scope is limited to "
|
||||
<< "QueueFamilyKHR for OpAtomic* operations";
|
||||
}
|
||||
}
|
||||
|
||||
if (value != SpvScopeWorkgroup && value != SpvScopeInvocation &&
|
||||
value != SpvScopeQueueFamilyKHR) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< spvOpcodeString(opcode)
|
||||
<< ": in WebGPU environment Memory Scope is limited to "
|
||||
<< "Workgroup, Invocation, and QueueFamilyKHR";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "source/instruction.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "test/unit_spirv.h"
|
||||
|
||||
namespace spvtools {
|
||||
@ -40,9 +41,8 @@ TEST_P(EncodeStringTest, Sample) {
|
||||
ASSERT_EQ(SPV_SUCCESS,
|
||||
context.binaryEncodeString(GetParam().str.c_str(), &inst));
|
||||
// We already trust MakeVector
|
||||
EXPECT_THAT(inst.words,
|
||||
Eq(Concatenate({GetParam().initial_contents,
|
||||
spvtest::MakeVector(GetParam().str)})));
|
||||
EXPECT_THAT(inst.words, Eq(Concatenate({GetParam().initial_contents,
|
||||
utils::MakeVector(GetParam().str)})));
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "gmock/gmock.h"
|
||||
#include "source/latest_version_opencl_std_header.h"
|
||||
#include "source/table.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "test/test_fixture.h"
|
||||
#include "test/unit_spirv.h"
|
||||
|
||||
@ -39,7 +40,7 @@ namespace {
|
||||
|
||||
using ::spvtest::Concatenate;
|
||||
using ::spvtest::MakeInstruction;
|
||||
using ::spvtest::MakeVector;
|
||||
using utils::MakeVector;
|
||||
using ::spvtest::ScopedContext;
|
||||
using ::testing::_;
|
||||
using ::testing::AnyOf;
|
||||
|
3
3rdparty/spirv-tools/test/comment_test.cpp
vendored
3
3rdparty/spirv-tools/test/comment_test.cpp
vendored
@ -15,6 +15,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "test/test_fixture.h"
|
||||
#include "test/unit_spirv.h"
|
||||
|
||||
@ -23,7 +24,7 @@ namespace {
|
||||
|
||||
using spvtest::Concatenate;
|
||||
using spvtest::MakeInstruction;
|
||||
using spvtest::MakeVector;
|
||||
using utils::MakeVector;
|
||||
using spvtest::TextToBinaryTest;
|
||||
using testing::Eq;
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include "DebugInfo.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "test/test_fixture.h"
|
||||
#include "test/unit_spirv.h"
|
||||
|
||||
@ -31,7 +32,7 @@ namespace {
|
||||
|
||||
using spvtest::Concatenate;
|
||||
using spvtest::MakeInstruction;
|
||||
using spvtest::MakeVector;
|
||||
using utils::MakeVector;
|
||||
using testing::Eq;
|
||||
|
||||
struct InstructionCase {
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "source/latest_version_opencl_std_header.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "test/test_fixture.h"
|
||||
#include "test/unit_spirv.h"
|
||||
|
||||
@ -25,7 +26,7 @@ namespace {
|
||||
|
||||
using spvtest::Concatenate;
|
||||
using spvtest::MakeInstruction;
|
||||
using spvtest::MakeVector;
|
||||
using utils::MakeVector;
|
||||
using spvtest::TextToBinaryTest;
|
||||
using testing::Eq;
|
||||
|
||||
|
@ -25,10 +25,12 @@ if (${SPIRV_BUILD_FUZZER})
|
||||
transformation_add_constant_boolean_test.cpp
|
||||
transformation_add_constant_scalar_test.cpp
|
||||
transformation_add_dead_break_test.cpp
|
||||
transformation_add_dead_continue_test.cpp
|
||||
transformation_add_type_boolean_test.cpp
|
||||
transformation_add_type_float_test.cpp
|
||||
transformation_add_type_int_test.cpp
|
||||
transformation_add_type_pointer_test.cpp
|
||||
transformation_copy_object_test.cpp
|
||||
transformation_move_block_down_test.cpp
|
||||
transformation_replace_boolean_constant_with_constant_binary_test.cpp
|
||||
transformation_replace_constant_with_uniform_test.cpp
|
||||
|
@ -79,6 +79,10 @@ bool IsValid(spv_target_env env, const opt::IRContext* ir) {
|
||||
std::string ToString(spv_target_env env, const opt::IRContext* ir) {
|
||||
std::vector<uint32_t> binary;
|
||||
ir->module()->ToBinary(&binary, false);
|
||||
return ToString(env, binary);
|
||||
}
|
||||
|
||||
std::string ToString(spv_target_env env, const std::vector<uint32_t>& binary) {
|
||||
SpirvTools t(env);
|
||||
std::string result;
|
||||
t.Disassemble(binary, &result, kFuzzDisassembleOption);
|
||||
|
29
3rdparty/spirv-tools/test/fuzz/fuzz_test_util.h
vendored
29
3rdparty/spirv-tools/test/fuzz/fuzz_test_util.h
vendored
@ -17,6 +17,8 @@
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "source/opt/build_module.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "spirv-tools/libspirv.h"
|
||||
@ -51,6 +53,10 @@ bool IsValid(spv_target_env env, const opt::IRContext* ir);
|
||||
// Useful for debugging.
|
||||
std::string ToString(spv_target_env env, const opt::IRContext* ir);
|
||||
|
||||
// Returns the disassembly of the given binary as a string.
|
||||
// Useful for debugging.
|
||||
std::string ToString(spv_target_env env, const std::vector<uint32_t>& binary);
|
||||
|
||||
// Assembly options for writing fuzzer tests. It simplifies matters if
|
||||
// numeric ids do not change.
|
||||
const uint32_t kFuzzAssembleOption =
|
||||
@ -64,6 +70,29 @@ const spvtools::MessageConsumer kSilentConsumer =
|
||||
[](spv_message_level_t, const char*, const spv_position_t&,
|
||||
const char*) -> void {};
|
||||
|
||||
const spvtools::MessageConsumer kConsoleMessageConsumer =
|
||||
[](spv_message_level_t level, const char*, const spv_position_t& position,
|
||||
const char* message) -> void {
|
||||
switch (level) {
|
||||
case SPV_MSG_FATAL:
|
||||
case SPV_MSG_INTERNAL_ERROR:
|
||||
case SPV_MSG_ERROR:
|
||||
std::cerr << "error: line " << position.index << ": " << message
|
||||
<< std::endl;
|
||||
break;
|
||||
case SPV_MSG_WARNING:
|
||||
std::cout << "warning: line " << position.index << ": " << message
|
||||
<< std::endl;
|
||||
break;
|
||||
case SPV_MSG_INFO:
|
||||
std::cout << "info: line " << position.index << ": " << message
|
||||
<< std::endl;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
|
@ -33,6 +33,7 @@ void RunFuzzerAndReplayer(const std::string& shader,
|
||||
|
||||
std::vector<uint32_t> binary_in;
|
||||
SpirvTools t(env);
|
||||
t.SetMessageConsumer(kConsoleMessageConsumer);
|
||||
ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
|
||||
ASSERT_TRUE(t.Validate(binary_in));
|
||||
|
||||
@ -239,9 +240,9 @@ TEST(FuzzerReplayerTest, Miscellaneous1) {
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
// Do 10 fuzzer runs, starting from an initial seed of 0 (seed value chosen
|
||||
// Do 5 fuzzer runs, starting from an initial seed of 0 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 0, 10);
|
||||
RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 0, 5);
|
||||
}
|
||||
|
||||
TEST(FuzzerReplayerTest, Miscellaneous2) {
|
||||
@ -484,9 +485,9 @@ TEST(FuzzerReplayerTest, Miscellaneous2) {
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
// Do 10 fuzzer runs, starting from an initial seed of 10 (seed value chosen
|
||||
// Do 5 fuzzer runs, starting from an initial seed of 10 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 10, 10);
|
||||
RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 10, 5);
|
||||
}
|
||||
|
||||
TEST(FuzzerReplayerTest, Miscellaneous3) {
|
||||
@ -969,9 +970,9 @@ TEST(FuzzerReplayerTest, Miscellaneous3) {
|
||||
*facts.mutable_fact()->Add() = temp;
|
||||
}
|
||||
|
||||
// Do 10 fuzzer runs, starting from an initial seed of 94 (seed value chosen
|
||||
// Do 5 fuzzer runs, starting from an initial seed of 94 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndReplayer(shader, facts, 94, 10);
|
||||
RunFuzzerAndReplayer(shader, facts, 94, 5);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -111,20 +111,21 @@ class InterestingThenRandom : public InterestingnessTest {
|
||||
// |transformation_sequence_in| gets performed with respect to
|
||||
// |interestingness_function|. If |expected_binary_out| is non-empty, it must
|
||||
// match the binary obtained by applying the final shrunk set of
|
||||
// transformations, in which case the number of such transforations should equal
|
||||
// |expected_transformations_out_size|.
|
||||
// transformations, in which case the number of such transformations should
|
||||
// equal |expected_transformations_out_size|.
|
||||
//
|
||||
// The |step_limit| parameter restricts the number of steps that the shrinker
|
||||
// will try; it can be set to something small for a faster (but less thorough)
|
||||
// test.
|
||||
void RunAndCheckShrinker(
|
||||
const spv_target_env& target_env, const std::vector<uint32_t>& binary_in,
|
||||
const protobufs::FactSequence& initial_facts,
|
||||
const protobufs::TransformationSequence& transformation_sequence_in,
|
||||
const Shrinker::InterestingnessFunction& interestingness_function,
|
||||
const std::vector<uint32_t>& expected_binary_out,
|
||||
uint32_t expected_transformations_out_size) {
|
||||
// We want tests to complete in reasonable time, so don't go on too long.
|
||||
const uint32_t kStepLimit = 50;
|
||||
|
||||
uint32_t expected_transformations_out_size, uint32_t step_limit) {
|
||||
// Run the shrinker.
|
||||
Shrinker shrinker(target_env, kStepLimit);
|
||||
Shrinker shrinker(target_env, step_limit);
|
||||
shrinker.SetMessageConsumer(kSilentConsumer);
|
||||
|
||||
std::vector<uint32_t> binary_out;
|
||||
@ -146,14 +147,13 @@ void RunAndCheckShrinker(
|
||||
}
|
||||
}
|
||||
|
||||
// Assembles the given |shader| text, and then does the following |num_runs|
|
||||
// times, with successive seeds starting from |initial_seed|:
|
||||
// - Runs the fuzzer with the seed to yield a set of transformations
|
||||
// Assembles the given |shader| text, and then:
|
||||
// - Runs the fuzzer with |seed| to yield a set of transformations
|
||||
// - Shrinks the transformation with various interestingness functions,
|
||||
// asserting some properties about the result each time
|
||||
void RunFuzzerAndShrinker(const std::string& shader,
|
||||
const protobufs::FactSequence& initial_facts,
|
||||
uint32_t initial_seed, uint32_t num_runs) {
|
||||
uint32_t seed) {
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||
|
||||
std::vector<uint32_t> binary_in;
|
||||
@ -161,47 +161,50 @@ void RunFuzzerAndShrinker(const std::string& shader,
|
||||
ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
|
||||
ASSERT_TRUE(t.Validate(binary_in));
|
||||
|
||||
for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) {
|
||||
// Run the fuzzer and check that it successfully yields a valid binary.
|
||||
std::vector<uint32_t> fuzzer_binary_out;
|
||||
protobufs::TransformationSequence fuzzer_transformation_sequence_out;
|
||||
spvtools::FuzzerOptions fuzzer_options;
|
||||
spvFuzzerOptionsSetRandomSeed(fuzzer_options, seed);
|
||||
Fuzzer fuzzer(env);
|
||||
fuzzer.SetMessageConsumer(kSilentConsumer);
|
||||
auto fuzzer_result_status =
|
||||
fuzzer.Run(binary_in, initial_facts, fuzzer_options, &fuzzer_binary_out,
|
||||
&fuzzer_transformation_sequence_out);
|
||||
ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
|
||||
ASSERT_TRUE(t.Validate(fuzzer_binary_out));
|
||||
// Run the fuzzer and check that it successfully yields a valid binary.
|
||||
std::vector<uint32_t> fuzzer_binary_out;
|
||||
protobufs::TransformationSequence fuzzer_transformation_sequence_out;
|
||||
spvtools::FuzzerOptions fuzzer_options;
|
||||
spvFuzzerOptionsSetRandomSeed(fuzzer_options, seed);
|
||||
Fuzzer fuzzer(env);
|
||||
fuzzer.SetMessageConsumer(kSilentConsumer);
|
||||
auto fuzzer_result_status =
|
||||
fuzzer.Run(binary_in, initial_facts, fuzzer_options, &fuzzer_binary_out,
|
||||
&fuzzer_transformation_sequence_out);
|
||||
ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
|
||||
ASSERT_TRUE(t.Validate(fuzzer_binary_out));
|
||||
|
||||
// With the AlwaysInteresting test, we should quickly shrink to the original
|
||||
// binary with no transformations remaining.
|
||||
RunAndCheckShrinker(env, binary_in, initial_facts,
|
||||
fuzzer_transformation_sequence_out,
|
||||
AlwaysInteresting().AsFunction(), binary_in, 0);
|
||||
const uint32_t kReasonableStepLimit = 50;
|
||||
const uint32_t kSmallStepLimit = 20;
|
||||
|
||||
// With the OnlyInterestingFirstTime test, no shrinking should be achieved.
|
||||
RunAndCheckShrinker(
|
||||
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out,
|
||||
static_cast<uint32_t>(
|
||||
fuzzer_transformation_sequence_out.transformation_size()));
|
||||
// With the AlwaysInteresting test, we should quickly shrink to the original
|
||||
// binary with no transformations remaining.
|
||||
RunAndCheckShrinker(
|
||||
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
AlwaysInteresting().AsFunction(), binary_in, 0, kReasonableStepLimit);
|
||||
|
||||
// The PingPong test is unpredictable; passing an empty expected binary
|
||||
// means that we don't check anything beyond that shrinking completes
|
||||
// successfully.
|
||||
RunAndCheckShrinker(env, binary_in, initial_facts,
|
||||
fuzzer_transformation_sequence_out,
|
||||
PingPong().AsFunction(), {}, 0);
|
||||
// With the OnlyInterestingFirstTime test, no shrinking should be achieved.
|
||||
RunAndCheckShrinker(
|
||||
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out,
|
||||
static_cast<uint32_t>(
|
||||
fuzzer_transformation_sequence_out.transformation_size()),
|
||||
kReasonableStepLimit);
|
||||
|
||||
// The InterestingThenRandom test is unpredictable; passing an empty
|
||||
// expected binary means that we do not check anything about shrinking
|
||||
// results.
|
||||
RunAndCheckShrinker(
|
||||
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0);
|
||||
}
|
||||
// The PingPong test is unpredictable; passing an empty expected binary
|
||||
// means that we don't check anything beyond that shrinking completes
|
||||
// successfully.
|
||||
RunAndCheckShrinker(env, binary_in, initial_facts,
|
||||
fuzzer_transformation_sequence_out,
|
||||
PingPong().AsFunction(), {}, 0, kSmallStepLimit);
|
||||
|
||||
// The InterestingThenRandom test is unpredictable; passing an empty
|
||||
// expected binary means that we do not check anything about shrinking
|
||||
// results.
|
||||
RunAndCheckShrinker(
|
||||
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0,
|
||||
kSmallStepLimit);
|
||||
}
|
||||
|
||||
TEST(FuzzerShrinkerTest, Miscellaneous1) {
|
||||
@ -369,9 +372,7 @@ TEST(FuzzerShrinkerTest, Miscellaneous1) {
|
||||
|
||||
)";
|
||||
|
||||
// Do 2 fuzz-and-shrink runs, starting from an initial seed of 2 (seed value
|
||||
// chosen arbitrarily).
|
||||
RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 2, 2);
|
||||
RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 2);
|
||||
}
|
||||
|
||||
TEST(FuzzerShrinkerTest, Miscellaneous2) {
|
||||
@ -614,9 +615,7 @@ TEST(FuzzerShrinkerTest, Miscellaneous2) {
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
// Do 2 fuzzer runs, starting from an initial seed of 19 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 19, 2);
|
||||
RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 19);
|
||||
}
|
||||
|
||||
TEST(FuzzerShrinkerTest, Miscellaneous3) {
|
||||
@ -1101,7 +1100,7 @@ TEST(FuzzerShrinkerTest, Miscellaneous3) {
|
||||
|
||||
// Do 2 fuzzer runs, starting from an initial seed of 194 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndShrinker(shader, facts, 194, 2);
|
||||
RunFuzzerAndShrinker(shader, facts, 194);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
1034
3rdparty/spirv-tools/test/fuzz/transformation_add_dead_continue_test.cpp
vendored
Normal file
1034
3rdparty/spirv-tools/test/fuzz/transformation_add_dead_continue_test.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
539
3rdparty/spirv-tools/test/fuzz/transformation_copy_object_test.cpp
vendored
Normal file
539
3rdparty/spirv-tools/test/fuzz/transformation_copy_object_test.cpp
vendored
Normal file
@ -0,0 +1,539 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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/fuzz/transformation_copy_object.h"
|
||||
#include "source/fuzz/data_descriptor.h"
|
||||
#include "test/fuzz/fuzz_test_util.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
namespace {
|
||||
|
||||
TEST(TransformationCopyObjectTest, CopyBooleanConstants) {
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %4 "main"
|
||||
%2 = OpTypeVoid
|
||||
%6 = OpTypeBool
|
||||
%7 = OpConstantTrue %6
|
||||
%8 = OpConstantFalse %6
|
||||
%3 = OpTypeFunction %2
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||
const auto consumer = nullptr;
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
|
||||
ASSERT_EQ(0, fact_manager.GetIdsForWhichSynonymsAreKnown().size());
|
||||
|
||||
TransformationCopyObject copy_true(7, 5, 1, 100);
|
||||
ASSERT_TRUE(copy_true.IsApplicable(context.get(), fact_manager));
|
||||
copy_true.Apply(context.get(), &fact_manager);
|
||||
|
||||
const std::set<uint32_t>& ids_for_which_synonyms_are_known =
|
||||
fact_manager.GetIdsForWhichSynonymsAreKnown();
|
||||
ASSERT_EQ(1, ids_for_which_synonyms_are_known.size());
|
||||
ASSERT_TRUE(ids_for_which_synonyms_are_known.find(7) !=
|
||||
ids_for_which_synonyms_are_known.end());
|
||||
ASSERT_EQ(1, fact_manager.GetSynonymsForId(7).size());
|
||||
protobufs::DataDescriptor descriptor_100 = MakeDataDescriptor(100, {});
|
||||
ASSERT_TRUE(DataDescriptorEquals()(&descriptor_100,
|
||||
&fact_manager.GetSynonymsForId(7)[0]));
|
||||
|
||||
TransformationCopyObject copy_false(8, 100, 1, 101);
|
||||
ASSERT_TRUE(copy_false.IsApplicable(context.get(), fact_manager));
|
||||
copy_false.Apply(context.get(), &fact_manager);
|
||||
ASSERT_EQ(2, ids_for_which_synonyms_are_known.size());
|
||||
ASSERT_TRUE(ids_for_which_synonyms_are_known.find(8) !=
|
||||
ids_for_which_synonyms_are_known.end());
|
||||
ASSERT_EQ(1, fact_manager.GetSynonymsForId(8).size());
|
||||
protobufs::DataDescriptor descriptor_101 = MakeDataDescriptor(101, {});
|
||||
ASSERT_TRUE(DataDescriptorEquals()(&descriptor_101,
|
||||
&fact_manager.GetSynonymsForId(8)[0]));
|
||||
|
||||
TransformationCopyObject copy_false_again(101, 5, 3, 102);
|
||||
ASSERT_TRUE(copy_false_again.IsApplicable(context.get(), fact_manager));
|
||||
copy_false_again.Apply(context.get(), &fact_manager);
|
||||
ASSERT_EQ(3, ids_for_which_synonyms_are_known.size());
|
||||
ASSERT_TRUE(ids_for_which_synonyms_are_known.find(101) !=
|
||||
ids_for_which_synonyms_are_known.end());
|
||||
ASSERT_EQ(1, fact_manager.GetSynonymsForId(101).size());
|
||||
protobufs::DataDescriptor descriptor_102 = MakeDataDescriptor(102, {});
|
||||
ASSERT_TRUE(DataDescriptorEquals()(&descriptor_102,
|
||||
&fact_manager.GetSynonymsForId(101)[0]));
|
||||
|
||||
TransformationCopyObject copy_true_again(7, 102, 1, 103);
|
||||
ASSERT_TRUE(copy_true_again.IsApplicable(context.get(), fact_manager));
|
||||
copy_true_again.Apply(context.get(), &fact_manager);
|
||||
// This does re-uses an id for which synonyms are already known, so the count
|
||||
// of such ids does not change.
|
||||
ASSERT_EQ(3, ids_for_which_synonyms_are_known.size());
|
||||
ASSERT_TRUE(ids_for_which_synonyms_are_known.find(7) !=
|
||||
ids_for_which_synonyms_are_known.end());
|
||||
ASSERT_EQ(2, fact_manager.GetSynonymsForId(7).size());
|
||||
protobufs::DataDescriptor descriptor_103 = MakeDataDescriptor(103, {});
|
||||
ASSERT_TRUE(DataDescriptorEquals()(&descriptor_103,
|
||||
&fact_manager.GetSynonymsForId(7)[0]) ||
|
||||
DataDescriptorEquals()(&descriptor_103,
|
||||
&fact_manager.GetSynonymsForId(7)[1]));
|
||||
|
||||
std::string after_transformation = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %4 "main"
|
||||
%2 = OpTypeVoid
|
||||
%6 = OpTypeBool
|
||||
%7 = OpConstantTrue %6
|
||||
%8 = OpConstantFalse %6
|
||||
%3 = OpTypeFunction %2
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%100 = OpCopyObject %6 %7
|
||||
%101 = OpCopyObject %6 %8
|
||||
%102 = OpCopyObject %6 %101
|
||||
%103 = OpCopyObject %6 %7
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
|
||||
}
|
||||
|
||||
TEST(TransformationCopyObjectTest, CheckIllegalCases) {
|
||||
// The following SPIR-V comes from this GLSL, pushed through spirv-opt
|
||||
// and then doctored a bit.
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// precision highp float;
|
||||
//
|
||||
// struct S {
|
||||
// int a;
|
||||
// float b;
|
||||
// };
|
||||
//
|
||||
// layout(set = 0, binding = 2) uniform block {
|
||||
// S s;
|
||||
// lowp float f;
|
||||
// int ii;
|
||||
// } ubuf;
|
||||
//
|
||||
// layout(location = 0) out vec4 color;
|
||||
//
|
||||
// void main() {
|
||||
// float c = 0.0;
|
||||
// lowp float d = 0.0;
|
||||
// S localS = ubuf.s;
|
||||
// for (int i = 0; i < ubuf.s.a; i++) {
|
||||
// switch (ubuf.ii) {
|
||||
// case 0:
|
||||
// c += 0.1;
|
||||
// d += 0.2;
|
||||
// case 1:
|
||||
// c += 0.1;
|
||||
// if (c > d) {
|
||||
// d += 0.2;
|
||||
// } else {
|
||||
// d += c;
|
||||
// }
|
||||
// break;
|
||||
// default:
|
||||
// i += 1;
|
||||
// localS.b += d;
|
||||
// }
|
||||
// }
|
||||
// color = vec4(c, d, localS.b, 1.0);
|
||||
// }
|
||||
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main" %80
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %4 "main"
|
||||
OpName %12 "S"
|
||||
OpMemberName %12 0 "a"
|
||||
OpMemberName %12 1 "b"
|
||||
OpName %15 "S"
|
||||
OpMemberName %15 0 "a"
|
||||
OpMemberName %15 1 "b"
|
||||
OpName %16 "block"
|
||||
OpMemberName %16 0 "s"
|
||||
OpMemberName %16 1 "f"
|
||||
OpMemberName %16 2 "ii"
|
||||
OpName %18 "ubuf"
|
||||
OpName %80 "color"
|
||||
OpMemberDecorate %12 0 RelaxedPrecision
|
||||
OpMemberDecorate %15 0 RelaxedPrecision
|
||||
OpMemberDecorate %15 0 Offset 0
|
||||
OpMemberDecorate %15 1 Offset 4
|
||||
OpMemberDecorate %16 0 Offset 0
|
||||
OpMemberDecorate %16 1 RelaxedPrecision
|
||||
OpMemberDecorate %16 1 Offset 16
|
||||
OpMemberDecorate %16 2 RelaxedPrecision
|
||||
OpMemberDecorate %16 2 Offset 20
|
||||
OpDecorate %16 Block
|
||||
OpDecorate %18 DescriptorSet 0
|
||||
OpDecorate %18 Binding 2
|
||||
OpDecorate %38 RelaxedPrecision
|
||||
OpDecorate %43 RelaxedPrecision
|
||||
OpDecorate %53 RelaxedPrecision
|
||||
OpDecorate %62 RelaxedPrecision
|
||||
OpDecorate %69 RelaxedPrecision
|
||||
OpDecorate %77 RelaxedPrecision
|
||||
OpDecorate %80 Location 0
|
||||
OpDecorate %101 RelaxedPrecision
|
||||
OpDecorate %102 RelaxedPrecision
|
||||
OpDecorate %96 RelaxedPrecision
|
||||
OpDecorate %108 RelaxedPrecision
|
||||
OpDecorate %107 RelaxedPrecision
|
||||
OpDecorate %98 RelaxedPrecision
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%9 = OpConstant %6 0
|
||||
%11 = OpTypeInt 32 1
|
||||
%12 = OpTypeStruct %11 %6
|
||||
%15 = OpTypeStruct %11 %6
|
||||
%16 = OpTypeStruct %15 %6 %11
|
||||
%17 = OpTypePointer Uniform %16
|
||||
%18 = OpVariable %17 Uniform
|
||||
%19 = OpConstant %11 0
|
||||
%20 = OpTypePointer Uniform %15
|
||||
%27 = OpConstant %11 1
|
||||
%36 = OpTypePointer Uniform %11
|
||||
%39 = OpTypeBool
|
||||
%41 = OpConstant %11 2
|
||||
%48 = OpConstant %6 0.100000001
|
||||
%51 = OpConstant %6 0.200000003
|
||||
%78 = OpTypeVector %6 4
|
||||
%79 = OpTypePointer Output %78
|
||||
%80 = OpVariable %79 Output
|
||||
%85 = OpConstant %6 1
|
||||
%95 = OpUndef %12
|
||||
%112 = OpTypePointer Uniform %6
|
||||
%113 = OpTypeInt 32 0
|
||||
%114 = OpConstant %113 1
|
||||
%179 = OpTypePointer Function %39
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%180 = OpVariable %179 Function
|
||||
%181 = OpVariable %179 Function
|
||||
%182 = OpVariable %179 Function
|
||||
%21 = OpAccessChain %20 %18 %19
|
||||
%115 = OpAccessChain %112 %21 %114
|
||||
%116 = OpLoad %6 %115
|
||||
%90 = OpCompositeInsert %12 %116 %95 1
|
||||
OpBranch %30
|
||||
%30 = OpLabel
|
||||
%99 = OpPhi %12 %90 %5 %109 %47
|
||||
%98 = OpPhi %6 %9 %5 %107 %47
|
||||
%97 = OpPhi %6 %9 %5 %105 %47
|
||||
%96 = OpPhi %11 %19 %5 %77 %47
|
||||
%37 = OpAccessChain %36 %18 %19 %19
|
||||
%38 = OpLoad %11 %37
|
||||
%40 = OpSLessThan %39 %96 %38
|
||||
OpLoopMerge %32 %47 None
|
||||
OpBranchConditional %40 %31 %32
|
||||
%31 = OpLabel
|
||||
%42 = OpAccessChain %36 %18 %41
|
||||
%43 = OpLoad %11 %42
|
||||
OpSelectionMerge %47 None
|
||||
OpSwitch %43 %46 0 %44 1 %45
|
||||
%46 = OpLabel
|
||||
%69 = OpIAdd %11 %96 %27
|
||||
%72 = OpCompositeExtract %6 %99 1
|
||||
%73 = OpFAdd %6 %72 %98
|
||||
%93 = OpCompositeInsert %12 %73 %99 1
|
||||
OpBranch %47
|
||||
%44 = OpLabel
|
||||
%50 = OpFAdd %6 %97 %48
|
||||
%53 = OpFAdd %6 %98 %51
|
||||
OpBranch %45
|
||||
%45 = OpLabel
|
||||
%101 = OpPhi %6 %98 %31 %53 %44
|
||||
%100 = OpPhi %6 %97 %31 %50 %44
|
||||
%55 = OpFAdd %6 %100 %48
|
||||
%58 = OpFOrdGreaterThan %39 %55 %101
|
||||
OpSelectionMerge %60 None
|
||||
OpBranchConditional %58 %59 %63
|
||||
%59 = OpLabel
|
||||
%62 = OpFAdd %6 %101 %51
|
||||
OpBranch %60
|
||||
%63 = OpLabel
|
||||
%66 = OpFAdd %6 %101 %55
|
||||
OpBranch %60
|
||||
%60 = OpLabel
|
||||
%108 = OpPhi %6 %62 %59 %66 %63
|
||||
OpBranch %47
|
||||
%47 = OpLabel
|
||||
%109 = OpPhi %12 %93 %46 %99 %60
|
||||
%107 = OpPhi %6 %98 %46 %108 %60
|
||||
%105 = OpPhi %6 %97 %46 %55 %60
|
||||
%102 = OpPhi %11 %69 %46 %96 %60
|
||||
%77 = OpIAdd %11 %102 %27
|
||||
OpBranch %30
|
||||
%32 = OpLabel
|
||||
%84 = OpCompositeExtract %6 %99 1
|
||||
%86 = OpCompositeConstruct %78 %97 %98 %84 %85
|
||||
OpStore %80 %86
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||
const auto consumer = nullptr;
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
|
||||
// Inapplicable because %18 is decorated.
|
||||
ASSERT_FALSE(TransformationCopyObject(18, 21, 0, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because %77 is decorated.
|
||||
ASSERT_FALSE(TransformationCopyObject(17, 17, 1, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because %80 is decorated.
|
||||
ASSERT_FALSE(TransformationCopyObject(80, 77, 0, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because %84 is not available at the requested point
|
||||
ASSERT_FALSE(TransformationCopyObject(84, 32, 1, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Fine because %84 is available at the requested point
|
||||
ASSERT_TRUE(TransformationCopyObject(84, 32, 2, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because id %9 is already in use
|
||||
ASSERT_FALSE(TransformationCopyObject(84, 32, 2, 9)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because the requested point is not in a block
|
||||
ASSERT_FALSE(TransformationCopyObject(84, 86, 3, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because %9 is not in a function
|
||||
ASSERT_FALSE(TransformationCopyObject(9, 9, 1, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because %9 is not in a function
|
||||
ASSERT_FALSE(TransformationCopyObject(9, 9, 1, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because the insert point is right before, or inside, a chunk
|
||||
// of OpPhis
|
||||
ASSERT_FALSE(TransformationCopyObject(9, 30, 1, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
ASSERT_FALSE(TransformationCopyObject(9, 99, 1, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// OK, because the insert point is just after a chunk of OpPhis.
|
||||
ASSERT_TRUE(TransformationCopyObject(9, 96, 1, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because the insert point is right after an OpSelectionMerge
|
||||
ASSERT_FALSE(TransformationCopyObject(9, 58, 2, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// OK, because the insert point is right before the OpSelectionMerge
|
||||
ASSERT_TRUE(TransformationCopyObject(9, 58, 1, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because the insert point is right after an OpSelectionMerge
|
||||
ASSERT_FALSE(TransformationCopyObject(9, 43, 2, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// OK, because the insert point is right before the OpSelectionMerge
|
||||
ASSERT_TRUE(TransformationCopyObject(9, 43, 1, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because the insert point is right after an OpLoopMerge
|
||||
ASSERT_FALSE(TransformationCopyObject(9, 40, 2, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// OK, because the insert point is right before the OpLoopMerge
|
||||
ASSERT_TRUE(TransformationCopyObject(9, 40, 1, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because id %300 does not exist
|
||||
ASSERT_FALSE(TransformationCopyObject(300, 40, 1, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Inapplicable because the following instruction is OpVariable
|
||||
ASSERT_FALSE(TransformationCopyObject(9, 180, 0, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
ASSERT_FALSE(TransformationCopyObject(9, 181, 0, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
ASSERT_FALSE(TransformationCopyObject(9, 182, 0, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// OK, because this is just past the group of OpVariable instructions.
|
||||
ASSERT_TRUE(TransformationCopyObject(9, 182, 1, 200)
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
}
|
||||
|
||||
TEST(TransformationCopyObjectTest, MiscellaneousCopies) {
|
||||
// The following SPIR-V comes from this GLSL:
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// precision highp float;
|
||||
//
|
||||
// float g;
|
||||
//
|
||||
// vec4 h;
|
||||
//
|
||||
// void main() {
|
||||
// int a;
|
||||
// int b;
|
||||
// b = int(g);
|
||||
// h.x = float(a);
|
||||
// }
|
||||
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %4 "main"
|
||||
OpName %8 "b"
|
||||
OpName %11 "g"
|
||||
OpName %16 "h"
|
||||
OpName %17 "a"
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeInt 32 1
|
||||
%7 = OpTypePointer Function %6
|
||||
%9 = OpTypeFloat 32
|
||||
%10 = OpTypePointer Private %9
|
||||
%11 = OpVariable %10 Private
|
||||
%14 = OpTypeVector %9 4
|
||||
%15 = OpTypePointer Private %14
|
||||
%16 = OpVariable %15 Private
|
||||
%20 = OpTypeInt 32 0
|
||||
%21 = OpConstant %20 0
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%8 = OpVariable %7 Function
|
||||
%17 = OpVariable %7 Function
|
||||
%12 = OpLoad %9 %11
|
||||
%13 = OpConvertFToS %6 %12
|
||||
OpStore %8 %13
|
||||
%18 = OpLoad %6 %17
|
||||
%19 = OpConvertSToF %9 %18
|
||||
%22 = OpAccessChain %10 %16 %21
|
||||
OpStore %22 %19
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||
const auto consumer = nullptr;
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
|
||||
std::vector<TransformationCopyObject> transformations = {
|
||||
TransformationCopyObject(19, 22, 1, 100),
|
||||
TransformationCopyObject(22, 22, 1, 101),
|
||||
TransformationCopyObject(12, 22, 1, 102),
|
||||
TransformationCopyObject(11, 22, 1, 103),
|
||||
TransformationCopyObject(16, 22, 1, 104),
|
||||
TransformationCopyObject(8, 22, 1, 105),
|
||||
TransformationCopyObject(17, 22, 1, 106)};
|
||||
|
||||
for (auto& transformation : transformations) {
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
std::string after_transformation = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %4 "main"
|
||||
OpName %8 "b"
|
||||
OpName %11 "g"
|
||||
OpName %16 "h"
|
||||
OpName %17 "a"
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeInt 32 1
|
||||
%7 = OpTypePointer Function %6
|
||||
%9 = OpTypeFloat 32
|
||||
%10 = OpTypePointer Private %9
|
||||
%11 = OpVariable %10 Private
|
||||
%14 = OpTypeVector %9 4
|
||||
%15 = OpTypePointer Private %14
|
||||
%16 = OpVariable %15 Private
|
||||
%20 = OpTypeInt 32 0
|
||||
%21 = OpConstant %20 0
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%8 = OpVariable %7 Function
|
||||
%17 = OpVariable %7 Function
|
||||
%12 = OpLoad %9 %11
|
||||
%13 = OpConvertFToS %6 %12
|
||||
OpStore %8 %13
|
||||
%18 = OpLoad %6 %17
|
||||
%19 = OpConvertSToF %9 %18
|
||||
%22 = OpAccessChain %10 %16 %21
|
||||
%106 = OpCopyObject %7 %17
|
||||
%105 = OpCopyObject %7 %8
|
||||
%104 = OpCopyObject %15 %16
|
||||
%103 = OpCopyObject %10 %11
|
||||
%102 = OpCopyObject %9 %12
|
||||
%101 = OpCopyObject %10 %22
|
||||
%100 = OpCopyObject %9 %19
|
||||
OpStore %22 %19
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
3
3rdparty/spirv-tools/test/opt/CMakeLists.txt
vendored
3
3rdparty/spirv-tools/test/opt/CMakeLists.txt
vendored
@ -25,6 +25,7 @@ add_spvtools_unittest(TARGET opt
|
||||
code_sink_test.cpp
|
||||
combine_access_chains_test.cpp
|
||||
compact_ids_test.cpp
|
||||
constants_test.cpp
|
||||
constant_manager_test.cpp
|
||||
copy_prop_array_test.cpp
|
||||
dead_branch_elim_test.cpp
|
||||
@ -33,6 +34,7 @@ add_spvtools_unittest(TARGET opt
|
||||
decompose_initialized_variables_test.cpp
|
||||
decoration_manager_test.cpp
|
||||
def_use_test.cpp
|
||||
desc_sroa_test.cpp
|
||||
eliminate_dead_const_test.cpp
|
||||
eliminate_dead_functions_test.cpp
|
||||
eliminate_dead_member_test.cpp
|
||||
@ -44,6 +46,7 @@ add_spvtools_unittest(TARGET opt
|
||||
freeze_spec_const_test.cpp
|
||||
function_test.cpp
|
||||
generate_webgpu_initializers_test.cpp
|
||||
graphics_robust_access_test.cpp
|
||||
if_conversion_test.cpp
|
||||
inline_opaque_test.cpp
|
||||
inline_test.cpp
|
||||
|
@ -6616,6 +6616,152 @@ OpFunctionEnd
|
||||
SinglePassRunAndCheck<AggressiveDCEPass>(spirv, spirv, true);
|
||||
}
|
||||
|
||||
TEST_F(AggressiveDCETest, NoEliminateForwardPointer) {
|
||||
// clang-format off
|
||||
//
|
||||
// #version 450
|
||||
// #extension GL_EXT_buffer_reference : enable
|
||||
//
|
||||
// // forward reference
|
||||
// layout(buffer_reference) buffer blockType;
|
||||
//
|
||||
// layout(buffer_reference, std430, buffer_reference_align = 16) buffer blockType {
|
||||
// int x;
|
||||
// blockType next;
|
||||
// };
|
||||
//
|
||||
// layout(std430) buffer rootBlock {
|
||||
// blockType root;
|
||||
// } r;
|
||||
//
|
||||
// void main()
|
||||
// {
|
||||
// blockType b = r.root;
|
||||
// b = b.next;
|
||||
// b.x = 531;
|
||||
// }
|
||||
//
|
||||
// clang-format on
|
||||
|
||||
const std::string predefs1 =
|
||||
R"(OpCapability Shader
|
||||
OpCapability PhysicalStorageBufferAddressesEXT
|
||||
OpExtension "SPV_EXT_physical_storage_buffer"
|
||||
OpExtension "SPV_KHR_storage_buffer_storage_class"
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
|
||||
OpEntryPoint GLCompute %main "main"
|
||||
OpExecutionMode %main LocalSize 1 1 1
|
||||
OpSource GLSL 450
|
||||
OpSourceExtension "GL_EXT_buffer_reference"
|
||||
)";
|
||||
|
||||
const std::string names_before =
|
||||
R"(OpName %main "main"
|
||||
OpName %blockType "blockType"
|
||||
OpMemberName %blockType 0 "x"
|
||||
OpMemberName %blockType 1 "next"
|
||||
OpName %b "b"
|
||||
OpName %rootBlock "rootBlock"
|
||||
OpMemberName %rootBlock 0 "root"
|
||||
OpName %r "r"
|
||||
OpMemberDecorate %blockType 0 Offset 0
|
||||
OpMemberDecorate %blockType 1 Offset 8
|
||||
OpDecorate %blockType Block
|
||||
OpDecorate %b AliasedPointerEXT
|
||||
OpMemberDecorate %rootBlock 0 Offset 0
|
||||
OpDecorate %rootBlock Block
|
||||
OpDecorate %r DescriptorSet 0
|
||||
OpDecorate %r Binding 0
|
||||
)";
|
||||
|
||||
const std::string names_after =
|
||||
R"(OpName %main "main"
|
||||
OpName %blockType "blockType"
|
||||
OpMemberName %blockType 0 "x"
|
||||
OpMemberName %blockType 1 "next"
|
||||
OpName %rootBlock "rootBlock"
|
||||
OpMemberName %rootBlock 0 "root"
|
||||
OpName %r "r"
|
||||
OpMemberDecorate %blockType 0 Offset 0
|
||||
OpMemberDecorate %blockType 1 Offset 8
|
||||
OpDecorate %blockType Block
|
||||
OpMemberDecorate %rootBlock 0 Offset 0
|
||||
OpDecorate %rootBlock Block
|
||||
OpDecorate %r DescriptorSet 0
|
||||
OpDecorate %r Binding 0
|
||||
)";
|
||||
|
||||
const std::string predefs2_before =
|
||||
R"(%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
OpTypeForwardPointer %_ptr_PhysicalStorageBufferEXT_blockType PhysicalStorageBufferEXT
|
||||
%int = OpTypeInt 32 1
|
||||
%blockType = OpTypeStruct %int %_ptr_PhysicalStorageBufferEXT_blockType
|
||||
%_ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer PhysicalStorageBufferEXT %blockType
|
||||
%_ptr_Function__ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer Function %_ptr_PhysicalStorageBufferEXT_blockType
|
||||
%rootBlock = OpTypeStruct %_ptr_PhysicalStorageBufferEXT_blockType
|
||||
%_ptr_StorageBuffer_rootBlock = OpTypePointer StorageBuffer %rootBlock
|
||||
%r = OpVariable %_ptr_StorageBuffer_rootBlock StorageBuffer
|
||||
%int_0 = OpConstant %int 0
|
||||
%_ptr_StorageBuffer__ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBufferEXT_blockType
|
||||
%int_1 = OpConstant %int 1
|
||||
%_ptr_PhysicalStorageBufferEXT__ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer PhysicalStorageBufferEXT %_ptr_PhysicalStorageBufferEXT_blockType
|
||||
%int_531 = OpConstant %int 531
|
||||
%_ptr_PhysicalStorageBufferEXT_int = OpTypePointer PhysicalStorageBufferEXT %int
|
||||
)";
|
||||
|
||||
const std::string predefs2_after =
|
||||
R"(%void = OpTypeVoid
|
||||
%8 = OpTypeFunction %void
|
||||
OpTypeForwardPointer %_ptr_PhysicalStorageBufferEXT_blockType PhysicalStorageBufferEXT
|
||||
%int = OpTypeInt 32 1
|
||||
%blockType = OpTypeStruct %int %_ptr_PhysicalStorageBufferEXT_blockType
|
||||
%_ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer PhysicalStorageBufferEXT %blockType
|
||||
%rootBlock = OpTypeStruct %_ptr_PhysicalStorageBufferEXT_blockType
|
||||
%_ptr_StorageBuffer_rootBlock = OpTypePointer StorageBuffer %rootBlock
|
||||
%r = OpVariable %_ptr_StorageBuffer_rootBlock StorageBuffer
|
||||
%int_0 = OpConstant %int 0
|
||||
%_ptr_StorageBuffer__ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBufferEXT_blockType
|
||||
%int_1 = OpConstant %int 1
|
||||
%_ptr_PhysicalStorageBufferEXT__ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer PhysicalStorageBufferEXT %_ptr_PhysicalStorageBufferEXT_blockType
|
||||
%int_531 = OpConstant %int 531
|
||||
%_ptr_PhysicalStorageBufferEXT_int = OpTypePointer PhysicalStorageBufferEXT %int
|
||||
)";
|
||||
|
||||
const std::string func_before =
|
||||
R"(%main = OpFunction %void None %3
|
||||
%5 = OpLabel
|
||||
%b = OpVariable %_ptr_Function__ptr_PhysicalStorageBufferEXT_blockType Function
|
||||
%16 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBufferEXT_blockType %r %int_0
|
||||
%17 = OpLoad %_ptr_PhysicalStorageBufferEXT_blockType %16
|
||||
%21 = OpAccessChain %_ptr_PhysicalStorageBufferEXT__ptr_PhysicalStorageBufferEXT_blockType %17 %int_1
|
||||
%22 = OpLoad %_ptr_PhysicalStorageBufferEXT_blockType %21 Aligned 8
|
||||
OpStore %b %22
|
||||
%26 = OpAccessChain %_ptr_PhysicalStorageBufferEXT_int %22 %int_0
|
||||
OpStore %26 %int_531 Aligned 16
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string func_after =
|
||||
R"(%main = OpFunction %void None %8
|
||||
%19 = OpLabel
|
||||
%20 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBufferEXT_blockType %r %int_0
|
||||
%21 = OpLoad %_ptr_PhysicalStorageBufferEXT_blockType %20
|
||||
%22 = OpAccessChain %_ptr_PhysicalStorageBufferEXT__ptr_PhysicalStorageBufferEXT_blockType %21 %int_1
|
||||
%23 = OpLoad %_ptr_PhysicalStorageBufferEXT_blockType %22 Aligned 8
|
||||
%24 = OpAccessChain %_ptr_PhysicalStorageBufferEXT_int %23 %int_0
|
||||
OpStore %24 %int_531 Aligned 16
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndCheck<AggressiveDCEPass>(
|
||||
predefs1 + names_before + predefs2_before + func_before,
|
||||
predefs1 + names_after + predefs2_after + func_after, true, true);
|
||||
}
|
||||
|
||||
// TODO(greg-lunarg): Add tests to verify handling of these cases:
|
||||
//
|
||||
// Check that logical addressing required
|
||||
|
167
3rdparty/spirv-tools/test/opt/constants_test.cpp
vendored
Normal file
167
3rdparty/spirv-tools/test/opt/constants_test.cpp
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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/constants.h"
|
||||
|
||||
#include <gtest/gtest-param-test.h>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "source/opt/types.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
namespace analysis {
|
||||
namespace {
|
||||
|
||||
using ConstantTest = ::testing::Test;
|
||||
using ::testing::ValuesIn;
|
||||
|
||||
template <typename T>
|
||||
struct GetExtendedValueCase {
|
||||
bool is_signed;
|
||||
int width;
|
||||
std::vector<uint32_t> words;
|
||||
T expected_value;
|
||||
};
|
||||
|
||||
using GetSignExtendedValueCase = GetExtendedValueCase<int64_t>;
|
||||
using GetZeroExtendedValueCase = GetExtendedValueCase<uint64_t>;
|
||||
|
||||
using GetSignExtendedValueTest =
|
||||
::testing::TestWithParam<GetSignExtendedValueCase>;
|
||||
using GetZeroExtendedValueTest =
|
||||
::testing::TestWithParam<GetZeroExtendedValueCase>;
|
||||
|
||||
TEST_P(GetSignExtendedValueTest, Case) {
|
||||
Integer type(GetParam().width, GetParam().is_signed);
|
||||
IntConstant value(&type, GetParam().words);
|
||||
|
||||
EXPECT_EQ(GetParam().expected_value, value.GetSignExtendedValue());
|
||||
}
|
||||
|
||||
TEST_P(GetZeroExtendedValueTest, Case) {
|
||||
Integer type(GetParam().width, GetParam().is_signed);
|
||||
IntConstant value(&type, GetParam().words);
|
||||
|
||||
EXPECT_EQ(GetParam().expected_value, value.GetZeroExtendedValue());
|
||||
}
|
||||
|
||||
const uint32_t k32ones = ~uint32_t(0);
|
||||
const uint64_t k64ones = ~uint64_t(0);
|
||||
const int64_t kSBillion = 1000 * 1000 * 1000;
|
||||
const uint64_t kUBillion = 1000 * 1000 * 1000;
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(AtMost32Bits, GetSignExtendedValueTest,
|
||||
ValuesIn(std::vector<GetSignExtendedValueCase>{
|
||||
// 4 bits
|
||||
{false, 4, {0}, 0},
|
||||
{false, 4, {7}, 7},
|
||||
{false, 4, {15}, 15},
|
||||
{true, 4, {0}, 0},
|
||||
{true, 4, {7}, 7},
|
||||
{true, 4, {0xfffffff8}, -8},
|
||||
{true, 4, {k32ones}, -1},
|
||||
// 16 bits
|
||||
{false, 16, {0}, 0},
|
||||
{false, 16, {32767}, 32767},
|
||||
{false, 16, {32768}, 32768},
|
||||
{false, 16, {65000}, 65000},
|
||||
{true, 16, {0}, 0},
|
||||
{true, 16, {32767}, 32767},
|
||||
{true, 16, {0xfffffff8}, -8},
|
||||
{true, 16, {k32ones}, -1},
|
||||
// 32 bits
|
||||
{false, 32, {0}, 0},
|
||||
{false, 32, {1000000}, 1000000},
|
||||
{true, 32, {0xfffffff8}, -8},
|
||||
{true, 32, {k32ones}, -1},
|
||||
}));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(AtMost64Bits, GetSignExtendedValueTest,
|
||||
ValuesIn(std::vector<GetSignExtendedValueCase>{
|
||||
// 48 bits
|
||||
{false, 48, {0, 0}, 0},
|
||||
{false, 48, {5, 0}, 5},
|
||||
{false, 48, {0xfffffff8, k32ones}, -8},
|
||||
{false, 48, {k32ones, k32ones}, -1},
|
||||
{false, 48, {0xdcd65000, 1}, 8 * kSBillion},
|
||||
{true, 48, {0xfffffff8, k32ones}, -8},
|
||||
{true, 48, {k32ones, k32ones}, -1},
|
||||
{true, 48, {0xdcd65000, 1}, 8 * kSBillion},
|
||||
|
||||
// 64 bits
|
||||
{false, 64, {12, 0}, 12},
|
||||
{false, 64, {0xdcd65000, 1}, 8 * kSBillion},
|
||||
{false, 48, {0xfffffff8, k32ones}, -8},
|
||||
{false, 64, {k32ones, k32ones}, -1},
|
||||
{true, 64, {12, 0}, 12},
|
||||
{true, 64, {0xdcd65000, 1}, 8 * kSBillion},
|
||||
{true, 48, {0xfffffff8, k32ones}, -8},
|
||||
{true, 64, {k32ones, k32ones}, -1},
|
||||
}));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(AtMost32Bits, GetZeroExtendedValueTest,
|
||||
ValuesIn(std::vector<GetZeroExtendedValueCase>{
|
||||
// 4 bits
|
||||
{false, 4, {0}, 0},
|
||||
{false, 4, {7}, 7},
|
||||
{false, 4, {15}, 15},
|
||||
{true, 4, {0}, 0},
|
||||
{true, 4, {7}, 7},
|
||||
{true, 4, {0xfffffff8}, 0xfffffff8},
|
||||
{true, 4, {k32ones}, k32ones},
|
||||
// 16 bits
|
||||
{false, 16, {0}, 0},
|
||||
{false, 16, {32767}, 32767},
|
||||
{false, 16, {32768}, 32768},
|
||||
{false, 16, {65000}, 65000},
|
||||
{true, 16, {0}, 0},
|
||||
{true, 16, {32767}, 32767},
|
||||
{true, 16, {0xfffffff8}, 0xfffffff8},
|
||||
{true, 16, {k32ones}, k32ones},
|
||||
// 32 bits
|
||||
{false, 32, {0}, 0},
|
||||
{false, 32, {1000000}, 1000000},
|
||||
{true, 32, {0xfffffff8}, 0xfffffff8},
|
||||
{true, 32, {k32ones}, k32ones},
|
||||
}));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(AtMost64Bits, GetZeroExtendedValueTest,
|
||||
ValuesIn(std::vector<GetZeroExtendedValueCase>{
|
||||
// 48 bits
|
||||
{false, 48, {0, 0}, 0},
|
||||
{false, 48, {5, 0}, 5},
|
||||
{false, 48, {0xfffffff8, k32ones}, uint64_t(-8)},
|
||||
{false, 48, {k32ones, k32ones}, uint64_t(-1)},
|
||||
{false, 48, {0xdcd65000, 1}, 8 * kUBillion},
|
||||
{true, 48, {0xfffffff8, k32ones}, uint64_t(-8)},
|
||||
{true, 48, {k32ones, k32ones}, uint64_t(-1)},
|
||||
{true, 48, {0xdcd65000, 1}, 8 * kUBillion},
|
||||
|
||||
// 64 bits
|
||||
{false, 64, {12, 0}, 12},
|
||||
{false, 64, {0xdcd65000, 1}, 8 * kUBillion},
|
||||
{false, 48, {0xfffffff8, k32ones}, uint64_t(-8)},
|
||||
{false, 64, {k32ones, k32ones}, k64ones},
|
||||
{true, 64, {12, 0}, 12},
|
||||
{true, 64, {0xdcd65000, 1}, 8 * kUBillion},
|
||||
{true, 48, {0xfffffff8, k32ones}, uint64_t(-8)},
|
||||
{true, 64, {k32ones, k32ones}, k64ones},
|
||||
}));
|
||||
|
||||
} // namespace
|
||||
} // namespace analysis
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
@ -22,6 +22,7 @@
|
||||
#include "source/opt/decoration_manager.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/spirv_constant.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "test/unit_spirv.h"
|
||||
|
||||
namespace spvtools {
|
||||
@ -29,7 +30,7 @@ namespace opt {
|
||||
namespace analysis {
|
||||
namespace {
|
||||
|
||||
using spvtest::MakeVector;
|
||||
using utils::MakeVector;
|
||||
|
||||
class DecorationManagerTest : public ::testing::Test {
|
||||
public:
|
||||
|
209
3rdparty/spirv-tools/test/opt/desc_sroa_test.cpp
vendored
Normal file
209
3rdparty/spirv-tools/test/opt/desc_sroa_test.cpp
vendored
Normal file
@ -0,0 +1,209 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// 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 <string>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "test/opt/assembly_builder.h"
|
||||
#include "test/opt/pass_fixture.h"
|
||||
#include "test/opt/pass_utils.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
namespace {
|
||||
|
||||
using DescriptorScalarReplacementTest = PassTest<::testing::Test>;
|
||||
|
||||
TEST_F(DescriptorScalarReplacementTest, ExpandTexture) {
|
||||
const std::string text = R"(
|
||||
; CHECK: OpDecorate [[var1:%\w+]] DescriptorSet 0
|
||||
; CHECK: OpDecorate [[var1]] Binding 0
|
||||
; CHECK: OpDecorate [[var2:%\w+]] DescriptorSet 0
|
||||
; CHECK: OpDecorate [[var2]] Binding 1
|
||||
; CHECK: OpDecorate [[var3:%\w+]] DescriptorSet 0
|
||||
; CHECK: OpDecorate [[var3]] Binding 2
|
||||
; CHECK: OpDecorate [[var4:%\w+]] DescriptorSet 0
|
||||
; CHECK: OpDecorate [[var4]] Binding 3
|
||||
; CHECK: OpDecorate [[var5:%\w+]] DescriptorSet 0
|
||||
; CHECK: OpDecorate [[var5]] Binding 4
|
||||
; CHECK: [[image_type:%\w+]] = OpTypeImage
|
||||
; CHECK: [[ptr_type:%\w+]] = OpTypePointer UniformConstant [[image_type]]
|
||||
; CHECK: [[var1]] = OpVariable [[ptr_type]] UniformConstant
|
||||
; CHECK: [[var2]] = OpVariable [[ptr_type]] UniformConstant
|
||||
; CHECK: [[var3]] = OpVariable [[ptr_type]] UniformConstant
|
||||
; CHECK: [[var4]] = OpVariable [[ptr_type]] UniformConstant
|
||||
; CHECK: [[var5]] = OpVariable [[ptr_type]] UniformConstant
|
||||
; CHECK: OpLoad [[image_type]] [[var1]]
|
||||
; CHECK: OpLoad [[image_type]] [[var2]]
|
||||
; CHECK: OpLoad [[image_type]] [[var3]]
|
||||
; CHECK: OpLoad [[image_type]] [[var4]]
|
||||
; CHECK: OpLoad [[image_type]] [[var5]]
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main"
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpSource HLSL 600
|
||||
OpDecorate %MyTextures DescriptorSet 0
|
||||
OpDecorate %MyTextures Binding 0
|
||||
%int = OpTypeInt 32 1
|
||||
%int_0 = OpConstant %int 0
|
||||
%int_1 = OpConstant %int 1
|
||||
%int_2 = OpConstant %int 2
|
||||
%int_3 = OpConstant %int 3
|
||||
%int_4 = OpConstant %int 4
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_5 = OpConstant %uint 5
|
||||
%float = OpTypeFloat 32
|
||||
%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
|
||||
%_arr_type_2d_image_uint_5 = OpTypeArray %type_2d_image %uint_5
|
||||
%_ptr_UniformConstant__arr_type_2d_image_uint_5 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_5
|
||||
%v2float = OpTypeVector %float 2
|
||||
%void = OpTypeVoid
|
||||
%26 = OpTypeFunction %void
|
||||
%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
|
||||
%MyTextures = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_5 UniformConstant
|
||||
%main = OpFunction %void None %26
|
||||
%28 = OpLabel
|
||||
%29 = OpUndef %v2float
|
||||
%30 = OpAccessChain %_ptr_UniformConstant_type_2d_image %MyTextures %int_0
|
||||
%31 = OpLoad %type_2d_image %30
|
||||
%35 = OpAccessChain %_ptr_UniformConstant_type_2d_image %MyTextures %int_1
|
||||
%36 = OpLoad %type_2d_image %35
|
||||
%40 = OpAccessChain %_ptr_UniformConstant_type_2d_image %MyTextures %int_2
|
||||
%41 = OpLoad %type_2d_image %40
|
||||
%45 = OpAccessChain %_ptr_UniformConstant_type_2d_image %MyTextures %int_3
|
||||
%46 = OpLoad %type_2d_image %45
|
||||
%50 = OpAccessChain %_ptr_UniformConstant_type_2d_image %MyTextures %int_4
|
||||
%51 = OpLoad %type_2d_image %50
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
|
||||
)";
|
||||
|
||||
SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
|
||||
}
|
||||
|
||||
TEST_F(DescriptorScalarReplacementTest, ExpandSampler) {
|
||||
const std::string text = R"(
|
||||
; CHECK: OpDecorate [[var1:%\w+]] DescriptorSet 0
|
||||
; CHECK: OpDecorate [[var1]] Binding 1
|
||||
; CHECK: OpDecorate [[var2:%\w+]] DescriptorSet 0
|
||||
; CHECK: OpDecorate [[var2]] Binding 2
|
||||
; CHECK: OpDecorate [[var3:%\w+]] DescriptorSet 0
|
||||
; CHECK: OpDecorate [[var3]] Binding 3
|
||||
; CHECK: [[sampler_type:%\w+]] = OpTypeSampler
|
||||
; CHECK: [[ptr_type:%\w+]] = OpTypePointer UniformConstant [[sampler_type]]
|
||||
; CHECK: [[var1]] = OpVariable [[ptr_type]] UniformConstant
|
||||
; CHECK: [[var2]] = OpVariable [[ptr_type]] UniformConstant
|
||||
; CHECK: [[var3]] = OpVariable [[ptr_type]] UniformConstant
|
||||
; CHECK: OpLoad [[sampler_type]] [[var1]]
|
||||
; CHECK: OpLoad [[sampler_type]] [[var2]]
|
||||
; CHECK: OpLoad [[sampler_type]] [[var3]]
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main"
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpSource HLSL 600
|
||||
OpDecorate %MySampler DescriptorSet 0
|
||||
OpDecorate %MySampler Binding 1
|
||||
%int = OpTypeInt 32 1
|
||||
%int_0 = OpConstant %int 0
|
||||
%int_1 = OpConstant %int 1
|
||||
%int_2 = OpConstant %int 2
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_3 = OpConstant %uint 3
|
||||
%type_sampler = OpTypeSampler
|
||||
%_arr_type_sampler_uint_3 = OpTypeArray %type_sampler %uint_3
|
||||
%_ptr_UniformConstant__arr_type_sampler_uint_3 = OpTypePointer UniformConstant %_arr_type_sampler_uint_3
|
||||
%void = OpTypeVoid
|
||||
%26 = OpTypeFunction %void
|
||||
%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
|
||||
%MySampler = OpVariable %_ptr_UniformConstant__arr_type_sampler_uint_3 UniformConstant
|
||||
%main = OpFunction %void None %26
|
||||
%28 = OpLabel
|
||||
%31 = OpAccessChain %_ptr_UniformConstant_type_sampler %MySampler %int_0
|
||||
%32 = OpLoad %type_sampler %31
|
||||
%35 = OpAccessChain %_ptr_UniformConstant_type_sampler %MySampler %int_1
|
||||
%36 = OpLoad %type_sampler %35
|
||||
%40 = OpAccessChain %_ptr_UniformConstant_type_sampler %MySampler %int_2
|
||||
%41 = OpLoad %type_sampler %40
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
|
||||
}
|
||||
|
||||
TEST_F(DescriptorScalarReplacementTest, ExpandSSBO) {
|
||||
// Tests the expansion of an SSBO. Also check that an access chain with more
|
||||
// than 1 index is correctly handled.
|
||||
const std::string text = R"(
|
||||
; CHECK: OpDecorate [[var1:%\w+]] DescriptorSet 0
|
||||
; CHECK: OpDecorate [[var1]] Binding 0
|
||||
; CHECK: OpDecorate [[var2:%\w+]] DescriptorSet 0
|
||||
; CHECK: OpDecorate [[var2]] Binding 1
|
||||
; CHECK: OpTypeStruct
|
||||
; CHECK: [[struct_type:%\w+]] = OpTypeStruct
|
||||
; CHECK: [[ptr_type:%\w+]] = OpTypePointer Uniform [[struct_type]]
|
||||
; CHECK: [[var1]] = OpVariable [[ptr_type]] Uniform
|
||||
; CHECK: [[var2]] = OpVariable [[ptr_type]] Uniform
|
||||
; CHECK: [[ac1:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[var1]] %uint_0 %uint_0 %uint_0
|
||||
; CHECK: OpLoad %v4float [[ac1]]
|
||||
; CHECK: [[ac2:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[var2]] %uint_0 %uint_0 %uint_0
|
||||
; CHECK: OpLoad %v4float [[ac2]]
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main"
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpSource HLSL 600
|
||||
OpDecorate %buffers DescriptorSet 0
|
||||
OpDecorate %buffers Binding 0
|
||||
OpMemberDecorate %S 0 Offset 0
|
||||
OpDecorate %_runtimearr_S ArrayStride 16
|
||||
OpMemberDecorate %type_StructuredBuffer_S 0 Offset 0
|
||||
OpMemberDecorate %type_StructuredBuffer_S 0 NonWritable
|
||||
OpDecorate %type_StructuredBuffer_S BufferBlock
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_0 = OpConstant %uint 0
|
||||
%uint_1 = OpConstant %uint 1
|
||||
%uint_2 = OpConstant %uint 2
|
||||
%float = OpTypeFloat 32
|
||||
%v4float = OpTypeVector %float 4
|
||||
%S = OpTypeStruct %v4float
|
||||
%_runtimearr_S = OpTypeRuntimeArray %S
|
||||
%type_StructuredBuffer_S = OpTypeStruct %_runtimearr_S
|
||||
%_arr_type_StructuredBuffer_S_uint_2 = OpTypeArray %type_StructuredBuffer_S %uint_2
|
||||
%_ptr_Uniform__arr_type_StructuredBuffer_S_uint_2 = OpTypePointer Uniform %_arr_type_StructuredBuffer_S_uint_2
|
||||
%_ptr_Uniform_type_StructuredBuffer_S = OpTypePointer Uniform %type_StructuredBuffer_S
|
||||
%void = OpTypeVoid
|
||||
%19 = OpTypeFunction %void
|
||||
%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
|
||||
%buffers = OpVariable %_ptr_Uniform__arr_type_StructuredBuffer_S_uint_2 Uniform
|
||||
%main = OpFunction %void None %19
|
||||
%21 = OpLabel
|
||||
%22 = OpAccessChain %_ptr_Uniform_v4float %buffers %uint_0 %uint_0 %uint_0 %uint_0
|
||||
%23 = OpLoad %v4float %22
|
||||
%24 = OpAccessChain %_ptr_Uniform_type_StructuredBuffer_S %buffers %uint_1
|
||||
%25 = OpAccessChain %_ptr_Uniform_v4float %24 %uint_0 %uint_0 %uint_0
|
||||
%26 = OpLoad %v4float %25
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndMatch<DescriptorScalarReplacement>(text, true);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
15
3rdparty/spirv-tools/test/opt/fold_test.cpp
vendored
15
3rdparty/spirv-tools/test/opt/fold_test.cpp
vendored
@ -11,6 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "source/opt/fold.h"
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@ -22,7 +24,6 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "source/opt/build_module.h"
|
||||
#include "source/opt/def_use_manager.h"
|
||||
#include "source/opt/fold.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/opt/module.h"
|
||||
#include "spirv-tools/libspirv.hpp"
|
||||
@ -2980,7 +2981,17 @@ INSTANTIATE_TEST_SUITE_P(CompositeExtractFoldingTest, GeneralInstructionFoldingT
|
||||
"%4 = OpCompositeExtract %int %3 0\n" +
|
||||
"OpReturn\n" +
|
||||
"OpFunctionEnd",
|
||||
4, INT_7_ID)
|
||||
4, INT_7_ID),
|
||||
// Test case 13: https://github.com/KhronosGroup/SPIRV-Tools/issues/2608
|
||||
// Out of bounds access. Do not fold.
|
||||
InstructionFoldingCase<uint32_t>(
|
||||
Header() + "%main = OpFunction %void None %void_func\n" +
|
||||
"%main_lab = OpLabel\n" +
|
||||
"%2 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1\n" +
|
||||
"%3 = OpCompositeExtract %float %2 4\n" +
|
||||
"OpReturn\n" +
|
||||
"OpFunctionEnd",
|
||||
3, 0)
|
||||
));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest,
|
||||
|
1307
3rdparty/spirv-tools/test/opt/graphics_robust_access_test.cpp
vendored
Normal file
1307
3rdparty/spirv-tools/test/opt/graphics_robust_access_test.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -5706,6 +5706,282 @@ OpFunctionEnd
|
||||
true, 7u, 23u, false, false, 2u);
|
||||
}
|
||||
|
||||
TEST_F(InstBindlessTest, InstrumentTeseSimpleV2) {
|
||||
// This test verifies that the pass will correctly instrument tessellation
|
||||
// evaluation shader doing bindless buffer load.
|
||||
//
|
||||
// clang-format off
|
||||
//
|
||||
// #version 450
|
||||
// #extension GL_EXT_nonuniform_qualifier : enable
|
||||
//
|
||||
// layout(std140, set = 0, binding = 0) uniform ufoo { uint index; } uniform_index_buffer;
|
||||
//
|
||||
// layout(set = 0, binding = 1) buffer bfoo { vec4 val; } adds[11];
|
||||
//
|
||||
// layout(triangles, equal_spacing, cw) in;
|
||||
//
|
||||
// void main() {
|
||||
// gl_Position = adds[uniform_index_buffer.index].val;
|
||||
// }
|
||||
//
|
||||
// clang-format on
|
||||
|
||||
const std::string defs_before =
|
||||
R"(OpCapability Tessellation
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint TessellationEvaluation %main "main" %_
|
||||
OpExecutionMode %main Triangles
|
||||
OpExecutionMode %main SpacingEqual
|
||||
OpExecutionMode %main VertexOrderCw
|
||||
OpSource GLSL 450
|
||||
OpSourceExtension "GL_EXT_nonuniform_qualifier"
|
||||
OpName %main "main"
|
||||
OpName %gl_PerVertex "gl_PerVertex"
|
||||
OpMemberName %gl_PerVertex 0 "gl_Position"
|
||||
OpMemberName %gl_PerVertex 1 "gl_PointSize"
|
||||
OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
|
||||
OpMemberName %gl_PerVertex 3 "gl_CullDistance"
|
||||
OpName %_ ""
|
||||
OpName %bfoo "bfoo"
|
||||
OpMemberName %bfoo 0 "val"
|
||||
OpName %adds "adds"
|
||||
OpName %ufoo "ufoo"
|
||||
OpMemberName %ufoo 0 "index"
|
||||
OpName %uniform_index_buffer "uniform_index_buffer"
|
||||
OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
|
||||
OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
|
||||
OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
|
||||
OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
|
||||
OpDecorate %gl_PerVertex Block
|
||||
OpMemberDecorate %bfoo 0 Offset 0
|
||||
OpDecorate %bfoo Block
|
||||
OpDecorate %adds DescriptorSet 0
|
||||
OpDecorate %adds Binding 1
|
||||
OpMemberDecorate %ufoo 0 Offset 0
|
||||
OpDecorate %ufoo Block
|
||||
OpDecorate %uniform_index_buffer DescriptorSet 0
|
||||
OpDecorate %uniform_index_buffer Binding 0
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%v4float = OpTypeVector %float 4
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_1 = OpConstant %uint 1
|
||||
%_arr_float_uint_1 = OpTypeArray %float %uint_1
|
||||
%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
|
||||
%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
|
||||
%_ = OpVariable %_ptr_Output_gl_PerVertex Output
|
||||
%int = OpTypeInt 32 1
|
||||
%int_0 = OpConstant %int 0
|
||||
%bfoo = OpTypeStruct %v4float
|
||||
%uint_11 = OpConstant %uint 11
|
||||
%_arr_bfoo_uint_11 = OpTypeArray %bfoo %uint_11
|
||||
%_ptr_StorageBuffer__arr_bfoo_uint_11 = OpTypePointer StorageBuffer %_arr_bfoo_uint_11
|
||||
%adds = OpVariable %_ptr_StorageBuffer__arr_bfoo_uint_11 StorageBuffer
|
||||
%ufoo = OpTypeStruct %uint
|
||||
%_ptr_Uniform_ufoo = OpTypePointer Uniform %ufoo
|
||||
%uniform_index_buffer = OpVariable %_ptr_Uniform_ufoo Uniform
|
||||
%_ptr_Uniform_uint = OpTypePointer Uniform %uint
|
||||
%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float
|
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float
|
||||
)";
|
||||
|
||||
const std::string defs_after =
|
||||
R"(OpCapability Tessellation
|
||||
OpExtension "SPV_KHR_storage_buffer_storage_class"
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint TessellationEvaluation %main "main" %_ %gl_PrimitiveID %gl_TessCoord
|
||||
OpExecutionMode %main Triangles
|
||||
OpExecutionMode %main SpacingEqual
|
||||
OpExecutionMode %main VertexOrderCw
|
||||
OpSource GLSL 450
|
||||
OpSourceExtension "GL_EXT_nonuniform_qualifier"
|
||||
OpName %main "main"
|
||||
OpName %gl_PerVertex "gl_PerVertex"
|
||||
OpMemberName %gl_PerVertex 0 "gl_Position"
|
||||
OpMemberName %gl_PerVertex 1 "gl_PointSize"
|
||||
OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
|
||||
OpMemberName %gl_PerVertex 3 "gl_CullDistance"
|
||||
OpName %_ ""
|
||||
OpName %bfoo "bfoo"
|
||||
OpMemberName %bfoo 0 "val"
|
||||
OpName %adds "adds"
|
||||
OpName %ufoo "ufoo"
|
||||
OpMemberName %ufoo 0 "index"
|
||||
OpName %uniform_index_buffer "uniform_index_buffer"
|
||||
OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
|
||||
OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
|
||||
OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
|
||||
OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
|
||||
OpDecorate %gl_PerVertex Block
|
||||
OpMemberDecorate %bfoo 0 Offset 0
|
||||
OpDecorate %bfoo Block
|
||||
OpDecorate %adds DescriptorSet 0
|
||||
OpDecorate %adds Binding 1
|
||||
OpMemberDecorate %ufoo 0 Offset 0
|
||||
OpDecorate %ufoo Block
|
||||
OpDecorate %uniform_index_buffer DescriptorSet 0
|
||||
OpDecorate %uniform_index_buffer Binding 0
|
||||
OpDecorate %_runtimearr_uint ArrayStride 4
|
||||
OpDecorate %_struct_47 Block
|
||||
OpMemberDecorate %_struct_47 0 Offset 0
|
||||
OpMemberDecorate %_struct_47 1 Offset 4
|
||||
OpDecorate %49 DescriptorSet 7
|
||||
OpDecorate %49 Binding 0
|
||||
OpDecorate %gl_PrimitiveID BuiltIn PrimitiveId
|
||||
OpDecorate %gl_TessCoord BuiltIn TessCoord
|
||||
%void = OpTypeVoid
|
||||
%10 = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%v4float = OpTypeVector %float 4
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_1 = OpConstant %uint 1
|
||||
%_arr_float_uint_1 = OpTypeArray %float %uint_1
|
||||
%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
|
||||
%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
|
||||
%_ = OpVariable %_ptr_Output_gl_PerVertex Output
|
||||
%int = OpTypeInt 32 1
|
||||
%int_0 = OpConstant %int 0
|
||||
%bfoo = OpTypeStruct %v4float
|
||||
%uint_11 = OpConstant %uint 11
|
||||
%_arr_bfoo_uint_11 = OpTypeArray %bfoo %uint_11
|
||||
%_ptr_StorageBuffer__arr_bfoo_uint_11 = OpTypePointer StorageBuffer %_arr_bfoo_uint_11
|
||||
%adds = OpVariable %_ptr_StorageBuffer__arr_bfoo_uint_11 StorageBuffer
|
||||
%ufoo = OpTypeStruct %uint
|
||||
%_ptr_Uniform_ufoo = OpTypePointer Uniform %ufoo
|
||||
%uniform_index_buffer = OpVariable %_ptr_Uniform_ufoo Uniform
|
||||
%_ptr_Uniform_uint = OpTypePointer Uniform %uint
|
||||
%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float
|
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float
|
||||
%uint_0 = OpConstant %uint 0
|
||||
%bool = OpTypeBool
|
||||
%40 = OpTypeFunction %void %uint %uint %uint %uint
|
||||
%_runtimearr_uint = OpTypeRuntimeArray %uint
|
||||
%_struct_47 = OpTypeStruct %uint %_runtimearr_uint
|
||||
%_ptr_StorageBuffer__struct_47 = OpTypePointer StorageBuffer %_struct_47
|
||||
%49 = OpVariable %_ptr_StorageBuffer__struct_47 StorageBuffer
|
||||
%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
|
||||
%uint_10 = OpConstant %uint 10
|
||||
%uint_4 = OpConstant %uint 4
|
||||
%uint_23 = OpConstant %uint 23
|
||||
%uint_2 = OpConstant %uint 2
|
||||
%uint_3 = OpConstant %uint 3
|
||||
%_ptr_Input_uint = OpTypePointer Input %uint
|
||||
%gl_PrimitiveID = OpVariable %_ptr_Input_uint Input
|
||||
%v3float = OpTypeVector %float 3
|
||||
%_ptr_Input_v3float = OpTypePointer Input %v3float
|
||||
%gl_TessCoord = OpVariable %_ptr_Input_v3float Input
|
||||
%v3uint = OpTypeVector %uint 3
|
||||
%uint_5 = OpConstant %uint 5
|
||||
%uint_6 = OpConstant %uint 6
|
||||
%uint_7 = OpConstant %uint 7
|
||||
%uint_8 = OpConstant %uint 8
|
||||
%uint_9 = OpConstant %uint 9
|
||||
%uint_63 = OpConstant %uint 63
|
||||
%101 = OpConstantNull %v4float
|
||||
)";
|
||||
|
||||
const std::string func_before =
|
||||
R"(%main = OpFunction %void None %3
|
||||
%5 = OpLabel
|
||||
%25 = OpAccessChain %_ptr_Uniform_uint %uniform_index_buffer %int_0
|
||||
%26 = OpLoad %uint %25
|
||||
%28 = OpAccessChain %_ptr_StorageBuffer_v4float %adds %26 %int_0
|
||||
%29 = OpLoad %v4float %28
|
||||
%31 = OpAccessChain %_ptr_Output_v4float %_ %int_0
|
||||
OpStore %31 %29
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string func_after =
|
||||
R"(%main = OpFunction %void None %10
|
||||
%26 = OpLabel
|
||||
%27 = OpAccessChain %_ptr_Uniform_uint %uniform_index_buffer %int_0
|
||||
%28 = OpLoad %uint %27
|
||||
%29 = OpAccessChain %_ptr_StorageBuffer_v4float %adds %28 %int_0
|
||||
%34 = OpULessThan %bool %28 %uint_11
|
||||
OpSelectionMerge %35 None
|
||||
OpBranchConditional %34 %36 %37
|
||||
%36 = OpLabel
|
||||
%38 = OpLoad %v4float %29
|
||||
OpBranch %35
|
||||
%37 = OpLabel
|
||||
%100 = OpFunctionCall %void %39 %uint_63 %uint_0 %28 %uint_11
|
||||
OpBranch %35
|
||||
%35 = OpLabel
|
||||
%102 = OpPhi %v4float %38 %36 %101 %37
|
||||
%31 = OpAccessChain %_ptr_Output_v4float %_ %int_0
|
||||
OpStore %31 %102
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string output_func =
|
||||
R"(%39 = OpFunction %void None %40
|
||||
%41 = OpFunctionParameter %uint
|
||||
%42 = OpFunctionParameter %uint
|
||||
%43 = OpFunctionParameter %uint
|
||||
%44 = OpFunctionParameter %uint
|
||||
%45 = OpLabel
|
||||
%51 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_0
|
||||
%54 = OpAtomicIAdd %uint %51 %uint_4 %uint_0 %uint_10
|
||||
%55 = OpIAdd %uint %54 %uint_10
|
||||
%56 = OpArrayLength %uint %49 1
|
||||
%57 = OpULessThanEqual %bool %55 %56
|
||||
OpSelectionMerge %58 None
|
||||
OpBranchConditional %57 %59 %58
|
||||
%59 = OpLabel
|
||||
%60 = OpIAdd %uint %54 %uint_0
|
||||
%61 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %60
|
||||
OpStore %61 %uint_10
|
||||
%63 = OpIAdd %uint %54 %uint_1
|
||||
%64 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %63
|
||||
OpStore %64 %uint_23
|
||||
%66 = OpIAdd %uint %54 %uint_2
|
||||
%67 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %66
|
||||
OpStore %67 %41
|
||||
%69 = OpIAdd %uint %54 %uint_3
|
||||
%70 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %69
|
||||
OpStore %70 %uint_2
|
||||
%73 = OpLoad %uint %gl_PrimitiveID
|
||||
%74 = OpIAdd %uint %54 %uint_4
|
||||
%75 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %74
|
||||
OpStore %75 %73
|
||||
%79 = OpLoad %v3float %gl_TessCoord
|
||||
%81 = OpBitcast %v3uint %79
|
||||
%82 = OpCompositeExtract %uint %81 0
|
||||
%83 = OpCompositeExtract %uint %81 1
|
||||
%85 = OpIAdd %uint %54 %uint_5
|
||||
%86 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %85
|
||||
OpStore %86 %82
|
||||
%88 = OpIAdd %uint %54 %uint_6
|
||||
%89 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %88
|
||||
OpStore %89 %83
|
||||
%91 = OpIAdd %uint %54 %uint_7
|
||||
%92 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %91
|
||||
OpStore %92 %42
|
||||
%94 = OpIAdd %uint %54 %uint_8
|
||||
%95 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %94
|
||||
OpStore %95 %43
|
||||
%97 = OpIAdd %uint %54 %uint_9
|
||||
%98 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %97
|
||||
OpStore %98 %44
|
||||
OpBranch %58
|
||||
%58 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
// SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
SinglePassRunAndCheck<InstBindlessCheckPass>(
|
||||
defs_before + func_before, defs_after + func_after + output_func, true,
|
||||
true, 7u, 23u, false, false, 2u);
|
||||
}
|
||||
|
||||
TEST_F(InstBindlessTest, MultipleDebugFunctionsV2) {
|
||||
// Same source as Simple, but compiled -g and not optimized, especially not
|
||||
// inlined. The OpSource has had the source extracted for the sake of brevity.
|
||||
|
26
3rdparty/spirv-tools/test/opt/pass_fixture.h
vendored
26
3rdparty/spirv-tools/test/opt/pass_fixture.h
vendored
@ -188,6 +188,32 @@ class PassTest : public TestT {
|
||||
<< disassembly;
|
||||
}
|
||||
|
||||
// Runs a single pass of class |PassT| on the binary assembled from the
|
||||
// |original| assembly. Check for failure and expect an Effcee matcher
|
||||
// to pass when run on the diagnostic messages. This does *not* involve
|
||||
// pass manager. Callers are suggested to use SCOPED_TRACE() for better
|
||||
// messages.
|
||||
template <typename PassT, typename... Args>
|
||||
void SinglePassRunAndFail(const std::string& original, Args&&... args) {
|
||||
context_ =
|
||||
std::move(BuildModule(env_, consumer_, original, assemble_options_));
|
||||
EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n"
|
||||
<< original << std::endl;
|
||||
std::ostringstream errs;
|
||||
auto error_consumer = [&errs](spv_message_level_t, const char*,
|
||||
const spv_position_t&, const char* message) {
|
||||
errs << message << std::endl;
|
||||
};
|
||||
auto pass = MakeUnique<PassT>(std::forward<Args>(args)...);
|
||||
pass->SetMessageConsumer(error_consumer);
|
||||
const auto status = pass->Run(context());
|
||||
EXPECT_EQ(Pass::Status::Failure, status);
|
||||
auto match_result = effcee::Match(errs.str(), original);
|
||||
EXPECT_EQ(effcee::Result::Status::Ok, match_result.status())
|
||||
<< match_result.message() << "\nChecking messages:\n"
|
||||
<< errs.str();
|
||||
}
|
||||
|
||||
// Adds a pass to be run.
|
||||
template <typename PassT, typename... Args>
|
||||
void AddPass(Args&&... args) {
|
||||
|
@ -1779,6 +1779,7 @@ TEST_F(MergeReturnPassTest, MergeToMergeBranch) {
|
||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
SinglePassRunAndMatch<MergeReturnPass>(text, true);
|
||||
}
|
||||
|
||||
TEST_F(MergeReturnPassTest, PhiInSecondMerge) {
|
||||
// Add and use a phi in the second merge block from the return.
|
||||
const std::string text =
|
||||
@ -1831,6 +1832,50 @@ TEST_F(MergeReturnPassTest, PhiInSecondMerge) {
|
||||
SinglePassRunAndMatch<MergeReturnPass>(text, true);
|
||||
}
|
||||
|
||||
TEST_F(MergeReturnPassTest, UnreachableMergeAndContinue) {
|
||||
// Make sure that the pass can handle a single block that is both a merge and
|
||||
// a continue.
|
||||
const std::string text =
|
||||
R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main"
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
%void = OpTypeVoid
|
||||
%4 = OpTypeFunction %void
|
||||
%bool = OpTypeBool
|
||||
%true = OpConstantTrue %bool
|
||||
%2 = OpFunction %void None %4
|
||||
%7 = OpLabel
|
||||
OpBranch %8
|
||||
%8 = OpLabel
|
||||
OpLoopMerge %9 %10 None
|
||||
OpBranch %11
|
||||
%11 = OpLabel
|
||||
OpSelectionMerge %10 None
|
||||
OpBranchConditional %true %12 %13
|
||||
%12 = OpLabel
|
||||
OpReturn
|
||||
%13 = OpLabel
|
||||
OpReturn
|
||||
%10 = OpLabel
|
||||
OpBranch %8
|
||||
%9 = OpLabel
|
||||
OpUnreachable
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
auto result = SinglePassRunAndDisassemble<MergeReturnPass>(text, true, true);
|
||||
|
||||
// Not looking for any particular output. Other tests do that.
|
||||
// Just want to make sure the check for unreachable blocks does not emit an
|
||||
// error.
|
||||
EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
@ -1657,6 +1657,153 @@ OpFunctionEnd
|
||||
EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
|
||||
}
|
||||
|
||||
// Test that replacements for OpAccessChain do not go out of bounds.
|
||||
// https://github.com/KhronosGroup/SPIRV-Tools/issues/2609.
|
||||
TEST_F(ScalarReplacementTest, OutOfBoundOpAccessChain) {
|
||||
const std::string text = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main" %_GLF_color
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %main "main"
|
||||
OpName %a "a"
|
||||
OpName %_GLF_color "_GLF_color"
|
||||
OpDecorate %_GLF_color Location 0
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Function_int = OpTypePointer Function %int
|
||||
%int_1 = OpConstant %int 1
|
||||
%float = OpTypeFloat 32
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_1 = OpConstant %uint 1
|
||||
%_arr_float_uint_1 = OpTypeArray %float %uint_1
|
||||
%_ptr_Function__arr_float_uint_1 = OpTypePointer Function %_arr_float_uint_1
|
||||
%_ptr_Function_float = OpTypePointer Function %float
|
||||
%_ptr_Output_float = OpTypePointer Output %float
|
||||
%_GLF_color = OpVariable %_ptr_Output_float Output
|
||||
%main = OpFunction %void None %3
|
||||
%5 = OpLabel
|
||||
%a = OpVariable %_ptr_Function__arr_float_uint_1 Function
|
||||
%21 = OpAccessChain %_ptr_Function_float %a %int_1
|
||||
%22 = OpLoad %float %21
|
||||
OpStore %_GLF_color %22
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
|
||||
auto result =
|
||||
SinglePassRunAndDisassemble<ScalarReplacementPass>(text, true, false);
|
||||
EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
|
||||
}
|
||||
|
||||
TEST_F(ScalarReplacementTest, CharIndex) {
|
||||
const std::string text = R"(
|
||||
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
|
||||
; CHECK: [[ptr:%\w+]] = OpTypePointer Function [[int]]
|
||||
; CHECK: OpVariable [[ptr]] Function
|
||||
OpCapability Shader
|
||||
OpCapability Int8
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %main "main"
|
||||
OpExecutionMode %main LocalSize 1 1 1
|
||||
%void = OpTypeVoid
|
||||
%int = OpTypeInt 32 0
|
||||
%int_1024 = OpConstant %int 1024
|
||||
%char = OpTypeInt 8 0
|
||||
%char_1 = OpConstant %char 1
|
||||
%array = OpTypeArray %int %int_1024
|
||||
%ptr_func_array = OpTypePointer Function %array
|
||||
%ptr_func_int = OpTypePointer Function %int
|
||||
%void_fn = OpTypeFunction %void
|
||||
%main = OpFunction %void None %void_fn
|
||||
%entry = OpLabel
|
||||
%var = OpVariable %ptr_func_array Function
|
||||
%gep = OpAccessChain %ptr_func_int %var %char_1
|
||||
OpStore %gep %int_1024
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndMatch<ScalarReplacementPass>(text, true, 0);
|
||||
}
|
||||
|
||||
TEST_F(ScalarReplacementTest, OutOfBoundsOpAccessChainNegative) {
|
||||
const std::string text = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Int8
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %main "main"
|
||||
OpExecutionMode %main LocalSize 1 1 1
|
||||
%void = OpTypeVoid
|
||||
%int = OpTypeInt 32 0
|
||||
%int_1024 = OpConstant %int 1024
|
||||
%char = OpTypeInt 8 1
|
||||
%char_n1 = OpConstant %char -1
|
||||
%array = OpTypeArray %int %int_1024
|
||||
%ptr_func_array = OpTypePointer Function %array
|
||||
%ptr_func_int = OpTypePointer Function %int
|
||||
%void_fn = OpTypeFunction %void
|
||||
%main = OpFunction %void None %void_fn
|
||||
%entry = OpLabel
|
||||
%var = OpVariable %ptr_func_array Function
|
||||
%gep = OpAccessChain %ptr_func_int %var %char_n1
|
||||
OpStore %gep %int_1024
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
auto result =
|
||||
SinglePassRunAndDisassemble<ScalarReplacementPass>(text, true, true, 0);
|
||||
EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
|
||||
}
|
||||
|
||||
TEST_F(ScalarReplacementTest, RelaxedPrecisionMemberDecoration) {
|
||||
const std::string text = R"(
|
||||
; CHECK: OpDecorate {{%\w+}} RelaxedPrecision
|
||||
; CHECK: OpDecorate [[new_var:%\w+]] RelaxedPrecision
|
||||
; CHECK: [[new_var]] = OpVariable %_ptr_Function_v3float Function
|
||||
; CHECK: OpLoad %v3float [[new_var]]
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Vertex %1 "Draw2DTexCol_VS" %2 %3
|
||||
OpSource HLSL 600
|
||||
OpDecorate %2 Location 0
|
||||
OpDecorate %3 Location 1
|
||||
OpDecorate %3 RelaxedPrecision
|
||||
OpMemberDecorate %_struct_4 1 RelaxedPrecision
|
||||
%float = OpTypeFloat 32
|
||||
%int = OpTypeInt 32 1
|
||||
%int_1 = OpConstant %int 1
|
||||
%v3float = OpTypeVector %float 3
|
||||
%_ptr_Input_v3float = OpTypePointer Input %v3float
|
||||
%void = OpTypeVoid
|
||||
%11 = OpTypeFunction %void
|
||||
%_struct_4 = OpTypeStruct %v3float %v3float
|
||||
%_ptr_Function__struct_4 = OpTypePointer Function %_struct_4
|
||||
%_ptr_Function_v3float = OpTypePointer Function %v3float
|
||||
%2 = OpVariable %_ptr_Input_v3float Input
|
||||
%3 = OpVariable %_ptr_Input_v3float Input
|
||||
%1 = OpFunction %void None %11
|
||||
%14 = OpLabel
|
||||
%15 = OpVariable %_ptr_Function__struct_4 Function
|
||||
%16 = OpLoad %v3float %2
|
||||
%17 = OpLoad %v3float %3
|
||||
%18 = OpCompositeConstruct %_struct_4 %16 %17
|
||||
OpStore %15 %18
|
||||
%19 = OpAccessChain %_ptr_Function_v3float %15 %int_1
|
||||
%20 = OpLoad %v3float %19
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
@ -279,6 +279,52 @@ OpFunctionEnd
|
||||
SinglePassRunAndCheck<SimplificationPass>(before, after, false);
|
||||
}
|
||||
|
||||
TEST_F(SimplificationTest, DontMoveDecorations) {
|
||||
const std::string spirv = R"(
|
||||
; CHECK-NOT: RelaxedPrecision
|
||||
; CHECK: [[sub:%\w+]] = OpFSub
|
||||
; CHECK: OpStore {{.*}} [[sub]]
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %main "main"
|
||||
OpExecutionMode %main LocalSize 1 1 1
|
||||
OpDecorate %add RelaxedPrecision
|
||||
OpDecorate %block Block
|
||||
OpMemberDecorate %block 0 Offset 0
|
||||
OpMemberDecorate %block 1 Offset 4
|
||||
OpDecorate %in DescriptorSet 0
|
||||
OpDecorate %in Binding 0
|
||||
OpDecorate %out DescriptorSet 0
|
||||
OpDecorate %out Binding 1
|
||||
%void = OpTypeVoid
|
||||
%float = OpTypeFloat 32
|
||||
%void_fn = OpTypeFunction %void
|
||||
%block = OpTypeStruct %float %float
|
||||
%ptr_ssbo_block = OpTypePointer StorageBuffer %block
|
||||
%in = OpVariable %ptr_ssbo_block StorageBuffer
|
||||
%out = OpVariable %ptr_ssbo_block StorageBuffer
|
||||
%ptr_ssbo_float = OpTypePointer StorageBuffer %float
|
||||
%int = OpTypeInt 32 0
|
||||
%int_0 = OpConstant %int 0
|
||||
%int_1 = OpConstant %int 1
|
||||
%float_0 = OpConstant %float 0
|
||||
%main = OpFunction %void None %void_fn
|
||||
%entry = OpLabel
|
||||
%in_gep_0 = OpAccessChain %ptr_ssbo_float %in %int_0
|
||||
%in_gep_1 = OpAccessChain %ptr_ssbo_float %in %int_1
|
||||
%load_0 = OpLoad %float %in_gep_0
|
||||
%load_1 = OpLoad %float %in_gep_1
|
||||
%sub = OpFSub %float %load_0 %load_1
|
||||
%add = OpFAdd %float %float_0 %sub
|
||||
%out_gep_0 = OpAccessChain %ptr_ssbo_float %out %int_0
|
||||
OpStore %out_gep_0 %add
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndMatch<SimplificationPass>(spirv, true);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "test/test_fixture.h"
|
||||
#include "test/unit_spirv.h"
|
||||
|
||||
@ -29,7 +30,7 @@ namespace {
|
||||
|
||||
using spvtest::EnumCase;
|
||||
using spvtest::MakeInstruction;
|
||||
using spvtest::MakeVector;
|
||||
using utils::MakeVector;
|
||||
using spvtest::TextToBinaryTest;
|
||||
using ::testing::Combine;
|
||||
using ::testing::Eq;
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "test/test_fixture.h"
|
||||
#include "test/unit_spirv.h"
|
||||
|
||||
@ -26,7 +27,7 @@ namespace spvtools {
|
||||
namespace {
|
||||
|
||||
using spvtest::MakeInstruction;
|
||||
using spvtest::MakeVector;
|
||||
using utils::MakeVector;
|
||||
using spvtest::TextToBinaryTest;
|
||||
using ::testing::Eq;
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "gmock/gmock.h"
|
||||
#include "source/latest_version_glsl_std_450_header.h"
|
||||
#include "source/latest_version_opencl_std_header.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "test/test_fixture.h"
|
||||
#include "test/unit_spirv.h"
|
||||
|
||||
@ -30,7 +31,7 @@ namespace {
|
||||
|
||||
using spvtest::Concatenate;
|
||||
using spvtest::MakeInstruction;
|
||||
using spvtest::MakeVector;
|
||||
using utils::MakeVector;
|
||||
using spvtest::TextToBinaryTest;
|
||||
using ::testing::Combine;
|
||||
using ::testing::Eq;
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "test/test_fixture.h"
|
||||
#include "test/unit_spirv.h"
|
||||
|
||||
@ -28,7 +29,7 @@ namespace {
|
||||
|
||||
using spvtest::EnumCase;
|
||||
using spvtest::MakeInstruction;
|
||||
using spvtest::MakeVector;
|
||||
using utils::MakeVector;
|
||||
using ::testing::Combine;
|
||||
using ::testing::Eq;
|
||||
using ::testing::TestWithParam;
|
||||
|
3
3rdparty/spirv-tools/test/unit_spirv.cpp
vendored
3
3rdparty/spirv-tools/test/unit_spirv.cpp
vendored
@ -15,12 +15,13 @@
|
||||
#include "test/unit_spirv.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "test/test_fixture.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace {
|
||||
|
||||
using spvtest::MakeVector;
|
||||
using utils::MakeVector;
|
||||
using ::testing::Eq;
|
||||
using Words = std::vector<uint32_t>;
|
||||
|
||||
|
23
3rdparty/spirv-tools/test/unit_spirv.h
vendored
23
3rdparty/spirv-tools/test/unit_spirv.h
vendored
@ -133,29 +133,6 @@ inline std::vector<uint32_t> Concatenate(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Encodes a string as a sequence of words, using the SPIR-V encoding.
|
||||
inline std::vector<uint32_t> MakeVector(std::string input) {
|
||||
std::vector<uint32_t> result;
|
||||
uint32_t word = 0;
|
||||
size_t num_bytes = input.size();
|
||||
// SPIR-V strings are null-terminated. The byte_index == num_bytes
|
||||
// case is used to push the terminating null byte.
|
||||
for (size_t byte_index = 0; byte_index <= num_bytes; byte_index++) {
|
||||
const auto new_byte =
|
||||
(byte_index < num_bytes ? uint8_t(input[byte_index]) : uint8_t(0));
|
||||
word |= (new_byte << (8 * (byte_index % sizeof(uint32_t))));
|
||||
if (3 == (byte_index % sizeof(uint32_t))) {
|
||||
result.push_back(word);
|
||||
word = 0;
|
||||
}
|
||||
}
|
||||
// Emit a trailing partial word.
|
||||
if ((num_bytes + 1) % sizeof(uint32_t)) {
|
||||
result.push_back(word);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// A type for easily creating spv_text_t values, with an implicit conversion to
|
||||
// spv_text.
|
||||
struct AutoText {
|
||||
|
@ -377,34 +377,34 @@ TEST_F(ValidateAtomics, AtomicLoadVulkanInt64) {
|
||||
TEST_F(ValidateAtomics, AtomicLoadWebGPUSuccess) {
|
||||
const std::string body = R"(
|
||||
%val1 = OpAtomicLoad %u32 %u32_var %queuefamily %relaxed
|
||||
%val2 = OpAtomicLoad %u32 %u32_var %workgroup %relaxed
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
}
|
||||
|
||||
TEST_F(ValidateAtomics, AtomicLoadWebGPUNonQueueFamilyFailure) {
|
||||
const std::string body = R"(
|
||||
%val3 = OpAtomicLoad %u32 %u32_var %invocation %relaxed
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("Memory Scope is limited to QueueFamilyKHR for "
|
||||
"OpAtomic* operations"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateAtomics, AtomicLoadWebGPUNonRelaxedFailure) {
|
||||
const std::string body = R"(
|
||||
%val1 = OpAtomicLoad %u32 %u32_var %queuefamily %acquire
|
||||
%val2 = OpAtomicLoad %u32 %u32_var %workgroup %release
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateAtomics, AtomicLoadWebGPUSequentiallyConsistentFailure) {
|
||||
const std::string body = R"(
|
||||
%val3 = OpAtomicLoad %u32 %u32_var %invocation %sequentially_consistent
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
|
||||
HasSubstr("no bits may be set for Memory Semantics of OpAtomic* "
|
||||
"instructions"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateAtomics, VK_KHR_shader_atomic_int64Success) {
|
||||
@ -592,6 +592,17 @@ OpAtomicStore %u32_var %queuefamily %relaxed %u32_1
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
}
|
||||
TEST_F(ValidateAtomics, AtomicStoreWebGPUNonQueueFamilyFailure) {
|
||||
const std::string body = R"(
|
||||
OpAtomicStore %u32_var %workgroup %relaxed %u32_1
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("Memory Scope is limited to QueueFamilyKHR for "
|
||||
"OpAtomic* operations"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateAtomics, AtomicStoreWebGPUNonRelaxedFailure) {
|
||||
const std::string body = R"(
|
||||
@ -601,18 +612,8 @@ OpAtomicStore %u32_var %queuefamily %release %u32_1
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateAtomics, AtomicStoreWebGPUSequentiallyConsistent) {
|
||||
const std::string body = R"(
|
||||
OpAtomicStore %u32_var %queuefamily %sequentially_consistent %u32_1
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
|
||||
HasSubstr("no bits may be set for Memory Semantics of OpAtomic* "
|
||||
"instructions"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateAtomics, AtomicStoreWrongPointerType) {
|
||||
@ -1919,11 +1920,9 @@ TEST_F(ValidateAtomics, WebGPUCrossDeviceMemoryScopeBad) {
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(
|
||||
getDiagnosticString(),
|
||||
HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to "
|
||||
"Workgroup, Invocation, and QueueFamilyKHR\n"
|
||||
" %34 = OpAtomicLoad %uint %29 %uint_0_0 %uint_0_1\n"));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("in WebGPU environment Memory Scope is limited to "
|
||||
"QueueFamilyKHR for OpAtomic* operations"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateAtomics, WebGPUDeviceMemoryScopeBad) {
|
||||
@ -1933,20 +1932,21 @@ TEST_F(ValidateAtomics, WebGPUDeviceMemoryScopeBad) {
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(
|
||||
getDiagnosticString(),
|
||||
HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to "
|
||||
"Workgroup, Invocation, and QueueFamilyKHR\n"
|
||||
" %34 = OpAtomicLoad %uint %29 %uint_1_0 %uint_0_1\n"));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("in WebGPU environment Memory Scope is limited to "
|
||||
"QueueFamilyKHR for OpAtomic* operations"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateAtomics, WebGPUWorkgroupMemoryScopeGood) {
|
||||
TEST_F(ValidateAtomics, WebGPUWorkgroupMemoryScopeBad) {
|
||||
const std::string body = R"(
|
||||
%val1 = OpAtomicLoad %u32 %u32_var %workgroup %relaxed
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("in WebGPU environment Memory Scope is limited to "
|
||||
"QueueFamilyKHR for OpAtomic* operations"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateAtomics, WebGPUSubgroupMemoryScopeBad) {
|
||||
@ -1956,20 +1956,21 @@ TEST_F(ValidateAtomics, WebGPUSubgroupMemoryScopeBad) {
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(
|
||||
getDiagnosticString(),
|
||||
HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to "
|
||||
"Workgroup, Invocation, and QueueFamilyKHR\n"
|
||||
" %34 = OpAtomicLoad %uint %29 %uint_3 %uint_0_1\n"));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("in WebGPU environment Memory Scope is limited to "
|
||||
"QueueFamilyKHR for OpAtomic* operations"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateAtomics, WebGPUInvocationMemoryScopeGood) {
|
||||
TEST_F(ValidateAtomics, WebGPUInvocationMemoryScopeBad) {
|
||||
const std::string body = R"(
|
||||
%val1 = OpAtomicLoad %u32 %u32_var %invocation %relaxed
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("in WebGPU environment Memory Scope is limited to "
|
||||
"QueueFamilyKHR for OpAtomic* operations"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateAtomics, WebGPUQueueFamilyMemoryScopeGood) {
|
||||
|
104
3rdparty/spirv-tools/test/val/val_barriers_test.cpp
vendored
104
3rdparty/spirv-tools/test/val/val_barriers_test.cpp
vendored
@ -82,9 +82,12 @@ OpCapability Shader
|
||||
%release_uniform_workgroup = OpConstant %u32 324
|
||||
%acquire_and_release_uniform = OpConstant %u32 70
|
||||
%acquire_release_subgroup = OpConstant %u32 136
|
||||
%acquire_release_workgroup = OpConstant %u32 264
|
||||
%uniform = OpConstant %u32 64
|
||||
%uniform_workgroup = OpConstant %u32 320
|
||||
|
||||
%workgroup_memory = OpConstant %u32 256
|
||||
%image_memory = OpConstant %u32 2048
|
||||
%uniform_image_memory = OpConstant %u32 2112
|
||||
|
||||
%main = OpFunction %void None %func
|
||||
%main_entry = OpLabel
|
||||
@ -251,7 +254,7 @@ OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
|
||||
|
||||
TEST_F(ValidateBarriers, OpControlBarrierWebGPUAcquireReleaseSuccess) {
|
||||
const std::string body = R"(
|
||||
OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
|
||||
OpControlBarrier %workgroup %workgroup %acquire_release_workgroup
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
@ -260,25 +263,39 @@ OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
|
||||
|
||||
TEST_F(ValidateBarriers, OpControlBarrierWebGPURelaxedFailure) {
|
||||
const std::string body = R"(
|
||||
OpControlBarrier %workgroup %workgroup %uniform_workgroup
|
||||
OpControlBarrier %workgroup %workgroup %workgroup
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("WebGPU spec requires AcquireRelease to set"));
|
||||
HasSubstr("For WebGPU, AcquireRelease must be set for Memory "
|
||||
"Semantics of OpControlBarrier"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpControlBarrierWebGPUAcquireFailure) {
|
||||
TEST_F(ValidateBarriers, OpControlBarrierWebGPUMissingWorkgroupFailure) {
|
||||
const std::string body = R"(
|
||||
OpControlBarrier %workgroup %workgroup %acquire_uniform_workgroup
|
||||
OpControlBarrier %workgroup %workgroup %acquire_release
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("For WebGPU, WorkgroupMemory must be set for Memory "
|
||||
"Semantics"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpControlBarrierWebGPUUniformFailure) {
|
||||
const std::string body = R"(
|
||||
OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(
|
||||
getDiagnosticString(),
|
||||
HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
|
||||
HasSubstr("For WebGPU only WorkgroupMemory and AcquireRelease may be set "
|
||||
"for Memory Semantics of OpControlBarrier."));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpControlBarrierWebGPUReleaseFailure) {
|
||||
@ -288,9 +305,9 @@ OpControlBarrier %workgroup %workgroup %release_uniform_workgroup
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(
|
||||
getDiagnosticString(),
|
||||
HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("For WebGPU, AcquireRelease must be set for Memory "
|
||||
"Semantics of OpControlBarrier"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpControlBarrierExecutionModelFragmentSpirv12) {
|
||||
@ -461,6 +478,18 @@ OpControlBarrier %subgroup %cross_device %none
|
||||
"cannot be CrossDevice"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpControlBarrierWebGPUMemoryScopeNonWorkgroup) {
|
||||
const std::string body = R"(
|
||||
OpControlBarrier %workgroup %subgroup %acquire_release_workgroup
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("ControlBarrier: in WebGPU environment Memory Scope is "
|
||||
"limited to Workgroup for OpControlBarrier"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpControlBarrierAcquireAndRelease) {
|
||||
const std::string body = R"(
|
||||
OpControlBarrier %device %device %acquire_and_release_uniform
|
||||
@ -680,13 +709,37 @@ OpMemoryBarrier %workgroup %acquire_release_uniform_workgroup
|
||||
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUAcquireReleaseSuccess) {
|
||||
TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUImageMemorySuccess) {
|
||||
const std::string body = R"(
|
||||
OpMemoryBarrier %workgroup %image_memory
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUDeviceFailure) {
|
||||
const std::string body = R"(
|
||||
OpMemoryBarrier %subgroup %image_memory
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("in WebGPU environment Memory Scope is limited to "
|
||||
"Workgroup for OpMemoryBarrier"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUAcquireReleaseFailure) {
|
||||
const std::string body = R"(
|
||||
OpMemoryBarrier %workgroup %acquire_release_uniform_workgroup
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("ImageMemory must be set for Memory Semantics of "
|
||||
"OpMemoryBarrier"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpMemoryBarrierWebGPURelaxedFailure) {
|
||||
@ -697,7 +750,8 @@ OpMemoryBarrier %workgroup %uniform_workgroup
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("WebGPU spec requires AcquireRelease to set"));
|
||||
HasSubstr("ImageMemory must be set for Memory Semantics of "
|
||||
"OpMemoryBarrier"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUAcquireFailure) {
|
||||
@ -707,9 +761,9 @@ OpMemoryBarrier %workgroup %acquire_uniform_workgroup
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(
|
||||
getDiagnosticString(),
|
||||
HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("ImageMemory must be set for Memory Semantics of "
|
||||
"OpMemoryBarrier"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUReleaseFailure) {
|
||||
@ -719,9 +773,21 @@ OpMemoryBarrier %workgroup %release_uniform_workgroup
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(
|
||||
getDiagnosticString(),
|
||||
HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("ImageMemory must be set for Memory Semantics of "
|
||||
"OpMemoryBarrier"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUUniformFailure) {
|
||||
const std::string body = R"(
|
||||
OpMemoryBarrier %workgroup %uniform_image_memory
|
||||
)";
|
||||
|
||||
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("only ImageMemory may be set for Memory Semantics of "
|
||||
"OpMemoryBarrier"));
|
||||
}
|
||||
|
||||
TEST_F(ValidateBarriers, OpMemoryBarrierFloatMemoryScope) {
|
||||
|
9
3rdparty/spirv-tools/tools/fuzz/fuzz.cpp
vendored
9
3rdparty/spirv-tools/tools/fuzz/fuzz.cpp
vendored
@ -74,12 +74,12 @@ void PrintUsage(const char* program) {
|
||||
USAGE: %s [options] <input.spv> -o <output.spv>
|
||||
|
||||
The SPIR-V binary is read from <input.spv>, which must have extension .spv. If
|
||||
<input.json> is also present, facts about the SPIR-V binary are read from this
|
||||
<input.facts> is also present, facts about the SPIR-V binary are read from this
|
||||
file.
|
||||
|
||||
The transformed SPIR-V binary is written to <output.spv>. Human-readable and
|
||||
binary representations of the transformations that were applied to obtain this
|
||||
binary are written to <output.json> and <output.transformations>, respectively.
|
||||
binary representations of the transformations that were applied are written to
|
||||
<output.transformations_json> and <output.transformations>, respectively.
|
||||
|
||||
NOTE: The fuzzer is a work in progress.
|
||||
|
||||
@ -472,7 +472,8 @@ int main(int argc, const char** argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::ofstream transformations_json_file(output_file_prefix + ".json");
|
||||
std::ofstream transformations_json_file(output_file_prefix +
|
||||
".transformations_json");
|
||||
transformations_json_file << json_string;
|
||||
transformations_json_file.close();
|
||||
|
||||
|
14
3rdparty/spirv-tools/tools/opt/opt.cpp
vendored
14
3rdparty/spirv-tools/tools/opt/opt.cpp
vendored
@ -147,6 +147,15 @@ Options (in lexicographical order):)",
|
||||
around known issues with some Vulkan drivers for initialize
|
||||
variables.)");
|
||||
printf(R"(
|
||||
--descriptor-scalar-replacement
|
||||
Replaces every array variable |desc| that has a DescriptorSet
|
||||
and Binding decorations with a new variable for each element of
|
||||
the array. Suppose |desc| was bound at binding |b|. Then the
|
||||
variable corresponding to |desc[i]| will have binding |b+i|.
|
||||
The descriptor set will be the same. All accesses to |desc|
|
||||
must be in OpAccessChain instructions with a literal index for
|
||||
the first index.)");
|
||||
printf(R"(
|
||||
--eliminate-dead-branches
|
||||
Convert conditional branches with constant condition to the
|
||||
indicated unconditional brranch. Delete all resulting dead
|
||||
@ -206,6 +215,11 @@ Options (in lexicographical order):)",
|
||||
Freeze the values of specialization constants to their default
|
||||
values.)");
|
||||
printf(R"(
|
||||
--graphics-robust-access
|
||||
Clamp indices used to access buffers and internal composite
|
||||
values, providing guarantees that satisfy Vulkan's
|
||||
robustBufferAccess rules.)");
|
||||
printf(R"(
|
||||
--generate-webgpu-initializers
|
||||
Adds initial values to OpVariable instructions that are missing
|
||||
them, due to their storage type requiring them for WebGPU.)");
|
||||
|
Loading…
Reference in New Issue
Block a user