spirv-fuzz: Skip unreachable blocks (#3729)

Fixes #3722, fixes #3713, fixes #3714.
This commit is contained in:
Vasyl Teliman 2020-09-16 01:35:42 +03:00 committed by GitHub
parent f20b523cb1
commit e7c84feda0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 144 additions and 22 deletions

View File

@ -107,7 +107,20 @@ void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
action) { action) {
// Consider every block in every function. // Consider every block in every function.
for (auto& function : *GetIRContext()->module()) { 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) { 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 // We now consider every instruction in the block, randomly deciding
// whether to apply a transformation before it. // whether to apply a transformation before it.
@ -122,7 +135,7 @@ void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
base_opcode_skip_triples; base_opcode_skip_triples;
// The initial base instruction is the block label. // 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 // Counts the number of times we have seen each opcode since we reset the
// base instruction. // base instruction.
@ -131,7 +144,7 @@ void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
// Consider every instruction in the block. The label is excluded: it is // 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 // only necessary to consider it as a base in case the first instruction
// in the block does not have a result id. // 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()) { if (inst_it->HasResultId()) {
// In the case that the instruction has a result id, we use the // 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 // instruction as its own base, and clear the skip counts we have
@ -142,7 +155,7 @@ void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
const SpvOp opcode = inst_it->opcode(); const SpvOp opcode = inst_it->opcode();
// Invoke the provided function, which might apply a transformation. // Invoke the provided function, which might apply a transformation.
action(&function, &block, inst_it, action(&function, block, inst_it,
MakeInstructionDescriptor( MakeInstructionDescriptor(
base, opcode, base, opcode,
skip_count.count(opcode) ? skip_count.at(opcode) : 0)); skip_count.count(opcode) ? skip_count.at(opcode) : 0));

View File

@ -70,9 +70,9 @@ class FuzzerPass {
std::function<bool(opt::IRContext*, opt::Instruction*)> std::function<bool(opt::IRContext*, opt::Instruction*)>
instruction_is_relevant) const; instruction_is_relevant) const;
// A helper method that iterates through each instruction in each block, at // A helper method that iterates through each instruction in each reachable
// all times tracking an instruction descriptor that allows the latest // block, at all times tracking an instruction descriptor that allows the
// instruction to be located even if it has no result id. // latest instruction to be located even if it has no result id.
// //
// The code to manipulate the instruction descriptor is a bit fiddly. The // 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 // point of this method is to avoiding having to duplicate it in multiple

View File

@ -503,6 +503,9 @@ bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id) {
bool IdIsAvailableAtUse(opt::IRContext* context, bool IdIsAvailableAtUse(opt::IRContext* context,
opt::Instruction* use_instruction, opt::Instruction* use_instruction,
uint32_t use_input_operand_index, uint32_t id) { 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 defining_instruction = context->get_def_use_mgr()->GetDef(id);
auto enclosing_function = auto enclosing_function =
context->get_instr_block(use_instruction)->GetParent(); context->get_instr_block(use_instruction)->GetParent();
@ -521,6 +524,12 @@ bool IdIsAvailableAtUse(opt::IRContext* context,
return false; return false;
} }
auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function); 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) { if (use_instruction->opcode() == SpvOpPhi) {
// In the case where the use is an operand to OpPhi, it is actually the // 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 // *parent* block associated with the operand that must be dominated by
@ -536,6 +545,9 @@ bool IdIsAvailableAtUse(opt::IRContext* context,
bool IdIsAvailableBeforeInstruction(opt::IRContext* context, bool IdIsAvailableBeforeInstruction(opt::IRContext* context,
opt::Instruction* instruction, opt::Instruction* instruction,
uint32_t id) { 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 defining_instruction = context->get_def_use_mgr()->GetDef(id);
auto enclosing_function = context->get_instr_block(instruction)->GetParent(); auto enclosing_function = context->get_instr_block(instruction)->GetParent();
// If the id a function parameter, it needs to be associated with the // If the id a function parameter, it needs to be associated with the
@ -552,8 +564,12 @@ bool IdIsAvailableBeforeInstruction(opt::IRContext* context,
// The instruction is not available right before its own definition. // The instruction is not available right before its own definition.
return false; return false;
} }
return context->GetDominatorAnalysis(enclosing_function) const auto* dominator_analysis =
->Dominates(defining_instruction, instruction); 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, bool InstructionIsFunctionParameter(opt::Instruction* instruction,

View File

@ -188,13 +188,14 @@ bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id);
// Checks whether |id| is available (according to dominance rules) at the use // Checks whether |id| is available (according to dominance rules) at the use
// point defined by input operand |use_input_operand_index| of // 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, bool IdIsAvailableAtUse(opt::IRContext* context,
opt::Instruction* use_instruction, opt::Instruction* use_instruction,
uint32_t use_input_operand_index, uint32_t id); uint32_t use_input_operand_index, uint32_t id);
// Checks whether |id| is available (according to dominance rules) at the // 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, bool IdIsAvailableBeforeInstruction(opt::IRContext* context,
opt::Instruction* instruction, uint32_t id); opt::Instruction* instruction, uint32_t id);

View File

@ -110,9 +110,6 @@ bool TransformationAddOpPhiSynonym::IsApplicable(
// the predecessors list of |block|. // the predecessors list of |block|.
assert(pred_block && "Could not find one of the predecessor blocks."); 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( if (!fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, pred_block->terminator(), id)) { ir_context, pred_block->terminator(), id)) {
return false; return false;

View File

@ -76,12 +76,6 @@ bool TransformationAddParameter::IsApplicable(
} }
// If the id of the value of the map is not available before the caller, // If the id of the value of the map is not available before the caller,
// return false. // 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, if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, instr,
value_id)) { value_id)) {
return false; return false;

View File

@ -77,9 +77,6 @@ bool TransformationReplaceIrrelevantId::IsApplicable(
} }
// The id must be available to use at the use point. // 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( return fuzzerutil::IdIsAvailableAtUse(
ir_context, use_instruction, ir_context, use_instruction,
message_.id_use_descriptor().in_operand_index(), message_.id_use_descriptor().in_operand_index(),

View File

@ -28,6 +28,7 @@ if (${SPIRV_BUILD_FUZZER})
fuzzer_pass_donate_modules_test.cpp fuzzer_pass_donate_modules_test.cpp
fuzzer_pass_outline_functions_test.cpp fuzzer_pass_outline_functions_test.cpp
instruction_descriptor_test.cpp instruction_descriptor_test.cpp
fuzzer_pass_test.cpp
replayer_test.cpp replayer_test.cpp
transformation_access_chain_test.cpp transformation_access_chain_test.cpp
transformation_add_constant_boolean_test.cpp transformation_add_constant_boolean_test.cpp

View File

@ -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