spirv-fuzz: Wrap OpKill and similar in function calls (#3884)

Part of #3717.
This commit is contained in:
Alastair Donaldson 2020-10-08 22:33:14 +01:00 коммит произвёл GitHub
Родитель 11d5924227
Коммит e022659922
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 586 добавлений и 0 удалений

Просмотреть файл

@ -220,6 +220,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_swap_conditional_branch_operands.h
transformation_toggle_access_chain_instruction.h
transformation_vector_shuffle.h
transformation_wrap_early_terminator_in_function.h
transformation_wrap_region_in_selection.h
uniform_buffer_element_descriptor.h
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
@ -404,6 +405,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_swap_conditional_branch_operands.cpp
transformation_toggle_access_chain_instruction.cpp
transformation_vector_shuffle.cpp
transformation_wrap_early_terminator_in_function.cpp
transformation_wrap_region_in_selection.cpp
uniform_buffer_element_descriptor.cpp
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc

Просмотреть файл

@ -554,6 +554,7 @@ message Transformation {
TransformationAddEarlyTerminatorWrapper add_early_terminator_wrapper = 80;
TransformationPropagateInstructionDown propagate_instruction_down = 81;
TransformationReplaceBranchFromDeadBlockWithExit replace_branch_from_dead_block_with_exit = 82;
TransformationWrapEarlyTerminatorInFunction wrap_early_terminator_in_function = 83;
// Add additional option using the next available number.
}
}
@ -2260,6 +2261,26 @@ message TransformationVectorShuffle {
}
message TransformationWrapEarlyTerminatorInFunction {
// Replaces an early terminator - OpKill, OpReachable or OpTerminateInvocation
// - with a call to a wrapper function for the terminator.
// A fresh id for a new OpFunctionCall instruction.
uint32 fresh_id = 1;
// A descriptor for an OpKill, OpUnreachable or OpTerminateInvocation
// instruction.
InstructionDescriptor early_terminator_instruction = 2;
// An id with the same type as the enclosing function's return type that is
// available at the early terminator. This is used to change the terminator
// to OpReturnValue. Ignored if the enclosing function has void return type,
// in which case OpReturn can be used as the new terminator.
uint32 returned_value_id = 3;
}
message TransformationWrapRegionInSelection {
// Transforms a single-entry-single-exit region R into

Просмотреть файл

@ -98,6 +98,7 @@
#include "source/fuzz/transformation_swap_conditional_branch_operands.h"
#include "source/fuzz/transformation_toggle_access_chain_instruction.h"
#include "source/fuzz/transformation_vector_shuffle.h"
#include "source/fuzz/transformation_wrap_early_terminator_in_function.h"
#include "source/fuzz/transformation_wrap_region_in_selection.h"
#include "source/util/make_unique.h"
@ -358,6 +359,10 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
message.toggle_access_chain_instruction());
case protobufs::Transformation::TransformationCase::kVectorShuffle:
return MakeUnique<TransformationVectorShuffle>(message.vector_shuffle());
case protobufs::Transformation::TransformationCase::
kWrapEarlyTerminatorInFunction:
return MakeUnique<TransformationWrapEarlyTerminatorInFunction>(
message.wrap_early_terminator_in_function());
case protobufs::Transformation::TransformationCase::kWrapRegionInSelection:
return MakeUnique<TransformationWrapRegionInSelection>(
message.wrap_region_in_selection());

Просмотреть файл

