spirv-fuzz: Fuzzer passes to add local and global variables (#3175)
Adds two new fuzzer passes to add variables to a module: one that adds
Private storage class global variables, another that adds Function
storage class local variables.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index c816f87..b3c1970 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -40,6 +40,8 @@
fuzzer_pass_add_dead_blocks.h
fuzzer_pass_add_dead_breaks.h
fuzzer_pass_add_dead_continues.h
+ fuzzer_pass_add_global_variables.h
+ fuzzer_pass_add_local_variables.h
fuzzer_pass_add_no_contraction_decorations.h
fuzzer_pass_add_useful_constructs.h
fuzzer_pass_adjust_function_controls.h
@@ -74,6 +76,7 @@
transformation_add_function.h
transformation_add_global_undef.h
transformation_add_global_variable.h
+ transformation_add_local_variable.h
transformation_add_no_contraction_decoration.h
transformation_add_type_array.h
transformation_add_type_boolean.h
@@ -112,6 +115,8 @@
fuzzer_pass_add_dead_blocks.cpp
fuzzer_pass_add_dead_breaks.cpp
fuzzer_pass_add_dead_continues.cpp
+ fuzzer_pass_add_global_variables.cpp
+ fuzzer_pass_add_local_variables.cpp
fuzzer_pass_add_no_contraction_decorations.cpp
fuzzer_pass_add_useful_constructs.cpp
fuzzer_pass_adjust_function_controls.cpp
@@ -145,6 +150,7 @@
transformation_add_function.cpp
transformation_add_global_undef.cpp
transformation_add_global_variable.cpp
+ transformation_add_local_variable.cpp
transformation_add_no_contraction_decoration.cpp
transformation_add_type_array.cpp
transformation_add_type_boolean.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 27b697c..8caa853 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -25,6 +25,8 @@
#include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
#include "source/fuzz/fuzzer_pass_add_dead_continues.h"
+#include "source/fuzz/fuzzer_pass_add_global_variables.h"
+#include "source/fuzz/fuzzer_pass_add_local_variables.h"
#include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
#include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
#include "source/fuzz/fuzzer_pass_adjust_function_controls.h"
@@ -191,6 +193,12 @@
MaybeAddPass<FuzzerPassAddDeadContinues>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
+ MaybeAddPass<FuzzerPassAddGlobalVariables>(&passes, ir_context.get(),
+ &fact_manager, &fuzzer_context,
+ transformation_sequence_out);
+ MaybeAddPass<FuzzerPassAddLocalVariables>(&passes, ir_context.get(),
+ &fact_manager, &fuzzer_context,
+ transformation_sequence_out);
MaybeAddPass<FuzzerPassApplyIdSynonyms>(&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 916803a..0fb2758 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -29,6 +29,8 @@
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBlock = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80};
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadContinue = {5, 80};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingGlobalVariable = {20, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingLocalVariable = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingMatrixType = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = {
5, 70};
@@ -88,6 +90,10 @@
ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak);
chance_of_adding_dead_continue_ =
ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue);
+ chance_of_adding_global_variable_ =
+ ChooseBetweenMinAndMax(kChanceOfAddingGlobalVariable);
+ chance_of_adding_local_variable_ =
+ ChooseBetweenMinAndMax(kChanceOfAddingLocalVariable);
chance_of_adding_matrix_type_ =
ChooseBetweenMinAndMax(kChanceOfAddingMatrixType);
chance_of_adding_no_contraction_decoration_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index d4d6d58..2c48ac5 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -69,6 +69,12 @@
uint32_t GetChanceOfAddingDeadContinue() {
return chance_of_adding_dead_continue_;
}
+ uint32_t GetChanceOfAddingGlobalVariable() {
+ return chance_of_adding_global_variable_;
+ }
+ uint32_t GetChanceOfAddingLocalVariable() {
+ return chance_of_adding_local_variable_;
+ }
uint32_t GetChanceOfAddingMatrixType() {
return chance_of_adding_matrix_type_;
}
@@ -148,6 +154,8 @@
uint32_t chance_of_adding_dead_block_;
uint32_t chance_of_adding_dead_break_;
uint32_t chance_of_adding_dead_continue_;
+ uint32_t chance_of_adding_global_variable_;
+ uint32_t chance_of_adding_local_variable_;
uint32_t chance_of_adding_matrix_type_;
uint32_t chance_of_adding_no_contraction_decoration_;
uint32_t chance_of_adding_vector_type_;
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index 9964a6c..40eb3bd 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -14,7 +14,10 @@
#include "source/fuzz/fuzzer_pass.h"
+#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.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"
#include "source/fuzz/transformation_add_global_undef.h"
#include "source/fuzz/transformation_add_type_boolean.h"
@@ -243,6 +246,42 @@
return result;
}
+uint32_t FuzzerPass::FindOrCreate32BitFloatConstant(uint32_t word) {
+ auto float_type_id = FindOrCreate32BitFloatType();
+ opt::analysis::FloatConstant float_constant(
+ GetIRContext()->get_type_mgr()->GetType(float_type_id)->AsFloat(),
+ {word});
+ auto existing_constant =
+ GetIRContext()->get_constant_mgr()->FindConstant(&float_constant);
+ if (existing_constant) {
+ return GetIRContext()
+ ->get_constant_mgr()
+ ->GetDefiningInstruction(existing_constant)
+ ->result_id();
+ }
+ auto result = GetFuzzerContext()->GetFreshId();
+ ApplyTransformation(
+ TransformationAddConstantScalar(result, float_type_id, {word}));
+ return result;
+}
+
+uint32_t FuzzerPass::FindOrCreateBoolConstant(bool value) {
+ auto bool_type_id = FindOrCreateBoolType();
+ opt::analysis::BoolConstant bool_constant(
+ GetIRContext()->get_type_mgr()->GetType(bool_type_id)->AsBool(), value);
+ auto existing_constant =
+ GetIRContext()->get_constant_mgr()->FindConstant(&bool_constant);
+ if (existing_constant) {
+ return GetIRContext()
+ ->get_constant_mgr()
+ ->GetDefiningInstruction(existing_constant)
+ ->result_id();
+ }
+ auto result = GetFuzzerContext()->GetFreshId();
+ ApplyTransformation(TransformationAddConstantBoolean(result, value));
+ return result;
+}
+
uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) {
for (auto& inst : GetIRContext()->types_values()) {
if (inst.opcode() == SpvOpUndef && inst.type_id() == type_id) {
@@ -254,5 +293,147 @@
return result;
}
+std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
+FuzzerPass::GetAvailableBaseTypesAndPointers(
+ SpvStorageClass storage_class) const {
+ // Records all of the base types available in the module.
+ std::vector<uint32_t> base_types;
+
+ // For each base type, records all the associated pointer types that target
+ // that base type and that have |storage_class| as their storage class.
+ std::map<uint32_t, std::vector<uint32_t>> base_type_to_pointers;
+
+ for (auto& inst : GetIRContext()->types_values()) {
+ switch (inst.opcode()) {
+ case SpvOpTypeArray:
+ case SpvOpTypeBool:
+ case SpvOpTypeFloat:
+ case SpvOpTypeInt:
+ case SpvOpTypeMatrix:
+ case SpvOpTypeStruct:
+ case SpvOpTypeVector:
+ // These types are suitable as pointer base types. Record the type,
+ // and the fact that we cannot yet have seen any pointers that use this
+ // as its base type.
+ base_types.push_back(inst.result_id());
+ base_type_to_pointers.insert({inst.result_id(), {}});
+ break;
+ case SpvOpTypePointer:
+ if (inst.GetSingleWordInOperand(0) == storage_class) {
+ // The pointer has the desired storage class, so we are interested in
+ // it. Associate it with its base type.
+ base_type_to_pointers.at(inst.GetSingleWordInOperand(1))
+ .push_back(inst.result_id());
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return {base_types, base_type_to_pointers};
+}
+
+uint32_t FuzzerPass::FindOrCreateZeroConstant(
+ uint32_t scalar_or_composite_type_id) {
+ auto type_instruction =
+ GetIRContext()->get_def_use_mgr()->GetDef(scalar_or_composite_type_id);
+ assert(type_instruction && "The type instruction must exist.");
+ switch (type_instruction->opcode()) {
+ case SpvOpTypeBool:
+ return FindOrCreateBoolConstant(false);
+ case SpvOpTypeFloat:
+ return FindOrCreate32BitFloatConstant(0);
+ case SpvOpTypeInt:
+ return FindOrCreate32BitIntegerConstant(
+ 0, type_instruction->GetSingleWordInOperand(1) != 0);
+ case SpvOpTypeArray: {
+ return GetZeroConstantForHomogeneousComposite(
+ *type_instruction, type_instruction->GetSingleWordInOperand(0),
+ fuzzerutil::GetArraySize(*type_instruction, GetIRContext()));
+ }
+ case SpvOpTypeMatrix:
+ case SpvOpTypeVector: {
+ return GetZeroConstantForHomogeneousComposite(
+ *type_instruction, type_instruction->GetSingleWordInOperand(0),
+ type_instruction->GetSingleWordInOperand(1));
+ }
+ case SpvOpTypeStruct: {
+ std::vector<const opt::analysis::Constant*> field_zero_constants;
+ std::vector<uint32_t> field_zero_ids;
+ for (uint32_t index = 0; index < type_instruction->NumInOperands();
+ index++) {
+ uint32_t field_constant_id = FindOrCreateZeroConstant(
+ type_instruction->GetSingleWordInOperand(index));
+ field_zero_ids.push_back(field_constant_id);
+ field_zero_constants.push_back(
+ GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
+ field_constant_id));
+ }
+ return FindOrCreateCompositeConstant(
+ *type_instruction, field_zero_constants, field_zero_ids);
+ }
+ default:
+ assert(false && "Unknown type.");
+ return 0;
+ }
+}
+
+uint32_t FuzzerPass::FindOrCreateCompositeConstant(
+ const opt::Instruction& composite_type_instruction,
+ const std::vector<const opt::analysis::Constant*>& constants,
+ const std::vector<uint32_t>& constant_ids) {
+ assert(constants.size() == constant_ids.size() &&
+ "Precondition: |constants| and |constant_ids| must be in "
+ "correspondence.");
+
+ opt::analysis::Type* composite_type = GetIRContext()->get_type_mgr()->GetType(
+ composite_type_instruction.result_id());
+ std::unique_ptr<opt::analysis::Constant> composite_constant;
+ if (composite_type->AsArray()) {
+ composite_constant = MakeUnique<opt::analysis::ArrayConstant>(
+ composite_type->AsArray(), constants);
+ } else if (composite_type->AsMatrix()) {
+ composite_constant = MakeUnique<opt::analysis::MatrixConstant>(
+ composite_type->AsMatrix(), constants);
+ } else if (composite_type->AsStruct()) {
+ composite_constant = MakeUnique<opt::analysis::StructConstant>(
+ composite_type->AsStruct(), constants);
+ } else if (composite_type->AsVector()) {
+ composite_constant = MakeUnique<opt::analysis::VectorConstant>(
+ composite_type->AsVector(), constants);
+ } else {
+ assert(false &&
+ "Precondition: |composite_type| must declare a composite type.");
+ return 0;
+ }
+
+ uint32_t existing_constant =
+ GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
+ composite_constant.get(), composite_type_instruction.result_id());
+ if (existing_constant) {
+ return existing_constant;
+ }
+ uint32_t result = GetFuzzerContext()->GetFreshId();
+ ApplyTransformation(TransformationAddConstantComposite(
+ result, composite_type_instruction.result_id(), constant_ids));
+ return result;
+}
+
+uint32_t FuzzerPass::GetZeroConstantForHomogeneousComposite(
+ const opt::Instruction& composite_type_instruction,
+ uint32_t component_type_id, uint32_t num_components) {
+ std::vector<const opt::analysis::Constant*> zero_constants;
+ std::vector<uint32_t> zero_ids;
+ uint32_t zero_component = FindOrCreateZeroConstant(component_type_id);
+ const opt::analysis::Constant* registered_zero_component =
+ GetIRContext()->get_constant_mgr()->FindDeclaredConstant(zero_component);
+ for (uint32_t i = 0; i < num_components; i++) {
+ zero_constants.push_back(registered_zero_component);
+ zero_ids.push_back(zero_component);
+ }
+ return FindOrCreateCompositeConstant(composite_type_instruction,
+ zero_constants, zero_ids);
+}
+
} // namespace fuzz
} // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index e1e8aec..4b78f29 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -138,12 +138,85 @@
// applied to add them.
uint32_t FindOrCreate32BitIntegerConstant(uint32_t word, bool is_signed);
+ // Returns the id of an OpConstant instruction, with 32-bit floating-point
+ // type, with |word| as its value. If either the required floating-point type
+ // or the constant do not exist, transformations are applied to add them.
+ uint32_t FindOrCreate32BitFloatConstant(uint32_t word);
+
+ // Returns the id of an OpConstantTrue or OpConstantFalse instruction,
+ // according to |value|. If either the required instruction or the bool
+ // type do not exist, transformations are applied to add them.
+ uint32_t FindOrCreateBoolConstant(bool value);
+
// Returns the result id of an instruction of the form:
// %id = OpUndef %|type_id|
// If no such instruction exists, a transformation is applied to add it.
uint32_t FindOrCreateGlobalUndef(uint32_t type_id);
+ // Yields a pair, (base_type_ids, base_type_ids_to_pointers), such that:
+ // - base_type_ids captures every scalar or composite type declared in the
+ // module (i.e., all int, bool, float, vector, matrix, struct and array
+ // types
+ // - base_type_ids_to_pointers maps every such base type to the sequence
+ // of all pointer types that have storage class |storage_class| and the
+ // given base type as their pointee type. The sequence may be empty for
+ // some base types if no pointers to those types are defined for the given
+ // storage class, and the sequence will have multiple elements if there are
+ // repeated pointer declarations for the same base type and storage class.
+ std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
+ GetAvailableBaseTypesAndPointers(SpvStorageClass storage_class) const;
+
+ // Given a type id, |scalar_or_composite_type_id|, which must correspond to
+ // some scalar or composite type, returns the result id of an instruction
+ // defining a constant of the given type that is zero or false at everywhere.
+ // If such an instruction does not yet exist, transformations are applied to
+ // add it.
+ //
+ // Examples:
+ // --------------+-------------------------------
+ // TYPE | RESULT is id corresponding to
+ // --------------+-------------------------------
+ // bool | false
+ // --------------+-------------------------------
+ // bvec4 | (false, false, false, false)
+ // --------------+-------------------------------
+ // float | 0.0
+ // --------------+-------------------------------
+ // vec2 | (0.0, 0.0)
+ // --------------+-------------------------------
+ // int[3] | [0, 0, 0]
+ // --------------+-------------------------------
+ // struct S { |
+ // int i; | S(0, false, (0u, 0u))
+ // bool b; |
+ // uint2 u; |
+ // } |
+ // --------------+-------------------------------
+ uint32_t FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id);
+
private:
+ // Array, matrix and vector are *homogeneous* composite types in the sense
+ // that every component of one of these types has the same type. Given a
+ // homogeneous composite type instruction, |composite_type_instruction|,
+ // returns the id of a composite constant instruction for which every element
+ // is zero/false. If such an instruction does not yet exist, transformations
+ // are applied to add it.
+ uint32_t GetZeroConstantForHomogeneousComposite(
+ const opt::Instruction& composite_type_instruction,
+ uint32_t component_type_id, uint32_t num_components);
+
+ // Helper to find an existing composite constant instruction of the given
+ // composite type with the given constant components, or to apply
+ // transformations to create such an instruction if it does not yet exist.
+ // Parameter |composite_type_instruction| must be a composite type
+ // instruction. The parameters |constants| and |constant_ids| must have the
+ // same size, and it must be the case that for each i, |constant_ids[i]| is
+ // the result id of an instruction that defines |constants[i]|.
+ uint32_t FindOrCreateCompositeConstant(
+ const opt::Instruction& composite_type_instruction,
+ const std::vector<const opt::analysis::Constant*>& constants,
+ const std::vector<uint32_t>& constant_ids);
+
opt::IRContext* ir_context_;
FactManager* fact_manager_;
FuzzerContext* fuzzer_context_;
diff --git a/source/fuzz/fuzzer_pass_add_global_variables.cpp b/source/fuzz/fuzzer_pass_add_global_variables.cpp
new file mode 100644
index 0000000..1371f46
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_global_variables.cpp
@@ -0,0 +1,75 @@
+// 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_global_variables.h"
+
+#include "source/fuzz/transformation_add_global_variable.h"
+#include "source/fuzz/transformation_add_type_pointer.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddGlobalVariables::FuzzerPassAddGlobalVariables(
+ opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations)
+ : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassAddGlobalVariables::~FuzzerPassAddGlobalVariables() = default;
+
+void FuzzerPassAddGlobalVariables::Apply() {
+ auto base_type_ids_and_pointers =
+ GetAvailableBaseTypesAndPointers(SpvStorageClassPrivate);
+
+ // These are the base types that are available to this fuzzer pass.
+ auto& base_types = base_type_ids_and_pointers.first;
+
+ // These are the pointers to those base types that are *initially* available
+ // to the fuzzer pass. The fuzzer pass might add pointer types in cases where
+ // none are available for a given base type.
+ auto& base_type_to_pointers = base_type_ids_and_pointers.second;
+
+ // Probabilistically keep adding global variables.
+ while (GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()->GetChanceOfAddingGlobalVariable())) {
+ // Choose a random base type; the new variable's type will be a pointer to
+ // this base type.
+ uint32_t base_type =
+ base_types[GetFuzzerContext()->RandomIndex(base_types)];
+ uint32_t pointer_type_id;
+ std::vector<uint32_t>& available_pointers_to_base_type =
+ base_type_to_pointers.at(base_type);
+ // Determine whether there is at least one pointer to this base type.
+ if (available_pointers_to_base_type.empty()) {
+ // There is not. Make one, to use here, and add it to the available
+ // pointers for the base type so that future variables can potentially
+ // use it.
+ pointer_type_id = GetFuzzerContext()->GetFreshId();
+ available_pointers_to_base_type.push_back(pointer_type_id);
+ ApplyTransformation(TransformationAddTypePointer(
+ pointer_type_id, SpvStorageClassPrivate, base_type));
+ } else {
+ // There is - grab one.
+ pointer_type_id =
+ available_pointers_to_base_type[GetFuzzerContext()->RandomIndex(
+ available_pointers_to_base_type)];
+ }
+ ApplyTransformation(TransformationAddGlobalVariable(
+ GetFuzzerContext()->GetFreshId(), pointer_type_id,
+ FindOrCreateZeroConstant(base_type), true));
+ }
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_global_variables.h b/source/fuzz/fuzzer_pass_add_global_variables.h
new file mode 100644
index 0000000..c71d147
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_global_variables.h
@@ -0,0 +1,40 @@
+// 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_GLOBAL_VARIABLES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_GLOBAL_VARIABLES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that randomly adds global variables, with Private storage class,
+// to the module.
+class FuzzerPassAddGlobalVariables : public FuzzerPass {
+ public:
+ FuzzerPassAddGlobalVariables(
+ opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations);
+
+ ~FuzzerPassAddGlobalVariables();
+
+ void Apply() override;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_GLOBAL_VARIABLES_H_
diff --git a/source/fuzz/fuzzer_pass_add_local_variables.cpp b/source/fuzz/fuzzer_pass_add_local_variables.cpp
new file mode 100644
index 0000000..8d6d80d
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_local_variables.cpp
@@ -0,0 +1,79 @@
+// 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_local_variables.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_add_local_variable.h"
+#include "source/fuzz/transformation_add_type_pointer.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddLocalVariables::FuzzerPassAddLocalVariables(
+ opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations)
+ : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassAddLocalVariables::~FuzzerPassAddLocalVariables() = default;
+
+void FuzzerPassAddLocalVariables::Apply() {
+ auto base_type_ids_and_pointers =
+ GetAvailableBaseTypesAndPointers(SpvStorageClassFunction);
+
+ // These are the base types that are available to this fuzzer pass.
+ auto& base_types = base_type_ids_and_pointers.first;
+
+ // These are the pointers to those base types that are *initially* available
+ // to the fuzzer pass. The fuzzer pass might add pointer types in cases where
+ // none are available for a given base type.
+ auto& base_type_to_pointers = base_type_ids_and_pointers.second;
+
+ // Consider every function in the module.
+ for (auto& function : *GetIRContext()->module()) {
+ // Probabilistically keep adding random variables to this function.
+ while (GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()->GetChanceOfAddingLocalVariable())) {
+ // Choose a random base type; the new variable's type will be a pointer to
+ // this base type.
+ uint32_t base_type =
+ base_types[GetFuzzerContext()->RandomIndex(base_types)];
+ uint32_t pointer_type;
+ std::vector<uint32_t>& available_pointers_to_base_type =
+ base_type_to_pointers.at(base_type);
+ // Determine whether there is at least one pointer to this base type.
+ if (available_pointers_to_base_type.empty()) {
+ // There is not. Make one, to use here, and add it to the available
+ // pointers for the base type so that future variables can potentially
+ // use it.
+ pointer_type = GetFuzzerContext()->GetFreshId();
+ ApplyTransformation(TransformationAddTypePointer(
+ pointer_type, SpvStorageClassFunction, base_type));
+ available_pointers_to_base_type.push_back(pointer_type);
+ } else {
+ // There is - grab one.
+ pointer_type =
+ available_pointers_to_base_type[GetFuzzerContext()->RandomIndex(
+ available_pointers_to_base_type)];
+ }
+ ApplyTransformation(TransformationAddLocalVariable(
+ GetFuzzerContext()->GetFreshId(), pointer_type, function.result_id(),
+ FindOrCreateZeroConstant(base_type), true));
+ }
+ }
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_local_variables.h b/source/fuzz/fuzzer_pass_add_local_variables.h
new file mode 100644
index 0000000..ef002fb
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_local_variables.h
@@ -0,0 +1,43 @@
+// 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_LOCAL_VARIABLES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOCAL_VARIABLES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+#include <utility>
+#include <vector>
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that randomly adds local variables, with Function storage class,
+// to the module.
+class FuzzerPassAddLocalVariables : public FuzzerPass {
+ public:
+ FuzzerPassAddLocalVariables(
+ opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations);
+
+ ~FuzzerPassAddLocalVariables();
+
+ void Apply() override;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_LOCAL_VARIABLES_H_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index f9f9969..b81b17d 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -391,6 +391,15 @@
return 0;
}
+opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id) {
+ for (auto& function : *ir_context->module()) {
+ if (function.result_id() == function_id) {
+ return &function;
+ }
+ }
+ return nullptr;
+}
+
} // namespace fuzzerutil
} // namespace fuzz
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index f0a2953..1cbc59f 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -137,6 +137,10 @@
uint32_t FindFunctionType(opt::IRContext* ir_context,
const std::vector<uint32_t>& type_ids);
+// Returns the function with result id |function_id|, or |nullptr| if no such
+// function exists.
+opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id);
+
} // namespace fuzzerutil
} // namespace fuzz
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 52a3a78..67b362a 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -339,6 +339,7 @@
TransformationAddGlobalUndef add_global_undef = 32;
TransformationAddFunction add_function = 33;
TransformationAddDeadBlock add_dead_block = 34;
+ TransformationAddLocalVariable add_local_variable = 35;
// Add additional option using the next available number.
}
}
@@ -507,15 +508,38 @@
// Optional initializer; 0 if there is no initializer
uint32 initializer_id = 3;
- // True if and only if the value of the variable should be regarded, in
- // general, as totally unknown and subject to change (even if, due to an
- // initializer, the original value is known). This is the case for variables
- // added when a module is donated, for example, and means that stores to such
- // variables can be performed in an arbitrary fashion.
+ // True if and only if the behaviour of the module should not depend on the
+ // value of the variable, in which case stores to the variable can be
+ // performed in an arbitrary fashion.
bool value_is_arbitrary = 4;
}
+message TransformationAddLocalVariable {
+
+ // Adds a local variable of the given type (which must be a pointer with
+ // Function storage class) to the given function, initialized to the given
+ // id.
+
+ // Fresh id for the local variable
+ uint32 fresh_id = 1;
+
+ // The type of the local variable
+ uint32 type_id = 2;
+
+ // The id of the function to which the local variable should be added
+ uint32 function_id = 3;
+
+ // Initial value of the variable
+ uint32 initializer_id = 4;
+
+ // True if and only if the behaviour of the module should not depend on the
+ // value of the variable, in which case stores to the variable can be
+ // performed in an arbitrary fashion.
+ bool value_is_arbitrary = 5;
+
+}
+
message TransformationAddNoContractionDecoration {
// Applies OpDecorate NoContraction to the given result id
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 8037af1..1ed4318 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -26,6 +26,7 @@
#include "source/fuzz/transformation_add_function.h"
#include "source/fuzz/transformation_add_global_undef.h"
#include "source/fuzz/transformation_add_global_variable.h"
+#include "source/fuzz/transformation_add_local_variable.h"
#include "source/fuzz/transformation_add_no_contraction_decoration.h"
#include "source/fuzz/transformation_add_type_array.h"
#include "source/fuzz/transformation_add_type_boolean.h"
@@ -85,6 +86,9 @@
case protobufs::Transformation::TransformationCase::kAddGlobalVariable:
return MakeUnique<TransformationAddGlobalVariable>(
message.add_global_variable());
+ case protobufs::Transformation::TransformationCase::kAddLocalVariable:
+ return MakeUnique<TransformationAddLocalVariable>(
+ message.add_local_variable());
case protobufs::Transformation::TransformationCase::
kAddNoContractionDecoration:
return MakeUnique<TransformationAddNoContractionDecoration>(
diff --git a/source/fuzz/transformation_add_local_variable.cpp b/source/fuzz/transformation_add_local_variable.cpp
new file mode 100644
index 0000000..cdaea53
--- /dev/null
+++ b/source/fuzz/transformation_add_local_variable.cpp
@@ -0,0 +1,98 @@
+// 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_add_local_variable.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddLocalVariable::TransformationAddLocalVariable(
+ const spvtools::fuzz::protobufs::TransformationAddLocalVariable& message)
+ : message_(message) {}
+
+TransformationAddLocalVariable::TransformationAddLocalVariable(
+ uint32_t fresh_id, uint32_t type_id, uint32_t function_id,
+ uint32_t initializer_id, bool value_is_arbitrary) {
+ message_.set_fresh_id(fresh_id);
+ message_.set_type_id(type_id);
+ message_.set_function_id(function_id);
+ message_.set_initializer_id(initializer_id);
+ message_.set_value_is_arbitrary(value_is_arbitrary);
+}
+
+bool TransformationAddLocalVariable::IsApplicable(
+ opt::IRContext* context,
+ const spvtools::fuzz::FactManager& /*unused*/) const {
+ // The provided id must be fresh.
+ if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+ return false;
+ }
+ // The pointer type id must indeed correspond to a pointer, and it must have
+ // function storage class.
+ auto type_instruction =
+ context->get_def_use_mgr()->GetDef(message_.type_id());
+ if (!type_instruction || type_instruction->opcode() != SpvOpTypePointer ||
+ type_instruction->GetSingleWordInOperand(0) != SpvStorageClassFunction) {
+ return false;
+ }
+ // The initializer must...
+ auto initializer_instruction =
+ context->get_def_use_mgr()->GetDef(message_.initializer_id());
+ // ... exist, ...
+ if (!initializer_instruction) {
+ return false;
+ }
+ // ... be a constant, ...
+ if (!spvOpcodeIsConstant(initializer_instruction->opcode())) {
+ return false;
+ }
+ // ... and have the same type as the pointee type.
+ if (initializer_instruction->type_id() !=
+ type_instruction->GetSingleWordInOperand(1)) {
+ return false;
+ }
+ // The function to which the local variable is to be added must exist.
+ return fuzzerutil::FindFunction(context, message_.function_id());
+}
+
+void TransformationAddLocalVariable::Apply(
+ opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const {
+ fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+ fuzzerutil::FindFunction(context, message_.function_id())
+ ->begin()
+ ->begin()
+ ->InsertBefore(MakeUnique<opt::Instruction>(
+ context, SpvOpVariable, message_.type_id(), message_.fresh_id(),
+ opt::Instruction::OperandList(
+ {{SPV_OPERAND_TYPE_STORAGE_CLASS,
+ {
+
+ SpvStorageClassFunction}},
+ {SPV_OPERAND_TYPE_ID, {message_.initializer_id()}}})));
+ if (message_.value_is_arbitrary()) {
+ fact_manager->AddFactValueOfVariableIsArbitrary(message_.fresh_id());
+ }
+ context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddLocalVariable::ToMessage() const {
+ protobufs::Transformation result;
+ *result.mutable_add_local_variable() = message_;
+ return result;
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/transformation_add_local_variable.h b/source/fuzz/transformation_add_local_variable.h
new file mode 100644
index 0000000..6a97b71
--- /dev/null
+++ b/source/fuzz/transformation_add_local_variable.h
@@ -0,0 +1,60 @@
+// 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_ADD_LOCAL_VARIABLE_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_LOCAL_VARIABLE_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationAddLocalVariable : public Transformation {
+ public:
+ explicit TransformationAddLocalVariable(
+ const protobufs::TransformationAddLocalVariable& message);
+
+ TransformationAddLocalVariable(uint32_t fresh_id, uint32_t type_id,
+ uint32_t function_id, uint32_t initializer_id,
+ bool value_is_arbitrary);
+
+ // - |message_.fresh_id| must not be used by the module
+ // - |message_.type_id| must be the id of a pointer type with Function
+ // storage class
+ // - |message_.initializer_id| must be the id of a constant with the same
+ // type as the pointer's pointee type
+ // - |message_.function_id| must be the id of a function
+ bool IsApplicable(opt::IRContext* context,
+ const FactManager& fact_manager) const override;
+
+ // Adds an instruction to the start of |message_.function_id|, of the form:
+ // |message_.fresh_id| = OpVariable |message_.type_id| Function
+ // |message_.initializer_id|
+ // If |message_.value_is_arbitrary| holds, adds a corresponding fact to
+ // |fact_manager|.
+ void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+ protobufs::Transformation ToMessage() const override;
+
+ private:
+ protobufs::TransformationAddLocalVariable message_;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_LOCAL_VARIABLE_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 732d9fe..d371326 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -33,6 +33,7 @@
transformation_add_function_test.cpp
transformation_add_global_undef_test.cpp
transformation_add_global_variable_test.cpp
+ transformation_add_local_variable_test.cpp
transformation_add_no_contraction_decoration_test.cpp
transformation_add_type_array_test.cpp
transformation_add_type_boolean_test.cpp
diff --git a/test/fuzz/transformation_add_local_variable_test.cpp b/test/fuzz/transformation_add_local_variable_test.cpp
new file mode 100644
index 0000000..465af41
--- /dev/null
+++ b/test/fuzz/transformation_add_local_variable_test.cpp
@@ -0,0 +1,206 @@
+// 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_add_local_variable.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddLocalVariableTest, BasicTest) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeStruct %6 %6
+ %8 = OpTypePointer Function %7
+ %10 = OpConstant %6 1
+ %11 = OpConstant %6 2
+ %12 = OpConstantComposite %7 %10 %11
+ %13 = OpTypeFloat 32
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 3
+ %16 = OpTypeArray %13 %15
+ %17 = OpTypeBool
+ %18 = OpTypeStruct %16 %7 %17
+ %19 = OpTypePointer Function %18
+ %21 = OpConstant %13 1
+ %22 = OpConstant %13 2
+ %23 = OpConstant %13 4
+ %24 = OpConstantComposite %16 %21 %22 %23
+ %25 = OpConstant %6 5
+ %26 = OpConstant %6 6
+ %27 = OpConstantComposite %7 %25 %26
+ %28 = OpConstantFalse %17
+ %29 = OpConstantComposite %18 %24 %27 %28
+ %30 = OpTypeVector %13 2
+ %31 = OpTypePointer Function %30
+ %33 = OpConstantComposite %30 %21 %21
+ %34 = OpTypeVector %17 3
+ %35 = OpTypePointer Function %34
+ %37 = OpConstantTrue %17
+ %38 = OpConstantComposite %34 %37 %28 %28
+ %39 = OpTypeVector %13 4
+ %40 = OpTypeMatrix %39 3
+ %41 = OpTypePointer Function %40
+ %43 = OpConstantComposite %39 %21 %22 %23 %21
+ %44 = OpConstantComposite %39 %22 %23 %21 %22
+ %45 = OpConstantComposite %39 %23 %21 %22 %23
+ %46 = OpConstantComposite %40 %43 %44 %45
+ %50 = OpTypePointer Function %14
+ %51 = OpConstantNull %14
+ %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;
+
+ // A few cases of inapplicable transformations:
+ // Id 4 is already in use
+ ASSERT_FALSE(TransformationAddLocalVariable(4, 50, 4, 51, true)
+ .IsApplicable(context.get(), fact_manager));
+ // Type mismatch between initializer and pointer
+ ASSERT_FALSE(TransformationAddLocalVariable(105, 46, 4, 51, true)
+ .IsApplicable(context.get(), fact_manager));
+ // Id 5 is not a function
+ ASSERT_FALSE(TransformationAddLocalVariable(105, 50, 5, 51, true)
+ .IsApplicable(context.get(), fact_manager));
+
+ // %105 = OpVariable %50 Function %51
+ {
+ TransformationAddLocalVariable transformation(105, 50, 4, 51, true);
+ ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+ transformation.Apply(context.get(), &fact_manager);
+ }
+
+ // %104 = OpVariable %41 Function %46
+ {
+ TransformationAddLocalVariable transformation(104, 41, 4, 46, false);
+ ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+ transformation.Apply(context.get(), &fact_manager);
+ }
+
+ // %103 = OpVariable %35 Function %38
+ {
+ TransformationAddLocalVariable transformation(103, 35, 4, 38, true);
+ ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+ transformation.Apply(context.get(), &fact_manager);
+ }
+
+ // %102 = OpVariable %31 Function %33
+ {
+ TransformationAddLocalVariable transformation(102, 31, 4, 33, false);
+ ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+ transformation.Apply(context.get(), &fact_manager);
+ }
+
+ // %101 = OpVariable %19 Function %29
+ {
+ TransformationAddLocalVariable transformation(101, 19, 4, 29, true);
+ ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+ transformation.Apply(context.get(), &fact_manager);
+ }
+
+ // %100 = OpVariable %8 Function %12
+ {
+ TransformationAddLocalVariable transformation(100, 8, 4, 12, false);
+ ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+ transformation.Apply(context.get(), &fact_manager);
+ }
+
+ ASSERT_FALSE(fact_manager.VariableValueIsArbitrary(100));
+ ASSERT_TRUE(fact_manager.VariableValueIsArbitrary(101));
+ ASSERT_FALSE(fact_manager.VariableValueIsArbitrary(102));
+ ASSERT_TRUE(fact_manager.VariableValueIsArbitrary(103));
+ ASSERT_FALSE(fact_manager.VariableValueIsArbitrary(104));
+ ASSERT_TRUE(fact_manager.VariableValueIsArbitrary(105));
+
+ std::string after_transformation = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeStruct %6 %6
+ %8 = OpTypePointer Function %7
+ %10 = OpConstant %6 1
+ %11 = OpConstant %6 2
+ %12 = OpConstantComposite %7 %10 %11
+ %13 = OpTypeFloat 32
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 3
+ %16 = OpTypeArray %13 %15
+ %17 = OpTypeBool
+ %18 = OpTypeStruct %16 %7 %17
+ %19 = OpTypePointer Function %18
+ %21 = OpConstant %13 1
+ %22 = OpConstant %13 2
+ %23 = OpConstant %13 4
+ %24 = OpConstantComposite %16 %21 %22 %23
+ %25 = OpConstant %6 5
+ %26 = OpConstant %6 6
+ %27 = OpConstantComposite %7 %25 %26
+ %28 = OpConstantFalse %17
+ %29 = OpConstantComposite %18 %24 %27 %28
+ %30 = OpTypeVector %13 2
+ %31 = OpTypePointer Function %30
+ %33 = OpConstantComposite %30 %21 %21
+ %34 = OpTypeVector %17 3
+ %35 = OpTypePointer Function %34
+ %37 = OpConstantTrue %17
+ %38 = OpConstantComposite %34 %37 %28 %28
+ %39 = OpTypeVector %13 4
+ %40 = OpTypeMatrix %39 3
+ %41 = OpTypePointer Function %40
+ %43 = OpConstantComposite %39 %21 %22 %23 %21
+ %44 = OpConstantComposite %39 %22 %23 %21 %22
+ %45 = OpConstantComposite %39 %23 %21 %22 %23
+ %46 = OpConstantComposite %40 %43 %44 %45
+ %50 = OpTypePointer Function %14
+ %51 = OpConstantNull %14
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %100 = OpVariable %8 Function %12
+ %101 = OpVariable %19 Function %29
+ %102 = OpVariable %31 Function %33
+ %103 = OpVariable %35 Function %38
+ %104 = OpVariable %41 Function %46
+ %105 = OpVariable %50 Function %51
+ OpReturn
+ OpFunctionEnd
+ )";
+ ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools