Updated spirv-tools.

This commit is contained in:
Бранимир Караџић 2020-08-21 20:48:40 -07:00
parent 4d3fcfd4c7
commit 06e2677783
30 changed files with 1087 additions and 85 deletions

View File

@ -1 +1 @@
"v2020.5", "SPIRV-Tools v2020.5 1a2bfbb06fe9545a7aa26e85e4b92b876ca316fd"
"v2020.5", "SPIRV-Tools v2020.5 46c9719f1b46f358df41b11c2575182cb5befbd5"

View File

@ -127,7 +127,7 @@ class CFA {
template <class BB>
bool CFA<BB>::FindInWorkList(const std::vector<block_info>& work_list,
uint32_t id) {
for (const auto b : work_list) {
for (const auto& b : work_list) {
if (b.block->id() == id) return true;
}
return false;

View File

@ -38,6 +38,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_context.h
fuzzer_pass.h
fuzzer_pass_add_access_chains.h
fuzzer_pass_add_composite_inserts.h
fuzzer_pass_add_composite_types.h
fuzzer_pass_add_copy_memory.h
fuzzer_pass_add_dead_blocks.h
@ -131,6 +132,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_adjust_branch_weights.h
transformation_composite_construct.h
transformation_composite_extract.h
transformation_composite_insert.h
transformation_compute_data_synonym_fact_closure.h
transformation_context.h
transformation_equation_instruction.h
@ -178,6 +180,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_context.cpp
fuzzer_pass.cpp
fuzzer_pass_add_access_chains.cpp
fuzzer_pass_add_composite_inserts.cpp
fuzzer_pass_add_composite_types.cpp
fuzzer_pass_add_copy_memory.cpp
fuzzer_pass_add_dead_blocks.cpp
@ -270,6 +273,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_adjust_branch_weights.cpp
transformation_composite_construct.cpp
transformation_composite_extract.cpp
transformation_composite_insert.cpp
transformation_compute_data_synonym_fact_closure.cpp
transformation_context.cpp
transformation_equation_instruction.cpp

View File

@ -21,6 +21,7 @@
#include "source/fuzz/fact_manager.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_pass_add_access_chains.h"
#include "source/fuzz/fuzzer_pass_add_composite_inserts.h"
#include "source/fuzz/fuzzer_pass_add_composite_types.h"
#include "source/fuzz/fuzzer_pass_add_copy_memory.h"
#include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
@ -35,6 +36,7 @@
#include "source/fuzz/fuzzer_pass_add_loop_preheaders.h"
#include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
#include "source/fuzz/fuzzer_pass_add_parameters.h"
#include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h"
#include "source/fuzz/fuzzer_pass_add_stores.h"
#include "source/fuzz/fuzzer_pass_add_synonyms.h"
#include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h"
@ -60,7 +62,11 @@
#include "source/fuzz/fuzzer_pass_permute_phi_operands.h"
#include "source/fuzz/fuzzer_pass_propagate_instructions_up.h"
#include "source/fuzz/fuzzer_pass_push_ids_through_variables.h"
#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h"
#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h"
#include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h"
#include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h"
#include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h"
#include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h"
#include "source/fuzz/fuzzer_pass_replace_params_with_struct.h"
#include "source/fuzz/fuzzer_pass_split_blocks.h"
@ -210,6 +216,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassAddAccessChains>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddCompositeInserts>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddCompositeTypes>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
@ -249,6 +258,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassAddParameters>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddRelaxedDecorations>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddStores>(&passes, ir_context.get(),
&transformation_context, &fuzzer_context,
transformation_sequence_out);
@ -300,6 +312,18 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassPushIdsThroughVariables>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassReplaceAddsSubsMulsWithCarryingExtended>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassReplaceCopyMemoriesWithLoadsStores>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassReplaceCopyObjectsWithStoresLoads>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassReplaceLoadsStoresWithCopyMemories>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassReplaceParameterWithGlobal>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);

View File

@ -27,6 +27,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfAddingAccessChain = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingAnotherStructField = {20,
90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingArrayOrStructType = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingCompositeInsert = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingCopyMemory = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBlock = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80};
@ -64,6 +65,8 @@ const std::pair<uint32_t, uint32_t> kChanceOfChoosingWorkgroupStorageClass = {
const std::pair<uint32_t, uint32_t> kChanceOfConstructingComposite = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfDonatingAdditionalModule = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperToInsertInComposite = {
30, 70};
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
{50, 95};
const std::pair<uint32_t, uint32_t> kChanceOfInterchangingZeroLikeConstants = {
@ -86,7 +89,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfPropagatingInstructionsUp = {20,
70};
const std::pair<uint32_t, uint32_t> kChanceOfPushingIdThroughVariable = {5, 50};
const std::pair<uint32_t, uint32_t>
kChanceOfReplacingAddSubMulWithCarryingExtended = {20, 90};
kChanceOfReplacingAddSubMulWithCarryingExtended = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyMemoryWithLoadStore =
{20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyObjectWithStoreLoad =
@ -153,6 +156,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField);
chance_of_adding_array_or_struct_type_ =
ChooseBetweenMinAndMax(kChanceOfAddingArrayOrStructType);
chance_of_adding_composite_insert_ =
ChooseBetweenMinAndMax(kChanceOfAddingCompositeInsert);
chance_of_adding_copy_memory_ =
ChooseBetweenMinAndMax(kChanceOfAddingCopyMemory);
chance_of_adding_dead_block_ =
@ -207,6 +212,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
chance_of_donating_additional_module_ =
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
chance_of_going_deeper_to_insert_in_composite_ =
ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite);
chance_of_going_deeper_when_making_access_chain_ =
ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
chance_of_interchanging_signedness_of_integer_operands_ =

View File

@ -115,6 +115,9 @@ class FuzzerContext {
uint32_t GetChanceOfAddingArrayOrStructType() {
return chance_of_adding_array_or_struct_type_;
}
uint32_t GetChanceOfAddingCompositeInsert() {
return chance_of_adding_composite_insert_;
}
uint32_t GetChanceOfAddingCopyMemory() {
return chance_of_adding_copy_memory_;
}
@ -186,6 +189,9 @@ class FuzzerContext {
uint32_t GetChanceOfDonatingAdditionalModule() {
return chance_of_donating_additional_module_;
}
uint32_t GetChanceOfGoingDeeperToInsertInComposite() {
return chance_of_going_deeper_to_insert_in_composite_;
}
uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
return chance_of_going_deeper_when_making_access_chain_;
}
@ -296,6 +302,9 @@ class FuzzerContext {
uint32_t GetRandomIndexForAccessChain(uint32_t composite_size_bound) {
return random_generator_->RandomUint32(composite_size_bound);
}
uint32_t GetRandomIndexForCompositeInsert(uint32_t number_of_components) {
return random_generator_->RandomUint32(number_of_components);
}
uint32_t GetRandomLoopControlPartialCount() {
return random_generator_->RandomUint32(max_loop_control_partial_count_);
}
@ -342,6 +351,7 @@ class FuzzerContext {
uint32_t chance_of_adding_access_chain_;
uint32_t chance_of_adding_another_struct_field_;
uint32_t chance_of_adding_array_or_struct_type_;
uint32_t chance_of_adding_composite_insert_;
uint32_t chance_of_adding_copy_memory_;
uint32_t chance_of_adding_dead_block_;
uint32_t chance_of_adding_dead_break_;
@ -371,6 +381,7 @@ class FuzzerContext {
uint32_t chance_of_constructing_composite_;
uint32_t chance_of_copying_object_;
uint32_t chance_of_donating_additional_module_;
uint32_t chance_of_going_deeper_to_insert_in_composite_;
uint32_t chance_of_going_deeper_when_making_access_chain_;
uint32_t chance_of_interchanging_signedness_of_integer_operands_;
uint32_t chance_of_interchanging_zero_like_constants_;

View File

@ -24,6 +24,8 @@
#include "source/fuzz/transformation_add_constant_null.h"
#include "source/fuzz/transformation_add_constant_scalar.h"
#include "source/fuzz/transformation_add_global_undef.h"
#include "source/fuzz/transformation_add_global_variable.h"
#include "source/fuzz/transformation_add_local_variable.h"
#include "source/fuzz/transformation_add_loop_preheader.h"
#include "source/fuzz/transformation_add_type_boolean.h"
#include "source/fuzz/transformation_add_type_float.h"
@ -516,6 +518,26 @@ uint32_t FuzzerPass::FindOrCreateZeroConstant(
}
}
bool FuzzerPass::CanFindOrCreateZeroConstant(const opt::analysis::Type& type) {
switch (type.kind()) {
case opt::analysis::Type::kBool:
case opt::analysis::Type::kInteger:
case opt::analysis::Type::kFloat:
case opt::analysis::Type::kArray:
case opt::analysis::Type::kMatrix:
case opt::analysis::Type::kVector:
return true;
case opt::analysis::Type::kStruct:
return std::all_of(type.AsStruct()->element_types().begin(),
type.AsStruct()->element_types().end(),
[this](const opt::analysis::Type* element_type) {
return CanFindOrCreateZeroConstant(*element_type);
});
default:
return false;
}
}
void FuzzerPass::MaybeAddUseToReplace(
opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
@ -595,5 +617,101 @@ opt::BasicBlock* FuzzerPass::GetOrCreateSimpleLoopPreheader(
return &*function->FindBlock(preheader_id);
}
uint32_t FuzzerPass::FindOrCreateLocalVariable(
uint32_t pointer_type_id, uint32_t function_id,
bool pointee_value_is_irrelevant) {
auto pointer_type = GetIRContext()->get_type_mgr()->GetType(pointer_type_id);
// No unused variables in release mode.
(void)pointer_type;
assert(pointer_type && pointer_type->AsPointer() &&
pointer_type->AsPointer()->storage_class() ==
SpvStorageClassFunction &&
"The pointer_type_id must refer to a defined pointer type with "
"storage class Function");
auto function = fuzzerutil::FindFunction(GetIRContext(), function_id);
assert(function && "The function must be defined.");
// First we try to find a suitable existing variable.
// All of the local variable declarations are located in the first block.
for (auto& instruction : *function->begin()) {
if (instruction.opcode() != SpvOpVariable) {
continue;
}
// The existing OpVariable must have type |pointer_type_id|.
if (instruction.type_id() != pointer_type_id) {
continue;
}
// Check if the found variable is marked with PointeeValueIsIrrelevant
// according to |pointee_value_is_irrelevant|.
if (GetTransformationContext()->GetFactManager()->PointeeValueIsIrrelevant(
instruction.result_id()) != pointee_value_is_irrelevant) {
continue;
}
return instruction.result_id();
}
// No such variable was found. Apply a transformation to get one.
uint32_t pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
GetIRContext(), pointer_type_id);
uint32_t result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddLocalVariable(
result_id, pointer_type_id, function_id,
FindOrCreateZeroConstant(pointee_type_id, pointee_value_is_irrelevant),
pointee_value_is_irrelevant));
return result_id;
}
uint32_t FuzzerPass::FindOrCreateGlobalVariable(
uint32_t pointer_type_id, bool pointee_value_is_irrelevant) {
auto pointer_type = GetIRContext()->get_type_mgr()->GetType(pointer_type_id);
// No unused variables in release mode.
(void)pointer_type;
assert(
pointer_type && pointer_type->AsPointer() &&
(pointer_type->AsPointer()->storage_class() == SpvStorageClassPrivate ||
pointer_type->AsPointer()->storage_class() ==
SpvStorageClassWorkgroup) &&
"The pointer_type_id must refer to a defined pointer type with storage "
"class Private or Workgroup");
// First we try to find a suitable existing variable.
for (auto& instruction : GetIRContext()->module()->types_values()) {
if (instruction.opcode() != SpvOpVariable) {
continue;
}
// The existing OpVariable must have type |pointer_type_id|.
if (instruction.type_id() != pointer_type_id) {
continue;
}
// Check if the found variable is marked with PointeeValueIsIrrelevant
// according to |pointee_value_is_irrelevant|.
if (GetTransformationContext()->GetFactManager()->PointeeValueIsIrrelevant(
instruction.result_id()) != pointee_value_is_irrelevant) {
continue;
}
return instruction.result_id();
}
// No such variable was found. Apply a transformation to get one.
uint32_t pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
GetIRContext(), pointer_type_id);
auto storage_class = fuzzerutil::GetStorageClassFromPointerType(
GetIRContext(), pointer_type_id);
uint32_t result_id = GetFuzzerContext()->GetFreshId();
// A variable with storage class Workgroup shouldn't have an initializer.
if (storage_class == SpvStorageClassWorkgroup) {
ApplyTransformation(TransformationAddGlobalVariable(
result_id, pointer_type_id, SpvStorageClassWorkgroup, 0,
pointee_value_is_irrelevant));
} else {
ApplyTransformation(TransformationAddGlobalVariable(
result_id, pointer_type_id, SpvStorageClassPrivate,
FindOrCreateZeroConstant(pointee_type_id, pointee_value_is_irrelevant),
pointee_value_is_irrelevant));
}
return result_id;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -273,6 +273,9 @@ class FuzzerPass {
uint32_t FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id,
bool is_irrelevant);
// Checks if FindOrCreateZeroConstant can be called on this type.
bool CanFindOrCreateZeroConstant(const opt::analysis::Type& type);
// Adds a pair (id_use_descriptor, |replacement_id|) to the vector
// |uses_to_replace|, where id_use_descriptor is the id use descriptor
// representing the usage of an id in the |use_inst| instruction, at operand
@ -293,6 +296,27 @@ class FuzzerPass {
// reachable in the CFG (and thus has at least 2 predecessors).
opt::BasicBlock* GetOrCreateSimpleLoopPreheader(uint32_t header_id);
// Returns the id of an available local variable (storage class Function) with
// the fact PointeeValueIsIrrelevant set according to
// |pointee_value_is_irrelevant|. If there is no such variable, it creates one
// in the |function| adding a zero initializer constant that is irrelevant.
// The new variable has the fact PointeeValueIsIrrelevant set according to
// |pointee_value_is_irrelevant|. The function returns the id of the created
// variable.
uint32_t FindOrCreateLocalVariable(uint32_t pointer_type_id,
uint32_t function_id,
bool pointee_value_is_irrelevant);
// Returns the id of an available global variable (storage class Private or
// Workgroup) with the fact PointeeValueIsIrrelevant set according to
// |pointee_value_is_irrelevant|. If there is no such variable, it creates
// one, adding a zero initializer constant that is irrelevant. The new
// variable has the fact PointeeValueIsIrrelevant set according to
// |pointee_value_is_irrelevant|. The function returns the id of the created
// variable.
uint32_t FindOrCreateGlobalVariable(uint32_t pointer_type_id,
bool pointee_value_is_irrelevant);
private:
opt::IRContext* ir_context_;
TransformationContext* transformation_context_;

View File

@ -0,0 +1,236 @@
// 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/fuzzer_pass_add_composite_inserts.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/pseudo_random_generator.h"
#include "source/fuzz/transformation_composite_insert.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddCompositeInserts::FuzzerPassAddCompositeInserts(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddCompositeInserts::~FuzzerPassAddCompositeInserts() = default;
void FuzzerPassAddCompositeInserts::Apply() {
ForEachInstructionWithInstructionDescriptor(
[this](opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator instruction_iterator,
const protobufs::InstructionDescriptor& instruction_descriptor)
-> void {
assert(instruction_iterator->opcode() ==
instruction_descriptor.target_instruction_opcode() &&
"The opcode of the instruction we might insert before must be "
"the same as the opcode in the descriptor for the instruction");
// Randomly decide whether to try adding an OpCompositeInsert
// instruction.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingCompositeInsert())) {
return;
}
// It must be possible to insert an OpCompositeInsert instruction
// before |instruction_iterator|.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
SpvOpCompositeInsert, instruction_iterator)) {
return;
}
// Look for available values that have composite type.
std::vector<opt::Instruction*> available_composites =
FindAvailableInstructions(
function, block, instruction_iterator,
[instruction_descriptor](
opt::IRContext* ir_context,
opt::Instruction* instruction) -> bool {
// |instruction| must be a supported instruction of composite
// type.
if (!TransformationCompositeInsert::
IsCompositeInstructionSupported(ir_context,
instruction)) {
return false;
}
auto instruction_type = ir_context->get_type_mgr()->GetType(
instruction->type_id());
// No components of the composite can have type
// OpTypeRuntimeArray.
if (ContainsRuntimeArray(*instruction_type)) {
return false;
}
// No components of the composite can be pointers.
// TODO:
// (https://github.com/KhronosGroup/SPIRV-Tools/issues/3658)
// Structs can have components of pointer type.
// FindOrCreateZeroConstant cannot be called on a
// pointer. We ignore pointers for now. Consider adding
// support for pointer types.
if (ContainsPointer(*instruction_type)) {
return false;
}
return true;
});
// If there are no available values, then return.
if (available_composites.empty()) {
return;
}
// Choose randomly one available composite value.
auto available_composite =
available_composites[GetFuzzerContext()->RandomIndex(
available_composites)];
// Take a random component of the chosen composite value. If the chosen
// component is itself a composite, then randomly decide whether to take
// its component and repeat.
uint32_t current_node_type_id = available_composite->type_id();
std::vector<uint32_t> path_to_replaced;
while (true) {
auto current_node_type_inst =
GetIRContext()->get_def_use_mgr()->GetDef(current_node_type_id);
uint32_t num_of_components = fuzzerutil::GetBoundForCompositeIndex(
*current_node_type_inst, GetIRContext());
// If the composite is empty, then end the iteration.
if (num_of_components == 0) {
break;
}
uint32_t one_selected_index =
GetFuzzerContext()->GetRandomIndexForCompositeInsert(
num_of_components);
// Construct a final index by appending the current index.
path_to_replaced.push_back(one_selected_index);
current_node_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
GetIRContext(), current_node_type_id, one_selected_index);
// If the component is not a composite then end the iteration.
if (!fuzzerutil::IsCompositeType(
GetIRContext()->get_type_mgr()->GetType(
current_node_type_id))) {
break;
}
// If the component is a composite, but we decide not to go deeper,
// then end the iteration.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfGoingDeeperToInsertInComposite())) {
break;
}
}
// Look for available objects that have the type id
// |current_node_type_id| and can be inserted.
std::vector<opt::Instruction*> available_objects =
FindAvailableInstructions(
function, block, instruction_iterator,
[instruction_descriptor, current_node_type_id](
opt::IRContext* /*unused*/,
opt::Instruction* instruction) -> bool {
if (instruction->result_id() == 0 ||
instruction->type_id() == 0) {
return false;
}
if (instruction->type_id() != current_node_type_id) {
return false;
}
return true;
});
// If there are no objects of the specific type available, check if
// FindOrCreateZeroConstant can be called and create a zero constant of
// this type.
uint32_t available_object_id;
if (available_objects.empty()) {
auto current_node_type =
GetIRContext()->get_type_mgr()->GetType(current_node_type_id);
if (!CanFindOrCreateZeroConstant(*current_node_type)) {
return;
}
available_object_id =
FindOrCreateZeroConstant(current_node_type_id, false);
} else {
available_object_id =
available_objects[GetFuzzerContext()->RandomIndex(
available_objects)]
->result_id();
}
auto new_result_id = GetFuzzerContext()->GetFreshId();
// Insert an OpCompositeInsert instruction which copies
// |available_composite| and in the copy inserts the object
// of type |available_object_id| at index |index_to_replace|.
ApplyTransformation(TransformationCompositeInsert(
instruction_descriptor, new_result_id,
available_composite->result_id(), available_object_id,
path_to_replaced));
});
}
bool FuzzerPassAddCompositeInserts::ContainsPointer(
const opt::analysis::Type& type) {
switch (type.kind()) {
case opt::analysis::Type::kPointer:
return true;
case opt::analysis::Type::kArray:
return ContainsPointer(*type.AsArray()->element_type());
case opt::analysis::Type::kMatrix:
return ContainsPointer(*type.AsMatrix()->element_type());
case opt::analysis::Type::kVector:
return ContainsPointer(*type.AsVector()->element_type());
case opt::analysis::Type::kStruct:
return std::any_of(type.AsStruct()->element_types().begin(),
type.AsStruct()->element_types().end(),
[](const opt::analysis::Type* element_type) {
return ContainsPointer(*element_type);
});
default:
return false;
}
}
bool FuzzerPassAddCompositeInserts::ContainsRuntimeArray(
const opt::analysis::Type& type) {
switch (type.kind()) {
case opt::analysis::Type::kRuntimeArray:
return true;
case opt::analysis::Type::kStruct:
// If any component of a struct is of type OpTypeRuntimeArray, return
// true.
return std::any_of(type.AsStruct()->element_types().begin(),
type.AsStruct()->element_types().end(),
[](const opt::analysis::Type* element_type) {
return ContainsRuntimeArray(*element_type);
});
default:
return false;
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,45 @@
// 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 SPIRV_TOOLS_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H
#define SPIRV_TOOLS_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Fuzzer pass that randomly adds new OpCompositeInsert instructions to
// available values that have the composite type.
class FuzzerPassAddCompositeInserts : public FuzzerPass {
public:
FuzzerPassAddCompositeInserts(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddCompositeInserts();
void Apply() override;
// Checks if any component of a composite is a pointer.
static bool ContainsPointer(const opt::analysis::Type& type);
// Checks if any component of a composite has type OpTypeRuntimeArray.
static bool ContainsRuntimeArray(const opt::analysis::Type& type);
};
} // namespace fuzz
} // namespace spvtools
#endif // SPIRV_TOOLS_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H

View File

@ -69,14 +69,66 @@ void FuzzerPassAddParameters::Apply() {
auto num_new_parameters =
GetFuzzerContext()->GetRandomNumberOfNewParameters(
GetNumberOfParameters(function));
for (uint32_t i = 0; i < num_new_parameters; ++i) {
auto current_type_id =
type_candidates[GetFuzzerContext()->RandomIndex(type_candidates)];
auto current_type =
GetIRContext()->get_type_mgr()->GetType(current_type_id);
std::map<uint32_t, uint32_t> call_parameter_ids;
// Consider the case when a pointer type was selected.
if (current_type->kind() == opt::analysis::Type::kPointer) {
auto storage_class = fuzzerutil::GetStorageClassFromPointerType(
GetIRContext(), current_type_id);
switch (storage_class) {
case SpvStorageClassFunction: {
// In every caller find or create a local variable that has the
// selected type.
for (auto* instr :
fuzzerutil::GetCallers(GetIRContext(), function.result_id())) {
auto block = GetIRContext()->get_instr_block(instr);
auto function_id = block->GetParent()->result_id();
uint32_t variable_id =
FindOrCreateLocalVariable(current_type_id, function_id, true);
call_parameter_ids[instr->result_id()] = variable_id;
}
} break;
case SpvStorageClassPrivate:
case SpvStorageClassWorkgroup: {
// If there exists at least one caller, find or create a global
// variable that has the selected type.
std::vector<opt::Instruction*> callers =
fuzzerutil::GetCallers(GetIRContext(), function.result_id());
if (!callers.empty()) {
uint32_t variable_id =
FindOrCreateGlobalVariable(current_type_id, true);
for (auto* instr : callers) {
call_parameter_ids[instr->result_id()] = variable_id;
}
}
} break;
default:
break;
}
} else {
// If there exists at least one caller, find or create a zero constant
// that has the selected type.
std::vector<opt::Instruction*> callers =
fuzzerutil::GetCallers(GetIRContext(), function.result_id());
if (!callers.empty()) {
uint32_t constant_id =
FindOrCreateZeroConstant(current_type_id, true);
for (auto* instr :
fuzzerutil::GetCallers(GetIRContext(), function.result_id())) {
call_parameter_ids[instr->result_id()] = constant_id;
}
}
}
ApplyTransformation(TransformationAddParameter(
function.result_id(), GetFuzzerContext()->GetFreshId(),
// We mark the constant as irrelevant so that we can replace it with a
// more interesting value later.
FindOrCreateZeroConstant(
type_candidates[GetFuzzerContext()->RandomIndex(type_candidates)],
true),
current_type_id, std::move(call_parameter_ids),
GetFuzzerContext()->GetFreshId()));
}
}

View File

@ -1234,23 +1234,19 @@ bool FuzzerPassDonateModules::MaybeAddLivesafeFunction(
}
}
// If the function contains OpKill or OpUnreachable instructions, and has
// non-void return type, then we need a value %v to use in order to turn
// these into instructions of the form OpReturn %v.
uint32_t kill_unreachable_return_value_id;
// If |function_to_donate| has non-void return type and contains an
// OpKill/OpUnreachable instruction, then a value is needed in order to turn
// these into instructions of the form OpReturnValue %value_id.
uint32_t kill_unreachable_return_value_id = 0;
auto function_return_type_inst =
donor_ir_context->get_def_use_mgr()->GetDef(function_to_donate.type_id());
if (function_return_type_inst->opcode() == SpvOpTypeVoid) {
// The return type is void, so we don't need a return value.
kill_unreachable_return_value_id = 0;
} else {
// We do need a return value; we use zero.
assert(function_return_type_inst->opcode() != SpvOpTypePointer &&
"Function return type must not be a pointer.");
if (function_return_type_inst->opcode() != SpvOpTypeVoid &&
fuzzerutil::FunctionContainsOpKillOrUnreachable(function_to_donate)) {
kill_unreachable_return_value_id = FindOrCreateZeroConstant(
original_id_to_donated_id.at(function_return_type_inst->result_id()),
false);
}
// Add the function in a livesafe manner.
ApplyTransformation(TransformationAddFunction(
donated_instructions, loop_limiter_variable_id, loop_limit, loop_limiters,

View File

@ -37,6 +37,7 @@ FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::
~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() = default;
void FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::Apply() {
std::vector<opt::Instruction> instructions_for_transformation;
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
for (auto& instruction : block) {
@ -52,25 +53,27 @@ void FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::Apply() {
IsInstructionSuitable(GetIRContext(), instruction)) {
continue;
}
// Get the operand type id. We know that both operands have the same
// type.
uint32_t operand_type_id =
GetIRContext()
->get_def_use_mgr()
->GetDef(instruction.GetSingleWordInOperand(
kArithmeticInstructionIndexLeftInOperand))
->type_id();
// Ensure the required struct type exists. The struct type is based on
// the operand type.
FindOrCreateStructType({operand_type_id, operand_type_id});
ApplyTransformation(TransformationReplaceAddSubMulWithCarryingExtended(
GetFuzzerContext()->GetFreshId(), instruction.result_id()));
instructions_for_transformation.push_back(instruction);
}
}
}
for (auto& instruction : instructions_for_transformation) {
// Get the operand type id. We know that both operands have the same
// type.
uint32_t operand_type_id =
GetIRContext()
->get_def_use_mgr()
->GetDef(instruction.GetSingleWordInOperand(
kArithmeticInstructionIndexLeftInOperand))
->type_id();
// Ensure the required struct type exists. The struct type is based on
// the operand type.
FindOrCreateStructType({operand_type_id, operand_type_id});
ApplyTransformation(TransformationReplaceAddSubMulWithCarryingExtended(
GetFuzzerContext()->GetFreshId(), instruction.result_id()));
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -66,7 +66,14 @@ void FuzzerPassReplaceCopyObjectsWithStoresLoads::Apply() {
? SpvStorageClassPrivate
: SpvStorageClassFunction;
// Find or create a constant to initialize the variable from.
// Find or create a constant to initialize the variable from. The type of
// |instruction| must be such that the function FindOrCreateConstant can be
// called.
auto instruction_type =
GetIRContext()->get_type_mgr()->GetType(instruction->type_id());
if (!CanFindOrCreateZeroConstant(*instruction_type)) {
return;
}
auto variable_initializer_id =
FindOrCreateZeroConstant(instruction->type_id(), false);

View File

@ -476,6 +476,16 @@ opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id) {
return nullptr;
}
bool FunctionContainsOpKillOrUnreachable(const opt::Function& function) {
for (auto& block : function) {
if (block.terminator()->opcode() == SpvOpKill ||
block.terminator()->opcode() == SpvOpUnreachable) {
return true;
}
}
return false;
}
bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id) {
for (auto& entry_point : context->module()->entry_points()) {
if (entry_point.GetSingleWordInOperand(1) == function_id) {
@ -1343,6 +1353,5 @@ opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context,
}
} // namespace fuzzerutil
} // namespace fuzz
} // namespace spvtools

View File

@ -179,6 +179,10 @@ opt::Instruction* GetFunctionType(opt::IRContext* context,
// function exists.
opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id);
// Returns true if |function| has a block that the termination instruction is
// OpKill or OpUnreachable.
bool FunctionContainsOpKillOrUnreachable(const opt::Function& function);
// Returns |true| if one of entry points has function id |function_id|.
bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id);
@ -494,7 +498,6 @@ opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context,
SpvOp opcode);
} // namespace fuzzerutil
} // namespace fuzz
} // namespace spvtools

View File

@ -412,6 +412,7 @@ message Transformation {
TransformationMakeVectorOperationDynamic make_vector_operation_dynamic = 65;
TransformationReplaceAddSubMulWithCarryingExtended replace_add_sub_mul_with_carrying_extended = 66;
TransformationPropagateInstructionUp propagate_instruction_up = 67;
TransformationCompositeInsert composite_insert = 68;
// Add additional option using the next available number.
}
}
@ -754,15 +755,17 @@ message TransformationAddParameter {
// Fresh id for a new parameter.
uint32 parameter_fresh_id = 2;
// Result id of the instruction, used to initializer new parameter
// in function calls. Type id of that instruction is the type id of the parameter.
// It may not be OpTypeVoid.
uint32 initializer_id = 3;
// Type id for a new parameter.
uint32 parameter_type_id = 3;
// A map that maps from the OpFunctionCall id to the id that will be passed as the new
// parameter at that call site. It must have the same type as that of the new parameter.
repeated UInt32Pair call_parameter_ids = 4;
// A fresh id for a new function type. This might not be used
// if a required function type already exists or if we can change
// the old function type.
uint32 function_type_fresh_id = 4;
uint32 function_type_fresh_id = 5;
}
@ -1019,6 +1022,29 @@ message TransformationCompositeExtract {
}
message TransformationCompositeInsert {
// A transformation that adds an instruction OpCompositeInsert which creates
// a new composite from an existing composite, with an element inserted.
// A descriptor for an instruction before which the new instruction
// OpCompositeInsert should be inserted.
InstructionDescriptor instruction_to_insert_before = 1;
// Result id of the inserted OpCompositeInsert instruction.
uint32 fresh_id = 2;
// Id of the composite used as the basis for the insertion.
uint32 composite_id = 3;
// Id of the object to be inserted.
uint32 object_id = 4;
// Indices that indicate which part of the composite should be inserted into.
repeated uint32 index = 5;
}
message TransformationComputeDataSynonymFactClosure {
// A transformation that impacts the fact manager only, forcing a computation

View File

@ -49,6 +49,7 @@
#include "source/fuzz/transformation_adjust_branch_weights.h"
#include "source/fuzz/transformation_composite_construct.h"
#include "source/fuzz/transformation_composite_extract.h"
#include "source/fuzz/transformation_composite_insert.h"
#include "source/fuzz/transformation_compute_data_synonym_fact_closure.h"
#include "source/fuzz/transformation_equation_instruction.h"
#include "source/fuzz/transformation_function_call.h"
@ -179,6 +180,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
case protobufs::Transformation::TransformationCase::kCompositeExtract:
return MakeUnique<TransformationCompositeExtract>(
message.composite_extract());
case protobufs::Transformation::TransformationCase::kCompositeInsert:
return MakeUnique<TransformationCompositeInsert>(
message.composite_insert());
case protobufs::Transformation::TransformationCase::
kComputeDataSynonymFactClosure:
return MakeUnique<TransformationComputeDataSynonymFactClosure>(

View File

@ -64,13 +64,12 @@ bool TransformationAddConstantScalar::IsApplicable(
void TransformationAddConstantScalar::Apply(
opt::IRContext* ir_context,
TransformationContext* transformation_context) const {
opt::Instruction::OperandList operand_list;
for (auto word : message_.word()) {
operand_list.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {word}});
}
ir_context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
ir_context, SpvOpConstant, message_.type_id(), message_.fresh_id(),
operand_list));
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_LITERAL_INTEGER,
std::vector<uint32_t>(message_.word().begin(),
message_.word().end())}})));
fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());

View File

@ -78,6 +78,27 @@ bool TransformationAddDeadBlock::IsApplicable(
return false;
}
// |existing_block| must be reachable.
opt::DominatorAnalysis* dominator_analysis =
ir_context->GetDominatorAnalysis(existing_block->GetParent());
if (!dominator_analysis->IsReachable(existing_block->id())) {
return false;
}
assert(existing_block->id() != successor_block_id &&
"|existing_block| must be different from |successor_block_id|");
// Even though we know |successor_block_id| is not a merge block, it might
// still have multiple predecessors because divergent control flow is allowed
// to converge early (before the merge block). In this case, when we create
// the selection construct, its header |existing_block| will not dominate the
// merge block |successor_block_id|, which is invalid. Thus, |existing_block|
// must dominate |successor_block_id|.
if (!dominator_analysis->Dominates(existing_block->id(),
successor_block_id)) {
return false;
}
return true;
}

View File

@ -24,17 +24,20 @@ TransformationAddParameter::TransformationAddParameter(
: message_(message) {}
TransformationAddParameter::TransformationAddParameter(
uint32_t function_id, uint32_t parameter_fresh_id, uint32_t initializer_id,
uint32_t function_id, uint32_t parameter_fresh_id,
uint32_t parameter_type_id, std::map<uint32_t, uint32_t> call_parameter_ids,
uint32_t function_type_fresh_id) {
message_.set_function_id(function_id);
message_.set_parameter_fresh_id(parameter_fresh_id);
message_.set_initializer_id(initializer_id);
message_.set_parameter_type_id(parameter_type_id);
*message_.mutable_call_parameter_ids() =
fuzzerutil::MapToRepeatedUInt32Pair(call_parameter_ids);
message_.set_function_type_fresh_id(function_type_fresh_id);
}
bool TransformationAddParameter::IsApplicable(
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
// Check that function exists
// Check that function exists.
const auto* function =
fuzzerutil::FindFunction(ir_context, message_.function_id());
if (!function ||
@ -42,22 +45,59 @@ bool TransformationAddParameter::IsApplicable(
return false;
}
// Check that |initializer_id| is valid.
const auto* initializer_inst =
ir_context->get_def_use_mgr()->GetDef(message_.initializer_id());
if (!initializer_inst) {
// The type must be supported.
uint32_t new_parameter_type_id = message_.parameter_type_id();
auto new_parameter_type =
ir_context->get_type_mgr()->GetType(new_parameter_type_id);
if (!new_parameter_type) {
return false;
}
if (!IsParameterTypeSupported(*new_parameter_type)) {
return false;
}
// Check that initializer's type is valid.
const auto* initializer_type =
ir_context->get_type_mgr()->GetType(initializer_inst->type_id());
// Iterate over all callers.
std::map<uint32_t, uint32_t> call_parameter_ids_map =
fuzzerutil::RepeatedUInt32PairToMap(message_.call_parameter_ids());
for (auto* instr :
fuzzerutil::GetCallers(ir_context, message_.function_id())) {
uint32_t caller_id = instr->result_id();
if (!initializer_type || !IsParameterTypeSupported(*initializer_type)) {
return false;
// If there is no entry for this caller, return false.
if (call_parameter_ids_map.find(caller_id) ==
call_parameter_ids_map.end()) {
return false;
}
uint32_t value_id = call_parameter_ids_map[caller_id];
auto value_instr = ir_context->get_def_use_mgr()->GetDef(value_id);
if (!value_instr) {
return false;
}
// 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;
}
// The type of the value must be defined.
uint32_t value_type_id = fuzzerutil::GetTypeId(ir_context, value_id);
if (!value_type_id) {
return false;
}
// Type of every value of the map must be the same for all callers.
if (new_parameter_type_id != value_type_id) {
return false;
}
}
return fuzzerutil::IsFreshId(ir_context, message_.parameter_fresh_id()) &&
fuzzerutil::IsFreshId(ir_context, message_.function_type_fresh_id()) &&
message_.parameter_fresh_id() != message_.function_type_fresh_id();
@ -66,13 +106,17 @@ bool TransformationAddParameter::IsApplicable(
void TransformationAddParameter::Apply(
opt::IRContext* ir_context,
TransformationContext* transformation_context) const {
// Find the function that will be transformed
// Find the function that will be transformed.
auto* function = fuzzerutil::FindFunction(ir_context, message_.function_id());
assert(function && "Can't find the function");
const auto new_parameter_type_id =
fuzzerutil::GetTypeId(ir_context, message_.initializer_id());
assert(new_parameter_type_id != 0 && "Initializer has invalid type");
std::map<uint32_t, uint32_t> call_parameter_ids_map =
fuzzerutil::RepeatedUInt32PairToMap(message_.call_parameter_ids());
uint32_t new_parameter_type_id = message_.parameter_type_id();
auto new_parameter_type =
ir_context->get_type_mgr()->GetType(new_parameter_type_id);
assert(new_parameter_type && "New parameter has invalid type.");
// Add new parameters to the function.
function->AddParameter(MakeUnique<opt::Instruction>(
@ -81,17 +125,24 @@ void TransformationAddParameter::Apply(
fuzzerutil::UpdateModuleIdBound(ir_context, message_.parameter_fresh_id());
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
// Add an PointeeValueIsIrrelevant fact if the parameter is a pointer.
// Mark new parameter as irrelevant so that we can replace its use with some
// other id.
transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
message_.parameter_fresh_id());
// If the |new_parameter_type_id| is not a pointer type, mark id as
// irrelevant so that we can replace its use with some other id. If the
// |new_parameter_type_id| is a pointer type, we cannot mark it with
// IdIsIrrelevant, because this pointer might be replaced by a pointer from
// original shader. This would change the semantics of the module. In the case
// of a pointer type we mark it with PointeeValueIsIrrelevant.
if (new_parameter_type->kind() != opt::analysis::Type::kPointer) {
transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
message_.parameter_fresh_id());
} else {
transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
message_.parameter_fresh_id());
}
// Fix all OpFunctionCall instructions.
for (auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) {
inst->AddOperand({SPV_OPERAND_TYPE_ID, {message_.initializer_id()}});
inst->AddOperand(
{SPV_OPERAND_TYPE_ID, {call_parameter_ids_map[inst->result_id()]}});
}
// Update function's type.
@ -145,6 +196,19 @@ bool TransformationAddParameter::IsParameterTypeSupported(
[](const opt::analysis::Type* element_type) {
return IsParameterTypeSupported(*element_type);
});
case opt::analysis::Type::kPointer: {
auto storage_class = type.AsPointer()->storage_class();
switch (storage_class) {
case SpvStorageClassPrivate:
case SpvStorageClassFunction:
case SpvStorageClassWorkgroup: {
auto pointee_type = type.AsPointer()->pointee_type();
return IsParameterTypeSupported(*pointee_type);
}
default:
return false;
}
}
default:
return false;
}

View File

@ -29,14 +29,19 @@ class TransformationAddParameter : public Transformation {
const protobufs::TransformationAddParameter& message);
TransformationAddParameter(uint32_t function_id, uint32_t parameter_fresh_id,
uint32_t initializer_id,
uint32_t parameter_type_id,
std::map<uint32_t, uint32_t> call_parameter_ids,
uint32_t function_type_fresh_id);
// - |function_id| must be a valid result id of some non-entry-point function
// in the module.
// - |initializer_id| must be a valid result id of some instruction in the
// module. Instruction's type must be supported by this transformation
// as specified by IsParameterTypeSupported function.
// - |parameter_type_id| is a type id of the new parameter. The type must be
// supported by this transformation as specified by IsParameterTypeSupported
// function.
// - |call_parameter_id| must map from every id of an OpFunctionCall
// instruction of this function to the id that will be passed as the new
// parameter at that call site. There could be no callers, therefore this
// map can be empty.
// - |parameter_fresh_id| and |function_type_fresh_id| are fresh ids and are
// not equal.
bool IsApplicable(
@ -46,8 +51,8 @@ class TransformationAddParameter : public Transformation {
// - Creates a new OpFunctionParameter instruction with result id
// |parameter_fresh_id| for the function with |function_id|.
// - Adjusts function's type to include a new parameter.
// - Adds |initializer_id| as a new operand to every OpFunctionCall
// instruction that calls the function.
// - Adds an argument to every caller of the function to account for the added
// parameter. The argument is the value in |call_parameter_id| map.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;

View File

@ -0,0 +1,226 @@
// 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 "transformation_composite_insert.h"
#include "source/fuzz/fuzzer_pass_add_composite_inserts.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
namespace spvtools {
namespace fuzz {
TransformationCompositeInsert::TransformationCompositeInsert(
const spvtools::fuzz::protobufs::TransformationCompositeInsert& message)
: message_(message) {}
TransformationCompositeInsert::TransformationCompositeInsert(
const protobufs::InstructionDescriptor& instruction_to_insert_before,
uint32_t fresh_id, uint32_t composite_id, uint32_t object_id,
const std::vector<uint32_t>& index) {
*message_.mutable_instruction_to_insert_before() =
instruction_to_insert_before;
message_.set_fresh_id(fresh_id);
message_.set_composite_id(composite_id);
message_.set_object_id(object_id);
for (auto an_index : index) {
message_.add_index(an_index);
}
}
bool TransformationCompositeInsert::IsApplicable(
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
// |message_.fresh_id| must be fresh.
if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
return false;
}
// |message_.composite_id| must refer to an existing composite value.
auto composite =
ir_context->get_def_use_mgr()->GetDef(message_.composite_id());
if (!IsCompositeInstructionSupported(ir_context, composite)) {
return false;
}
// The indices in |message_.index| must be suitable for indexing into
// |composite->type_id()|.
auto component_to_be_replaced_type_id = fuzzerutil::WalkCompositeTypeIndices(
ir_context, composite->type_id(), message_.index());
if (component_to_be_replaced_type_id == 0) {
return false;
}
// The instruction having the id of |message_.object_id| must be defined.
auto object_instruction =
ir_context->get_def_use_mgr()->GetDef(message_.object_id());
if (object_instruction == nullptr || object_instruction->type_id() == 0) {
return false;
}
// We ignore pointers for now.
auto object_instruction_type =
ir_context->get_type_mgr()->GetType(object_instruction->type_id());
if (object_instruction_type->AsPointer() != nullptr) {
return false;
}
// The type id of the object having |message_.object_id| and the type id of
// the component of the composite at index |message_.index| must be the same.
if (component_to_be_replaced_type_id != object_instruction->type_id()) {
return false;
}
// |message_.instruction_to_insert_before| must be a defined instruction.
auto instruction_to_insert_before =
FindInstruction(message_.instruction_to_insert_before(), ir_context);
if (instruction_to_insert_before == nullptr) {
return false;
}
// |message_.composite_id| and |message_.object_id| must be available before
// the |message_.instruction_to_insert_before|.
if (!fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, instruction_to_insert_before, message_.composite_id())) {
return false;
}
if (!fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, instruction_to_insert_before, message_.object_id())) {
return false;
}
// It must be possible to insert an OpCompositeInsert before this
// instruction.
return fuzzerutil::CanInsertOpcodeBeforeInstruction(
SpvOpCompositeInsert, instruction_to_insert_before);
}
void TransformationCompositeInsert::Apply(
opt::IRContext* ir_context,
TransformationContext* transformation_context) const {
// |message_.struct_fresh_id| must be fresh.
assert(fuzzerutil::IsFreshId(ir_context, message_.fresh_id()) &&
"|message_.fresh_id| must be fresh");
std::vector<uint32_t> index =
fuzzerutil::RepeatedFieldToVector(message_.index());
opt::Instruction::OperandList in_operands;
in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.object_id()}});
in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.composite_id()}});
for (auto i : index) {
in_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}});
}
auto composite_type_id =
fuzzerutil::GetTypeId(ir_context, message_.composite_id());
FindInstruction(message_.instruction_to_insert_before(), ir_context)
->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpCompositeInsert, composite_type_id,
message_.fresh_id(), std::move(in_operands)));
fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
// We have modified the module so most analyzes are now invalid.
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
// Add facts about synonyms. Every element which hasn't been changed in
// the copy is synonymous to the corresponding element in the original
// composite which has id |message_.composite_id|. For every index that is a
// prefix of |index|, the components different from the one that
// contains the inserted object are synonymous with corresponding
// elements in the original composite.
// If |composite_id| is irrelevant then don't add any synonyms.
if (transformation_context->GetFactManager()->IdIsIrrelevant(
message_.composite_id())) {
return;
}
uint32_t current_node_type_id = composite_type_id;
std::vector<uint32_t> current_index;
for (uint32_t current_level = 0; current_level < index.size();
current_level++) {
auto current_node_type_inst =
ir_context->get_def_use_mgr()->GetDef(current_node_type_id);
uint32_t index_to_skip = index[current_level];
uint32_t num_of_components = fuzzerutil::GetBoundForCompositeIndex(
*current_node_type_inst, ir_context);
// Update the current_node_type_id.
current_node_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
ir_context, current_node_type_id, index_to_skip);
for (uint32_t i = 0; i < num_of_components; i++) {
if (i == index_to_skip) {
continue;
}
current_index.push_back(i);
// TODO: (https://github.com/KhronosGroup/SPIRV-Tools/issues/3659)
// Google C++ guide restricts the use of r-value references.
// https://google.github.io/styleguide/cppguide.html#Rvalue_references
// Consider changing the signature of MakeDataDescriptor()
transformation_context->GetFactManager()->AddFactDataSynonym(
MakeDataDescriptor(message_.fresh_id(),
std::vector<uint32_t>(current_index)),
MakeDataDescriptor(message_.composite_id(),
std::vector<uint32_t>(current_index)),
ir_context);
current_index.pop_back();
}
// Store the prefix of the |index|.
current_index.push_back(index[current_level]);
}
// The element which has been changed is synonymous to the found object
// itself. Add this fact only if |object_id| is not irrelevant.
if (!transformation_context->GetFactManager()->IdIsIrrelevant(
message_.object_id())) {
transformation_context->GetFactManager()->AddFactDataSynonym(
MakeDataDescriptor(message_.object_id(), {}),
MakeDataDescriptor(message_.fresh_id(), std::vector<uint32_t>(index)),
ir_context);
}
}
protobufs::Transformation TransformationCompositeInsert::ToMessage() const {
protobufs::Transformation result;
*result.mutable_composite_insert() = message_;
return result;
}
bool TransformationCompositeInsert::IsCompositeInstructionSupported(
opt::IRContext* ir_context, opt::Instruction* instruction) {
if (instruction == nullptr) {
return false;
}
if (instruction->result_id() == 0 || instruction->type_id() == 0) {
return false;
}
auto composite_type =
ir_context->get_type_mgr()->GetType(instruction->type_id());
if (!fuzzerutil::IsCompositeType(composite_type)) {
return false;
}
// Empty composites are not supported.
auto instruction_type_inst =
ir_context->get_def_use_mgr()->GetDef(instruction->type_id());
if (fuzzerutil::GetBoundForCompositeIndex(*instruction_type_inst,
ir_context) == 0) {
return false;
}
return true;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,72 @@
// 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 SPIRV_TOOLS_TRANSFORMATION_COMPOSITE_INSERT_H
#define SPIRV_TOOLS_TRANSFORMATION_COMPOSITE_INSERT_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 TransformationCompositeInsert : public Transformation {
public:
explicit TransformationCompositeInsert(
const protobufs::TransformationCompositeInsert& message);
TransformationCompositeInsert(
const protobufs::InstructionDescriptor& instruction_to_insert_before,
uint32_t fresh_id, uint32_t composite_id, uint32_t object_id,
const std::vector<uint32_t>& index);
// - |message_.fresh_id| must be fresh.
// - |message_.composite_id| must refer to an existing composite value.
// - |message_.index| must refer to a correct index in the composite.
// - The type id of the object and the type id of the component of the
// composite at index |message_.index| must be the same.
// - |message_.instruction_to_insert_before| must refer to a defined
// instruction.
// - It must be possible to insert OpCompositeInsert before
// |instruction_to_insert_before|.
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// Adds an instruction OpCompositeInsert before
// |instruction_to_insert_before|, which creates a new composite from
// |composite_id| by inserting |object_id| at the specified |index|.
// Synonyms are created between those components which are identical in the
// original and the modified composite and between the inserted object and its
// copy in the modified composite.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
protobufs::Transformation ToMessage() const override;
// Checks if |instruction| is a instruction of a composite type supported by
// this transformation.
static bool IsCompositeInstructionSupported(opt::IRContext* ir_context,
opt::Instruction* instruction);
private:
protobufs::TransformationCompositeInsert message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SPIRV_TOOLS_TRANSFORMATION_COMPOSITE_INSERT_H

View File

@ -533,6 +533,17 @@ bool AggressiveDCEPass::AggressiveDCE(Function* func) {
AddToWorklist(dec);
}
// Add DebugScope and DebugInlinedAt for |liveInst| to the work list.
if (liveInst->GetDebugScope().GetLexicalScope() != kNoDebugScope) {
auto* scope = get_def_use_mgr()->GetDef(
liveInst->GetDebugScope().GetLexicalScope());
AddToWorklist(scope);
}
if (liveInst->GetDebugInlinedAt() != kNoInlinedAt) {
auto* inlined_at =
get_def_use_mgr()->GetDef(liveInst->GetDebugInlinedAt());
AddToWorklist(inlined_at);
}
worklist_.pop();
}

View File

@ -135,6 +135,7 @@ SSAPropagator::PropStatus CCPPass::VisitAssignment(Instruction* instr) {
}
return it->second;
};
uint32_t next_id = context()->module()->IdBound();
Instruction* folded_inst =
context()->get_instruction_folder().FoldInstructionToConstant(instr,
map_func);
@ -143,6 +144,14 @@ SSAPropagator::PropStatus CCPPass::VisitAssignment(Instruction* instr) {
// instructions. When folding we can only generate new constants.
assert(folded_inst->IsConstant() && "CCP is only interested in constant.");
values_[instr->result_id()] = folded_inst->result_id();
// If the folded instruction has just been created, its result ID will
// match the previous ID bound. When this happens, we need to indicate
// that CCP has modified the IR, independently of whether the constant is
// actually propagated. See
// https://github.com/KhronosGroup/SPIRV-Tools/issues/3636 for details.
if (folded_inst->result_id() == next_id) created_new_constant_ = true;
return SSAPropagator::kInteresting;
}
@ -266,16 +275,22 @@ SSAPropagator::PropStatus CCPPass::VisitInstruction(Instruction* instr,
}
bool CCPPass::ReplaceValues() {
bool retval = false;
// Even if we make no changes to the function's IR, propagation may have
// created new constants. Even if those constants cannot be replaced in
// the IR, the constant definition itself is a change. To reflect this,
// we initialize the IR changed indicator with the value of the
// created_new_constant_ indicator. For an example, see the bug reported
// in https://github.com/KhronosGroup/SPIRV-Tools/issues/3636.
bool changed_ir = created_new_constant_;
for (const auto& it : values_) {
uint32_t id = it.first;
uint32_t cst_id = it.second;
if (!IsVaryingValue(cst_id) && id != cst_id) {
context()->KillNamesAndDecorates(id);
retval |= context()->ReplaceAllUsesWith(id, cst_id);
changed_ir |= context()->ReplaceAllUsesWith(id, cst_id);
}
}
return retval;
return changed_ir;
}
bool CCPPass::PropagateConstants(Function* fp) {
@ -313,6 +328,8 @@ void CCPPass::Initialize() {
values_[inst.result_id()] = kVaryingSSAId;
}
}
created_new_constant_ = false;
}
Pass::Status CCPPass::Process() {

View File

@ -105,6 +105,9 @@ class CCPPass : public MemPass {
// Propagator engine used.
std::unique_ptr<SSAPropagator> propagator_;
// True if the pass created new constant instructions during propagation.
bool created_new_constant_;
};
} // namespace opt