@ -0,0 +1,183 @@
// Copyright (c) 2020 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_wrap_early_terminator_in_function.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/util/make_unique.h"
namespace spvtools {
namespace fuzz {
TransformationWrapEarlyTerminatorInFunction::
TransformationWrapEarlyTerminatorInFunction(
const spvtools::fuzz::protobufs::
TransformationWrapEarlyTerminatorInFunction& message)
: message_(message) {}
TransformationWrapEarlyTerminatorInFunction::
TransformationWrapEarlyTerminatorInFunction(
uint32_t fresh_id,
const protobufs::InstructionDescriptor& early_terminator_instruction,
uint32_t returned_value_id) {
message_.set_fresh_id(fresh_id);
*message_.mutable_early_terminator_instruction() =
early_terminator_instruction;
message_.set_returned_value_id(returned_value_id);
}
bool TransformationWrapEarlyTerminatorInFunction::IsApplicable(
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
// The given id must be fresh.
if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
return false;
}
// |message_.early_terminator_instruction| must identify an instruciton, and
// the instruction must indeed be an early terminator.
auto early_terminator =
FindInstruction(message_.early_terminator_instruction(), ir_context);
if (!early_terminator) {
return false;
}
switch (early_terminator->opcode()) {
case SpvOpKill:
case SpvOpUnreachable:
case SpvOpTerminateInvocation:
break;
default:
return false;
}
// A wrapper function for the early terminator must exist.
auto wrapper_function =
MaybeGetWrapperFunction(ir_context, early_terminator->opcode());
if (wrapper_function == nullptr) {
return false;
}
auto enclosing_function =
ir_context->get_instr_block(early_terminator)->GetParent();
// The wrapper function cannot be the function containing the instruction we
// would like to wrap.
if (wrapper_function->result_id() == enclosing_function->result_id()) {
return false;
}
if (!ir_context->get_type_mgr()
->GetType(enclosing_function->type_id())
->AsVoid()) {
// The enclosing function has non-void return type. We thus need to make
// sure that |message_.returned_value_instruction| provides a suitable
// result id to use in an OpReturnValue instruction.
auto returned_value_instruction =
ir_context->get_def_use_mgr()->GetDef(message_.returned_value_id());
if (!returned_value_instruction || !returned_value_instruction->type_id() ||
returned_value_instruction->type_id() !=
enclosing_function->type_id()) {
return false;
}
if (!fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, early_terminator, message_.returned_value_id())) {
return false;
}
}
return true;
}
void TransformationWrapEarlyTerminatorInFunction::Apply(
opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
auto early_terminator =
FindInstruction(message_.early_terminator_instruction(), ir_context);
auto enclosing_block = ir_context->get_instr_block(early_terminator);
auto enclosing_function = enclosing_block->GetParent();
// We would like to add an OpFunctionCall before the block's terminator
// instruction, and then change the block's terminator to OpReturn or
// OpReturnValue.
// We get an iterator to the instruction we would like to insert the function
// call before. It will be an iterator to the final instruction in the block
// unless the block is a merge block in which case it will be to the
// penultimate instruction (because we cannot insert an OpFunctionCall after
// a merge instruction).
auto iterator = enclosing_block->tail();
if (enclosing_block->MergeBlockIdIfAny()) {
--iterator;
}
auto wrapper_function =
MaybeGetWrapperFunction(ir_context, early_terminator->opcode());
iterator->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpFunctionCall, wrapper_function->type_id(),
message_.fresh_id(),
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {wrapper_function->result_id()}}})));
opt::Instruction::OperandList new_in_operands;
if (!ir_context->get_type_mgr()
->GetType(enclosing_function->type_id())
->AsVoid()) {
new_in_operands.push_back(
{SPV_OPERAND_TYPE_ID, {message_.returned_value_id()}});
early_terminator->SetOpcode(SpvOpReturnValue);
} else {
early_terminator->SetOpcode(SpvOpReturn);
}
early_terminator->SetInOperands(std::move(new_in_operands));
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}
std::unordered_set<uint32_t>
TransformationWrapEarlyTerminatorInFunction::GetFreshIds() const {
return std::unordered_set<uint32_t>({message_.fresh_id()});
}
protobufs::Transformation
TransformationWrapEarlyTerminatorInFunction::ToMessage() const {
protobufs::Transformation result;
*result.mutable_wrap_early_terminator_in_function() = message_;
return result;
}
opt::Function*
TransformationWrapEarlyTerminatorInFunction::MaybeGetWrapperFunction(
opt::IRContext* ir_context, SpvOp early_terminator_opcode) {
assert((early_terminator_opcode == SpvOpKill ||
early_terminator_opcode == SpvOpUnreachable ||
early_terminator_opcode == SpvOpTerminateInvocation) &&
"Invalid opcode.");
auto void_type_id = fuzzerutil::MaybeGetVoidType(ir_context);
if (!void_type_id) {
return nullptr;
}
auto void_function_type_id =
fuzzerutil::FindFunctionType(ir_context, {void_type_id});
if (!void_function_type_id) {
return nullptr;
}
for (auto& function : *ir_context->module()) {
if (function.DefInst().GetSingleWordInOperand(1) != void_function_type_id) {
continue;
}
if (function.begin()->begin()->opcode() == early_terminator_opcode) {
return &function;
}
}
return nullptr;
}
} // namespace fuzz
} // namespace spvtools

Просмотреть файл

@ -0,0 +1,59 @@
// Copyright (c) 2020 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_WRAP_EARLY_TERMINATOR_IN_FUNCTION_H_
#define SOURCE_FUZZ_TRANSFORMATION_WRAP_EARLY_TERMINATOR_IN_FUNCTION_H_
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation.h"
#include "source/fuzz/transformation_context.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
class TransformationWrapEarlyTerminatorInFunction : public Transformation {
public:
explicit TransformationWrapEarlyTerminatorInFunction(
const protobufs::TransformationWrapEarlyTerminatorInFunction& message);
TransformationWrapEarlyTerminatorInFunction(
uint32_t fresh_id,
const protobufs::InstructionDescriptor& early_terminator_instruction,
uint32_t returned_value_id);
// TODO comment
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// TODO comment
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
std::unordered_set<uint32_t> GetFreshIds() const override;
protobufs::Transformation ToMessage() const override;
private:
static opt::Function* MaybeGetWrapperFunction(opt::IRContext* ir_context,
SpvOp early_terminator_opcode);
protobufs::TransformationWrapEarlyTerminatorInFunction message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_WRAP_EARLY_TERMINATOR_IN_FUNCTION_H_

Просмотреть файл

@ -115,6 +115,7 @@ if (${SPIRV_BUILD_FUZZER})
transformation_toggle_access_chain_instruction_test.cpp
transformation_record_synonymous_constants_test.cpp
transformation_vector_shuffle_test.cpp
transformation_wrap_early_terminator_in_function_test.cpp
transformation_wrap_region_in_selection_test.cpp
uniform_buffer_element_descriptor_test.cpp)

Просмотреть файл

@ -0,0 +1,315 @@
// Copyright (c) 2020 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_wrap_early_terminator_in_function.h"
#include "source/fuzz/instruction_descriptor.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
TEST(TransformationWrapEarlyTerminatorInFunctionTest, IsApplicable) {
std::string shader = R"(
OpCapability Shader
OpExtension "SPV_KHR_terminate_invocation"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
OpName %4 "main"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpConstant %6 0
%90 = OpTypeBool
%91 = OpConstantFalse %90
%20 = OpTypeFunction %2 %6
%21 = OpTypeFunction %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %11 None
OpSwitch %7 %11 0 %8 1 %9 2 %10
%8 = OpLabel
OpKill
%9 = OpLabel
OpUnreachable
%10 = OpLabel
OpTerminateInvocation
%11 = OpLabel
OpReturn
OpFunctionEnd
%30 = OpFunction %2 None %3
%31 = OpLabel
OpKill
OpFunctionEnd
%50 = OpFunction %2 None %3
%51 = OpLabel
OpTerminateInvocation
OpFunctionEnd
%60 = OpFunction %6 None %21
%61 = OpLabel
OpBranch %62
%62 = OpLabel
OpKill
OpFunctionEnd
%70 = OpFunction %6 None %21
%71 = OpLabel
OpUnreachable
OpFunctionEnd
%80 = OpFunction %2 None %20
%81 = OpFunctionParameter %6
%82 = OpLabel
OpTerminateInvocation
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_4;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
spvtools::ValidatorOptions validator_options;
TransformationContext transformation_context(
MakeUnique<FactManager>(context.get()), validator_options);
// Bad: id is not fresh
ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
61, MakeInstructionDescriptor(8, SpvOpKill, 0), 0)
.IsApplicable(context.get(), transformation_context));
// Bad: early terminator instruction descriptor does not exist
ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
100, MakeInstructionDescriptor(82, SpvOpKill, 0), 0)
.IsApplicable(context.get(), transformation_context));
// Bad: early terminator instruction does not identify an early terminator
ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
100, MakeInstructionDescriptor(5, SpvOpSelectionMerge, 0), 0)
.IsApplicable(context.get(), transformation_context));
// Bad: no wrapper function is available
ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
100, MakeInstructionDescriptor(9, SpvOpUnreachable, 0), 0)
.IsApplicable(context.get(), transformation_context));
// Bad: returned value does not exist
ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
100, MakeInstructionDescriptor(62, SpvOpKill, 0), 1000)
.IsApplicable(context.get(), transformation_context));
// Bad: returned value does not have a type
ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
100, MakeInstructionDescriptor(62, SpvOpKill, 0), 61)
.IsApplicable(context.get(), transformation_context));
// Bad: returned value type does not match
ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
100, MakeInstructionDescriptor(62, SpvOpKill, 0), 91)
.IsApplicable(context.get(), transformation_context));
// Bad: returned value is not available
ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
100, MakeInstructionDescriptor(62, SpvOpKill, 0), 81)
.IsApplicable(context.get(), transformation_context));
// Bad: the OpKill being targeted is in the only available wrapper; we cannot
// have the wrapper call itself.
ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
100, MakeInstructionDescriptor(31, SpvOpKill, 0), 0)
.IsApplicable(context.get(), transformation_context));
}
TEST(TransformationWrapEarlyTerminatorInFunctionTest, Apply) {
std::string shader = R"(
OpCapability Shader
OpExtension "SPV_KHR_terminate_invocation"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
OpName %4 "main"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpConstant %6 0
%20 = OpTypeFunction %2 %6
%21 = OpTypeFunction %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %11 None
OpSwitch %7 %11 0 %8 1 %9 2 %10
%8 = OpLabel
OpKill
%9 = OpLabel
OpUnreachable
%10 = OpLabel
OpTerminateInvocation
%11 = OpLabel
OpReturn
OpFunctionEnd
%30 = OpFunction %2 None %3
%31 = OpLabel
OpKill
OpFunctionEnd
%40 = OpFunction %2 None %3
%41 = OpLabel
OpUnreachable
OpFunctionEnd
%50 = OpFunction %2 None %3
%51 = OpLabel
OpTerminateInvocation
OpFunctionEnd
%60 = OpFunction %2 None %3
%61 = OpLabel
OpBranch %62
%62 = OpLabel
OpKill
OpFunctionEnd
%70 = OpFunction %6 None %21
%71 = OpLabel
OpUnreachable
OpFunctionEnd
%80 = OpFunction %2 None %20
%81 = OpFunctionParameter %6
%82 = OpLabel
OpTerminateInvocation
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_4;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
spvtools::ValidatorOptions validator_options;
TransformationContext transformation_context(
MakeUnique<FactManager>(context.get()), validator_options);
for (auto& transformation :
{TransformationWrapEarlyTerminatorInFunction(
100, MakeInstructionDescriptor(8, SpvOpKill, 0), 0),
TransformationWrapEarlyTerminatorInFunction(
101, MakeInstructionDescriptor(9, SpvOpUnreachable, 0), 0),
TransformationWrapEarlyTerminatorInFunction(
102, MakeInstructionDescriptor(10, SpvOpTerminateInvocation, 0), 0),
TransformationWrapEarlyTerminatorInFunction(
103, MakeInstructionDescriptor(62, SpvOpKill, 0), 0),
TransformationWrapEarlyTerminatorInFunction(
104, MakeInstructionDescriptor(71, SpvOpUnreachable, 0), 7),
TransformationWrapEarlyTerminatorInFunction(
105, MakeInstructionDescriptor(82, SpvOpTerminateInvocation, 0),
0)}) {
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
ApplyAndCheckFreshIds(transformation, context.get(),
&transformation_context);
}
ASSERT_TRUE(IsValid(env, context.get()));
std::string after_transformation = R"(
OpCapability Shader
OpExtension "SPV_KHR_terminate_invocation"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
OpName %4 "main"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpConstant %6 0
%20 = OpTypeFunction %2 %6
%21 = OpTypeFunction %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %11 None
OpSwitch %7 %11 0 %8 1 %9 2 %10
%8 = OpLabel
%100 = OpFunctionCall %2 %30
OpReturn
%9 = OpLabel
%101 = OpFunctionCall %2 %40
OpReturn
%10 = OpLabel
%102 = OpFunctionCall %2 %50
OpReturn
%11 = OpLabel
OpReturn
OpFunctionEnd
%30 = OpFunction %2 None %3
%31 = OpLabel
OpKill
OpFunctionEnd
%40 = OpFunction %2 None %3
%41 = OpLabel
OpUnreachable
OpFunctionEnd
%50 = OpFunction %2 None %3
%51 = OpLabel
OpTerminateInvocation
OpFunctionEnd
%60 = OpFunction %2 None %3
%61 = OpLabel
OpBranch %62
%62 = OpLabel
%103 = OpFunctionCall %2 %30
OpReturn
OpFunctionEnd
%70 = OpFunction %6 None %21
%71 = OpLabel
%104 = OpFunctionCall %2 %40
OpReturnValue %7
OpFunctionEnd
%80 = OpFunction %2 None %20
%81 = OpFunctionParameter %6
%82 = OpLabel
%105 = OpFunctionCall %2 %50
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools