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.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 330bbf0..4d5feea 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -37,6 +37,7 @@
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 @@
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 @@
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 @@
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 a427619..6c2821c 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 @@
// Apply some semantics-preserving passes.
std::vector<std::unique_ptr<FuzzerPass>> passes;
while (passes.empty()) {
+ MaybeAddPass<FuzzerPassAddAccessChains>(&passes, ir_context.get(),
+ &fact_manager, &fuzzer_context,
+ transformation_sequence_out);
MaybeAddPass<FuzzerPassAddCompositeTypes>(&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 a434661..1265772 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -23,6 +23,7 @@
// Default <minimum, maximum> pairs of probabilities for applying various
// transformations. All values are percentages. Keep them in alphabetical order.
+const std::pair<uint32_t, uint32_t> kChanceOfAddingAccessChain = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingAnotherStructField = {20,
90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingArrayOrStructType = {20, 90};
@@ -50,6 +51,8 @@
const std::pair<uint32_t, uint32_t> kChanceOfConstructingComposite = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfDonatingAdditionalModule = {5, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
+ {50, 95};
const std::pair<uint32_t, uint32_t> kChanceOfMakingDonorLivesafe = {40, 60};
const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95};
const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
@@ -81,8 +84,14 @@
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 @@
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 @@
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 1d1245c..23127ff 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -68,6 +68,9 @@
// 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 @@
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 @@
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 @@
// 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 @@
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 1ecfa8d..4a22a21 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -219,21 +219,27 @@
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 6853179..7052685 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -125,6 +125,12 @@
// 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 0000000..11f368e
--- /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<opt::Instruction*> 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<uint32_t> 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<SpvStorageClass>(
+ 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 0000000..7e8ed61
--- /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 cb90143..26961c8 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -262,44 +262,48 @@
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<google::protobuf::uint32>& 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 @@
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 292cd9f..daa836c 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -98,6 +98,21 @@
std::vector<uint32_t> RepeatedFieldToVector(
const google::protobuf::RepeatedField<uint32_t>& 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(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 8a931ba..9773b60 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -341,12 +341,32 @@
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 d41a734..52fcfd7 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -17,6 +17,7 @@
#include <cassert>
#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 @@
std::unique_ptr<Transformation> Transformation::FromMessage(
const protobufs::Transformation& message) {
switch (message.transformation_case()) {
+ case protobufs::Transformation::TransformationCase::kAccessChain:
+ return MakeUnique<TransformationAccessChain>(message.access_chain());
case protobufs::Transformation::TransformationCase::kAddConstantBoolean:
return MakeUnique<TransformationAddConstantBoolean>(
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 0000000..8c31006
--- /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 <vector>
+
+#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<uint32_t>& 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<bool, uint32_t> 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<SpvStorageClass>(
+ 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<SpvStorageClass>(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<opt::Instruction>(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<bool, uint32_t> 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 0000000..92d9e6a
--- /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 <utility>
+
+#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<uint32_t>& 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<bool, uint32_t> 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 4a423a9..4211ff2 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -24,6 +24,7 @@
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 0000000..516d371
--- /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