View File

@ -227,6 +227,18 @@ BasicBlock* Function::InsertBasicBlockBefore(
return nullptr;
}
bool Function::HasEarlyReturn() const {
auto post_dominator_analysis =
blocks_.front()->GetLabel()->context()->GetPostDominatorAnalysis(this);
for (auto& block : blocks_) {
if (spvOpcodeIsReturn(block->tail()->opcode()) &&
!post_dominator_analysis->Dominates(block.get(), entry().get())) {
return true;
}
}
return false;
}
bool Function::IsRecursive() const {
IRContext* ctx = blocks_.front()->GetLabel()->context();
IRContext::ProcessFunction mark_visited = [this](Function* fp) {

View File

@ -158,7 +158,10 @@ class Function {
BasicBlock* InsertBasicBlockBefore(std::unique_ptr<BasicBlock>&& new_block,
BasicBlock* position);
// Return true if the function calls itself either directly or indirectly.
// Returns true if the function has a return block other than the exit block.
bool HasEarlyReturn() const;
// Returns true if the function calls itself either directly or indirectly.
bool IsRecursive() const;
// Pretty-prints all the basic blocks in this function into a std::string.

View File

@ -179,7 +179,7 @@ spv_result_t ValidateOperandDebugType(
return true;
}
return OpenCLDebugInfo100DebugTypeBasic <= dbg_inst &&
dbg_inst <= OpenCLDebugInfo100DebugTypePtrToMember;
dbg_inst <= OpenCLDebugInfo100DebugTypeTemplate;
};
if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index))
return SPV_SUCCESS;