diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index 150ee314d..630dee2c1 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -72,6 +72,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_outline_functions.h fuzzer_pass_permute_blocks.h fuzzer_pass_permute_function_parameters.h + fuzzer_pass_permute_instructions.h fuzzer_pass_permute_phi_operands.h fuzzer_pass_push_ids_through_variables.h fuzzer_pass_replace_copy_memories_with_loads_stores.h @@ -134,6 +135,7 @@ if(SPIRV_BUILD_FUZZER) transformation_load.h transformation_merge_blocks.h transformation_move_block_down.h + transformation_move_instruction_down.h transformation_outline_function.h transformation_permute_function_parameters.h transformation_permute_phi_operands.h @@ -203,6 +205,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_outline_functions.cpp fuzzer_pass_permute_blocks.cpp fuzzer_pass_permute_function_parameters.cpp + fuzzer_pass_permute_instructions.cpp fuzzer_pass_permute_phi_operands.cpp fuzzer_pass_push_ids_through_variables.cpp fuzzer_pass_replace_copy_memories_with_loads_stores.cpp @@ -264,6 +267,7 @@ if(SPIRV_BUILD_FUZZER) transformation_load.cpp transformation_merge_blocks.cpp transformation_move_block_down.cpp + transformation_move_instruction_down.cpp transformation_outline_function.cpp transformation_permute_function_parameters.cpp transformation_permute_phi_operands.cpp diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index ae4332bde..89b504f06 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp @@ -54,6 +54,7 @@ #include "source/fuzz/fuzzer_pass_outline_functions.h" #include "source/fuzz/fuzzer_pass_permute_blocks.h" #include "source/fuzz/fuzzer_pass_permute_function_parameters.h" +#include "source/fuzz/fuzzer_pass_permute_instructions.h" #include "source/fuzz/fuzzer_pass_permute_phi_operands.h" #include "source/fuzz/fuzzer_pass_push_ids_through_variables.h" #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h" @@ -281,6 +282,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); + 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 dacdc58fd..7996813de 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -76,6 +76,7 @@ const std::pair kChanceOfMergingBlocks = {20, 95}; const std::pair kChanceOfMovingBlockDown = {20, 50}; const std::pair kChanceOfObfuscatingConstant = {10, 90}; const std::pair kChanceOfOutliningFunction = {10, 90}; +const std::pair kChanceOfPermutingInstructions = {20, 70}; const std::pair kChanceOfPermutingParameters = {30, 90}; const std::pair kChanceOfPermutingPhiOperands = {30, 90}; const std::pair kChanceOfPushingIdThroughVariable = {5, 50}; @@ -214,6 +215,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant); chance_of_outlining_function_ = ChooseBetweenMinAndMax(kChanceOfOutliningFunction); + chance_of_permuting_instructions_ = + ChooseBetweenMinAndMax(kChanceOfPermutingInstructions); chance_of_permuting_parameters_ = ChooseBetweenMinAndMax(kChanceOfPermutingParameters); chance_of_permuting_phi_operands_ = diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index 24aad0ce1..ce023ddae 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -206,6 +206,9 @@ class FuzzerContext { uint32_t GetChanceOfOutliningFunction() { return chance_of_outlining_function_; } + uint32_t GetChanceOfPermutingInstructions() { + return chance_of_permuting_instructions_; + } uint32_t GetChanceOfPermutingParameters() { return chance_of_permuting_parameters_; } @@ -364,6 +367,7 @@ class FuzzerContext { uint32_t chance_of_moving_block_down_; uint32_t chance_of_obfuscating_constant_; uint32_t chance_of_outlining_function_; + uint32_t chance_of_permuting_instructions_; uint32_t chance_of_permuting_parameters_; uint32_t chance_of_permuting_phi_operands_; uint32_t chance_of_pushing_id_through_variable_; diff --git a/source/fuzz/fuzzer_pass_permute_instructions.cpp b/source/fuzz/fuzzer_pass_permute_instructions.cpp new file mode 100644 index 000000000..6867053c6 --- /dev/null +++ b/source/fuzz/fuzzer_pass_permute_instructions.cpp @@ -0,0 +1,64 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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_permute_instructions.h" + +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_move_instruction_down.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassPermuteInstructions::FuzzerPassPermuteInstructions( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassPermuteInstructions::~FuzzerPassPermuteInstructions() = default; + +void FuzzerPassPermuteInstructions::Apply() { + // We are iterating over all instructions in all basic blocks. + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // We need to collect all instructions in the block into a separate vector + // since application of the transformation below might invalidate + // iterators. + std::vector instructions; + for (auto& instruction : block) { + instructions.push_back(&instruction); + } + + // We consider all instructions in reverse to increase the possible number + // of applied transformations. + for (auto it = instructions.rbegin(); it != instructions.rend(); ++it) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfPermutingInstructions())) { + continue; + } + + while (MaybeApplyTransformation(TransformationMoveInstructionDown( + MakeInstructionDescriptor(GetIRContext(), *it)))) { + // Apply the transformation as many times as possible. + } + } + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_permute_instructions.h b/source/fuzz/fuzzer_pass_permute_instructions.h new file mode 100644 index 000000000..e02ddfae0 --- /dev/null +++ b/source/fuzz/fuzzer_pass_permute_instructions.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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_PERMUTE_INSTRUCTIONS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_INSTRUCTIONS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Permutes instructions in every block of all while preserving the module's +// semantics. +class FuzzerPassPermuteInstructions : public FuzzerPass { + public: + FuzzerPassPermuteInstructions( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassPermuteInstructions() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_INSTRUCTIONS_H_ diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto index 43219a1df..748edf882 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -408,6 +408,7 @@ message Transformation { TransformationReplaceCopyMemoryWithLoadStore replace_copy_memory_with_load_store = 61; TransformationReplaceLoadStoreWithCopyMemory replace_load_store_with_copy_memory = 62; TransformationAddLoopPreheader add_loop_preheader = 63; + TransformationMoveInstructionDown move_instruction_down = 64; // Add additional option using the next available number. } } @@ -1124,6 +1125,15 @@ message TransformationMoveBlockDown { uint32 block_id = 1; } +message TransformationMoveInstructionDown { + + // Swaps |instruction| with the next instruction in the block. + + // The instruction to move down. + InstructionDescriptor instruction = 1; + +} + message TransformationOutlineFunction { // A transformation that outlines a single-entry single-exit region of a diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp index 8e03279ca..5d4850150 100644 --- a/source/fuzz/transformation.cpp +++ b/source/fuzz/transformation.cpp @@ -56,6 +56,7 @@ #include "source/fuzz/transformation_load.h" #include "source/fuzz/transformation_merge_blocks.h" #include "source/fuzz/transformation_move_block_down.h" +#include "source/fuzz/transformation_move_instruction_down.h" #include "source/fuzz/transformation_outline_function.h" #include "source/fuzz/transformation_permute_function_parameters.h" #include "source/fuzz/transformation_permute_phi_operands.h" @@ -194,6 +195,9 @@ std::unique_ptr Transformation::FromMessage( return MakeUnique(message.merge_blocks()); case protobufs::Transformation::TransformationCase::kMoveBlockDown: return MakeUnique(message.move_block_down()); + case protobufs::Transformation::TransformationCase::kMoveInstructionDown: + return MakeUnique( + message.move_instruction_down()); case protobufs::Transformation::TransformationCase::kOutlineFunction: return MakeUnique( message.outline_function()); diff --git a/source/fuzz/transformation_move_instruction_down.cpp b/source/fuzz/transformation_move_instruction_down.cpp new file mode 100644 index 000000000..7e383540b --- /dev/null +++ b/source/fuzz/transformation_move_instruction_down.cpp @@ -0,0 +1,219 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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_move_instruction_down.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationMoveInstructionDown::TransformationMoveInstructionDown( + const protobufs::TransformationMoveInstructionDown& message) + : message_(message) {} + +TransformationMoveInstructionDown::TransformationMoveInstructionDown( + const protobufs::InstructionDescriptor& instruction) { + *message_.mutable_instruction() = instruction; +} + +bool TransformationMoveInstructionDown::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // |instruction| must be valid. + auto* inst = FindInstruction(message_.instruction(), ir_context); + if (!inst) { + return false; + } + + // Instruction's opcode must be supported by this transformation. + if (!IsOpcodeSupported(inst->opcode())) { + return false; + } + + auto* inst_block = ir_context->get_instr_block(inst); + assert(inst_block && + "Global instructions and function parameters are not supported"); + + auto inst_it = fuzzerutil::GetIteratorForInstruction(inst_block, inst); + assert(inst_it != inst_block->end() && + "Can't get an iterator for the instruction"); + + // |instruction| can't be the last instruction in the block. + auto successor_it = ++inst_it; + if (successor_it == inst_block->end()) { + return false; + } + + // Check that we can insert |instruction| after |inst_it|. + auto successors_successor_it = ++inst_it; + if (successors_successor_it == inst_block->end() || + !fuzzerutil::CanInsertOpcodeBeforeInstruction(inst->opcode(), + successors_successor_it)) { + return false; + } + + // Check that |instruction|'s successor doesn't depend on the |instruction|. + if (inst->result_id()) { + for (uint32_t i = 0; i < successor_it->NumInOperands(); ++i) { + const auto& operand = successor_it->GetInOperand(i); + if (operand.type == SPV_OPERAND_TYPE_ID && + operand.words[0] == inst->result_id()) { + return false; + } + } + } + + return true; +} + +void TransformationMoveInstructionDown::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + auto* inst = FindInstruction(message_.instruction(), ir_context); + assert(inst && + "The instruction should've been validated in the IsApplicable"); + + auto inst_it = fuzzerutil::GetIteratorForInstruction( + ir_context->get_instr_block(inst), inst); + + // Move the instruction down in the block. + inst->InsertAfter(&*++inst_it); + + ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation TransformationMoveInstructionDown::ToMessage() const { + protobufs::Transformation result; + *result.mutable_move_instruction_down() = message_; + return result; +} + +bool TransformationMoveInstructionDown::IsOpcodeSupported(SpvOp opcode) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605): + // We only support "simple" instructions that work don't with memory. + // We should extend this so that we support the ones that modify the memory + // too. + switch (opcode) { + case SpvOpNop: + case SpvOpUndef: + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpArrayLength: + case SpvOpVectorExtractDynamic: + case SpvOpVectorInsertDynamic: + case SpvOpVectorShuffle: + case SpvOpCompositeConstruct: + case SpvOpCompositeExtract: + case SpvOpCompositeInsert: + case SpvOpCopyObject: + case SpvOpTranspose: + case SpvOpConvertFToU: + case SpvOpConvertFToS: + case SpvOpConvertSToF: + case SpvOpConvertUToF: + case SpvOpUConvert: + case SpvOpSConvert: + case SpvOpFConvert: + case SpvOpQuantizeToF16: + case SpvOpSatConvertSToU: + case SpvOpSatConvertUToS: + case SpvOpBitcast: + case SpvOpSNegate: + case SpvOpFNegate: + case SpvOpIAdd: + case SpvOpFAdd: + case SpvOpISub: + case SpvOpFSub: + case SpvOpIMul: + case SpvOpFMul: + case SpvOpUDiv: + case SpvOpSDiv: + case SpvOpFDiv: + case SpvOpUMod: + case SpvOpSRem: + case SpvOpSMod: + case SpvOpFRem: + case SpvOpFMod: + case SpvOpVectorTimesScalar: + case SpvOpMatrixTimesScalar: + case SpvOpVectorTimesMatrix: + case SpvOpMatrixTimesVector: + case SpvOpMatrixTimesMatrix: + case SpvOpOuterProduct: + case SpvOpDot: + case SpvOpIAddCarry: + case SpvOpISubBorrow: + case SpvOpUMulExtended: + case SpvOpSMulExtended: + case SpvOpAny: + case SpvOpAll: + case SpvOpIsNan: + case SpvOpIsInf: + case SpvOpIsFinite: + case SpvOpIsNormal: + case SpvOpSignBitSet: + case SpvOpLessOrGreater: + case SpvOpOrdered: + case SpvOpUnordered: + case SpvOpLogicalEqual: + case SpvOpLogicalNotEqual: + case SpvOpLogicalOr: + case SpvOpLogicalAnd: + case SpvOpLogicalNot: + case SpvOpSelect: + case SpvOpIEqual: + case SpvOpINotEqual: + case SpvOpUGreaterThan: + case SpvOpSGreaterThan: + case SpvOpUGreaterThanEqual: + case SpvOpSGreaterThanEqual: + case SpvOpULessThan: + case SpvOpSLessThan: + case SpvOpULessThanEqual: + case SpvOpSLessThanEqual: + case SpvOpFOrdEqual: + case SpvOpFUnordEqual: + case SpvOpFOrdNotEqual: + case SpvOpFUnordNotEqual: + case SpvOpFOrdLessThan: + case SpvOpFUnordLessThan: + case SpvOpFOrdGreaterThan: + case SpvOpFUnordGreaterThan: + case SpvOpFOrdLessThanEqual: + case SpvOpFUnordLessThanEqual: + case SpvOpFOrdGreaterThanEqual: + case SpvOpFUnordGreaterThanEqual: + case SpvOpShiftRightLogical: + case SpvOpShiftRightArithmetic: + case SpvOpShiftLeftLogical: + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpNot: + case SpvOpBitFieldInsert: + case SpvOpBitFieldSExtract: + case SpvOpBitFieldUExtract: + case SpvOpBitReverse: + case SpvOpBitCount: + case SpvOpCopyLogical: + case SpvOpPtrEqual: + case SpvOpPtrNotEqual: + return true; + default: + return false; + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_move_instruction_down.h b/source/fuzz/transformation_move_instruction_down.h new file mode 100644 index 000000000..3f3c30211 --- /dev/null +++ b/source/fuzz/transformation_move_instruction_down.h @@ -0,0 +1,62 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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_MOVE_INSTRUCTION_DOWN_H_ +#define SOURCE_FUZZ_TRANSFORMATION_MOVE_INSTRUCTION_DOWN_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 TransformationMoveInstructionDown : public Transformation { + public: + explicit TransformationMoveInstructionDown( + const protobufs::TransformationMoveInstructionDown& message); + + explicit TransformationMoveInstructionDown( + const protobufs::InstructionDescriptor& instruction); + + // - |instruction| should be a descriptor of a valid instruction in the module + // - |instruction|'s opcode should be supported by this transformation + // - neither |instruction| nor its successor may be the last instruction in + // the block + // - |instruction|'s successor may not be dependent on the |instruction| + // - it should be possible to insert |instruction|'s opcode after its + // successor + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Swaps |instruction| with its successor. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + // Returns true if the |opcode| is supported by this transformation (i.e. + // we can move an instruction with this opcode). + static bool IsOpcodeSupported(SpvOp opcode); + + protobufs::TransformationMoveInstructionDown message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_MOVE_INSTRUCTION_DOWN_H_ diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt index d6e85dcb5..8d239ff11 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -63,6 +63,7 @@ if (${SPIRV_BUILD_FUZZER}) transformation_load_test.cpp transformation_merge_blocks_test.cpp transformation_move_block_down_test.cpp + transformation_move_instruction_down_test.cpp transformation_outline_function_test.cpp transformation_permute_function_parameters_test.cpp transformation_permute_phi_operands_test.cpp diff --git a/test/fuzz/transformation_move_instruction_down_test.cpp b/test/fuzz/transformation_move_instruction_down_test.cpp new file mode 100644 index 000000000..088938ef8 --- /dev/null +++ b/test/fuzz/transformation_move_instruction_down_test.cpp @@ -0,0 +1,165 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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_move_instruction_down.h" + +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationMoveInstructionDownTest, 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 + %9 = OpConstant %6 0 + %16 = OpTypeBool + %17 = OpConstantFalse %16 + %20 = OpUndef %6 + %13 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %12 = OpVariable %13 Function + %10 = OpIAdd %6 %9 %9 + %11 = OpISub %6 %9 %10 + OpStore %12 %10 + %14 = OpLoad %6 %12 + %15 = OpIMul %6 %9 %14 + OpSelectionMerge %19 None + OpBranchConditional %17 %18 %19 + %18 = OpLabel + OpBranch %19 + %19 = OpLabel + %22 = OpIAdd %6 %15 %15 + %21 = OpIAdd %6 %15 %15 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + 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); + + // Instruction descriptor is invalid. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(30, SpvOpNop, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Opcode is not supported. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(5, SpvOpLabel, 0)) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(12, SpvOpVariable, 0)) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(11, SpvOpStore, 0)) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(14, SpvOpLoad, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Can't move the last instruction in the block. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(15, SpvOpBranchConditional, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Can't move the instruction if the next instruction is the last one in the + // block. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(21, SpvOpIAdd, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Can't insert instruction's opcode after its successor. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(15, SpvOpIMul, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Instruction's successor depends on the instruction. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(10, SpvOpIAdd, 0)) + .IsApplicable(context.get(), transformation_context)); + + { + TransformationMoveInstructionDown transformation( + MakeInstructionDescriptor(11, SpvOpISub, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + { + TransformationMoveInstructionDown transformation( + MakeInstructionDescriptor(22, SpvOpIAdd, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %9 = OpConstant %6 0 + %16 = OpTypeBool + %17 = OpConstantFalse %16 + %20 = OpUndef %6 + %13 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %12 = OpVariable %13 Function + %10 = OpIAdd %6 %9 %9 + OpStore %12 %10 + %11 = OpISub %6 %9 %10 + %14 = OpLoad %6 %12 + %15 = OpIMul %6 %9 %14 + OpSelectionMerge %19 None + OpBranchConditional %17 %18 %19 + %18 = OpLabel + OpBranch %19 + %19 = OpLabel + %21 = OpIAdd %6 %15 %15 + %22 = OpIAdd %6 %15 %15 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools