From 1f03ac10270a7ba5cd1b5b1969cab298503598c8 Mon Sep 17 00:00:00 2001 From: Alastair Donaldson Date: Wed, 5 Feb 2020 21:07:44 +0000 Subject: [PATCH] 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. --- source/fuzz/CMakeLists.txt | 6 + source/fuzz/fuzzer.cpp | 8 + source/fuzz/fuzzer_context.cpp | 6 + source/fuzz/fuzzer_context.h | 8 + source/fuzz/fuzzer_pass.cpp | 181 +++++++++++++++ source/fuzz/fuzzer_pass.h | 73 +++++++ .../fuzz/fuzzer_pass_add_global_variables.cpp | 75 +++++++ .../fuzz/fuzzer_pass_add_global_variables.h | 40 ++++ .../fuzz/fuzzer_pass_add_local_variables.cpp | 79 +++++++ source/fuzz/fuzzer_pass_add_local_variables.h | 43 ++++ source/fuzz/fuzzer_util.cpp | 9 + source/fuzz/fuzzer_util.h | 4 + source/fuzz/protobufs/spvtoolsfuzz.proto | 34 ++- source/fuzz/transformation.cpp | 4 + .../transformation_add_local_variable.cpp | 98 +++++++++ .../fuzz/transformation_add_local_variable.h | 60 +++++ test/fuzz/CMakeLists.txt | 1 + ...transformation_add_local_variable_test.cpp | 206 ++++++++++++++++++ 18 files changed, 930 insertions(+), 5 deletions(-) create mode 100644 source/fuzz/fuzzer_pass_add_global_variables.cpp create mode 100644 source/fuzz/fuzzer_pass_add_global_variables.h create mode 100644 source/fuzz/fuzzer_pass_add_local_variables.cpp create mode 100644 source/fuzz/fuzzer_pass_add_local_variables.h create mode 100644 source/fuzz/transformation_add_local_variable.cpp create mode 100644 source/fuzz/transformation_add_local_variable.h create mode 100644 test/fuzz/transformation_add_local_variable_test.cpp diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index c816f8757..b3c197034 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -40,6 +40,8 @@ if(SPIRV_BUILD_FUZZER) 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 @@ if(SPIRV_BUILD_FUZZER) 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 @@ if(SPIRV_BUILD_FUZZER) 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 @@ if(SPIRV_BUILD_FUZZER) 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 27b697c94..8caa85303 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 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); + MaybeAddPass(&passes, ir_context.get(), + &fact_manager, &fuzzer_context, + transformation_sequence_out); + MaybeAddPass(&passes, ir_context.get(), + &fact_manager, &fuzzer_context, + transformation_sequence_out); MaybeAddPass(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp index 916803a7b..0fb275851 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -29,6 +29,8 @@ const std::pair kChanceOfAddingArrayOrStructType = {20, 90}; const std::pair kChanceOfAddingDeadBlock = {20, 90}; const std::pair kChanceOfAddingDeadBreak = {5, 80}; const std::pair kChanceOfAddingDeadContinue = {5, 80}; +const std::pair kChanceOfAddingGlobalVariable = {20, 90}; +const std::pair kChanceOfAddingLocalVariable = {20, 90}; const std::pair kChanceOfAddingMatrixType = {20, 70}; const std::pair kChanceOfAddingNoContractionDecoration = { 5, 70}; @@ -88,6 +90,10 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, 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 d4d6d58fb..2c48ac5a1 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -69,6 +69,12 @@ class FuzzerContext { 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 @@ class FuzzerContext { 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 9964a6c3f..40eb3bd39 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 @@ uint32_t FuzzerPass::FindOrCreate32BitIntegerConstant(uint32_t word, 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 @@ uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) { return result; } +std::pair, std::map>> +FuzzerPass::GetAvailableBaseTypesAndPointers( + SpvStorageClass storage_class) const { + // Records all of the base types available in the module. + std::vector 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> 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 field_zero_constants; + std::vector 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& constants, + const std::vector& 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 composite_constant; + if (composite_type->AsArray()) { + composite_constant = MakeUnique( + composite_type->AsArray(), constants); + } else if (composite_type->AsMatrix()) { + composite_constant = MakeUnique( + composite_type->AsMatrix(), constants); + } else if (composite_type->AsStruct()) { + composite_constant = MakeUnique( + composite_type->AsStruct(), constants); + } else if (composite_type->AsVector()) { + composite_constant = MakeUnique( + 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 zero_constants; + std::vector 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 e1e8aec25..4b78f29a7 100644 --- a/source/fuzz/fuzzer_pass.h +++ b/source/fuzz/fuzzer_pass.h @@ -138,12 +138,85 @@ class FuzzerPass { // 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::map>> + 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& constants, + const std::vector& 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 000000000..1371f46c6 --- /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& 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 000000000..c71d14774 --- /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 000000000..8d6d80d63 --- /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& 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 000000000..ef002fb00 --- /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 +#include + +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 f9f9969cd..b81b17dab 100644 --- a/source/fuzz/fuzzer_util.cpp +++ b/source/fuzz/fuzzer_util.cpp @@ -391,6 +391,15 @@ uint32_t FindFunctionType(opt::IRContext* ir_context, 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 f0a2953fd..1cbc59fcd 100644 --- a/source/fuzz/fuzzer_util.h +++ b/source/fuzz/fuzzer_util.h @@ -137,6 +137,10 @@ bool IsMergeOrContinue(opt::IRContext* ir_context, uint32_t block_id); uint32_t FindFunctionType(opt::IRContext* ir_context, const std::vector& 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 52a3a788c..67b362a81 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -339,6 +339,7 @@ message Transformation { 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 @@ message TransformationAddGlobalVariable { // 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 8037af15e..1ed431839 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 @@ std::unique_ptr Transformation::FromMessage( case protobufs::Transformation::TransformationCase::kAddGlobalVariable: return MakeUnique( message.add_global_variable()); + case protobufs::Transformation::TransformationCase::kAddLocalVariable: + return MakeUnique( + message.add_local_variable()); case protobufs::Transformation::TransformationCase:: kAddNoContractionDecoration: return MakeUnique( diff --git a/source/fuzz/transformation_add_local_variable.cpp b/source/fuzz/transformation_add_local_variable.cpp new file mode 100644 index 000000000..cdaea53bb --- /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( + 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 000000000..6a97b7152 --- /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 732d9fed0..d371326c1 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -33,6 +33,7 @@ if (${SPIRV_BUILD_FUZZER}) 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 000000000..465af41f1 --- /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