diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index af259040..a4cbf8e4 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -30,17 +30,23 @@ if(SPIRV_BUILD_FUZZER) fuzzer.h fuzzer_context.h fuzzer_pass.h + fuzzer_pass_split_blocks.h + fuzzer_util.h protobufs/spirvfuzz_protobufs.h pseudo_random_generator.h random_generator.h + transformation_split_block.h ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h fact_manager.cpp fuzzer.cpp fuzzer_context.cpp fuzzer_pass.cpp + fuzzer_pass_split_blocks.cpp + fuzzer_util.cpp pseudo_random_generator.cpp random_generator.cpp + transformation_split_block.cpp ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc ) diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index 2f28d79b..559602c6 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp @@ -19,6 +19,7 @@ #include "source/fuzz/fact_manager.h" #include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_pass_split_blocks.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/pseudo_random_generator.h" #include "source/opt/build_module.h" @@ -50,7 +51,8 @@ void Fuzzer::SetMessageConsumer(MessageConsumer c) { Fuzzer::FuzzerResultStatus Fuzzer::Run( const std::vector& binary_in, const protobufs::FactSequence& initial_facts, - std::vector* binary_out, protobufs::TransformationSequence*, + std::vector* binary_out, + protobufs::TransformationSequence* transformation_sequence_out, spv_const_fuzzer_options options) const { // Check compatibility between the library version being linked with and the // header files being used. @@ -95,9 +97,10 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( return Fuzzer::FuzzerResultStatus::kInitialFactsInvalid; } - // Fuzzer passes will be created and applied here and will populate the - // output sequence of transformations. Currently there are no passes. - // TODO(afd) Implement fuzzer passes and invoke them here. + // Apply some semantics-preserving passes. + FuzzerPassSplitBlocks(ir_context.get(), &fact_manager, &fuzzer_context, + transformation_sequence_out) + .Apply(); // Encode the module as a binary. ir_context->module()->ToBinary(binary_out, false); diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp index 11de6127..959f25a5 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -17,9 +17,20 @@ namespace spvtools { namespace fuzz { +namespace { +// Default probabilities for applying various transformations. +// All values are percentages. +// Keep them in alphabetical order. + +const uint32_t kDefaultChanceOfSplittingBlock = 20; + +} // namespace + FuzzerContext::FuzzerContext(RandomGenerator* random_generator, uint32_t min_fresh_id) - : random_generator_(random_generator), next_fresh_id_(min_fresh_id) {} + : random_generator_(random_generator), + next_fresh_id_(min_fresh_id), + chance_of_splitting_block_(kDefaultChanceOfSplittingBlock) {} FuzzerContext::~FuzzerContext() = default; diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index 5ab0f6f0..fbd9c45c 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -39,11 +39,19 @@ class FuzzerContext { // or to have been issued before. uint32_t GetFreshId(); + // Probabilities associated with applying various transformations. + // Keep them in alphabetical order. + uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; } + private: // The source of randomness. RandomGenerator* random_generator_; // The next fresh id to be issued. uint32_t next_fresh_id_; + + // Probabilities associated with applying various transformations. + // Keep them in alphabetical order. + uint32_t chance_of_splitting_block_; }; } // namespace fuzz diff --git a/source/fuzz/fuzzer_pass_split_blocks.cpp b/source/fuzz/fuzzer_pass_split_blocks.cpp new file mode 100644 index 00000000..9d400c8c --- /dev/null +++ b/source/fuzz/fuzzer_pass_split_blocks.cpp @@ -0,0 +1,91 @@ +// Copyright (c) 2019 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_split_blocks.h" + +#include +#include + +#include "source/fuzz/transformation_split_block.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassSplitBlocks::FuzzerPassSplitBlocks( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassSplitBlocks::~FuzzerPassSplitBlocks() = default; + +void FuzzerPassSplitBlocks::Apply() { + // Consider each block in the module. + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // Probabilistically decide whether to try to split this block. + if (GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() > + GetFuzzerContext()->GetChanceOfSplittingBlock()) { + continue; + } + // We are going to try to split this block. We now need to choose where + // to split it. We do this by finding a base instruction that has a + // result id, and an offset from that base instruction. We would like + // offsets to be as small as possible and ideally 0 - we only need offsets + // because not all instructions can be identified by a result id (e.g. + // OpStore instructions cannot). + std::vector> base_offset_pairs; + // The initial base instruction is the block label. + uint32_t base = block.id(); + uint32_t offset = 0; + // Consider every instruction in the block. The label is excluded: it is + // only necessary to consider it as a base in case the first instruction + // in the block does not have a result id. + for (auto& inst : block) { + if (inst.HasResultId()) { + // In the case that the instruction has a result id, we use the + // instruction as its own base, with zero offset. + base = inst.result_id(); + offset = 0; + } else { + // The instruction does not have a result id, so we need to identify + // it via the latest instruction that did have a result id (base), and + // an incremented offset. + offset++; + } + base_offset_pairs.emplace_back(base, offset); + } + // Having identified all the places we might be able to split the block, + // we choose one of them. + auto base_offset = base_offset_pairs + [GetFuzzerContext()->GetRandomGenerator()->RandomUint32( + static_cast(base_offset_pairs.size()))]; + auto message = transformation::MakeTransformationSplitBlock( + base_offset.first, base_offset.second, + GetFuzzerContext()->GetFreshId()); + // If the position we have chosen turns out to be a valid place to split + // the block, we apply the split. Otherwise the block just doesn't get + // split. + if (transformation::IsApplicable(message, GetIRContext(), + *GetFactManager())) { + transformation::Apply(message, GetIRContext(), GetFactManager()); + *GetTransformations()->add_transformation()->mutable_split_block() = + message; + } + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_split_blocks.h b/source/fuzz/fuzzer_pass_split_blocks.h new file mode 100644 index 00000000..951022b2 --- /dev/null +++ b/source/fuzz/fuzzer_pass_split_blocks.h @@ -0,0 +1,39 @@ +// Copyright (c) 2019 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_SPLIT_BLOCKS_ +#define SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass for splitting blocks in the module, to create more blocks; this +// can be very useful for giving other passes a chance to apply. +class FuzzerPassSplitBlocks : public FuzzerPass { + public: + FuzzerPassSplitBlocks(opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassSplitBlocks() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // #define SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_ diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp new file mode 100644 index 00000000..645c121d --- /dev/null +++ b/source/fuzz/fuzzer_util.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2019 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_util.h" + +namespace spvtools { +namespace fuzz { + +namespace fuzzerutil { + +bool IsFreshId(opt::IRContext* context, uint32_t id) { + return !context->get_def_use_mgr()->GetDef(id); +} + +void UpdateModuleIdBound(opt::IRContext* context, uint32_t id) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) consider the + // case where the maximum id bound is reached. + context->module()->SetIdBound( + std::max(context->module()->id_bound(), id + 1)); +} + +} // namespace fuzzerutil + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h new file mode 100644 index 00000000..30d870b4 --- /dev/null +++ b/source/fuzz/fuzzer_util.h @@ -0,0 +1,38 @@ +// Copyright (c) 2019 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_UTIL_H_ +#define SOURCE_FUZZ_FUZZER_UTIL_H_ + +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +// Provides global utility methods for use by the fuzzer +namespace fuzzerutil { + +// Returns true if and only if the module does not define the given id. +bool IsFreshId(opt::IRContext* context, uint32_t id); + +// Updates the module's id bound if needed so that it is large enough to +// account for the given id. +void UpdateModuleIdBound(opt::IRContext* context, uint32_t id); + +} // namespace fuzzerutil + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_UTIL_H_ diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto index e87819a5..8f1975b8 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -34,5 +34,31 @@ message TransformationSequence { } message Transformation { + oneof transformation { + TransformationSplitBlock split_block = 1; + } // Currently there are no transformations. } + +message TransformationSplitBlock { + + // A transformation that splits a basic block into two basic blocks. + + // The result id of an instruction. + uint32 result_id = 1; + + // An offset, such that the block containing |result_id_| should be split + // right before the instruction |offset_| instructions after |result_id_|. + uint32 offset = 2; + + // An id that must not yet be used by the module to which this transformation + // is applied. Rather than having the transformation choose a suitable id on + // application, we require the id to be given upfront in order to facilitate + // reducing fuzzed shaders by removing transformations. The reason is that + // future transformations may refer to the fresh id introduced by this + // transformation, and if we end up changing what that id is, due to removing + // earlier transformations, it may inhibit later transformations from + // applying. + uint32 fresh_id = 3; + +} diff --git a/source/fuzz/pseudo_random_generator.cpp b/source/fuzz/pseudo_random_generator.cpp index 773b89df..9643264a 100644 --- a/source/fuzz/pseudo_random_generator.cpp +++ b/source/fuzz/pseudo_random_generator.cpp @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include - #include "source/fuzz/pseudo_random_generator.h" +#include + namespace spvtools { namespace fuzz { diff --git a/source/fuzz/transformation_split_block.cpp b/source/fuzz/transformation_split_block.cpp new file mode 100644 index 00000000..943bed07 --- /dev/null +++ b/source/fuzz/transformation_split_block.cpp @@ -0,0 +1,183 @@ +// Copyright (c) 2019 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_split_block.h" + +#include + +#include "source/fuzz/fuzzer_util.h" +#include "source/util/make_unique.h" + +namespace spvtools { +namespace fuzz { +namespace transformation { + +using opt::BasicBlock; +using opt::IRContext; +using opt::Instruction; +using opt::Operand; + +namespace { + +// Returns: +// - (true, block->end()) if the relevant instruction is in this block +// but inapplicable +// - (true, it) if 'it' is an iterator for the relevant instruction +// - (false, _) otherwise. +std::pair FindInstToSplitBefore( + const protobufs::TransformationSplitBlock& message, BasicBlock* block) { + // There are three possibilities: + // (1) the transformation wants to split at some offset from the block's + // label. + // (2) the transformation wants to split at some offset from a + // non-label instruction inside the block. + // (3) the split associated with this transformation has nothing to do with + // this block + if (message.result_id() == block->id()) { + // Case (1). + if (message.offset() == 0) { + // The offset is not allowed to be 0: this would mean splitting before the + // block's label. + // By returning (true, block->end()), we indicate that we did find the + // instruction (so that it is not worth searching further for it), but + // that splitting will not be possible. + return {true, block->end()}; + } + // Conceptually, the first instruction in the block is [label + 1]. + // We thus start from 1 when applying the offset. + auto inst_it = block->begin(); + for (uint32_t i = 1; i < message.offset() && inst_it != block->end(); i++) { + ++inst_it; + } + // This is either the desired instruction, or the end of the block. + return {true, inst_it}; + } + for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) { + if (message.result_id() == inst_it->result_id()) { + // Case (2): we have found the base instruction; we now apply the offset. + for (uint32_t i = 0; i < message.offset() && inst_it != block->end(); + i++) { + ++inst_it; + } + // This is either the desired instruction, or the end of the block. + return {true, inst_it}; + } + } + // Case (3). + return {false, block->end()}; +} + +} // namespace + +bool IsApplicable(const protobufs::TransformationSplitBlock& message, + IRContext* context, const FactManager& /*unused*/) { + if (!fuzzerutil::IsFreshId(context, message.fresh_id())) { + // We require the id for the new block to be unused. + return false; + } + // Consider every block in every function. + for (auto& function : *context->module()) { + for (auto& block : function) { + auto maybe_split_before = FindInstToSplitBefore(message, &block); + if (!maybe_split_before.first) { + continue; + } + if (maybe_split_before.second == block.end()) { + // The base instruction was found, but the offset was inappropriate. + return false; + } + if (block.IsLoopHeader()) { + // We cannot split a loop header block: back-edges would become invalid. + return false; + } + auto split_before = maybe_split_before.second; + if (split_before->PreviousNode() && + split_before->PreviousNode()->opcode() == SpvOpSelectionMerge) { + // We cannot split directly after a selection merge: this would separate + // the merge from its associated branch or switch operation. + return false; + } + if (split_before->opcode() == SpvOpVariable) { + // We cannot split directly after a variable; variables in a function + // must be contiguous in the entry block. + return false; + } + if (split_before->opcode() == SpvOpPhi && + split_before->NumInOperands() != 2) { + // We cannot split before an OpPhi unless the OpPhi has exactly one + // associated incoming edge. + return false; + } + return true; + } + } + return false; +} + +void Apply(const protobufs::TransformationSplitBlock& message, + IRContext* context, FactManager* /*unused*/) { + for (auto& function : *context->module()) { + for (auto& block : function) { + auto maybe_split_before = FindInstToSplitBefore(message, &block); + if (!maybe_split_before.first) { + continue; + } + assert(maybe_split_before.second != block.end() && + "If the transformation is applicable, we should have an " + "instruction to split on."); + // We need to make sure the module's id bound is large enough to add the + // fresh id. + fuzzerutil::UpdateModuleIdBound(context, message.fresh_id()); + // Split the block. + auto new_bb = block.SplitBasicBlock(context, message.fresh_id(), + maybe_split_before.second); + // The split does not automatically add a branch between the two parts of + // the original block, so we add one. + block.AddInstruction(MakeUnique( + context, SpvOpBranch, 0, 0, + std::initializer_list{Operand( + spv_operand_type_t::SPV_OPERAND_TYPE_ID, {message.fresh_id()})})); + // If we split before OpPhi instructions, we need to update their + // predecessor operand so that the block they used to be inside is now the + // predecessor. + new_bb->ForEachPhiInst([&block](Instruction* phi_inst) { + // The following assertion is a sanity check. It is guaranteed to hold + // if IsApplicable holds. + assert(phi_inst->NumInOperands() == 2 && + "We can only split a block before an OpPhi if block has exactly " + "one predecessor."); + phi_inst->SetInOperand(1, {block.id()}); + }); + // Invalidate all analyses + context->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone); + return; + } + } + assert(0 && + "Should be unreachable: it should have been possible to apply this " + "transformation."); +} + +protobufs::TransformationSplitBlock MakeTransformationSplitBlock( + uint32_t result_id, uint32_t offset, uint32_t fresh_id) { + protobufs::TransformationSplitBlock result; + result.set_result_id(result_id); + result.set_offset(offset); + result.set_fresh_id(fresh_id); + return result; +} + +} // namespace transformation +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_split_block.h b/source/fuzz/transformation_split_block.h new file mode 100644 index 00000000..9ae419bc --- /dev/null +++ b/source/fuzz/transformation_split_block.h @@ -0,0 +1,52 @@ +// Copyright (c) 2019 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_SPLIT_BLOCK_H_ +#define SOURCE_FUZZ_TRANSFORMATION_SPLIT_BLOCK_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { +namespace transformation { + +// - |result_id| must be the result id of an instruction 'base' in some +// block 'blk'. +// - 'blk' must contain an instruction 'inst' located |offset| instructions +// after 'inst' (if |offset| = 0 then 'inst' = 'base'). +// - Splitting 'blk' at 'inst', so that all instructions from 'inst' onwards +// appear in a new block that 'blk' directly jumps to must be valid. +// - |fresh_id| must not be used by the module. +bool IsApplicable(const protobufs::TransformationSplitBlock& message, + opt::IRContext* context, const FactManager& fact_manager); + +// - A new block with label |fresh_id| is inserted right after 'blk' in +// program order. +// - All instructions of 'blk' from 'inst' onwards are moved into the new +// block. +// - 'blk' is made to jump unconditionally to the new block. +void Apply(const protobufs::TransformationSplitBlock& message, + opt::IRContext* context, FactManager* fact_manager); + +// Creates a protobuf message representing a block-splitting transformation. +protobufs::TransformationSplitBlock MakeTransformationSplitBlock( + uint32_t result_id, uint32_t offset, uint32_t fresh_id); + +} // namespace transformation +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_SPLIT_BLOCK_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5def389d..3dca430c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -186,6 +186,7 @@ endif() add_subdirectory(link) add_subdirectory(opt) add_subdirectory(reduce) +add_subdirectory(fuzz) add_subdirectory(tools) add_subdirectory(util) add_subdirectory(val) diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt new file mode 100644 index 00000000..91650260 --- /dev/null +++ b/test/fuzz/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (c) 2019 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. + +if (${SPIRV_BUILD_FUZZER}) + + set(SOURCES + fuzz_test_util.h + + fuzz_test_util.cpp + transformation_split_block_test.cpp) + + add_spvtools_unittest(TARGET fuzz + SRCS ${SOURCES} + LIBS SPIRV-Tools-fuzz + ) +endif() diff --git a/test/fuzz/fuzz_test_util.cpp b/test/fuzz/fuzz_test_util.cpp new file mode 100644 index 00000000..545512f9 --- /dev/null +++ b/test/fuzz/fuzz_test_util.cpp @@ -0,0 +1,89 @@ +// Copyright (c) 2019 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 "test/fuzz/fuzz_test_util.h" + +#include + +namespace spvtools { +namespace fuzz { + +bool IsEqual(const spv_target_env env, + const std::vector& expected_binary, + const std::vector& actual_binary) { + if (expected_binary == actual_binary) { + return true; + } + SpirvTools t(env); + std::string expected_disassembled; + std::string actual_disassembled; + if (!t.Disassemble(expected_binary, &expected_disassembled, + kFuzzDisassembleOption)) { + return false; + } + if (!t.Disassemble(actual_binary, &actual_disassembled, + kFuzzDisassembleOption)) { + return false; + } + // Using expect gives us a string diff if the strings are not the same. + EXPECT_EQ(expected_disassembled, actual_disassembled); + // We then return the result of the equality comparison, to be used by an + // assertion in the test root function. + return expected_disassembled == actual_disassembled; +} + +bool IsEqual(const spv_target_env env, const std::string& expected_text, + const std::vector& actual_binary) { + std::vector expected_binary; + SpirvTools t(env); + if (!t.Assemble(expected_text, &expected_binary, kFuzzAssembleOption)) { + return false; + } + return IsEqual(env, expected_binary, actual_binary); +} + +bool IsEqual(const spv_target_env env, const std::string& expected_text, + const opt::IRContext* actual_ir) { + std::vector actual_binary; + actual_ir->module()->ToBinary(&actual_binary, false); + return IsEqual(env, expected_text, actual_binary); +} + +bool IsEqual(const spv_target_env env, const opt::IRContext* ir_1, + const opt::IRContext* ir_2) { + std::vector binary_1; + ir_1->module()->ToBinary(&binary_1, false); + std::vector binary_2; + ir_2->module()->ToBinary(&binary_2, false); + return IsEqual(env, binary_1, binary_2); +} + +bool IsValid(spv_target_env env, const opt::IRContext* ir) { + std::vector binary; + ir->module()->ToBinary(&binary, false); + SpirvTools t(env); + return t.Validate(binary); +} + +std::string ToString(spv_target_env env, const opt::IRContext* ir) { + std::vector binary; + ir->module()->ToBinary(&binary, false); + SpirvTools t(env); + std::string result; + t.Disassemble(binary, &result, kFuzzDisassembleOption); + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/fuzz_test_util.h b/test/fuzz/fuzz_test_util.h new file mode 100644 index 00000000..202622c8 --- /dev/null +++ b/test/fuzz/fuzz_test_util.h @@ -0,0 +1,65 @@ +// Copyright (c) 2019 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 TEST_FUZZ_FUZZ_TEST_UTIL_H_ +#define TEST_FUZZ_FUZZ_TEST_UTIL_H_ + +#include "gtest/gtest.h" + +#include "source/opt/build_module.h" +#include "source/opt/ir_context.h" +#include "spirv-tools/libspirv.h" + +namespace spvtools { +namespace fuzz { + +// Returns true if and only if the given binaries are bit-wise equal. +bool IsEqual(spv_target_env env, const std::vector& expected_binary, + const std::vector& actual_binary); + +// Assembles the given text and returns true if and only if the resulting binary +// is bit-wise equal to the given binary. +bool IsEqual(spv_target_env env, const std::string& expected_text, + const std::vector& actual_binary); + +// Assembles the given text and turns the given IR into binary, then returns +// true if and only if the resulting binaries are bit-wise equal. +bool IsEqual(spv_target_env env, const std::string& expected_text, + const opt::IRContext* actual_ir); + +// Turns the given IRs into binaries, then returns true if and only if the +// resulting binaries are bit-wise equal. +bool IsEqual(spv_target_env env, const opt::IRContext* ir_1, + const opt::IRContext* ir_2); + +// Assembles the given IR context and returns true if and only if +// the resulting binary is valid. +bool IsValid(spv_target_env env, const opt::IRContext* ir); + +// Assembles the given IR context, then returns its disassembly as a string. +// Useful for debugging. +std::string ToString(spv_target_env env, const opt::IRContext* ir); + +// Assembly options for writing fuzzer tests. It simplifies matters if +// numeric ids do not change. +const uint32_t kFuzzAssembleOption = + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS; +// Disassembly options for writing fuzzer tests. +const uint32_t kFuzzDisassembleOption = + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | SPV_BINARY_TO_TEXT_OPTION_INDENT; + +} // namespace fuzz +} // namespace spvtools + +#endif // TEST_FUZZ_FUZZ_TEST_UTIL_H_ diff --git a/test/fuzz/transformation_split_block_test.cpp b/test/fuzz/transformation_split_block_test.cpp new file mode 100644 index 00000000..976e81a8 --- /dev/null +++ b/test/fuzz/transformation_split_block_test.cpp @@ -0,0 +1,773 @@ +// Copyright (c) 2019 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_split_block.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationSplitBlockTest, NotApplicable) { + // The SPIR-V in this test came from the following fragment shader, with + // local store elimination applied to get some OpPhi instructions. + // + // void main() { + // int x; + // int i; + // for (i = 0; i < 100; i++) { + // x += i; + // } + // } + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i" + OpName %19 "x" + OpDecorate %8 RelaxedPrecision + OpDecorate %19 RelaxedPrecision + OpDecorate %22 RelaxedPrecision + OpDecorate %25 RelaxedPrecision + OpDecorate %26 RelaxedPrecision + OpDecorate %27 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 100 + %17 = OpTypeBool + %24 = OpConstant %6 1 + %28 = OpUndef %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %27 = OpPhi %6 %28 %5 %22 %13 + %26 = OpPhi %6 %9 %5 %25 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %26 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %22 = OpIAdd %6 %27 %26 + OpStore %19 %22 + OpBranch %13 + %13 = OpLabel + %25 = OpIAdd %6 %26 %24 + OpStore %8 %25 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager; + + // No split before OpVariable + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(8, 0, 100), context.get(), + fact_manager)); + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(8, 1, 100), context.get(), + fact_manager)); + + // No split before OpLabel + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(14, 0, 100), context.get(), + fact_manager)); + + // No split if base instruction is outside a function + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(1, 0, 100), context.get(), + fact_manager)); + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(1, 4, 100), context.get(), + fact_manager)); + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(1, 35, 100), context.get(), + fact_manager)); + + // No split if block is loop header + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(27, 0, 100), context.get(), + fact_manager)); + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(27, 1, 100), context.get(), + fact_manager)); + + // No split if base instruction does not exist + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(88, 0, 100), context.get(), + fact_manager)); + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(88, 22, 100), context.get(), + fact_manager)); + + // No split if offset is too large (goes into another block) + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(18, 3, 100), context.get(), + fact_manager)); + + // No split if id in use + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(18, 0, 27), context.get(), + fact_manager)); + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(18, 0, 14), context.get(), + fact_manager)); +} + +TEST(TransformationSplitBlockTest, SplitBlockSeveralTimes) { + // The SPIR-V in this test came from the following fragment shader: + // + // void main() { + // int a; + // int b; + // a = 1; + // b = a; + // a = b; + // b = 2; + // b++; + // } + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpDecorate %8 RelaxedPrecision + OpDecorate %10 RelaxedPrecision + OpDecorate %11 RelaxedPrecision + OpDecorate %12 RelaxedPrecision + OpDecorate %14 RelaxedPrecision + OpDecorate %15 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %13 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + OpStore %8 %9 + %11 = OpLoad %6 %8 + OpStore %10 %11 + %12 = OpLoad %6 %10 + OpStore %8 %12 + OpStore %10 %13 + %14 = OpLoad %6 %10 + %15 = OpIAdd %6 %14 %9 + OpStore %10 %15 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager; + + auto split_1 = transformation::MakeTransformationSplitBlock(5, 3, 100); + ASSERT_TRUE( + transformation::IsApplicable(split_1, context.get(), fact_manager)); + transformation::Apply(split_1, context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_split_1 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpDecorate %8 RelaxedPrecision + OpDecorate %10 RelaxedPrecision + OpDecorate %11 RelaxedPrecision + OpDecorate %12 RelaxedPrecision + OpDecorate %14 RelaxedPrecision + OpDecorate %15 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %13 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + OpBranch %100 + %100 = OpLabel + OpStore %8 %9 + %11 = OpLoad %6 %8 + OpStore %10 %11 + %12 = OpLoad %6 %10 + OpStore %8 %12 + OpStore %10 %13 + %14 = OpLoad %6 %10 + %15 = OpIAdd %6 %14 %9 + OpStore %10 %15 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_split_1, context.get())); + + auto split_2 = transformation::MakeTransformationSplitBlock(11, 1, 101); + ASSERT_TRUE( + transformation::IsApplicable(split_2, context.get(), fact_manager)); + transformation::Apply(split_2, context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_split_2 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpDecorate %8 RelaxedPrecision + OpDecorate %10 RelaxedPrecision + OpDecorate %11 RelaxedPrecision + OpDecorate %12 RelaxedPrecision + OpDecorate %14 RelaxedPrecision + OpDecorate %15 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %13 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + OpBranch %100 + %100 = OpLabel + OpStore %8 %9 + %11 = OpLoad %6 %8 + OpBranch %101 + %101 = OpLabel + OpStore %10 %11 + %12 = OpLoad %6 %10 + OpStore %8 %12 + OpStore %10 %13 + %14 = OpLoad %6 %10 + %15 = OpIAdd %6 %14 %9 + OpStore %10 %15 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_split_2, context.get())); + + auto split_3 = transformation::MakeTransformationSplitBlock(14, 0, 102); + ASSERT_TRUE( + transformation::IsApplicable(split_3, context.get(), fact_manager)); + transformation::Apply(split_3, context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_split_3 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpDecorate %8 RelaxedPrecision + OpDecorate %10 RelaxedPrecision + OpDecorate %11 RelaxedPrecision + OpDecorate %12 RelaxedPrecision + OpDecorate %14 RelaxedPrecision + OpDecorate %15 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %13 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + OpBranch %100 + %100 = OpLabel + OpStore %8 %9 + %11 = OpLoad %6 %8 + OpBranch %101 + %101 = OpLabel + OpStore %10 %11 + %12 = OpLoad %6 %10 + OpStore %8 %12 + OpStore %10 %13 + OpBranch %102 + %102 = OpLabel + %14 = OpLoad %6 %10 + %15 = OpIAdd %6 %14 %9 + OpStore %10 %15 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_split_3, context.get())); +} + +TEST(TransformationSplitBlockTest, SplitBlockBeforeSelectBranch) { + // The SPIR-V in this test came from the following fragment shader: + // + // void main() { + // int x, y; + // x = 2; + // if (x < y) { + // y = 3; + // } else { + // y = 4; + // } + // } + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + OpName %11 "y" + OpDecorate %8 RelaxedPrecision + OpDecorate %10 RelaxedPrecision + OpDecorate %11 RelaxedPrecision + OpDecorate %12 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 2 + %13 = OpTypeBool + %17 = OpConstant %6 3 + %19 = OpConstant %6 4 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %11 = OpVariable %7 Function + OpStore %8 %9 + %10 = OpLoad %6 %8 + %12 = OpLoad %6 %11 + %14 = OpSLessThan %13 %10 %12 + OpSelectionMerge %16 None + OpBranchConditional %14 %15 %18 + %15 = OpLabel + OpStore %11 %17 + OpBranch %16 + %18 = OpLabel + OpStore %11 %19 + OpBranch %16 + %16 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager; + + // Illegal to split between the merge and the conditional branch. + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(14, 2, 100), context.get(), + fact_manager)); + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(12, 3, 100), context.get(), + fact_manager)); + + auto split = transformation::MakeTransformationSplitBlock(14, 1, 100); + ASSERT_TRUE(transformation::IsApplicable(split, context.get(), fact_manager)); + transformation::Apply(split, context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_split = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + OpName %11 "y" + OpDecorate %8 RelaxedPrecision + OpDecorate %10 RelaxedPrecision + OpDecorate %11 RelaxedPrecision + OpDecorate %12 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 2 + %13 = OpTypeBool + %17 = OpConstant %6 3 + %19 = OpConstant %6 4 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %11 = OpVariable %7 Function + OpStore %8 %9 + %10 = OpLoad %6 %8 + %12 = OpLoad %6 %11 + %14 = OpSLessThan %13 %10 %12 + OpBranch %100 + %100 = OpLabel + OpSelectionMerge %16 None + OpBranchConditional %14 %15 %18 + %15 = OpLabel + OpStore %11 %17 + OpBranch %16 + %18 = OpLabel + OpStore %11 %19 + OpBranch %16 + %16 = OpLabel + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_split, context.get())); +} + +TEST(TransformationSplitBlockTest, SplitBlockBeforeSwitchBranch) { + // The SPIR-V in this test came from the following fragment shader: + // + // void main() { + // int x, y; + // switch (y) { + // case 1: + // x = 2; + // case 2: + // break; + // case 3: + // x = 4; + // default: + // x = 6; + // } + // } + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "y" + OpName %15 "x" + OpDecorate %8 RelaxedPrecision + OpDecorate %9 RelaxedPrecision + OpDecorate %15 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %16 = OpConstant %6 2 + %18 = OpConstant %6 4 + %19 = OpConstant %6 6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %15 = OpVariable %7 Function + %9 = OpLoad %6 %8 + OpSelectionMerge %14 None + OpSwitch %9 %13 1 %10 2 %11 3 %12 + %13 = OpLabel + OpStore %15 %19 + OpBranch %14 + %10 = OpLabel + OpStore %15 %16 + OpBranch %11 + %11 = OpLabel + OpBranch %14 + %12 = OpLabel + OpStore %15 %18 + OpBranch %13 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager; + + // Illegal to split between the merge and the conditional branch. + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(9, 2, 100), context.get(), + fact_manager)); + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(15, 3, 100), context.get(), + fact_manager)); + + auto split = transformation::MakeTransformationSplitBlock(9, 1, 100); + ASSERT_TRUE(transformation::IsApplicable(split, context.get(), fact_manager)); + transformation::Apply(split, context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_split = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "y" + OpName %15 "x" + OpDecorate %8 RelaxedPrecision + OpDecorate %9 RelaxedPrecision + OpDecorate %15 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %16 = OpConstant %6 2 + %18 = OpConstant %6 4 + %19 = OpConstant %6 6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %15 = OpVariable %7 Function + %9 = OpLoad %6 %8 + OpBranch %100 + %100 = OpLabel + OpSelectionMerge %14 None + OpSwitch %9 %13 1 %10 2 %11 3 %12 + %13 = OpLabel + OpStore %15 %19 + OpBranch %14 + %10 = OpLabel + OpStore %15 %16 + OpBranch %11 + %11 = OpLabel + OpBranch %14 + %12 = OpLabel + OpStore %15 %18 + OpBranch %13 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_split, context.get())); +} + +TEST(TransformationSplitBlockTest, NoSplitDuringOpPhis) { + // The SPIR-V in this test came from the following fragment shader, with + // local store elimination applied to get some OpPhi instructions. + // + // void main() { + // int x; + // int i; + // for (i = 0; i < 100; i++) { + // x += i; + // } + // } + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i" + OpName %19 "x" + OpDecorate %8 RelaxedPrecision + OpDecorate %19 RelaxedPrecision + OpDecorate %22 RelaxedPrecision + OpDecorate %25 RelaxedPrecision + OpDecorate %26 RelaxedPrecision + OpDecorate %27 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 100 + %17 = OpTypeBool + %24 = OpConstant %6 1 + %28 = OpUndef %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %27 = OpPhi %6 %28 %5 %22 %13 + %26 = OpPhi %6 %9 %5 %25 %13 + OpBranch %50 + %50 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %26 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %22 = OpIAdd %6 %27 %26 + OpStore %19 %22 + OpBranch %13 + %13 = OpLabel + %25 = OpIAdd %6 %26 %24 + OpStore %8 %25 + OpBranch %50 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager; + + // We cannot split before OpPhi instructions, since the number of incoming + // blocks may not appropriately match after splitting. + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(26, 0, 100), context.get(), + fact_manager)); + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(27, 0, 100), context.get(), + fact_manager)); + ASSERT_FALSE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(27, 1, 100), context.get(), + fact_manager)); +} + +TEST(TransformationSplitBlockTest, SplitOpPhiWithSinglePredecessor) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + OpName %10 "y" + OpDecorate %8 RelaxedPrecision + OpDecorate %10 RelaxedPrecision + OpDecorate %11 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + OpStore %8 %9 + %11 = OpLoad %6 %8 + OpBranch %20 + %20 = OpLabel + %21 = OpPhi %6 %11 %5 + OpStore %10 %21 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager; + + ASSERT_TRUE(transformation::IsApplicable( + transformation::MakeTransformationSplitBlock(21, 0, 100), context.get(), + fact_manager)); + auto split = transformation::MakeTransformationSplitBlock(20, 1, 100); + ASSERT_TRUE(transformation::IsApplicable(split, context.get(), fact_manager)); + transformation::Apply(split, context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_split = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + OpName %10 "y" + OpDecorate %8 RelaxedPrecision + OpDecorate %10 RelaxedPrecision + OpDecorate %11 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + OpStore %8 %9 + %11 = OpLoad %6 %8 + OpBranch %20 + %20 = OpLabel + OpBranch %100 + %100 = OpLabel + %21 = OpPhi %6 %11 %20 + OpStore %10 %21 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_split, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools