diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index db32902f..5828d209 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -66,6 +66,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_construct_composites.h fuzzer_pass_copy_objects.h fuzzer_pass_donate_modules.h + fuzzer_pass_inline_functions.h fuzzer_pass_invert_comparison_operators.h fuzzer_pass_interchange_signedness_of_integer_operands.h fuzzer_pass_interchange_zero_like_constants.h @@ -137,6 +138,7 @@ if(SPIRV_BUILD_FUZZER) transformation_context.h transformation_equation_instruction.h transformation_function_call.h + transformation_inline_function.h transformation_invert_comparison_operator.h transformation_load.h transformation_make_vector_operation_dynamic.h @@ -208,6 +210,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_construct_composites.cpp fuzzer_pass_copy_objects.cpp fuzzer_pass_donate_modules.cpp + fuzzer_pass_inline_functions.cpp fuzzer_pass_invert_comparison_operators.cpp fuzzer_pass_interchange_signedness_of_integer_operands.cpp fuzzer_pass_interchange_zero_like_constants.cpp @@ -278,6 +281,7 @@ if(SPIRV_BUILD_FUZZER) transformation_context.cpp transformation_equation_instruction.cpp transformation_function_call.cpp + transformation_inline_function.cpp transformation_invert_comparison_operator.cpp transformation_load.cpp transformation_make_vector_operation_dynamic.cpp diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index 651db36f..70e32308 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp @@ -49,6 +49,7 @@ #include "source/fuzz/fuzzer_pass_construct_composites.h" #include "source/fuzz/fuzzer_pass_copy_objects.h" #include "source/fuzz/fuzzer_pass_donate_modules.h" +#include "source/fuzz/fuzzer_pass_inline_functions.h" #include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h" #include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h" #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h" @@ -282,6 +283,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out, donor_suppliers); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp index a187daa4..ee8db862 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -69,6 +69,7 @@ const std::pair kChanceOfGoingDeeperToInsertInComposite = { 30, 70}; const std::pair kChanceOfGoingDeeperWhenMakingAccessChain = {50, 95}; +const std::pair kChanceOfInliningFunction = {10, 90}; const std::pair kChanceOfInterchangingZeroLikeConstants = { 10, 90}; const std::pair @@ -216,6 +217,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite); chance_of_going_deeper_when_making_access_chain_ = ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain); + chance_of_inlining_function_ = + ChooseBetweenMinAndMax(kChanceOfInliningFunction); chance_of_interchanging_signedness_of_integer_operands_ = ChooseBetweenMinAndMax(kChanceOfInterchangingSignednessOfIntegerOperands); chance_of_interchanging_zero_like_constants_ = diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index c5016140..9f85b134 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -195,6 +195,9 @@ class FuzzerContext { uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() { return chance_of_going_deeper_when_making_access_chain_; } + uint32_t GetChanceOfInliningFunction() { + return chance_of_inlining_function_; + } uint32_t GetChanceOfInterchangingSignednessOfIntegerOperands() { return chance_of_interchanging_signedness_of_integer_operands_; } @@ -383,6 +386,7 @@ class FuzzerContext { uint32_t chance_of_donating_additional_module_; uint32_t chance_of_going_deeper_to_insert_in_composite_; uint32_t chance_of_going_deeper_when_making_access_chain_; + uint32_t chance_of_inlining_function_; uint32_t chance_of_interchanging_signedness_of_integer_operands_; uint32_t chance_of_interchanging_zero_like_constants_; uint32_t chance_of_inverting_comparison_operators_; diff --git a/source/fuzz/fuzzer_pass_inline_functions.cpp b/source/fuzz/fuzzer_pass_inline_functions.cpp new file mode 100644 index 00000000..90160d83 --- /dev/null +++ b/source/fuzz/fuzzer_pass_inline_functions.cpp @@ -0,0 +1,104 @@ +// Copyright (c) 2020 André Perez Maselco +// +// 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_inline_functions.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_inline_function.h" +#include "source/fuzz/transformation_split_block.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassInlineFunctions::FuzzerPassInlineFunctions( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassInlineFunctions::~FuzzerPassInlineFunctions() = default; + +void FuzzerPassInlineFunctions::Apply() { + // |function_call_instructions| are the instructions that will be inlined. + // First, they will be collected and then do the inlining in another loop. + // This avoids changing the module while it is being inspected. + std::vector function_call_instructions; + + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + for (auto& instruction : block) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfInliningFunction())) { + continue; + } + + // |instruction| must be suitable for inlining. + if (!TransformationInlineFunction::IsSuitableForInlining( + GetIRContext(), &instruction)) { + continue; + } + + function_call_instructions.push_back(&instruction); + } + } + } + + // Once the function calls have been collected, it's time to actually create + // and apply the inlining transformations. + for (auto& function_call_instruction : function_call_instructions) { + // If |function_call_instruction| is not the penultimate instruction in its + // block or its block termination instruction is not OpBranch, then try to + // split |function_call_block| such that the conditions are met. + auto* function_call_block = + GetIRContext()->get_instr_block(function_call_instruction); + if ((function_call_instruction != &*--function_call_block->tail() || + function_call_block->terminator()->opcode() != SpvOpBranch) && + !MaybeApplyTransformation(TransformationSplitBlock( + MakeInstructionDescriptor(GetIRContext(), + function_call_instruction->NextNode()), + GetFuzzerContext()->GetFreshId()))) { + continue; + } + + auto* called_function = fuzzerutil::FindFunction( + GetIRContext(), function_call_instruction->GetSingleWordInOperand(0)); + + // Mapping the called function instructions. + std::map result_id_map; + for (auto& called_function_block : *called_function) { + // The called function entry block label will not be inlined. + if (&called_function_block != &*called_function->entry()) { + result_id_map[called_function_block.GetLabelInst()->result_id()] = + GetFuzzerContext()->GetFreshId(); + } + + for (auto& instruction_to_inline : called_function_block) { + // The instructions are mapped to fresh ids. + if (instruction_to_inline.HasResultId()) { + result_id_map[instruction_to_inline.result_id()] = + GetFuzzerContext()->GetFreshId(); + } + } + } + + // Applies the inline function transformation. + ApplyTransformation(TransformationInlineFunction( + function_call_instruction->result_id(), result_id_map)); + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_inline_functions.h b/source/fuzz/fuzzer_pass_inline_functions.h new file mode 100644 index 00000000..37295d1c --- /dev/null +++ b/source/fuzz/fuzzer_pass_inline_functions.h @@ -0,0 +1,41 @@ +// Copyright (c) 2020 André Perez Maselco +// +// 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_INLINE_FUNCTIONS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Looks for OpFunctionCall instructions and randomly decides which ones to +// inline. If the instructions of the called function are going to be inlined, +// then a mapping, between their result ids and suitable ids, is done. +class FuzzerPassInlineFunctions : public FuzzerPass { + public: + FuzzerPassInlineFunctions(opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassInlineFunctions() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_ diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto index 225aa4fe..c20fa99b 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -413,6 +413,7 @@ message Transformation { TransformationReplaceAddSubMulWithCarryingExtended replace_add_sub_mul_with_carrying_extended = 66; TransformationPropagateInstructionUp propagate_instruction_up = 67; TransformationCompositeInsert composite_insert = 68; + TransformationInlineFunction inline_function = 69; // Add additional option using the next available number. } } @@ -1103,6 +1104,20 @@ message TransformationFunctionCall { } +message TransformationInlineFunction { + + // This transformation inlines a function by mapping the function instructions to fresh ids. + + // Result id of the function call instruction. + uint32 function_call_id = 1; + + // For each result id defined by the called function, + // this map provides an associated fresh id that can + // be used in the inlined version of the function call. + repeated UInt32Pair result_id_map = 2; + +} + message TransformationInvertComparisonOperator { // For some instruction with result id |operator_id| that diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp index ec765b3f..0df47d50 100644 --- a/source/fuzz/transformation.cpp +++ b/source/fuzz/transformation.cpp @@ -53,6 +53,7 @@ #include "source/fuzz/transformation_compute_data_synonym_fact_closure.h" #include "source/fuzz/transformation_equation_instruction.h" #include "source/fuzz/transformation_function_call.h" +#include "source/fuzz/transformation_inline_function.h" #include "source/fuzz/transformation_invert_comparison_operator.h" #include "source/fuzz/transformation_load.h" #include "source/fuzz/transformation_make_vector_operation_dynamic.h" @@ -192,6 +193,9 @@ std::unique_ptr Transformation::FromMessage( message.equation_instruction()); case protobufs::Transformation::TransformationCase::kFunctionCall: return MakeUnique(message.function_call()); + case protobufs::Transformation::TransformationCase::kInlineFunction: + return MakeUnique( + message.inline_function()); case protobufs::Transformation::TransformationCase:: kInvertComparisonOperator: return MakeUnique( diff --git a/source/fuzz/transformation_inline_function.cpp b/source/fuzz/transformation_inline_function.cpp new file mode 100644 index 00000000..f244f0d8 --- /dev/null +++ b/source/fuzz/transformation_inline_function.cpp @@ -0,0 +1,281 @@ +// Copyright (c) 2020 André Perez Maselco +// +// 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_inline_function.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationInlineFunction::TransformationInlineFunction( + const spvtools::fuzz::protobufs::TransformationInlineFunction& message) + : message_(message) {} + +TransformationInlineFunction::TransformationInlineFunction( + uint32_t function_call_id, + const std::map& result_id_map) { + message_.set_function_call_id(function_call_id); + *message_.mutable_result_id_map() = + fuzzerutil::MapToRepeatedUInt32Pair(result_id_map); +} + +bool TransformationInlineFunction::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // The values in the |message_.result_id_map| must be all fresh and all + // distinct. + const auto result_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map()); + std::set ids_used_by_this_transformation; + for (auto& pair : result_id_map) { + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + pair.second, ir_context, &ids_used_by_this_transformation)) { + return false; + } + } + + // |function_call_instruction| must be suitable for inlining. + auto* function_call_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.function_call_id()); + if (!IsSuitableForInlining(ir_context, function_call_instruction)) { + return false; + } + + // |function_call_instruction| must be the penultimate instruction in its + // block and its block termination instruction must be an OpBranch. This + // avoids the case where the penultimate instruction is an OpLoopMerge, which + // would make the back-edge block not branch to the loop header. + auto* function_call_instruction_block = + ir_context->get_instr_block(function_call_instruction); + if (function_call_instruction != + &*--function_call_instruction_block->tail() || + function_call_instruction_block->terminator()->opcode() != SpvOpBranch) { + return false; + } + + auto* called_function = fuzzerutil::FindFunction( + ir_context, function_call_instruction->GetSingleWordInOperand(0)); + for (auto& block : *called_function) { + // Since the entry block label will not be inlined, only the remaining + // labels must have a corresponding value in the map. + if (&block != &*called_function->entry() && + !result_id_map.count(block.GetLabel()->result_id())) { + return false; + } + + // |result_id_map| must have an entry for every result id in the called + // function. + for (auto& instruction : block) { + // If |instruction| has result id, then it must have a mapped id in + // |result_id_map|. + if (instruction.HasResultId() && + !result_id_map.count(instruction.result_id())) { + return false; + } + } + } + + // |result_id_map| must not contain an entry for any parameter of the function + // that is being inlined. + bool found_entry_for_parameter = false; + called_function->ForEachParam( + [&result_id_map, &found_entry_for_parameter](opt::Instruction* param) { + if (result_id_map.count(param->result_id())) { + found_entry_for_parameter = true; + } + }); + return !found_entry_for_parameter; +} + +void TransformationInlineFunction::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + auto* function_call_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.function_call_id()); + auto* caller_function = + ir_context->get_instr_block(function_call_instruction)->GetParent(); + auto* called_function = fuzzerutil::FindFunction( + ir_context, function_call_instruction->GetSingleWordInOperand(0)); + const auto result_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map()); + auto* successor_block = ir_context->cfg()->block( + ir_context->get_instr_block(function_call_instruction) + ->terminator() + ->GetSingleWordInOperand(0)); + + // Inline the |called_function| entry block. + for (auto& entry_block_instruction : *called_function->entry()) { + opt::Instruction* inlined_instruction = nullptr; + + if (entry_block_instruction.opcode() == SpvOpVariable) { + // All OpVariable instructions in a function must be in the first block + // in the function. + inlined_instruction = caller_function->begin()->begin()->InsertBefore( + MakeUnique(entry_block_instruction)); + } else { + inlined_instruction = function_call_instruction->InsertBefore( + MakeUnique(entry_block_instruction)); + } + + AdaptInlinedInstruction(ir_context, inlined_instruction); + } + + // Inline the |called_function| non-entry blocks. + for (auto& block : *called_function) { + if (&block == &*called_function->entry()) { + continue; + } + + auto* cloned_block = block.Clone(ir_context); + cloned_block = caller_function->InsertBasicBlockBefore( + std::unique_ptr(cloned_block), successor_block); + cloned_block->SetParent(caller_function); + cloned_block->GetLabel()->SetResultId( + result_id_map.at(cloned_block->GetLabel()->result_id())); + fuzzerutil::UpdateModuleIdBound(ir_context, + cloned_block->GetLabel()->result_id()); + + for (auto& inlined_instruction : *cloned_block) { + AdaptInlinedInstruction(ir_context, &inlined_instruction); + } + } + + // Removes the function call instruction and its block termination instruction + // from |caller_function|. + ir_context->KillInst( + ir_context->get_instr_block(function_call_instruction)->terminator()); + ir_context->KillInst(function_call_instruction); + + // Since the SPIR-V module has changed, no analyses must be validated. + ir_context->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationInlineFunction::ToMessage() const { + protobufs::Transformation result; + *result.mutable_inline_function() = message_; + return result; +} + +bool TransformationInlineFunction::IsSuitableForInlining( + opt::IRContext* ir_context, opt::Instruction* function_call_instruction) { + // |function_call_instruction| must be defined and must be an OpFunctionCall + // instruction. + if (!function_call_instruction || + function_call_instruction->opcode() != SpvOpFunctionCall) { + return false; + } + + // If |function_call_instruction| return type is void, then + // |function_call_instruction| must not have uses. + if (ir_context->get_type_mgr() + ->GetType(function_call_instruction->type_id()) + ->AsVoid() && + ir_context->get_def_use_mgr()->NumUses(function_call_instruction) != 0) { + return false; + } + + // |called_function| must not have an early return. + auto called_function = fuzzerutil::FindFunction( + ir_context, function_call_instruction->GetSingleWordInOperand(0)); + if (called_function->HasEarlyReturn()) { + return false; + } + + // |called_function| must not use OpKill or OpUnreachable. + if (fuzzerutil::FunctionContainsOpKillOrUnreachable(*called_function)) { + return false; + } + + return true; +} + +void TransformationInlineFunction::AdaptInlinedInstruction( + opt::IRContext* ir_context, + opt::Instruction* instruction_to_be_inlined) const { + auto* function_call_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.function_call_id()); + auto* called_function = fuzzerutil::FindFunction( + ir_context, function_call_instruction->GetSingleWordInOperand(0)); + const auto result_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map()); + + // Replaces the operand ids with their mapped result ids. + instruction_to_be_inlined->ForEachInId([called_function, + function_call_instruction, + &result_id_map](uint32_t* id) { + // If |id| is mapped, then set it to its mapped value. + if (result_id_map.count(*id)) { + *id = result_id_map.at(*id); + return; + } + + uint32_t parameter_index = 0; + called_function->ForEachParam( + [id, function_call_instruction, + ¶meter_index](opt::Instruction* parameter_instruction) { + // If the id is a function parameter, then set it to the + // parameter value passed in the function call instruction. + if (*id == parameter_instruction->result_id()) { + // We do + 1 because the first in-operand for OpFunctionCall is + // the function id that is being called. + *id = function_call_instruction->GetSingleWordInOperand( + parameter_index + 1); + } + parameter_index++; + }); + }); + + // If |instruction_to_be_inlined| has result id, then set it to its mapped + // value. + if (instruction_to_be_inlined->HasResultId()) { + assert(result_id_map.count(instruction_to_be_inlined->result_id()) && + "Result id must be mapped to a fresh id."); + instruction_to_be_inlined->SetResultId( + result_id_map.at(instruction_to_be_inlined->result_id())); + fuzzerutil::UpdateModuleIdBound(ir_context, + instruction_to_be_inlined->result_id()); + } + + // The return instruction will be changed into an OpBranch to the basic + // block that follows the block containing the function call. + if (spvOpcodeIsReturn(instruction_to_be_inlined->opcode())) { + uint32_t successor_block_id = + ir_context->get_instr_block(function_call_instruction) + ->terminator() + ->GetSingleWordInOperand(0); + switch (instruction_to_be_inlined->opcode()) { + case SpvOpReturn: + instruction_to_be_inlined->AddOperand( + {SPV_OPERAND_TYPE_ID, {successor_block_id}}); + break; + case SpvOpReturnValue: { + instruction_to_be_inlined->InsertBefore(MakeUnique( + ir_context, SpvOpCopyObject, function_call_instruction->type_id(), + function_call_instruction->result_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, + {instruction_to_be_inlined->GetSingleWordOperand(0)}}}))); + instruction_to_be_inlined->SetInOperand(0, {successor_block_id}); + break; + } + default: + break; + } + instruction_to_be_inlined->SetOpcode(SpvOpBranch); + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_inline_function.h b/source/fuzz/transformation_inline_function.h new file mode 100644 index 00000000..29a9ea6f --- /dev/null +++ b/source/fuzz/transformation_inline_function.h @@ -0,0 +1,75 @@ +// Copyright (c) 2020 André Perez Maselco +// +// 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_INLINE_FUNCTION_H_ +#define SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationInlineFunction : public Transformation { + public: + explicit TransformationInlineFunction( + const protobufs::TransformationInlineFunction& message); + + TransformationInlineFunction( + uint32_t function_call_id, + const std::map& result_id_map); + + // - |message_.result_id_map| must map the instructions of the called function + // to fresh ids. + // - |message_.function_call_id| must be an OpFunctionCall instruction. + // It must not have an early return and must not use OpUnreachable or + // OpKill. This is to guard against making the module invalid when the + // caller is inside a continue construct. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3735): + // Allow functions that use OpKill or OpUnreachable to be inlined if the + // function call is not part of a continue construct. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Replaces the OpFunctionCall instruction, identified by + // |message_.function_call_id|, with a copy of the function's body. + // |message_.result_id_map| is used to provide fresh ids for duplicate + // instructions. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Returns true if |function_call_instruction| is defined, is an + // OpFunctionCall instruction, has no uses if its return type is void, has no + // early returns and has no uses of OpKill or OpUnreachable. + static bool IsSuitableForInlining( + opt::IRContext* ir_context, opt::Instruction* function_call_instruction); + + private: + protobufs::TransformationInlineFunction message_; + + // Inline |instruction_to_be_inlined| by setting its ids to the corresponding + // ids in |result_id_map|. + void AdaptInlinedInstruction(opt::IRContext* ir_context, + opt::Instruction* instruction) const; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_ diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt index 3b64567c..7204f692 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -61,6 +61,7 @@ if (${SPIRV_BUILD_FUZZER}) transformation_compute_data_synonym_fact_closure_test.cpp transformation_equation_instruction_test.cpp transformation_function_call_test.cpp + transformation_inline_function_test.cpp transformation_invert_comparison_operator_test.cpp transformation_load_test.cpp transformation_make_vector_operation_dynamic_test.cpp diff --git a/test/fuzz/transformation_inline_function_test.cpp b/test/fuzz/transformation_inline_function_test.cpp new file mode 100644 index 00000000..694f19cc --- /dev/null +++ b/test/fuzz/transformation_inline_function_test.cpp @@ -0,0 +1,756 @@ +// Copyright (c) 2020 André Perez Maselco +// +// 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_inline_function.h" +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationInlineFunctionTest, IsApplicable) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %52 "main" + OpExecutionMode %52 OriginUpperLeft + OpName %56 "function_with_void_return" + +; Types + %2 = OpTypeBool + %3 = OpTypeFloat 32 + %4 = OpTypeVector %3 4 + %5 = OpTypePointer Function %4 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeFunction %3 %5 %5 + +; Constant scalars + %9 = OpConstant %3 1 + %10 = OpConstant %3 2 + %11 = OpConstant %3 3 + %12 = OpConstant %3 4 + %13 = OpConstant %3 5 + %14 = OpConstant %3 6 + %15 = OpConstant %3 7 + %16 = OpConstant %3 8 + %17 = OpConstantTrue %2 + +; Constant vectors + %18 = OpConstantComposite %4 %9 %10 %11 %12 + %19 = OpConstantComposite %4 %13 %14 %15 %16 + +; function with void return + %20 = OpFunction %6 None %7 + %21 = OpLabel + OpReturn + OpFunctionEnd + +; function with early return + %22 = OpFunction %6 None %7 + %23 = OpLabel + OpSelectionMerge %26 None + OpBranchConditional %17 %24 %25 + %24 = OpLabel + OpReturn + %25 = OpLabel + OpBranch %26 + %26 = OpLabel + OpReturn + OpFunctionEnd + +; function containing an OpKill instruction + %27 = OpFunction %6 None %7 + %28 = OpLabel + OpKill + OpFunctionEnd + +; function containing an OpUnreachable instruction + %29 = OpFunction %6 None %7 + %30 = OpLabel + OpUnreachable + OpFunctionEnd + +; dot product function + %31 = OpFunction %3 None %8 + %32 = OpFunctionParameter %5 + %33 = OpFunctionParameter %5 + %34 = OpLabel + %35 = OpLoad %4 %32 + %36 = OpLoad %4 %33 + %37 = OpCompositeExtract %3 %35 0 + %38 = OpCompositeExtract %3 %36 0 + %39 = OpFMul %3 %37 %38 + %40 = OpCompositeExtract %3 %35 1 + %41 = OpCompositeExtract %3 %36 1 + %42 = OpFMul %3 %40 %41 + %43 = OpCompositeExtract %3 %35 2 + %44 = OpCompositeExtract %3 %36 2 + %45 = OpFMul %3 %43 %44 + %46 = OpCompositeExtract %3 %35 3 + %47 = OpCompositeExtract %3 %36 3 + %48 = OpFMul %3 %46 %47 + %49 = OpFAdd %3 %39 %42 + %50 = OpFAdd %3 %45 %49 + %51 = OpFAdd %3 %48 %50 + OpReturnValue %51 + OpFunctionEnd + +; main function + %52 = OpFunction %6 None %7 + %53 = OpLabel + %54 = OpVariable %5 Function + %55 = OpVariable %5 Function + %56 = OpFunctionCall %6 %20 ; function with void return + OpBranch %57 + %57 = OpLabel + %59 = OpFunctionCall %6 %22 ; function with early return + OpBranch %60 + %60 = OpLabel + %61 = OpFunctionCall %6 %27 ; function containing OpKill + OpBranch %62 + %62 = OpLabel + %63 = OpFunctionCall %6 %29 ; function containing OpUnreachable + OpBranch %64 + %64 = OpLabel + OpStore %54 %18 + OpStore %55 %19 + %65 = OpFunctionCall %3 %31 %54 %55 ; dot product function + OpBranch %66 + %66 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Tests undefined OpFunctionCall instruction. + auto transformation = TransformationInlineFunction(67, {}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests false OpFunctionCall instruction. + transformation = TransformationInlineFunction(42, {}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests use of called function with void return. + transformation = TransformationInlineFunction(56, {}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests called function having an early return. + transformation = + TransformationInlineFunction(59, {{24, 67}, {25, 68}, {26, 69}}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests called function containing an OpKill instruction. + transformation = TransformationInlineFunction(61, {}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests called function containing an OpUnreachable instruction. + transformation = TransformationInlineFunction(63, {}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests applicable transformation. + transformation = TransformationInlineFunction(65, {{35, 67}, + {36, 68}, + {37, 69}, + {38, 70}, + {39, 71}, + {40, 72}, + {41, 73}, + {42, 74}, + {43, 75}, + {44, 76}, + {45, 77}, + {46, 78}, + {47, 79}, + {48, 80}, + {49, 81}, + {50, 82}, + {51, 83}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationInlineFunctionTest, Apply) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %39 "main" + +; Types + %2 = OpTypeFloat 32 + %3 = OpTypeVector %2 4 + %4 = OpTypePointer Function %3 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeFunction %2 %4 %4 + +; Constant scalars + %8 = OpConstant %2 1 + %9 = OpConstant %2 2 + %10 = OpConstant %2 3 + %11 = OpConstant %2 4 + %12 = OpConstant %2 5 + %13 = OpConstant %2 6 + %14 = OpConstant %2 7 + %15 = OpConstant %2 8 + +; Constant vectors + %16 = OpConstantComposite %3 %8 %9 %10 %11 + %17 = OpConstantComposite %3 %12 %13 %14 %15 + +; dot product function + %18 = OpFunction %2 None %7 + %19 = OpFunctionParameter %4 + %20 = OpFunctionParameter %4 + %21 = OpLabel + %22 = OpLoad %3 %19 + %23 = OpLoad %3 %20 + %24 = OpCompositeExtract %2 %22 0 + %25 = OpCompositeExtract %2 %23 0 + %26 = OpFMul %2 %24 %25 + %27 = OpCompositeExtract %2 %22 1 + %28 = OpCompositeExtract %2 %23 1 + %29 = OpFMul %2 %27 %28 + %30 = OpCompositeExtract %2 %22 2 + %31 = OpCompositeExtract %2 %23 2 + %32 = OpFMul %2 %30 %31 + %33 = OpCompositeExtract %2 %22 3 + %34 = OpCompositeExtract %2 %23 3 + %35 = OpFMul %2 %33 %34 + %36 = OpFAdd %2 %26 %29 + %37 = OpFAdd %2 %32 %36 + %38 = OpFAdd %2 %35 %37 + OpReturnValue %38 + OpFunctionEnd + +; main function + %39 = OpFunction %5 None %6 + %40 = OpLabel + %41 = OpVariable %4 Function + %42 = OpVariable %4 Function + OpStore %41 %16 + OpStore %42 %17 + %43 = OpFunctionCall %2 %18 %41 %42 ; dot product function call + OpBranch %44 + %44 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto transformation = TransformationInlineFunction(43, {{22, 45}, + {23, 46}, + {24, 47}, + {25, 48}, + {26, 49}, + {27, 50}, + {28, 51}, + {29, 52}, + {30, 53}, + {31, 54}, + {32, 55}, + {33, 56}, + {34, 57}, + {35, 58}, + {36, 59}, + {37, 60}, + {38, 61}}); + transformation.Apply(context.get(), &transformation_context); + + std::string variant_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %39 "main" + +; Types + %2 = OpTypeFloat 32 + %3 = OpTypeVector %2 4 + %4 = OpTypePointer Function %3 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeFunction %2 %4 %4 + +; Constant scalars + %8 = OpConstant %2 1 + %9 = OpConstant %2 2 + %10 = OpConstant %2 3 + %11 = OpConstant %2 4 + %12 = OpConstant %2 5 + %13 = OpConstant %2 6 + %14 = OpConstant %2 7 + %15 = OpConstant %2 8 + +; Constant vectors + %16 = OpConstantComposite %3 %8 %9 %10 %11 + %17 = OpConstantComposite %3 %12 %13 %14 %15 + +; dot product function + %18 = OpFunction %2 None %7 + %19 = OpFunctionParameter %4 + %20 = OpFunctionParameter %4 + %21 = OpLabel + %22 = OpLoad %3 %19 + %23 = OpLoad %3 %20 + %24 = OpCompositeExtract %2 %22 0 + %25 = OpCompositeExtract %2 %23 0 + %26 = OpFMul %2 %24 %25 + %27 = OpCompositeExtract %2 %22 1 + %28 = OpCompositeExtract %2 %23 1 + %29 = OpFMul %2 %27 %28 + %30 = OpCompositeExtract %2 %22 2 + %31 = OpCompositeExtract %2 %23 2 + %32 = OpFMul %2 %30 %31 + %33 = OpCompositeExtract %2 %22 3 + %34 = OpCompositeExtract %2 %23 3 + %35 = OpFMul %2 %33 %34 + %36 = OpFAdd %2 %26 %29 + %37 = OpFAdd %2 %32 %36 + %38 = OpFAdd %2 %35 %37 + OpReturnValue %38 + OpFunctionEnd + +; main function + %39 = OpFunction %5 None %6 + %40 = OpLabel + %41 = OpVariable %4 Function + %42 = OpVariable %4 Function + OpStore %41 %16 + OpStore %42 %17 + %45 = OpLoad %3 %41 + %46 = OpLoad %3 %42 + %47 = OpCompositeExtract %2 %45 0 + %48 = OpCompositeExtract %2 %46 0 + %49 = OpFMul %2 %47 %48 + %50 = OpCompositeExtract %2 %45 1 + %51 = OpCompositeExtract %2 %46 1 + %52 = OpFMul %2 %50 %51 + %53 = OpCompositeExtract %2 %45 2 + %54 = OpCompositeExtract %2 %46 2 + %55 = OpFMul %2 %53 %54 + %56 = OpCompositeExtract %2 %45 3 + %57 = OpCompositeExtract %2 %46 3 + %58 = OpFMul %2 %56 %57 + %59 = OpFAdd %2 %49 %52 + %60 = OpFAdd %2 %55 %59 + %61 = OpFAdd %2 %58 %60 + %43 = OpCopyObject %2 %61 + OpBranch %44 + %44 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); +} + +TEST(TransformationInlineFunctionTest, ApplyToMultipleFunctions) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %15 "main" + +; Types + %2 = OpTypeInt 32 1 + %3 = OpTypeBool + %4 = OpTypePointer Private %2 + %5 = OpTypePointer Function %2 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeFunction %2 %5 + %9 = OpTypeFunction %2 %2 + +; Constants + %10 = OpConstant %2 0 + %11 = OpConstant %2 1 + %12 = OpConstant %2 2 + %13 = OpConstant %2 3 + +; Global variable + %14 = OpVariable %4 Private + +; main function + %15 = OpFunction %6 None %7 + %16 = OpLabel + %17 = OpVariable %5 Function + %18 = OpVariable %5 Function + %19 = OpVariable %5 Function + OpStore %17 %13 + %20 = OpLoad %2 %17 + OpStore %18 %20 + %21 = OpFunctionCall %2 %36 %18 + OpBranch %22 + %22 = OpLabel + %23 = OpFunctionCall %2 %36 %18 + OpStore %17 %21 + %24 = OpLoad %2 %17 + %25 = OpFunctionCall %2 %54 %24 + OpBranch %26 + %26 = OpLabel + %27 = OpFunctionCall %2 %54 %24 + %28 = OpLoad %2 %17 + %29 = OpIAdd %2 %28 %25 + OpStore %17 %29 + %30 = OpFunctionCall %6 %67 + OpBranch %31 + %31 = OpLabel + %32 = OpFunctionCall %6 %67 + %33 = OpLoad %2 %14 + %34 = OpLoad %2 %17 + %35 = OpIAdd %2 %34 %33 + OpStore %17 %35 + OpReturn + OpFunctionEnd + +; Function %36 + %36 = OpFunction %2 None %8 + %37 = OpFunctionParameter %5 + %38 = OpLabel + %39 = OpVariable %5 Function + %40 = OpVariable %5 Function + OpStore %39 %10 + OpBranch %41 + %41 = OpLabel + OpLoopMerge %52 %49 None + OpBranch %42 + %42 = OpLabel + %43 = OpLoad %2 %39 + %44 = OpLoad %2 %37 + %45 = OpSLessThan %3 %43 %44 + OpBranchConditional %45 %46 %52 + %46 = OpLabel + %47 = OpLoad %2 %40 + %48 = OpIAdd %2 %47 %11 + OpStore %40 %48 + OpBranch %49 + %49 = OpLabel + %50 = OpLoad %2 %39 + %51 = OpIAdd %2 %50 %12 + OpStore %39 %51 + OpBranch %41 + %52 = OpLabel + %53 = OpLoad %2 %40 + OpReturnValue %53 + OpFunctionEnd + +; Function %54 + %54 = OpFunction %2 None %9 + %55 = OpFunctionParameter %2 + %56 = OpLabel + %57 = OpVariable %5 Function + OpStore %57 %10 + %58 = OpSGreaterThan %3 %55 %10 + OpSelectionMerge %62 None + OpBranchConditional %58 %64 %59 + %59 = OpLabel + %60 = OpLoad %2 %57 + %61 = OpISub %2 %60 %12 + OpStore %57 %61 + OpBranch %62 + %62 = OpLabel + %63 = OpLoad %2 %57 + OpReturnValue %63 + %64 = OpLabel + %65 = OpLoad %2 %57 + %66 = OpIAdd %2 %65 %11 + OpStore %57 %66 + OpBranch %62 + OpFunctionEnd + +; Function %67 + %67 = OpFunction %6 None %7 + %68 = OpLabel + OpStore %14 %12 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto transformation = TransformationInlineFunction(30, {}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + // Tests a parameter included in the id map. + transformation = TransformationInlineFunction(25, {{55, 69}, + {56, 70}, + {57, 71}, + {58, 72}, + {59, 73}, + {60, 74}, + {61, 75}, + {62, 76}, + {63, 77}, + {64, 78}, + {65, 79}, + {66, 80}}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests the id of the returned value not included in the id map. + transformation = TransformationInlineFunction(25, {{56, 69}, + {57, 70}, + {58, 71}, + {59, 72}, + {60, 73}, + {61, 74}, + {62, 75}, + {64, 76}, + {65, 77}, + {66, 78}}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + transformation = TransformationInlineFunction(25, {{57, 69}, + {58, 70}, + {59, 71}, + {60, 72}, + {61, 73}, + {62, 74}, + {63, 75}, + {64, 76}, + {65, 77}, + {66, 78}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationInlineFunction(21, {{39, 79}, + {40, 80}, + {41, 81}, + {42, 82}, + {43, 83}, + {44, 84}, + {45, 85}, + {46, 86}, + {47, 87}, + {48, 88}, + {49, 89}, + {50, 90}, + {51, 91}, + {52, 92}, + {53, 93}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + std::string variant_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %15 "main" + +; Types + %2 = OpTypeInt 32 1 + %3 = OpTypeBool + %4 = OpTypePointer Private %2 + %5 = OpTypePointer Function %2 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeFunction %2 %5 + %9 = OpTypeFunction %2 %2 + +; Constants + %10 = OpConstant %2 0 + %11 = OpConstant %2 1 + %12 = OpConstant %2 2 + %13 = OpConstant %2 3 + +; Global variable + %14 = OpVariable %4 Private + +; main function + %15 = OpFunction %6 None %7 + %16 = OpLabel + %80 = OpVariable %5 Function + %79 = OpVariable %5 Function + %69 = OpVariable %5 Function + %17 = OpVariable %5 Function + %18 = OpVariable %5 Function + %19 = OpVariable %5 Function + OpStore %17 %13 + %20 = OpLoad %2 %17 + OpStore %18 %20 + OpStore %79 %10 + OpBranch %81 + %81 = OpLabel + OpLoopMerge %92 %89 None + OpBranch %82 + %82 = OpLabel + %83 = OpLoad %2 %79 + %84 = OpLoad %2 %18 + %85 = OpSLessThan %3 %83 %84 + OpBranchConditional %85 %86 %92 + %86 = OpLabel + %87 = OpLoad %2 %80 + %88 = OpIAdd %2 %87 %11 + OpStore %80 %88 + OpBranch %89 + %89 = OpLabel + %90 = OpLoad %2 %79 + %91 = OpIAdd %2 %90 %12 + OpStore %79 %91 + OpBranch %81 + %92 = OpLabel + %93 = OpLoad %2 %80 + %21 = OpCopyObject %2 %93 + OpBranch %22 + %22 = OpLabel + %23 = OpFunctionCall %2 %36 %18 + OpStore %17 %21 + %24 = OpLoad %2 %17 + OpStore %69 %10 + %70 = OpSGreaterThan %3 %24 %10 + OpSelectionMerge %74 None + OpBranchConditional %70 %76 %71 + %71 = OpLabel + %72 = OpLoad %2 %69 + %73 = OpISub %2 %72 %12 + OpStore %69 %73 + OpBranch %74 + %74 = OpLabel + %75 = OpLoad %2 %69 + %25 = OpCopyObject %2 %75 + OpBranch %26 + %76 = OpLabel + %77 = OpLoad %2 %69 + %78 = OpIAdd %2 %77 %11 + OpStore %69 %78 + OpBranch %74 + %26 = OpLabel + %27 = OpFunctionCall %2 %54 %24 + %28 = OpLoad %2 %17 + %29 = OpIAdd %2 %28 %25 + OpStore %17 %29 + OpStore %14 %12 + OpBranch %31 + %31 = OpLabel + %32 = OpFunctionCall %6 %67 + %33 = OpLoad %2 %14 + %34 = OpLoad %2 %17 + %35 = OpIAdd %2 %34 %33 + OpStore %17 %35 + OpReturn + OpFunctionEnd + +; Function %36 + %36 = OpFunction %2 None %8 + %37 = OpFunctionParameter %5 + %38 = OpLabel + %39 = OpVariable %5 Function + %40 = OpVariable %5 Function + OpStore %39 %10 + OpBranch %41 + %41 = OpLabel + OpLoopMerge %52 %49 None + OpBranch %42 + %42 = OpLabel + %43 = OpLoad %2 %39 + %44 = OpLoad %2 %37 + %45 = OpSLessThan %3 %43 %44 + OpBranchConditional %45 %46 %52 + %46 = OpLabel + %47 = OpLoad %2 %40 + %48 = OpIAdd %2 %47 %11 + OpStore %40 %48 + OpBranch %49 + %49 = OpLabel + %50 = OpLoad %2 %39 + %51 = OpIAdd %2 %50 %12 + OpStore %39 %51 + OpBranch %41 + %52 = OpLabel + %53 = OpLoad %2 %40 + OpReturnValue %53 + OpFunctionEnd + +; Function %54 + %54 = OpFunction %2 None %9 + %55 = OpFunctionParameter %2 + %56 = OpLabel + %57 = OpVariable %5 Function + OpStore %57 %10 + %58 = OpSGreaterThan %3 %55 %10 + OpSelectionMerge %62 None + OpBranchConditional %58 %64 %59 + %59 = OpLabel + %60 = OpLoad %2 %57 + %61 = OpISub %2 %60 %12 + OpStore %57 %61 + OpBranch %62 + %62 = OpLabel + %63 = OpLoad %2 %57 + OpReturnValue %63 + %64 = OpLabel + %65 = OpLoad %2 %57 + %66 = OpIAdd %2 %65 %11 + OpStore %57 %66 + OpBranch %62 + OpFunctionEnd + +; Function %67 + %67 = OpFunction %6 None %7 + %68 = OpLabel + OpStore %14 %12 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools