Updated spirv-tools.

This commit is contained in:
Бранимир Караџић 2019-08-09 20:32:29 -07:00
parent 4386b7770e
commit 30a86bf538
93 changed files with 7361 additions and 638 deletions

View File

@ -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 \

View File

@ -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",

View File

@ -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:

View File

@ -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)

View File

@ -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"

View File

@ -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_

View File

@ -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

View 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

View 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_

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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),

View File

@ -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_;

View File

@ -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();
}

View 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

View 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_

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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());

View File

@ -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);

View File

@ -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,

View 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

View 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_

View 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

View 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_

View File

@ -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 {

View File

@ -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_;
};

View File

@ -14,7 +14,7 @@
#include "source/fuzz/uniform_buffer_element_descriptor.h"
#include <source/opt/instruction.h>
#include <algorithm>
namespace spvtools {
namespace fuzz {

View File

@ -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);

View File

@ -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

View File

@ -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",
});
}

View File

@ -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;

View File

@ -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.

View File

@ -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

View 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

View 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_

View 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

View 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_

View File

@ -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.

View File

@ -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;

View File

@ -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_;

View File

@ -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)) {

View File

@ -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() {

View File

@ -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",
});
}

View File

@ -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) {

View File

@ -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",
});
}

View File

@ -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;
}

View File

@ -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

View File

@ -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"

View File

@ -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);

View File

@ -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

View File

@ -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) {

View File

@ -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());
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View 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

View File

@ -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

View File

@ -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

View 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

View File

@ -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:

View 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

View File

@ -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,

File diff suppressed because it is too large Load Diff

View File

@ -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.

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>;

View File

@ -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 {

View File

@ -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) {

View File

@ -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) {

View File

@ -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();

View File

@ -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.)");