spirv-fuzz: Skip unreachable blocks (#3729)
Fixes #3722, fixes #3713, fixes #3714.
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index a6a698e..926fc52 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -107,7 +107,20 @@
action) {
// Consider every block in every function.
for (auto& function : *GetIRContext()->module()) {
+ // Consider only reachable blocks. We do this in a separate loop to avoid
+ // recomputing the dominator analysis every time |action| changes the
+ // module.
+ std::vector<opt::BasicBlock*> reachable_blocks;
+
+ const auto* dominator_analysis =
+ GetIRContext()->GetDominatorAnalysis(&function);
for (auto& block : function) {
+ if (dominator_analysis->IsReachable(&block)) {
+ reachable_blocks.push_back(&block);
+ }
+ }
+
+ for (auto* block : reachable_blocks) {
// We now consider every instruction in the block, randomly deciding
// whether to apply a transformation before it.
@@ -122,7 +135,7 @@
base_opcode_skip_triples;
// The initial base instruction is the block label.
- uint32_t base = block.id();
+ uint32_t base = block->id();
// Counts the number of times we have seen each opcode since we reset the
// base instruction.
@@ -131,7 +144,7 @@
// 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_it = block.begin(); inst_it != block.end(); ++inst_it) {
+ for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) {
if (inst_it->HasResultId()) {
// In the case that the instruction has a result id, we use the
// instruction as its own base, and clear the skip counts we have
@@ -142,7 +155,7 @@
const SpvOp opcode = inst_it->opcode();
// Invoke the provided function, which might apply a transformation.
- action(&function, &block, inst_it,
+ action(&function, block, inst_it,
MakeInstructionDescriptor(
base, opcode,
skip_count.count(opcode) ? skip_count.at(opcode) : 0));
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index 1e6992a..8d75c63 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -70,9 +70,9 @@
std::function<bool(opt::IRContext*, opt::Instruction*)>
instruction_is_relevant) const;
- // A helper method that iterates through each instruction in each block, at
- // all times tracking an instruction descriptor that allows the latest
- // instruction to be located even if it has no result id.
+ // A helper method that iterates through each instruction in each reachable
+ // block, at all times tracking an instruction descriptor that allows the
+ // latest instruction to be located even if it has no result id.
//
// The code to manipulate the instruction descriptor is a bit fiddly. The
// point of this method is to avoiding having to duplicate it in multiple
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 001cd86..2502da7 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -503,6 +503,9 @@
bool IdIsAvailableAtUse(opt::IRContext* context,
opt::Instruction* use_instruction,
uint32_t use_input_operand_index, uint32_t id) {
+ assert(context->get_instr_block(use_instruction) &&
+ "|use_instruction| must be in a basic block");
+
auto defining_instruction = context->get_def_use_mgr()->GetDef(id);
auto enclosing_function =
context->get_instr_block(use_instruction)->GetParent();
@@ -521,6 +524,12 @@
return false;
}
auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function);
+ if (!dominator_analysis->IsReachable(
+ context->get_instr_block(use_instruction)) ||
+ !dominator_analysis->IsReachable(context->get_instr_block(id))) {
+ // Skip unreachable blocks.
+ return false;
+ }
if (use_instruction->opcode() == SpvOpPhi) {
// In the case where the use is an operand to OpPhi, it is actually the
// *parent* block associated with the operand that must be dominated by
@@ -536,6 +545,9 @@
bool IdIsAvailableBeforeInstruction(opt::IRContext* context,
opt::Instruction* instruction,
uint32_t id) {
+ assert(context->get_instr_block(instruction) &&
+ "|instruction| must be in a basic block");
+
auto defining_instruction = context->get_def_use_mgr()->GetDef(id);
auto enclosing_function = context->get_instr_block(instruction)->GetParent();
// If the id a function parameter, it needs to be associated with the
@@ -552,8 +564,12 @@
// The instruction is not available right before its own definition.
return false;
}
- return context->GetDominatorAnalysis(enclosing_function)
- ->Dominates(defining_instruction, instruction);
+ const auto* dominator_analysis =
+ context->GetDominatorAnalysis(enclosing_function);
+ return dominator_analysis->IsReachable(
+ context->get_instr_block(instruction)) &&
+ dominator_analysis->IsReachable(context->get_instr_block(id)) &&
+ dominator_analysis->Dominates(defining_instruction, instruction);
}
bool InstructionIsFunctionParameter(opt::Instruction* instruction,
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index 5f1e3f7..b4b9057 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -188,13 +188,14 @@
// Checks whether |id| is available (according to dominance rules) at the use
// point defined by input operand |use_input_operand_index| of
-// |use_instruction|.
+// |use_instruction|. |use_instruction| must be a in some basic block.
bool IdIsAvailableAtUse(opt::IRContext* context,
opt::Instruction* use_instruction,
uint32_t use_input_operand_index, uint32_t id);
// Checks whether |id| is available (according to dominance rules) at the
-// program point directly before |instruction|.
+// program point directly before |instruction|. |instruction| must be in some
+// basic block.
bool IdIsAvailableBeforeInstruction(opt::IRContext* context,
opt::Instruction* instruction, uint32_t id);
diff --git a/source/fuzz/transformation_add_opphi_synonym.cpp b/source/fuzz/transformation_add_opphi_synonym.cpp
index 3436dd7..0eee881 100644
--- a/source/fuzz/transformation_add_opphi_synonym.cpp
+++ b/source/fuzz/transformation_add_opphi_synonym.cpp
@@ -110,9 +110,6 @@
// the predecessors list of |block|.
assert(pred_block && "Could not find one of the predecessor blocks.");
- // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3722): This
- // function always returns false if the block is unreachable, so it may
- // need to be refactored.
if (!fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, pred_block->terminator(), id)) {
return false;
diff --git a/source/fuzz/transformation_add_parameter.cpp b/source/fuzz/transformation_add_parameter.cpp
index d0b80eb..42d68ae 100644
--- a/source/fuzz/transformation_add_parameter.cpp
+++ b/source/fuzz/transformation_add_parameter.cpp
@@ -76,12 +76,6 @@
}
// If the id of the value of the map is not available before the caller,
// return false.
- // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3722):
- // This can potentially trigger a bug if the caller is in an
- // unreachable block. fuzzerutil::IdIsAvailableBeforeInstruction uses
- // dominator analysis to check that value_id is available and the
- // domination rules are not defined for unreachable blocks.
- // The following code should be refactored.
if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, instr,
value_id)) {
return false;
diff --git a/source/fuzz/transformation_replace_irrelevant_id.cpp b/source/fuzz/transformation_replace_irrelevant_id.cpp
index 25a2fc4..5ac182a 100644
--- a/source/fuzz/transformation_replace_irrelevant_id.cpp
+++ b/source/fuzz/transformation_replace_irrelevant_id.cpp
@@ -77,9 +77,6 @@
}
// The id must be available to use at the use point.
- // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3722):
- // IdIsAvailable at use always returns false if the instruction is in an
- // unreachable block, so it might need to be fixed.
return fuzzerutil::IdIsAvailableAtUse(
ir_context, use_instruction,
message_.id_use_descriptor().in_operand_index(),
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 3d2b569..5bdb42c 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -28,6 +28,7 @@
fuzzer_pass_donate_modules_test.cpp
fuzzer_pass_outline_functions_test.cpp
instruction_descriptor_test.cpp
+ fuzzer_pass_test.cpp
replayer_test.cpp
transformation_access_chain_test.cpp
transformation_add_constant_boolean_test.cpp
diff --git a/test/fuzz/fuzzer_pass_test.cpp b/test/fuzz/fuzzer_pass_test.cpp
new file mode 100644
index 0000000..ee22632
--- /dev/null
+++ b/test/fuzz/fuzzer_pass_test.cpp
@@ -0,0 +1,103 @@
+// 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_add_opphi_synonyms.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+class FuzzerPassMock : public FuzzerPass {
+ public:
+ FuzzerPassMock(opt::IRContext* ir_context,
+ TransformationContext* transformation_context,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations)
+ : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+ transformations) {}
+
+ ~FuzzerPassMock() override = default;
+
+ const std::unordered_set<uint32_t>& GetReachedInstructions() const {
+ return reached_ids_;
+ }
+
+ void Apply() override {
+ ForEachInstructionWithInstructionDescriptor(
+ [this](opt::Function* /*unused*/, opt::BasicBlock* /*unused*/,
+ opt::BasicBlock::iterator inst_it,
+ const protobufs::InstructionDescriptor& /*unused*/) {
+ if (inst_it->result_id()) {
+ reached_ids_.insert(inst_it->result_id());
+ }
+ });
+ }
+
+ private:
+ std::unordered_set<uint32_t> reached_ids_;
+};
+
+TEST(FuzzerPassTest, ForEachInstructionWithInstructionDescriptor) {
+ 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 = OpTypeFloat 32
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %7 = OpUndef %6
+ OpReturn
+ %8 = OpLabel
+ %9 = OpUndef %6
+ 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);
+
+ // Check that %5 is reachable and %8 is unreachable as expected.
+ const auto* dominator_analysis =
+ context->GetDominatorAnalysis(context->GetFunction(4));
+ ASSERT_TRUE(dominator_analysis->IsReachable(5));
+ ASSERT_FALSE(dominator_analysis->IsReachable(8));
+
+ PseudoRandomGenerator prng(0);
+ FuzzerContext fuzzer_context(&prng, 100);
+ protobufs::TransformationSequence transformations;
+ FuzzerPassMock fuzzer_pass_mock(context.get(), &transformation_context,
+ &fuzzer_context, &transformations);
+ fuzzer_pass_mock.Apply();
+
+ ASSERT_TRUE(fuzzer_pass_mock.GetReachedInstructions().count(7));
+ ASSERT_FALSE(fuzzer_pass_mock.GetReachedInstructions().count(9));
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools