From 6c218ec60b5f6b525f1badb60c820cae20bd4df3 Mon Sep 17 00:00:00 2001 From: Alastair Donaldson Date: Tue, 11 Feb 2020 23:10:57 +0000 Subject: [PATCH] spirv-fuzz: Fuzzer pass that adds access chains (#3182) This change adds a fuzzer pass that sprinkles access chain instructions into a module at random. This allows other passes to have a richer set of pointers available to them, in particular the passes that add loads and stores. --- source/fuzz/CMakeLists.txt | 4 + source/fuzz/fuzzer.cpp | 4 + source/fuzz/fuzzer_context.cpp | 15 +- source/fuzz/fuzzer_context.h | 15 +- source/fuzz/fuzzer_pass.cpp | 20 +- source/fuzz/fuzzer_pass.h | 6 + source/fuzz/fuzzer_pass_add_access_chains.cpp | 169 +++++++ source/fuzz/fuzzer_pass_add_access_chains.h | 41 ++ source/fuzz/fuzzer_util.cpp | 87 ++-- source/fuzz/fuzzer_util.h | 20 + source/fuzz/protobufs/spvtoolsfuzz.proto | 20 + source/fuzz/transformation.cpp | 3 + source/fuzz/transformation_access_chain.cpp | 215 +++++++++ source/fuzz/transformation_access_chain.h | 80 ++++ test/fuzz/CMakeLists.txt | 1 + .../fuzz/transformation_access_chain_test.cpp | 448 ++++++++++++++++++ 16 files changed, 1102 insertions(+), 46 deletions(-) create mode 100644 source/fuzz/fuzzer_pass_add_access_chains.cpp create mode 100644 source/fuzz/fuzzer_pass_add_access_chains.h create mode 100644 source/fuzz/transformation_access_chain.cpp create mode 100644 source/fuzz/transformation_access_chain.h create mode 100644 test/fuzz/transformation_access_chain_test.cpp diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index 330bbf04..4d5feea5 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -37,6 +37,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer.h fuzzer_context.h fuzzer_pass.h + fuzzer_pass_add_access_chains.h fuzzer_pass_add_composite_types.h fuzzer_pass_add_dead_blocks.h fuzzer_pass_add_dead_breaks.h @@ -71,6 +72,7 @@ if(SPIRV_BUILD_FUZZER) replayer.h shrinker.h transformation.h + transformation_access_chain.h transformation_add_constant_boolean.h transformation_add_constant_composite.h transformation_add_constant_scalar.h @@ -119,6 +121,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer.cpp fuzzer_context.cpp fuzzer_pass.cpp + fuzzer_pass_add_access_chains.cpp fuzzer_pass_add_composite_types.cpp fuzzer_pass_add_dead_blocks.cpp fuzzer_pass_add_dead_breaks.cpp @@ -152,6 +155,7 @@ if(SPIRV_BUILD_FUZZER) replayer.cpp shrinker.cpp transformation.cpp + transformation_access_chain.cpp transformation_add_constant_boolean.cpp transformation_add_constant_composite.cpp transformation_add_constant_scalar.cpp diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index a4276196..6c2821c6 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp @@ -21,6 +21,7 @@ #include "fuzzer_pass_adjust_memory_operands_masks.h" #include "source/fuzz/fact_manager.h" #include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_pass_add_access_chains.h" #include "source/fuzz/fuzzer_pass_add_composite_types.h" #include "source/fuzz/fuzzer_pass_add_dead_blocks.h" #include "source/fuzz/fuzzer_pass_add_dead_breaks.h" @@ -184,6 +185,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( // Apply some semantics-preserving passes. std::vector> passes; while (passes.empty()) { + MaybeAddPass(&passes, ir_context.get(), + &fact_manager, &fuzzer_context, + transformation_sequence_out); MaybeAddPass(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp index a4346613..12657720 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -23,6 +23,7 @@ namespace { // Default pairs of probabilities for applying various // transformations. All values are percentages. Keep them in alphabetical order. +const std::pair kChanceOfAddingAccessChain = {5, 50}; const std::pair kChanceOfAddingAnotherStructField = {20, 90}; const std::pair kChanceOfAddingArrayOrStructType = {20, 90}; @@ -50,6 +51,8 @@ const std::pair kChanceOfChoosingStructTypeVsArrayType = { const std::pair kChanceOfConstructingComposite = {20, 50}; const std::pair kChanceOfCopyingObject = {20, 50}; const std::pair kChanceOfDonatingAdditionalModule = {5, 50}; +const std::pair kChanceOfGoingDeeperWhenMakingAccessChain = + {50, 95}; const std::pair kChanceOfMakingDonorLivesafe = {40, 60}; const std::pair kChanceOfMergingBlocks = {20, 95}; const std::pair kChanceOfMovingBlockDown = {20, 50}; @@ -81,8 +84,14 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, uint32_t min_fresh_id) : random_generator_(random_generator), next_fresh_id_(min_fresh_id), + max_loop_control_partial_count_(kDefaultMaxLoopControlPartialCount), + max_loop_control_peel_count_(kDefaultMaxLoopControlPeelCount), + max_loop_limit_(kDefaultMaxLoopLimit), + max_new_array_size_limit_(kDefaultMaxNewArraySizeLimit), go_deeper_in_constant_obfuscation_( kDefaultGoDeeperInConstantObfuscation) { + chance_of_adding_access_chain_ = + ChooseBetweenMinAndMax(kChanceOfAddingAccessChain); chance_of_adding_another_struct_field_ = ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField); chance_of_adding_array_or_struct_type_ = @@ -122,6 +131,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject); chance_of_donating_additional_module_ = ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule); + chance_of_going_deeper_when_making_access_chain_ = + ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain); chance_of_making_donor_livesafe_ = ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe); chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks); @@ -134,10 +145,6 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, chance_of_replacing_id_with_synonym_ = ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym); chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock); - max_loop_control_partial_count_ = kDefaultMaxLoopControlPartialCount; - max_loop_control_peel_count_ = kDefaultMaxLoopControlPeelCount; - max_loop_limit_ = kDefaultMaxLoopLimit; - max_new_array_size_limit_ = kDefaultMaxNewArraySizeLimit; } FuzzerContext::~FuzzerContext() = default; diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index 1d1245ce..23127ff9 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -68,6 +68,9 @@ class FuzzerContext { // Probabilities associated with applying various transformations. // Keep them in alphabetical order. + uint32_t GetChanceOfAddingAccessChain() { + return chance_of_adding_access_chain_; + } uint32_t GetChanceOfAddingAnotherStructField() { return chance_of_adding_another_struct_field_; } @@ -119,6 +122,9 @@ class FuzzerContext { uint32_t GetChanceOfDonatingAdditionalModule() { return chance_of_donating_additional_module_; } + uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() { + return chance_of_going_deeper_when_making_access_chain_; + } uint32_t ChanceOfMakingDonorLivesafe() { return chance_of_making_donor_livesafe_; } @@ -148,8 +154,11 @@ class FuzzerContext { return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1; } - // Functions to control how deeply to recurse. - // Keep them in alphabetical order. + // Other functions to control transformations. Keep them in alphabetical + // order. + uint32_t GetRandomIndexForAccessChain(uint32_t composite_size_bound) { + return random_generator_->RandomUint32(composite_size_bound); + } bool GoDeeperInConstantObfuscation(uint32_t depth) { return go_deeper_in_constant_obfuscation_(depth, random_generator_); } @@ -162,6 +171,7 @@ class FuzzerContext { // Probabilities associated with applying various transformations. // Keep them in alphabetical order. + uint32_t chance_of_adding_access_chain_; uint32_t chance_of_adding_another_struct_field_; uint32_t chance_of_adding_array_or_struct_type_; uint32_t chance_of_adding_dead_block_; @@ -183,6 +193,7 @@ class FuzzerContext { uint32_t chance_of_constructing_composite_; uint32_t chance_of_copying_object_; uint32_t chance_of_donating_additional_module_; + uint32_t chance_of_going_deeper_when_making_access_chain_; uint32_t chance_of_making_donor_livesafe_; uint32_t chance_of_merging_blocks_; uint32_t chance_of_moving_block_down_; diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp index 1ecfa8d3..4a22a211 100644 --- a/source/fuzz/fuzzer_pass.cpp +++ b/source/fuzz/fuzzer_pass.cpp @@ -219,21 +219,27 @@ uint32_t FuzzerPass::FindOrCreateMatrixType(uint32_t column_count, return result; } -uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType( - bool is_signed, SpvStorageClass storage_class) { - auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed); - opt::analysis::Pointer pointer_type( - GetIRContext()->get_type_mgr()->GetType(uint32_type_id), storage_class); - auto existing_id = GetIRContext()->get_type_mgr()->GetId(&pointer_type); +uint32_t FuzzerPass::FindOrCreatePointerType(uint32_t base_type_id, + SpvStorageClass storage_class) { + // We do not use the type manager here, due to problems related to isomorphic + // but distinct structs not being regarded as different. + auto existing_id = fuzzerutil::MaybeGetPointerType( + GetIRContext(), base_type_id, storage_class); if (existing_id) { return existing_id; } auto result = GetFuzzerContext()->GetFreshId(); ApplyTransformation( - TransformationAddTypePointer(result, storage_class, uint32_type_id)); + TransformationAddTypePointer(result, storage_class, base_type_id)); return result; } +uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType( + bool is_signed, SpvStorageClass storage_class) { + return FindOrCreatePointerType(FindOrCreate32BitIntegerType(is_signed), + storage_class); +} + uint32_t FuzzerPass::FindOrCreate32BitIntegerConstant(uint32_t word, bool is_signed) { auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed); diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h index 68531796..7052685e 100644 --- a/source/fuzz/fuzzer_pass.h +++ b/source/fuzz/fuzzer_pass.h @@ -125,6 +125,12 @@ class FuzzerPass { // type itself do not exist, transformations are applied to add them. uint32_t FindOrCreateMatrixType(uint32_t column_count, uint32_t row_count); + // Returns the id of a pointer type with base type |base_type_id| (which must + // already exist) and storage class |storage_class|. A transformation is + // applied to add the pointer if it does not already exist. + uint32_t FindOrCreatePointerType(uint32_t base_type_id, + SpvStorageClass storage_class); + // Returns the id of an OpTypePointer instruction, with a 32-bit integer base // type of signedness specified by |is_signed|. If the pointer type or // required integer base type do not exist, transformations are applied to add diff --git a/source/fuzz/fuzzer_pass_add_access_chains.cpp b/source/fuzz/fuzzer_pass_add_access_chains.cpp new file mode 100644 index 00000000..11f368e6 --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_access_chains.cpp @@ -0,0 +1,169 @@ +// Copyright (c) 2020 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_access_chains.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_access_chain.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddAccessChains::FuzzerPassAddAccessChains( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassAddAccessChains::~FuzzerPassAddAccessChains() = default; + +void FuzzerPassAddAccessChains::Apply() { + MaybeAddTransformationBeforeEachInstruction( + [this](opt::Function* function, opt::BasicBlock* block, + opt::BasicBlock::iterator inst_it, + const protobufs::InstructionDescriptor& instruction_descriptor) + -> void { + assert(inst_it->opcode() == + instruction_descriptor.target_instruction_opcode() && + "The opcode of the instruction we might insert before must be " + "the same as the opcode in the descriptor for the instruction"); + + // Check whether it is legitimate to insert an access chain + // instruction before this instruction. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpAccessChain, + inst_it)) { + return; + } + + // Randomly decide whether to try inserting a load here. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingAccessChain())) { + return; + } + + // Get all of the pointers that are currently in scope, excluding + // explicitly null and undefined pointers. + std::vector relevant_pointer_instructions = + FindAvailableInstructions( + function, block, inst_it, + [](opt::IRContext* context, + opt::Instruction* instruction) -> bool { + if (!instruction->result_id() || !instruction->type_id()) { + // A pointer needs both a result and type id. + return false; + } + switch (instruction->opcode()) { + case SpvOpConstantNull: + case SpvOpUndef: + // Do not allow making an access chain from a null or + // undefined pointer. (We can eliminate these cases + // before actually checking that the instruction is a + // pointer.) + return false; + default: + break; + } + // If the instruction has pointer type, we can legitimately + // make an access chain from it. + return context->get_def_use_mgr() + ->GetDef(instruction->type_id()) + ->opcode() == SpvOpTypePointer; + }); + + // At this point, |relevant_instructions| contains all the pointers + // we might think of making an access chain from. + if (relevant_pointer_instructions.empty()) { + return; + } + + auto chosen_pointer = + relevant_pointer_instructions[GetFuzzerContext()->RandomIndex( + relevant_pointer_instructions)]; + std::vector index_ids; + auto pointer_type = GetIRContext()->get_def_use_mgr()->GetDef( + chosen_pointer->type_id()); + uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1); + while (true) { + auto subobject_type = + GetIRContext()->get_def_use_mgr()->GetDef(subobject_type_id); + if (!spvOpcodeIsComposite(subobject_type->opcode())) { + break; + } + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfGoingDeeperWhenMakingAccessChain())) { + break; + } + uint32_t bound; + switch (subobject_type->opcode()) { + case SpvOpTypeArray: + bound = fuzzerutil::GetArraySize(*subobject_type, GetIRContext()); + break; + case SpvOpTypeMatrix: + case SpvOpTypeVector: + bound = subobject_type->GetSingleWordInOperand(1); + break; + case SpvOpTypeStruct: + bound = fuzzerutil::GetNumberOfStructMembers(*subobject_type); + break; + default: + assert(false && "Not a composite type opcode."); + // Set the bound to a value in order to keep release compilers + // happy. + bound = 0; + break; + } + if (bound == 0) { + // It is possible for a composite type to legitimately have zero + // sub-components, at least in the case of a struct, which + // can have no fields. + break; + } + + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3179) We + // could allow non-constant indices when looking up non-structs, + // using clamping to ensure they are in-bounds. + uint32_t index_value = + GetFuzzerContext()->GetRandomIndexForAccessChain(bound); + index_ids.push_back(FindOrCreate32BitIntegerConstant( + index_value, GetFuzzerContext()->ChooseEven())); + switch (subobject_type->opcode()) { + case SpvOpTypeArray: + case SpvOpTypeMatrix: + case SpvOpTypeVector: + subobject_type_id = subobject_type->GetSingleWordInOperand(0); + break; + case SpvOpTypeStruct: + subobject_type_id = + subobject_type->GetSingleWordInOperand(index_value); + break; + default: + assert(false && "Not a composite type opcode."); + } + } + // The transformation we are about to create will only apply if a + // pointer suitable for the access chain's result type exists, so we + // create one if it does not. + FindOrCreatePointerType(subobject_type_id, + static_cast( + pointer_type->GetSingleWordInOperand(0))); + // Apply the transformation to add an access chain. + ApplyTransformation(TransformationAccessChain( + GetFuzzerContext()->GetFreshId(), chosen_pointer->result_id(), + index_ids, instruction_descriptor)); + }); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_add_access_chains.h b/source/fuzz/fuzzer_pass_add_access_chains.h new file mode 100644 index 00000000..7e8ed612 --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_access_chains.h @@ -0,0 +1,41 @@ +// Copyright (c) 2020 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_ACCESS_CHAINS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_ACCESS_CHAINS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass that randomly adds access chains based on pointers available in +// the module. Other passes can use these access chains, e.g. by loading from +// them. +class FuzzerPassAddAccessChains : public FuzzerPass { + public: + FuzzerPassAddAccessChains(opt::IRContext* ir_context, + FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddAccessChains(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_ACCESS_CHAINS_H_ diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp index cb90143f..26961c8d 100644 --- a/source/fuzz/fuzzer_util.cpp +++ b/source/fuzz/fuzzer_util.cpp @@ -262,44 +262,48 @@ std::vector RepeatedFieldToVector( return result; } +uint32_t WalkOneCompositeTypeIndex(opt::IRContext* context, + uint32_t base_object_type_id, + uint32_t index) { + auto should_be_composite_type = + context->get_def_use_mgr()->GetDef(base_object_type_id); + assert(should_be_composite_type && "The type should exist."); + switch (should_be_composite_type->opcode()) { + case SpvOpTypeArray: { + auto array_length = GetArraySize(*should_be_composite_type, context); + if (array_length == 0 || index >= array_length) { + return 0; + } + return should_be_composite_type->GetSingleWordInOperand(0); + } + case SpvOpTypeMatrix: + case SpvOpTypeVector: { + auto count = should_be_composite_type->GetSingleWordInOperand(1); + if (index >= count) { + return 0; + } + return should_be_composite_type->GetSingleWordInOperand(0); + } + case SpvOpTypeStruct: { + if (index >= GetNumberOfStructMembers(*should_be_composite_type)) { + return 0; + } + return should_be_composite_type->GetSingleWordInOperand(index); + } + default: + return 0; + } +} + uint32_t WalkCompositeTypeIndices( opt::IRContext* context, uint32_t base_object_type_id, const google::protobuf::RepeatedField& indices) { uint32_t sub_object_type_id = base_object_type_id; for (auto index : indices) { - auto should_be_composite_type = - context->get_def_use_mgr()->GetDef(sub_object_type_id); - assert(should_be_composite_type && "The type should exist."); - switch (should_be_composite_type->opcode()) { - case SpvOpTypeArray: { - auto array_length = GetArraySize(*should_be_composite_type, context); - if (array_length == 0 || index >= array_length) { - return 0; - } - sub_object_type_id = - should_be_composite_type->GetSingleWordInOperand(0); - break; - } - case SpvOpTypeMatrix: - case SpvOpTypeVector: { - auto count = should_be_composite_type->GetSingleWordInOperand(1); - if (index >= count) { - return 0; - } - sub_object_type_id = - should_be_composite_type->GetSingleWordInOperand(0); - break; - } - case SpvOpTypeStruct: { - if (index >= GetNumberOfStructMembers(*should_be_composite_type)) { - return 0; - } - sub_object_type_id = - should_be_composite_type->GetSingleWordInOperand(index); - break; - } - default: - return 0; + sub_object_type_id = + WalkOneCompositeTypeIndex(context, sub_object_type_id, index); + if (!sub_object_type_id) { + return 0; } } return sub_object_type_id; @@ -501,6 +505,23 @@ SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context, context->get_def_use_mgr()->GetDef(pointer_type_id)); } +uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id, + SpvStorageClass storage_class) { + for (auto& inst : context->types_values()) { + switch (inst.opcode()) { + case SpvOpTypePointer: + if (inst.GetSingleWordInOperand(0) == storage_class && + inst.GetSingleWordInOperand(1) == pointee_type_id) { + return inst.result_id(); + } + break; + default: + break; + } + } + return 0; +} + } // namespace fuzzerutil } // namespace fuzz diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h index 292cd9f9..daa836c1 100644 --- a/source/fuzz/fuzzer_util.h +++ b/source/fuzz/fuzzer_util.h @@ -98,6 +98,21 @@ bool IsCompositeType(const opt::analysis::Type* type); std::vector RepeatedFieldToVector( const google::protobuf::RepeatedField& repeated_field); +// Given a type id, |base_object_type_id|, returns 0 if the type is not a +// composite type or if |index| is too large to be used as an index into the +// composite. Otherwise returns the type id of the type associated with the +// composite's index. +// +// Example: if |base_object_type_id| is 10, and we have: +// +// %10 = OpTypeStruct %3 %4 %5 +// +// then 3 will be returned if |index| is 0, 5 if |index| is 2, and 0 if index +// is 3 or larger. +uint32_t WalkOneCompositeTypeIndex(opt::IRContext* context, + uint32_t base_object_type_id, + uint32_t index); + // Given a type id, |base_object_type_id|, checks that the given sequence of // |indices| is suitable for indexing into this type. Returns the id of the // type of the final sub-object reached via the indices if they are valid, and @@ -181,6 +196,11 @@ SpvStorageClass GetStorageClassFromPointerType( SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context, uint32_t pointer_type_id); +// Returns the id of a pointer with pointee type |pointee_type_id| and storage +// class |storage_class|, if it exists, and 0 otherwise. +uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id, + SpvStorageClass storage_class); + } // namespace fuzzerutil } // namespace fuzz diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto index 8a931ba0..9773b606 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -341,12 +341,32 @@ message Transformation { TransformationLoad load = 36; TransformationStore store = 37; TransformationFunctionCall function_call = 38; + TransformationAccessChain access_chain = 39; // Add additional option using the next available number. } } // Keep transformation message types in alphabetical order: +message TransformationAccessChain { + + // Adds an access chain instruction based on a given pointer and indices. + + // Result id for the access chain + uint32 fresh_id = 1; + + // The pointer from which the access chain starts + uint32 pointer_id = 2; + + // Zero or more access chain indices + repeated uint32 index_id = 3; + + // A descriptor for an instruction in a block before which the new + // OpAccessChain instruction should be inserted + InstructionDescriptor instruction_to_insert_before = 4; + +} + message TransformationAddConstantBoolean { // Supports adding the constants true and false to a module, which may be diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp index d41a7345..52fcfd73 100644 --- a/source/fuzz/transformation.cpp +++ b/source/fuzz/transformation.cpp @@ -17,6 +17,7 @@ #include #include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_access_chain.h" #include "source/fuzz/transformation_add_constant_boolean.h" #include "source/fuzz/transformation_add_constant_composite.h" #include "source/fuzz/transformation_add_constant_scalar.h" @@ -65,6 +66,8 @@ Transformation::~Transformation() = default; std::unique_ptr Transformation::FromMessage( const protobufs::Transformation& message) { switch (message.transformation_case()) { + case protobufs::Transformation::TransformationCase::kAccessChain: + return MakeUnique(message.access_chain()); case protobufs::Transformation::TransformationCase::kAddConstantBoolean: return MakeUnique( message.add_constant_boolean()); diff --git a/source/fuzz/transformation_access_chain.cpp b/source/fuzz/transformation_access_chain.cpp new file mode 100644 index 00000000..8c310064 --- /dev/null +++ b/source/fuzz/transformation_access_chain.cpp @@ -0,0 +1,215 @@ +// Copyright (c) 2020 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_access_chain.h" + +#include + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationAccessChain::TransformationAccessChain( + const spvtools::fuzz::protobufs::TransformationAccessChain& message) + : message_(message) {} + +TransformationAccessChain::TransformationAccessChain( + uint32_t fresh_id, uint32_t pointer_id, + const std::vector& index_id, + const protobufs::InstructionDescriptor& instruction_to_insert_before) { + message_.set_fresh_id(fresh_id); + message_.set_pointer_id(pointer_id); + for (auto id : index_id) { + message_.add_index_id(id); + } + *message_.mutable_instruction_to_insert_before() = + instruction_to_insert_before; +} + +bool TransformationAccessChain::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // The result id must be fresh + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + // The pointer id must exist and have a type. + auto pointer = context->get_def_use_mgr()->GetDef(message_.pointer_id()); + if (!pointer || !pointer->type_id()) { + return false; + } + // The type must indeed be a pointer + auto pointer_type = context->get_def_use_mgr()->GetDef(pointer->type_id()); + if (pointer_type->opcode() != SpvOpTypePointer) { + return false; + } + + // The described instruction to insert before must exist and be a suitable + // point where an OpAccessChain instruction could be inserted. + auto instruction_to_insert_before = + FindInstruction(message_.instruction_to_insert_before(), context); + if (!instruction_to_insert_before) { + return false; + } + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction( + SpvOpAccessChain, instruction_to_insert_before)) { + return false; + } + + // Do not allow making an access chain from a null or undefined pointer, as + // we do not want to allow accessing such pointers. This might be acceptable + // in dead blocks, but we conservatively avoid it. + switch (pointer->opcode()) { + case SpvOpConstantNull: + case SpvOpUndef: + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3185): When + // fuzzing for real we would like an 'assert(false)' here. But we also + // want to be able to write negative unit tests. + return false; + default: + break; + } + + // The pointer on which the access chain is to be based needs to be available + // (according to dominance rules) at the insertion point. + if (!fuzzerutil::IdIsAvailableBeforeInstruction( + context, instruction_to_insert_before, message_.pointer_id())) { + return false; + } + + // We now need to use the given indices to walk the type structure of the + // base type of the pointer, making sure that (a) the indices correspond to + // integers, and (b) these integer values are in-bounds. + + // Start from the base type of the pointer. + uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1); + + // Consider the given index ids in turn. + for (auto index_id : message_.index_id()) { + // Try to get the integer value associated with this index is. The first + // component of the result will be false if the id did not correspond to an + // integer. Otherwise, the integer with which the id is associated is the + // second component. + std::pair maybe_index_value = + GetIndexValue(context, index_id); + if (!maybe_index_value.first) { + // There was no integer: this index is no good. + return false; + } + // Try to walk down the type using this index. This will yield 0 if the + // type is not a composite or the index is out of bounds, and the id of + // the next type otherwise. + subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex( + context, subobject_type_id, maybe_index_value.second); + if (!subobject_type_id) { + // Either the type was not a composite (so that too many indices were + // provided), or the index was out of bounds. + return false; + } + } + // At this point, |subobject_type_id| is the type of the value targeted by + // the new access chain. The result type of the access chain should be a + // pointer to this type, with the same storage class as for the original + // pointer. Such a pointer type needs to exist in the module. + // + // We do not use the type manager to look up this type, due to problems + // associated with pointers to isomorphic structs being regarded as the same. + return fuzzerutil::MaybeGetPointerType( + context, subobject_type_id, + static_cast( + pointer_type->GetSingleWordInOperand(0))) != 0; +} + +void TransformationAccessChain::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const { + // The operands to the access chain are the pointer followed by the indices. + // The result type of the access chain is determined by where the indices + // lead. We thus push the pointer to a sequence of operands, and then follow + // the indices, pushing each to the operand list and tracking the type + // obtained by following it. Ultimately this yields the type of the + // component reached by following all the indices, and the result type is + // a pointer to this component type. + opt::Instruction::OperandList operands; + + // Add the pointer id itself. + operands.push_back({SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}); + + // Start walking the indices, starting with the pointer's base type. + auto pointer_type = context->get_def_use_mgr()->GetDef( + context->get_def_use_mgr()->GetDef(message_.pointer_id())->type_id()); + uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1); + + // Go through the index ids in turn. + for (auto index_id : message_.index_id()) { + // Add the index id to the operands. + operands.push_back({SPV_OPERAND_TYPE_ID, {index_id}}); + // Get the integer value associated with the index id. + uint32_t index_value = GetIndexValue(context, index_id).second; + // Walk to the next type in the composite object using this index. + subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex( + context, subobject_type_id, index_value); + } + // The access chain's result type is a pointer to the composite component that + // was reached after following all indices. The storage class is that of the + // original pointer. + uint32_t result_type = fuzzerutil::MaybeGetPointerType( + context, subobject_type_id, + static_cast(pointer_type->GetSingleWordInOperand(0))); + + // Add the access chain instruction to the module, and update the module's id + // bound. + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + FindInstruction(message_.instruction_to_insert_before(), context) + ->InsertBefore( + MakeUnique(context, SpvOpAccessChain, result_type, + message_.fresh_id(), operands)); + + // Conservatively invalidate all analyses. + context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + + // If the base pointer's pointee value was irrelevant, the same is true of the + // pointee value of the result of this access chain. + if (fact_manager->PointeeValueIsIrrelevant(message_.pointer_id())) { + fact_manager->AddFactValueOfPointeeIsIrrelevant(message_.fresh_id()); + } +} + +protobufs::Transformation TransformationAccessChain::ToMessage() const { + protobufs::Transformation result; + *result.mutable_access_chain() = message_; + return result; +} + +std::pair TransformationAccessChain::GetIndexValue( + opt::IRContext* context, uint32_t index_id) const { + auto index_instruction = context->get_def_use_mgr()->GetDef(index_id); + if (!index_instruction || !spvOpcodeIsConstant(index_instruction->opcode())) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3179) We could + // allow non-constant indices when looking up non-structs, using clamping + // to ensure they are in-bounds. + return {false, 0}; + } + auto index_type = + context->get_def_use_mgr()->GetDef(index_instruction->type_id()); + if (index_type->opcode() != SpvOpTypeInt || + index_type->GetSingleWordInOperand(0) != 32) { + return {false, 0}; + } + return {true, index_instruction->GetSingleWordInOperand(0)}; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_access_chain.h b/source/fuzz/transformation_access_chain.h new file mode 100644 index 00000000..92d9e6a6 --- /dev/null +++ b/source/fuzz/transformation_access_chain.h @@ -0,0 +1,80 @@ +// Copyright (c) 2020 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_ACCESS_CHAIN_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ACCESS_CHAIN_H_ + +#include + +#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 TransformationAccessChain : public Transformation { + public: + explicit TransformationAccessChain( + const protobufs::TransformationAccessChain& message); + + TransformationAccessChain( + uint32_t fresh_id, uint32_t pointer_id, + const std::vector& index_id, + const protobufs::InstructionDescriptor& instruction_to_insert_before); + + // - |message_.fresh_id| must be fresh + // - |message_.instruction_to_insert_before| must identify an instruction + // before which it is legitimate to insert an OpAccessChain instruction + // - |message_.pointer_id| must be a result id with pointer type that is + // available (according to dominance rules) at the insertion point. + // - The pointer must not be OpConstantNull or OpUndef + // - |message_.index_id| must be a sequence of ids of 32-bit integer constants + // such that it is possible to walk the pointee type of + // |message_.pointer_id| using these indices, remaining in-bounds. + // - If type t is the final type reached by walking these indices, the module + // must include an instruction "OpTypePointer SC %t" where SC is the storage + // class associated with |message_.pointer_id| + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an instruction of the form: + // |message_.fresh_id| = OpAccessChain %ptr |message_.index_id| + // where %ptr is the result if of an instruction declaring a pointer to the + // type reached by walking the pointee type of |message_.pointer_id| using + // the indices in |message_.index_id|, and with the same storage class as + // |message_.pointer_id|. + // + // If |fact_manager| reports that |message_.pointer_id| has an irrelevant + // pointee value, then the fact that |message_.fresh_id| (the result of the + // access chain) also has an irrelevant pointee value is also recorded. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + // Returns {false, 0} if |index_id| does not correspond to a 32-bit integer + // constant. Otherwise, returns {true, value}, where value is the value of + // the 32-bit integer constant to which |index_id| corresponds. + std::pair GetIndexValue(opt::IRContext* context, + uint32_t index_id) const; + + protobufs::TransformationAccessChain message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ACCESS_CHAIN_H_ diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt index 4a423a9a..4211ff2b 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -24,6 +24,7 @@ if (${SPIRV_BUILD_FUZZER}) fuzzer_pass_add_useful_constructs_test.cpp fuzzer_pass_donate_modules_test.cpp instruction_descriptor_test.cpp + transformation_access_chain_test.cpp transformation_add_constant_boolean_test.cpp transformation_add_constant_composite_test.cpp transformation_add_constant_scalar_test.cpp diff --git a/test/fuzz/transformation_access_chain_test.cpp b/test/fuzz/transformation_access_chain_test.cpp new file mode 100644 index 00000000..516d371b --- /dev/null +++ b/test/fuzz/transformation_access_chain_test.cpp @@ -0,0 +1,448 @@ +// Copyright (c) 2020 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_access_chain.h" +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationAccessChainTest, BasicTest) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %48 %54 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 2 + %50 = OpTypeMatrix %7 2 + %70 = OpTypePointer Function %7 + %71 = OpTypePointer Function %50 + %8 = OpTypeStruct %7 %6 + %9 = OpTypePointer Function %8 + %10 = OpTypeInt 32 1 + %11 = OpTypePointer Function %10 + %12 = OpTypeFunction %10 %9 %11 + %17 = OpConstant %10 0 + %18 = OpTypeInt 32 0 + %19 = OpConstant %18 0 + %20 = OpTypePointer Function %6 + %99 = OpTypePointer Private %6 + %29 = OpConstant %6 0 + %30 = OpConstant %6 1 + %31 = OpConstantComposite %7 %29 %30 + %32 = OpConstant %6 2 + %33 = OpConstantComposite %8 %31 %32 + %35 = OpConstant %10 10 + %51 = OpConstant %18 10 + %80 = OpConstant %18 0 + %81 = OpConstant %10 1 + %82 = OpConstant %18 2 + %83 = OpConstant %10 3 + %84 = OpConstant %18 4 + %85 = OpConstant %10 5 + %52 = OpTypeArray %50 %51 + %53 = OpTypePointer Private %52 + %45 = OpUndef %9 + %46 = OpConstantNull %9 + %47 = OpTypePointer Private %8 + %48 = OpVariable %47 Private + %54 = OpVariable %53 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + %28 = OpVariable %9 Function + %34 = OpVariable %11 Function + %36 = OpVariable %9 Function + %38 = OpVariable %11 Function + %44 = OpCopyObject %9 %36 + OpStore %28 %33 + OpStore %34 %35 + %37 = OpLoad %8 %28 + OpStore %36 %37 + %39 = OpLoad %10 %34 + OpStore %38 %39 + %40 = OpFunctionCall %10 %15 %36 %38 + %41 = OpLoad %10 %34 + %42 = OpIAdd %10 %41 %40 + OpStore %34 %42 + OpReturn + OpFunctionEnd + %15 = OpFunction %10 None %12 + %13 = OpFunctionParameter %9 + %14 = OpFunctionParameter %11 + %16 = OpLabel + %21 = OpAccessChain %20 %13 %17 %19 + %43 = OpCopyObject %9 %13 + %22 = OpLoad %6 %21 + %23 = OpConvertFToS %10 %22 + %24 = OpLoad %10 %14 + %25 = OpIAdd %10 %23 %24 + OpReturnValue %25 + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + // Types: + // Ptr | Pointee | Storage class | GLSL for pointee | Ids of this type + // ----+---------+---------------+---------------------+------------------ + // 9 | 8 | Function | struct(vec2, float) | 28, 36, 44, 13, 43 + // 11 | 10 | Function | int | 34, 38, 14 + // 20 | 6 | Function | float | - + // 99 | 6 | Private | float | - + // 53 | 52 | Private | mat2x2[10] | 54 + // 47 | 8 | Private | struct(vec2, float) | 48 + // 70 | 7 | Function | vec2 | - + // 71 | 59 | Function | mat2x2 | - + + // Indices 0-5 are in ids 80-85 + + FactManager fact_manager; + fact_manager.AddFactValueOfPointeeIsIrrelevant(54); + + // Bad: id is not fresh + ASSERT_FALSE(TransformationAccessChain( + 43, 43, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: pointer id does not exist + ASSERT_FALSE(TransformationAccessChain( + 100, 1000, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: pointer id is not a type + ASSERT_FALSE(TransformationAccessChain( + 100, 5, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: pointer id is not a pointer + ASSERT_FALSE(TransformationAccessChain( + 100, 23, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: index id does not exist + ASSERT_FALSE(TransformationAccessChain( + 100, 43, {1000}, MakeInstructionDescriptor(24, SpvOpLoad, 0)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: index id is not a constant + ASSERT_FALSE(TransformationAccessChain( + 100, 43, {24}, MakeInstructionDescriptor(25, SpvOpIAdd, 0)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: too many indices + ASSERT_FALSE( + TransformationAccessChain(100, 43, {80, 80, 80}, + MakeInstructionDescriptor(24, SpvOpLoad, 0)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: index id is out of bounds + ASSERT_FALSE( + TransformationAccessChain(100, 43, {80, 83}, + MakeInstructionDescriptor(24, SpvOpLoad, 0)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: attempt to insert before variable + ASSERT_FALSE(TransformationAccessChain( + 100, 34, {}, MakeInstructionDescriptor(36, SpvOpVariable, 0)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: pointer not available + ASSERT_FALSE( + TransformationAccessChain( + 100, 43, {80}, MakeInstructionDescriptor(21, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: instruction descriptor does not identify anything + ASSERT_FALSE(TransformationAccessChain( + 100, 43, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 100)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: pointer is null + ASSERT_FALSE(TransformationAccessChain( + 100, 45, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: pointer is undef + ASSERT_FALSE(TransformationAccessChain( + 100, 46, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0)) + .IsApplicable(context.get(), fact_manager)); + + // Bad: pointer to result type does not exist + ASSERT_FALSE(TransformationAccessChain( + 100, 52, {0}, MakeInstructionDescriptor(24, SpvOpLoad, 0)) + .IsApplicable(context.get(), fact_manager)); + + { + TransformationAccessChain transformation( + 100, 43, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0)); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(100)); + } + + { + TransformationAccessChain transformation( + 101, 28, {81}, MakeInstructionDescriptor(42, SpvOpReturn, 0)); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(101)); + } + + { + TransformationAccessChain transformation( + 102, 36, {80, 81}, MakeInstructionDescriptor(37, SpvOpStore, 0)); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(102)); + } + + { + TransformationAccessChain transformation( + 103, 44, {}, MakeInstructionDescriptor(44, SpvOpStore, 0)); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(103)); + } + + { + TransformationAccessChain transformation( + 104, 13, {80}, MakeInstructionDescriptor(21, SpvOpAccessChain, 0)); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(104)); + } + + { + TransformationAccessChain transformation( + 105, 34, {}, MakeInstructionDescriptor(44, SpvOpStore, 1)); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(105)); + } + + { + TransformationAccessChain transformation( + 106, 38, {}, MakeInstructionDescriptor(40, SpvOpFunctionCall, 0)); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(106)); + } + + { + TransformationAccessChain transformation( + 107, 14, {}, MakeInstructionDescriptor(24, SpvOpLoad, 0)); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(107)); + } + + { + TransformationAccessChain transformation( + 108, 54, {85, 81, 81}, MakeInstructionDescriptor(24, SpvOpLoad, 0)); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(fact_manager.PointeeValueIsIrrelevant(108)); + } + + { + TransformationAccessChain transformation( + 109, 48, {80, 80}, MakeInstructionDescriptor(24, SpvOpLoad, 0)); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(109)); + } + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %48 %54 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 2 + %50 = OpTypeMatrix %7 2 + %70 = OpTypePointer Function %7 + %71 = OpTypePointer Function %50 + %8 = OpTypeStruct %7 %6 + %9 = OpTypePointer Function %8 + %10 = OpTypeInt 32 1 + %11 = OpTypePointer Function %10 + %12 = OpTypeFunction %10 %9 %11 + %17 = OpConstant %10 0 + %18 = OpTypeInt 32 0 + %19 = OpConstant %18 0 + %20 = OpTypePointer Function %6 + %99 = OpTypePointer Private %6 + %29 = OpConstant %6 0 + %30 = OpConstant %6 1 + %31 = OpConstantComposite %7 %29 %30 + %32 = OpConstant %6 2 + %33 = OpConstantComposite %8 %31 %32 + %35 = OpConstant %10 10 + %51 = OpConstant %18 10 + %80 = OpConstant %18 0 + %81 = OpConstant %10 1 + %82 = OpConstant %18 2 + %83 = OpConstant %10 3 + %84 = OpConstant %18 4 + %85 = OpConstant %10 5 + %52 = OpTypeArray %50 %51 + %53 = OpTypePointer Private %52 + %45 = OpUndef %9 + %46 = OpConstantNull %9 + %47 = OpTypePointer Private %8 + %48 = OpVariable %47 Private + %54 = OpVariable %53 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + %28 = OpVariable %9 Function + %34 = OpVariable %11 Function + %36 = OpVariable %9 Function + %38 = OpVariable %11 Function + %44 = OpCopyObject %9 %36 + %103 = OpAccessChain %9 %44 + OpStore %28 %33 + %105 = OpAccessChain %11 %34 + OpStore %34 %35 + %37 = OpLoad %8 %28 + %102 = OpAccessChain %20 %36 %80 %81 + OpStore %36 %37 + %39 = OpLoad %10 %34 + OpStore %38 %39 + %106 = OpAccessChain %11 %38 + %40 = OpFunctionCall %10 %15 %36 %38 + %41 = OpLoad %10 %34 + %42 = OpIAdd %10 %41 %40 + OpStore %34 %42 + %101 = OpAccessChain %20 %28 %81 + OpReturn + OpFunctionEnd + %15 = OpFunction %10 None %12 + %13 = OpFunctionParameter %9 + %14 = OpFunctionParameter %11 + %16 = OpLabel + %104 = OpAccessChain %70 %13 %80 + %21 = OpAccessChain %20 %13 %17 %19 + %43 = OpCopyObject %9 %13 + %22 = OpLoad %6 %21 + %23 = OpConvertFToS %10 %22 + %100 = OpAccessChain %70 %43 %80 + %107 = OpAccessChain %11 %14 + %108 = OpAccessChain %99 %54 %85 %81 %81 + %109 = OpAccessChain %99 %48 %80 %80 + %24 = OpLoad %10 %14 + %25 = OpIAdd %10 %23 %24 + OpReturnValue %25 + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationAccessChainTest, IsomorphicStructs) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %11 %12 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeStruct %6 + %8 = OpTypePointer Private %7 + %9 = OpTypeStruct %6 + %10 = OpTypePointer Private %9 + %11 = OpVariable %8 Private + %12 = OpVariable %10 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + + { + TransformationAccessChain transformation( + 100, 11, {}, MakeInstructionDescriptor(5, SpvOpReturn, 0)); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + } + { + TransformationAccessChain transformation( + 101, 12, {}, MakeInstructionDescriptor(5, SpvOpReturn, 0)); + 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" %11 %12 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeStruct %6 + %8 = OpTypePointer Private %7 + %9 = OpTypeStruct %6 + %10 = OpTypePointer Private %9 + %11 = OpVariable %8 Private + %12 = OpVariable %10 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + %100 = OpAccessChain %8 %11 + %101 = OpAccessChain %10 %12 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools