Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2020-09-05 11:33:44 -07:00
parent 86402fec24
commit 765b7b02eb
315 changed files with 501 additions and 40707 deletions

View File

@@ -1 +1 @@
"v2020.5", "SPIRV-Tools v2020.5 e272a31e19b278158551bc64142947df09ede4ac"
"v2020.5", "SPIRV-Tools v2020.5 86402fec24134b6d4103b6ad1e689fb5840f720f"

View File

@@ -1,392 +0,0 @@
# Copyright (c) 2019 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
if(SPIRV_BUILD_FUZZER)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/protobufs)
set(PROTOBUF_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/protobufs/spvtoolsfuzz.proto)
add_custom_command(
OUTPUT protobufs/spvtoolsfuzz.pb.cc protobufs/spvtoolsfuzz.pb.h
COMMAND protobuf::protoc
-I=${CMAKE_CURRENT_SOURCE_DIR}/protobufs
--cpp_out=protobufs
${PROTOBUF_SOURCE}
DEPENDS ${PROTOBUF_SOURCE}
COMMENT "Generate protobuf sources from proto definition file."
)
set(SPIRV_TOOLS_FUZZ_SOURCES
call_graph.h
counter_overflow_id_source.h
data_descriptor.h
equivalence_relation.h
fact_manager/constant_uniform_facts.h
fact_manager/data_synonym_and_id_equation_facts.h
fact_manager/dead_block_facts.h
fact_manager/fact_manager.h
fact_manager/irrelevant_value_facts.h
fact_manager/livesafe_function_facts.h
force_render_red.h
fuzzer.h
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
fuzzer_pass_add_dead_breaks.h
fuzzer_pass_add_dead_continues.h
fuzzer_pass_add_equation_instructions.h
fuzzer_pass_add_function_calls.h
fuzzer_pass_add_global_variables.h
fuzzer_pass_add_image_sample_unused_components.h
fuzzer_pass_add_synonyms.h
fuzzer_pass_add_loads.h
fuzzer_pass_add_local_variables.h
fuzzer_pass_add_loop_preheaders.h
fuzzer_pass_add_no_contraction_decorations.h
fuzzer_pass_add_opphi_synonyms.h
fuzzer_pass_add_parameters.h
fuzzer_pass_add_relaxed_decorations.h
fuzzer_pass_add_stores.h
fuzzer_pass_add_vector_shuffle_instructions.h
fuzzer_pass_adjust_branch_weights.h
fuzzer_pass_adjust_function_controls.h
fuzzer_pass_adjust_loop_controls.h
fuzzer_pass_adjust_memory_operands_masks.h
fuzzer_pass_adjust_selection_controls.h
fuzzer_pass_apply_id_synonyms.h
fuzzer_pass_construct_composites.h
fuzzer_pass_copy_objects.h
fuzzer_pass_donate_modules.h
fuzzer_pass_inline_functions.h
fuzzer_pass_invert_comparison_operators.h
fuzzer_pass_interchange_signedness_of_integer_operands.h
fuzzer_pass_interchange_zero_like_constants.h
fuzzer_pass_make_vector_operations_dynamic.h
fuzzer_pass_merge_blocks.h
fuzzer_pass_obfuscate_constants.h
fuzzer_pass_outline_functions.h
fuzzer_pass_permute_blocks.h
fuzzer_pass_permute_function_parameters.h
fuzzer_pass_permute_instructions.h
fuzzer_pass_permute_phi_operands.h
fuzzer_pass_propagate_instructions_up.h
fuzzer_pass_push_ids_through_variables.h
fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
fuzzer_pass_replace_copy_memories_with_loads_stores.h
fuzzer_pass_replace_copy_objects_with_stores_loads.h
fuzzer_pass_replace_linear_algebra_instructions.h
fuzzer_pass_replace_loads_stores_with_copy_memories.h
fuzzer_pass_replace_parameter_with_global.h
fuzzer_pass_replace_params_with_struct.h
fuzzer_pass_split_blocks.h
fuzzer_pass_swap_commutable_operands.h
fuzzer_pass_swap_conditional_branch_operands.h
fuzzer_pass_toggle_access_chain_instruction.h
fuzzer_util.h
id_use_descriptor.h
instruction_descriptor.h
instruction_message.h
overflow_id_source.h
protobufs/spirvfuzz_protobufs.h
pseudo_random_generator.h
random_generator.h
replayer.h
shrinker.h
transformation.h
transformation_access_chain.h
transformation_add_constant_boolean.h
transformation_add_constant_composite.h
transformation_add_constant_null.h
transformation_add_constant_scalar.h
transformation_add_copy_memory.h
transformation_add_dead_block.h
transformation_add_dead_break.h
transformation_add_dead_continue.h
transformation_add_function.h
transformation_add_global_undef.h
transformation_add_global_variable.h
transformation_add_image_sample_unused_components.h
transformation_add_local_variable.h
transformation_add_loop_preheader.h
transformation_add_no_contraction_decoration.h
transformation_add_opphi_synonym.h
transformation_add_parameter.h
transformation_add_relaxed_decoration.h
transformation_add_spec_constant_op.h
transformation_add_synonym.h
transformation_add_type_array.h
transformation_add_type_boolean.h
transformation_add_type_float.h
transformation_add_type_function.h
transformation_add_type_int.h
transformation_add_type_matrix.h
transformation_add_type_pointer.h
transformation_add_type_struct.h
transformation_add_type_vector.h
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
transformation_function_call.h
transformation_inline_function.h
transformation_invert_comparison_operator.h
transformation_load.h
transformation_make_vector_operation_dynamic.h
transformation_merge_blocks.h
transformation_move_block_down.h
transformation_move_instruction_down.h
transformation_outline_function.h
transformation_permute_function_parameters.h
transformation_permute_phi_operands.h
transformation_propagate_instruction_up.h
transformation_push_id_through_variable.h
transformation_record_synonymous_constants.h
transformation_replace_add_sub_mul_with_carrying_extended.h
transformation_replace_boolean_constant_with_constant_binary.h
transformation_replace_constant_with_uniform.h
transformation_replace_copy_memory_with_load_store.h
transformation_replace_copy_object_with_store_load.h
transformation_replace_id_with_synonym.h
transformation_replace_linear_algebra_instruction.h
transformation_replace_load_store_with_copy_memory.h
transformation_replace_parameter_with_global.h
transformation_replace_params_with_struct.h
transformation_set_function_control.h
transformation_set_loop_control.h
transformation_set_memory_operands_mask.h
transformation_set_selection_control.h
transformation_split_block.h
transformation_store.h
transformation_swap_commutable_operands.h
transformation_swap_conditional_branch_operands.h
transformation_toggle_access_chain_instruction.h
transformation_vector_shuffle.h
uniform_buffer_element_descriptor.h
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
call_graph.cpp
counter_overflow_id_source.cpp
data_descriptor.cpp
fact_manager/constant_uniform_facts.cpp
fact_manager/data_synonym_and_id_equation_facts.cpp
fact_manager/dead_block_facts.cpp
fact_manager/fact_manager.cpp
fact_manager/irrelevant_value_facts.cpp
fact_manager/livesafe_function_facts.cpp
force_render_red.cpp
fuzzer.cpp
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
fuzzer_pass_add_dead_breaks.cpp
fuzzer_pass_add_dead_continues.cpp
fuzzer_pass_add_equation_instructions.cpp
fuzzer_pass_add_function_calls.cpp
fuzzer_pass_add_global_variables.cpp
fuzzer_pass_add_image_sample_unused_components.cpp
fuzzer_pass_add_synonyms.cpp
fuzzer_pass_add_loads.cpp
fuzzer_pass_add_local_variables.cpp
fuzzer_pass_add_loop_preheaders.cpp
fuzzer_pass_add_no_contraction_decorations.cpp
fuzzer_pass_add_opphi_synonyms.cpp
fuzzer_pass_add_parameters.cpp
fuzzer_pass_add_relaxed_decorations.cpp
fuzzer_pass_add_stores.cpp
fuzzer_pass_add_vector_shuffle_instructions.cpp
fuzzer_pass_adjust_branch_weights.cpp
fuzzer_pass_adjust_function_controls.cpp
fuzzer_pass_adjust_loop_controls.cpp
fuzzer_pass_adjust_memory_operands_masks.cpp
fuzzer_pass_adjust_selection_controls.cpp
fuzzer_pass_apply_id_synonyms.cpp
fuzzer_pass_construct_composites.cpp
fuzzer_pass_copy_objects.cpp
fuzzer_pass_donate_modules.cpp
fuzzer_pass_inline_functions.cpp
fuzzer_pass_invert_comparison_operators.cpp
fuzzer_pass_interchange_signedness_of_integer_operands.cpp
fuzzer_pass_interchange_zero_like_constants.cpp
fuzzer_pass_make_vector_operations_dynamic.cpp
fuzzer_pass_merge_blocks.cpp
fuzzer_pass_obfuscate_constants.cpp
fuzzer_pass_outline_functions.cpp
fuzzer_pass_permute_blocks.cpp
fuzzer_pass_permute_function_parameters.cpp
fuzzer_pass_permute_instructions.cpp
fuzzer_pass_permute_phi_operands.cpp
fuzzer_pass_propagate_instructions_up.cpp
fuzzer_pass_push_ids_through_variables.cpp
fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
fuzzer_pass_replace_copy_memories_with_loads_stores.cpp
fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
fuzzer_pass_replace_linear_algebra_instructions.cpp
fuzzer_pass_replace_loads_stores_with_copy_memories.cpp
fuzzer_pass_replace_parameter_with_global.cpp
fuzzer_pass_replace_params_with_struct.cpp
fuzzer_pass_split_blocks.cpp
fuzzer_pass_swap_commutable_operands.cpp
fuzzer_pass_swap_conditional_branch_operands.cpp
fuzzer_pass_toggle_access_chain_instruction.cpp
fuzzer_util.cpp
id_use_descriptor.cpp
instruction_descriptor.cpp
instruction_message.cpp
overflow_id_source.cpp
pseudo_random_generator.cpp
random_generator.cpp
replayer.cpp
shrinker.cpp
transformation.cpp
transformation_access_chain.cpp
transformation_add_constant_boolean.cpp
transformation_add_constant_composite.cpp
transformation_add_constant_null.cpp
transformation_add_constant_scalar.cpp
transformation_add_copy_memory.cpp
transformation_add_dead_block.cpp
transformation_add_dead_break.cpp
transformation_add_dead_continue.cpp
transformation_add_function.cpp
transformation_add_global_undef.cpp
transformation_add_global_variable.cpp
transformation_add_image_sample_unused_components.cpp
transformation_add_local_variable.cpp
transformation_add_loop_preheader.cpp
transformation_add_no_contraction_decoration.cpp
transformation_add_opphi_synonym.cpp
transformation_add_parameter.cpp
transformation_add_relaxed_decoration.cpp
transformation_add_spec_constant_op.cpp
transformation_add_synonym.cpp
transformation_add_type_array.cpp
transformation_add_type_boolean.cpp
transformation_add_type_float.cpp
transformation_add_type_function.cpp
transformation_add_type_int.cpp
transformation_add_type_matrix.cpp
transformation_add_type_pointer.cpp
transformation_add_type_struct.cpp
transformation_add_type_vector.cpp
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
transformation_function_call.cpp
transformation_inline_function.cpp
transformation_invert_comparison_operator.cpp
transformation_load.cpp
transformation_make_vector_operation_dynamic.cpp
transformation_merge_blocks.cpp
transformation_move_block_down.cpp
transformation_move_instruction_down.cpp
transformation_outline_function.cpp
transformation_permute_function_parameters.cpp
transformation_permute_phi_operands.cpp
transformation_propagate_instruction_up.cpp
transformation_push_id_through_variable.cpp
transformation_record_synonymous_constants.cpp
transformation_replace_add_sub_mul_with_carrying_extended.cpp
transformation_replace_boolean_constant_with_constant_binary.cpp
transformation_replace_constant_with_uniform.cpp
transformation_replace_copy_memory_with_load_store.cpp
transformation_replace_copy_object_with_store_load.cpp
transformation_replace_id_with_synonym.cpp
transformation_replace_linear_algebra_instruction.cpp
transformation_replace_load_store_with_copy_memory.cpp
transformation_replace_parameter_with_global.cpp
transformation_replace_params_with_struct.cpp
transformation_set_function_control.cpp
transformation_set_loop_control.cpp
transformation_set_memory_operands_mask.cpp
transformation_set_selection_control.cpp
transformation_split_block.cpp
transformation_store.cpp
transformation_swap_commutable_operands.cpp
transformation_swap_conditional_branch_operands.cpp
transformation_toggle_access_chain_instruction.cpp
transformation_vector_shuffle.cpp
uniform_buffer_element_descriptor.cpp
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
)
if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")))
# Enable parallel builds across four cores for this lib
add_definitions(/MP4)
endif()
spvtools_pch(SPIRV_TOOLS_FUZZ_SOURCES pch_source_fuzz)
add_library(SPIRV-Tools-fuzz ${SPIRV_TOOLS_FUZZ_SOURCES})
spvtools_default_compile_options(SPIRV-Tools-fuzz)
target_compile_definitions(SPIRV-Tools-fuzz PUBLIC -DGOOGLE_PROTOBUF_NO_RTTI -DGOOGLE_PROTOBUF_USE_UNALIGNED=0)
# Compilation of the auto-generated protobuf source file will yield warnings,
# which we have no control over and thus wish to ignore.
if(${COMPILER_IS_LIKE_GNU})
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc PROPERTIES COMPILE_FLAGS -w)
endif()
if(MSVC)
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc PROPERTIES COMPILE_FLAGS /w)
endif()
target_include_directories(SPIRV-Tools-fuzz
PUBLIC
$<BUILD_INTERFACE:${spirv-tools_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${SPIRV_HEADER_INCLUDE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
PRIVATE ${spirv-tools_BINARY_DIR}
PRIVATE ${CMAKE_BINARY_DIR})
# The fuzzer reuses a lot of functionality from the SPIRV-Tools library.
target_link_libraries(SPIRV-Tools-fuzz
PUBLIC ${SPIRV_TOOLS}-static
PUBLIC SPIRV-Tools-opt
PUBLIC protobuf::libprotobuf)
set_property(TARGET SPIRV-Tools-fuzz PROPERTY FOLDER "SPIRV-Tools libraries")
spvtools_check_symbol_exports(SPIRV-Tools-fuzz)
if(ENABLE_SPIRV_TOOLS_INSTALL)
install(TARGETS SPIRV-Tools-fuzz EXPORT SPIRV-Tools-fuzzTargets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
export(EXPORT SPIRV-Tools-fuzzTargets FILE SPIRV-Tools-fuzzTarget.cmake)
spvtools_config_package_dir(SPIRV-Tools-fuzz PACKAGE_DIR)
install(EXPORT SPIRV-Tools-fuzzTargets FILE SPIRV-Tools-fuzzTarget.cmake
DESTINATION ${PACKAGE_DIR})
spvtools_generate_config_file(SPIRV-Tools-fuzz)
install(FILES ${CMAKE_BINARY_DIR}/SPIRV-Tools-fuzzConfig.cmake DESTINATION ${PACKAGE_DIR})
endif(ENABLE_SPIRV_TOOLS_INSTALL)
endif(SPIRV_BUILD_FUZZER)

View File

@@ -1,81 +0,0 @@
// 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/call_graph.h"
#include <queue>
namespace spvtools {
namespace fuzz {
CallGraph::CallGraph(opt::IRContext* context) {
// Initialize function in-degree and call graph edges to 0 and empty.
for (auto& function : *context->module()) {
function_in_degree_[function.result_id()] = 0;
call_graph_edges_[function.result_id()] = std::set<uint32_t>();
}
// Consider every function.
for (auto& function : *context->module()) {
// Avoid considering the same callee of this function multiple times by
// recording known callees.
std::set<uint32_t> known_callees;
// Consider every function call instruction in every block.
for (auto& block : function) {
for (auto& instruction : block) {
if (instruction.opcode() != SpvOpFunctionCall) {
continue;
}
// Get the id of the function being called.
uint32_t callee = instruction.GetSingleWordInOperand(0);
if (known_callees.count(callee)) {
// We have already considered a call to this function - ignore it.
continue;
}
// Increase the callee's in-degree and add an edge to the call graph.
function_in_degree_[callee]++;
call_graph_edges_[function.result_id()].insert(callee);
// Mark the callee as 'known'.
known_callees.insert(callee);
}
}
}
}
void CallGraph::PushDirectCallees(uint32_t function_id,
std::queue<uint32_t>* queue) const {
for (auto callee : GetDirectCallees(function_id)) {
queue->push(callee);
}
}
std::set<uint32_t> CallGraph::GetIndirectCallees(uint32_t function_id) const {
std::set<uint32_t> result;
std::queue<uint32_t> queue;
PushDirectCallees(function_id, &queue);
while (!queue.empty()) {
auto next = queue.front();
queue.pop();
if (result.count(next)) {
continue;
}
result.insert(next);
PushDirectCallees(next, &queue);
}
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,62 +0,0 @@
// 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_CALL_GRAPH_H_
#define SOURCE_FUZZ_CALL_GRAPH_H_
#include <map>
#include <set>
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
// Represents the acyclic call graph of a SPIR-V module.
class CallGraph {
public:
// Creates a call graph corresponding to the given SPIR-V module.
explicit CallGraph(opt::IRContext* context);
// Returns a mapping from each function to its number of distinct callers.
const std::map<uint32_t, uint32_t>& GetFunctionInDegree() const {
return function_in_degree_;
}
// Returns the ids of the functions that |function_id| directly invokes.
const std::set<uint32_t>& GetDirectCallees(uint32_t function_id) const {
return call_graph_edges_.at(function_id);
}
// Returns the ids of the functions that |function_id| directly or indirectly
// invokes.
std::set<uint32_t> GetIndirectCallees(uint32_t function_id) const;
private:
// Pushes the direct callees of |function_id| on to |queue|.
void PushDirectCallees(uint32_t function_id,
std::queue<uint32_t>* queue) const;
// Maps each function id to the ids of its immediate callees.
std::map<uint32_t, std::set<uint32_t>> call_graph_edges_;
// For each function id, stores the number of distinct functions that call
// the function.
std::map<uint32_t, uint32_t> function_in_degree_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_CALL_GRAPH_H_

View File

@@ -1,30 +0,0 @@
// 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/counter_overflow_id_source.h"
namespace spvtools {
namespace fuzz {
CounterOverflowIdSource::CounterOverflowIdSource(uint32_t first_available_id)
: next_available_id_(first_available_id) {}
bool CounterOverflowIdSource::HasOverflowIds() const { return true; }
uint32_t CounterOverflowIdSource::GetNextOverflowId() {
return next_available_id_++;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,45 +0,0 @@
// 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_COUNTER_OVERFLOW_ID_SOURCE_H_
#define SOURCE_FUZZ_COUNTER_OVERFLOW_ID_SOURCE_H_
#include "source/fuzz/overflow_id_source.h"
namespace spvtools {
namespace fuzz {
// A source of overflow ids that uses a counter to provide successive ids from
// a given starting value.
class CounterOverflowIdSource : public OverflowIdSource {
public:
// |first_available_id| is the starting value for the counter.
explicit CounterOverflowIdSource(uint32_t first_available_id);
// Always returns true.
bool HasOverflowIds() const override;
// Returns the current counter value and increments the counter.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) We should
// account for the case where the maximum allowed id is reached.
uint32_t GetNextOverflowId() override;
private:
uint32_t next_available_id_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_OVERFLOW_ID_SOURCE_COUNTER_H_

View File

@@ -1,69 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/data_descriptor.h"
#include <algorithm>
namespace spvtools {
namespace fuzz {
protobufs::DataDescriptor MakeDataDescriptor(uint32_t object,
std::vector<uint32_t>&& indices) {
protobufs::DataDescriptor result;
result.set_object(object);
for (auto index : indices) {
result.add_index(index);
}
return result;
}
size_t DataDescriptorHash::operator()(
const protobufs::DataDescriptor* data_descriptor) const {
std::u32string hash;
hash.push_back(data_descriptor->object());
for (auto an_index : data_descriptor->index()) {
hash.push_back(an_index);
}
return std::hash<std::u32string>()(hash);
}
bool DataDescriptorEquals::operator()(
const protobufs::DataDescriptor* first,
const protobufs::DataDescriptor* second) const {
return first->object() == second->object() &&
first->index().size() == second->index().size() &&
std::equal(first->index().begin(), first->index().end(),
second->index().begin());
}
std::ostream& operator<<(std::ostream& out,
const protobufs::DataDescriptor& data_descriptor) {
out << data_descriptor.object();
out << "[";
bool first = true;
for (auto index : data_descriptor.index()) {
if (first) {
first = false;
} else {
out << ", ";
}
out << index;
}
out << "]";
return out;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,48 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_DATA_DESCRIPTOR_H_
#define SOURCE_FUZZ_DATA_DESCRIPTOR_H_
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include <ostream>
#include <vector>
namespace spvtools {
namespace fuzz {
// Factory method to create a data descriptor message from an object id and a
// list of indices.
protobufs::DataDescriptor MakeDataDescriptor(uint32_t object,
std::vector<uint32_t>&& indices);
// Hash function for data descriptors.
struct DataDescriptorHash {
size_t operator()(const protobufs::DataDescriptor* data_descriptor) const;
};
// Equality function for data descriptors.
struct DataDescriptorEquals {
bool operator()(const protobufs::DataDescriptor* first,
const protobufs::DataDescriptor* second) const;
};
std::ostream& operator<<(std::ostream& out,
const protobufs::DataDescriptor& data_descriptor);
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_DATA_DESCRIPTOR_H_

View File

@@ -1,245 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_EQUIVALENCE_RELATION_H_
#define SOURCE_FUZZ_EQUIVALENCE_RELATION_H_
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "source/util/make_unique.h"
namespace spvtools {
namespace fuzz {
// A class for representing an equivalence relation on objects of type |T|,
// which should be a value type. The type |T| is required to have a copy
// constructor, and |PointerHashT| and |PointerEqualsT| must be functors
// providing hashing and equality testing functionality for pointers to objects
// of type |T|.
//
// A disjoint-set (a.k.a. union-find or merge-find) data structure is used to
// represent the equivalence relation. Path compression is used. Union by
// rank/size is not used.
//
// Each disjoint set is represented as a tree, rooted at the representative
// of the set.
//
// Getting the representative of a value simply requires chasing parent pointers
// from the value until you reach the root.
//
// Checking equivalence of two elements requires checking that the
// representatives are equal.
//
// Traversing the tree rooted at a value's representative visits the value's
// equivalence class.
//
// |PointerHashT| and |PointerEqualsT| are used to define *equality* between
// values, and otherwise are *not* used to define the equivalence relation
// (except that equal values are equivalent). The equivalence relation is
// constructed by repeatedly adding pairs of (typically non-equal) values that
// are deemed to be equivalent.
//
// For example in an equivalence relation on integers, 1 and 5 might be added
// as equivalent, so that IsEquivalent(1, 5) holds, because they represent
// IDs in a SPIR-V binary that are known to contain the same value at run time,
// but clearly 1 != 5. Since 1 and 1 are equal, IsEquivalent(1, 1) will also
// hold.
//
// Each unique (up to equality) value added to the relation is copied into
// |owned_values_|, so there is one canonical memory address per unique value.
// Uniqueness is ensured by storing (and checking) a set of pointers to these
// values in |value_set_|, which uses |PointerHashT| and |PointerEqualsT|.
//
// |parent_| and |children_| encode the equivalence relation, i.e., the trees.
template <typename T, typename PointerHashT, typename PointerEqualsT>
class EquivalenceRelation {
public:
// Requires that |value1| and |value2| are already registered in the
// equivalence relation. Merges the equivalence classes associated with
// |value1| and |value2|.
void MakeEquivalent(const T& value1, const T& value2) {
assert(Exists(value1) &&
"Precondition: value1 must already be registered.");
assert(Exists(value2) &&
"Precondition: value2 must already be registered.");
// Look up canonical pointers to each of the values in the value pool.
const T* value1_ptr = *value_set_.find(&value1);
const T* value2_ptr = *value_set_.find(&value2);
// If the values turn out to be identical, they are already in the same
// equivalence class so there is nothing to do.
if (value1_ptr == value2_ptr) {
return;
}
// Find the representative for each value's equivalence class, and if they
// are not already in the same class, make one the parent of the other.
const T* representative1 = Find(value1_ptr);
const T* representative2 = Find(value2_ptr);
assert(representative1 && "Representatives should never be null.");
assert(representative2 && "Representatives should never be null.");
if (representative1 != representative2) {
parent_[representative1] = representative2;
children_[representative2].push_back(representative1);
}
}
// Requires that |value| is not known to the equivalence relation. Registers
// it in its own equivalence class and returns a pointer to the equivalence
// class representative.
const T* Register(const T& value) {
assert(!Exists(value));
// This relies on T having a copy constructor.
auto unique_pointer_to_value = MakeUnique<T>(value);
auto pointer_to_value = unique_pointer_to_value.get();
owned_values_.push_back(std::move(unique_pointer_to_value));
value_set_.insert(pointer_to_value);
// Initially say that the value is its own parent and that it has no
// children.
assert(pointer_to_value && "Representatives should never be null.");
parent_[pointer_to_value] = pointer_to_value;
children_[pointer_to_value] = std::vector<const T*>();
return pointer_to_value;
}
// Returns exactly one representative per equivalence class.
std::vector<const T*> GetEquivalenceClassRepresentatives() const {
std::vector<const T*> result;
for (auto& value : owned_values_) {
if (parent_[value.get()] == value.get()) {
result.push_back(value.get());
}
}
return result;
}
// Returns pointers to all values in the equivalence class of |value|, which
// must already be part of the equivalence relation.
std::vector<const T*> GetEquivalenceClass(const T& value) const {
assert(Exists(value));
std::vector<const T*> result;
// Traverse the tree of values rooted at the representative of the
// equivalence class to which |value| belongs, and collect up all the values
// that are encountered. This constitutes the whole equivalence class.
std::vector<const T*> stack;
stack.push_back(Find(*value_set_.find(&value)));
while (!stack.empty()) {
const T* item = stack.back();
result.push_back(item);
stack.pop_back();
for (auto child : children_[item]) {
stack.push_back(child);
}
}
return result;
}
// Returns true if and only if |value1| and |value2| are in the same
// equivalence class. Both values must already be known to the equivalence
// relation.
bool IsEquivalent(const T& value1, const T& value2) const {
return Find(&value1) == Find(&value2);
}
// Returns all values known to be part of the equivalence relation.
std::vector<const T*> GetAllKnownValues() const {
std::vector<const T*> result;
for (auto& value : owned_values_) {
result.push_back(value.get());
}
return result;
}
// Returns true if and only if |value| is known to be part of the equivalence
// relation.
bool Exists(const T& value) const {
return value_set_.find(&value) != value_set_.end();
}
// Returns the representative of the equivalence class of |value|, which must
// already be known to the equivalence relation. This is the 'Find' operation
// in a classic union-find data structure.
const T* Find(const T* value) const {
assert(Exists(*value));
// Get the canonical pointer to the value from the value pool.
const T* known_value = *value_set_.find(value);
assert(parent_[known_value] && "Every known value should have a parent.");
// Compute the result by chasing parents until we find a value that is its
// own parent.
const T* result = known_value;
while (parent_[result] != result) {
result = parent_[result];
}
assert(result && "Representatives should never be null.");
// At this point, |result| is the representative of the equivalence class.
// Now perform the 'path compression' optimization by doing another pass up
// the parent chain, setting the parent of each node to be the
// representative, and rewriting children correspondingly.
const T* current = known_value;
while (parent_[current] != result) {
const T* next = parent_[current];
parent_[current] = result;
children_[result].push_back(current);
auto child_iterator =
std::find(children_[next].begin(), children_[next].end(), current);
assert(child_iterator != children_[next].end() &&
"'next' is the parent of 'current', so 'current' should be a "
"child of 'next'");
children_[next].erase(child_iterator);
current = next;
}
return result;
}
private:
// Maps every value to a parent. The representative of an equivalence class
// is its own parent. A value's representative can be found by walking its
// chain of ancestors.
//
// Mutable because the intuitively const method, 'Find', performs path
// compression.
mutable std::unordered_map<const T*, const T*> parent_;
// Stores the children of each value. This allows the equivalence class of
// a value to be calculated by traversing all descendents of the class's
// representative.
//
// Mutable because the intuitively const method, 'Find', performs path
// compression.
mutable std::unordered_map<const T*, std::vector<const T*>> children_;
// The values known to the equivalence relation are allocated in
// |owned_values_|, and |value_pool_| provides (via |PointerHashT| and
// |PointerEqualsT|) a means for mapping a value of interest to a pointer
// into an equivalent value in |owned_values_|.
std::unordered_set<const T*, PointerHashT, PointerEqualsT> value_set_;
std::vector<std::unique_ptr<T>> owned_values_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_EQUIVALENCE_RELATION_H_

View File

@@ -1,235 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fact_manager/constant_uniform_facts.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/uniform_buffer_element_descriptor.h"
namespace spvtools {
namespace fuzz {
namespace fact_manager {
uint32_t ConstantUniformFacts::GetConstantId(
opt::IRContext* context,
const protobufs::FactConstantUniform& constant_uniform_fact,
uint32_t type_id) {
auto type = context->get_type_mgr()->GetType(type_id);
assert(type != nullptr && "Unknown type id.");
const opt::analysis::Constant* known_constant;
if (type->AsInteger()) {
opt::analysis::IntConstant candidate_constant(
type->AsInteger(), GetConstantWords(constant_uniform_fact));
known_constant =
context->get_constant_mgr()->FindConstant(&candidate_constant);
} else {
assert(
type->AsFloat() &&
"Uniform constant facts are only supported for int and float types.");
opt::analysis::FloatConstant candidate_constant(
type->AsFloat(), GetConstantWords(constant_uniform_fact));
known_constant =
context->get_constant_mgr()->FindConstant(&candidate_constant);
}
if (!known_constant) {
return 0;
}
return context->get_constant_mgr()->FindDeclaredConstant(known_constant,
type_id);
}
std::vector<uint32_t> ConstantUniformFacts::GetConstantWords(
const protobufs::FactConstantUniform& constant_uniform_fact) {
std::vector<uint32_t> result;
for (auto constant_word : constant_uniform_fact.constant_word()) {
result.push_back(constant_word);
}
return result;
}
bool ConstantUniformFacts::DataMatches(
const opt::Instruction& constant_instruction,
const protobufs::FactConstantUniform& constant_uniform_fact) {
assert(constant_instruction.opcode() == SpvOpConstant);
std::vector<uint32_t> data_in_constant;
for (uint32_t i = 0; i < constant_instruction.NumInOperands(); i++) {
data_in_constant.push_back(constant_instruction.GetSingleWordInOperand(i));
}
return data_in_constant == GetConstantWords(constant_uniform_fact);
}
std::vector<uint32_t>
ConstantUniformFacts::GetConstantsAvailableFromUniformsForType(
opt::IRContext* ir_context, uint32_t type_id) const {
std::vector<uint32_t> result;
std::set<uint32_t> already_seen;
for (auto& fact_and_type_id : facts_and_type_ids_) {
if (fact_and_type_id.second != type_id) {
continue;
}
if (auto constant_id =
GetConstantId(ir_context, fact_and_type_id.first, type_id)) {
if (already_seen.find(constant_id) == already_seen.end()) {
result.push_back(constant_id);
already_seen.insert(constant_id);
}
}
}
return result;
}
std::vector<protobufs::UniformBufferElementDescriptor>
ConstantUniformFacts::GetUniformDescriptorsForConstant(
opt::IRContext* ir_context, uint32_t constant_id) const {
std::vector<protobufs::UniformBufferElementDescriptor> result;
auto constant_inst = ir_context->get_def_use_mgr()->GetDef(constant_id);
assert(constant_inst->opcode() == SpvOpConstant &&
"The given id must be that of a constant");
auto type_id = constant_inst->type_id();
for (auto& fact_and_type_id : facts_and_type_ids_) {
if (fact_and_type_id.second != type_id) {
continue;
}
if (DataMatches(*constant_inst, fact_and_type_id.first)) {
result.emplace_back(
fact_and_type_id.first.uniform_buffer_element_descriptor());
}
}
return result;
}
uint32_t ConstantUniformFacts::GetConstantFromUniformDescriptor(
opt::IRContext* context,
const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
// Consider each fact.
for (auto& fact_and_type : facts_and_type_ids_) {
// Check whether the uniform descriptor associated with the fact matches
// |uniform_descriptor|.
if (UniformBufferElementDescriptorEquals()(
&uniform_descriptor,
&fact_and_type.first.uniform_buffer_element_descriptor())) {
return GetConstantId(context, fact_and_type.first, fact_and_type.second);
}
}
// No fact associated with the given uniform descriptor was found.
return 0;
}
std::vector<uint32_t>
ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown() const {
std::vector<uint32_t> result;
for (auto& fact_and_type : facts_and_type_ids_) {
if (std::find(result.begin(), result.end(), fact_and_type.second) ==
result.end()) {
result.push_back(fact_and_type.second);
}
}
return result;
}
bool ConstantUniformFacts::FloatingPointValueIsSuitable(
const protobufs::FactConstantUniform& fact, uint32_t width) {
const uint32_t kFloatWidth = 32;
const uint32_t kDoubleWidth = 64;
if (width != kFloatWidth && width != kDoubleWidth) {
// Only 32- and 64-bit floating-point types are handled.
return false;
}
std::vector<uint32_t> words = GetConstantWords(fact);
if (width == 32) {
float value;
memcpy(&value, words.data(), sizeof(float));
if (!std::isfinite(value)) {
return false;
}
} else {
double value;
memcpy(&value, words.data(), sizeof(double));
if (!std::isfinite(value)) {
return false;
}
}
return true;
}
bool ConstantUniformFacts::AddFact(const protobufs::FactConstantUniform& fact,
opt::IRContext* context) {
// Try to find a unique instruction that declares a variable such that the
// variable is decorated with the descriptor set and binding associated with
// the constant uniform fact.
opt::Instruction* uniform_variable = FindUniformVariable(
fact.uniform_buffer_element_descriptor(), context, true);
if (!uniform_variable) {
return false;
}
assert(SpvOpVariable == uniform_variable->opcode());
assert(SpvStorageClassUniform == uniform_variable->GetSingleWordInOperand(0));
auto should_be_uniform_pointer_type =
context->get_type_mgr()->GetType(uniform_variable->type_id());
if (!should_be_uniform_pointer_type->AsPointer()) {
return false;
}
if (should_be_uniform_pointer_type->AsPointer()->storage_class() !=
SpvStorageClassUniform) {
return false;
}
auto should_be_uniform_pointer_instruction =
context->get_def_use_mgr()->GetDef(uniform_variable->type_id());
auto composite_type =
should_be_uniform_pointer_instruction->GetSingleWordInOperand(1);
auto final_element_type_id = fuzzerutil::WalkCompositeTypeIndices(
context, composite_type,
fact.uniform_buffer_element_descriptor().index());
if (!final_element_type_id) {
return false;
}
auto final_element_type =
context->get_type_mgr()->GetType(final_element_type_id);
assert(final_element_type &&
"There should be a type corresponding to this id.");
if (!(final_element_type->AsFloat() || final_element_type->AsInteger())) {
return false;
}
auto width = final_element_type->AsFloat()
? final_element_type->AsFloat()->width()
: final_element_type->AsInteger()->width();
if (final_element_type->AsFloat() &&
!FloatingPointValueIsSuitable(fact, width)) {
return false;
}
auto required_words = (width + 32 - 1) / 32;
if (static_cast<uint32_t>(fact.constant_word().size()) != required_words) {
return false;
}
facts_and_type_ids_.emplace_back(
std::pair<protobufs::FactConstantUniform, uint32_t>(
fact, final_element_type_id));
return true;
}
const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
ConstantUniformFacts::GetConstantUniformFactsAndTypes() const {
return facts_and_type_ids_;
}
} // namespace fact_manager
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,90 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_
#define SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_
#include <vector>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
namespace fact_manager {
// The purpose of this class is to group the fields and data used to represent
// facts about uniform constants.
class ConstantUniformFacts {
public:
// See method in FactManager which delegates to this method.
bool AddFact(const protobufs::FactConstantUniform& fact,
opt::IRContext* context);
// See method in FactManager which delegates to this method.
std::vector<uint32_t> GetConstantsAvailableFromUniformsForType(
opt::IRContext* ir_context, uint32_t type_id) const;
// See method in FactManager which delegates to this method.
std::vector<protobufs::UniformBufferElementDescriptor>
GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
uint32_t constant_id) const;
// See method in FactManager which delegates to this method.
uint32_t GetConstantFromUniformDescriptor(
opt::IRContext* context,
const protobufs::UniformBufferElementDescriptor& uniform_descriptor)
const;
// See method in FactManager which delegates to this method.
std::vector<uint32_t> GetTypesForWhichUniformValuesAreKnown() const;
// See method in FactManager which delegates to this method.
const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
GetConstantUniformFactsAndTypes() const;
private:
// Returns true if and only if the words associated with
// |constant_instruction| exactly match the words for the constant associated
// with |constant_uniform_fact|.
static bool DataMatches(
const opt::Instruction& constant_instruction,
const protobufs::FactConstantUniform& constant_uniform_fact);
// Yields the constant words associated with |constant_uniform_fact|.
static std::vector<uint32_t> GetConstantWords(
const protobufs::FactConstantUniform& constant_uniform_fact);
// Yields the id of a constant of type |type_id| whose data matches the
// constant data in |constant_uniform_fact|, or 0 if no such constant is
// declared.
static uint32_t GetConstantId(
opt::IRContext* context,
const protobufs::FactConstantUniform& constant_uniform_fact,
uint32_t type_id);
// Checks that the width of a floating-point constant is supported, and that
// the constant is finite.
static bool FloatingPointValueIsSuitable(
const protobufs::FactConstantUniform& fact, uint32_t width);
std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>
facts_and_type_ids_;
};
} // namespace fact_manager
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_

View File

@@ -1,861 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h"
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
namespace fact_manager {
size_t DataSynonymAndIdEquationFacts::OperationHash::operator()(
const Operation& operation) const {
std::u32string hash;
hash.push_back(operation.opcode);
for (auto operand : operation.operands) {
hash.push_back(static_cast<uint32_t>(DataDescriptorHash()(operand)));
}
return std::hash<std::u32string>()(hash);
}
bool DataSynonymAndIdEquationFacts::OperationEquals::operator()(
const Operation& first, const Operation& second) const {
// Equal operations require...
//
// Equal opcodes.
if (first.opcode != second.opcode) {
return false;
}
// Matching operand counts.
if (first.operands.size() != second.operands.size()) {
return false;
}
// Equal operands.
for (uint32_t i = 0; i < first.operands.size(); i++) {
if (!DataDescriptorEquals()(first.operands[i], second.operands[i])) {
return false;
}
}
return true;
}
void DataSynonymAndIdEquationFacts::AddFact(
const protobufs::FactDataSynonym& fact, opt::IRContext* context) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550)
// Assert that ids are not irrelevant.
// Add the fact, including all facts relating sub-components of the data
// descriptors that are involved.
AddDataSynonymFactRecursive(fact.data1(), fact.data2(), context);
}
void DataSynonymAndIdEquationFacts::AddFact(
const protobufs::FactIdEquation& fact, opt::IRContext* context) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550)
// Assert that ids are not irrelevant.
protobufs::DataDescriptor lhs_dd = MakeDataDescriptor(fact.lhs_id(), {});
// Register the LHS in the equivalence relation if needed.
RegisterDataDescriptor(lhs_dd);
// Get equivalence class representatives for all ids used on the RHS of the
// equation.
std::vector<const protobufs::DataDescriptor*> rhs_dds;
for (auto rhs_id : fact.rhs_id()) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550)
// Assert that ids are not irrelevant.
// Register a data descriptor based on this id in the equivalence relation
// if needed, and then record the equivalence class representative.
rhs_dds.push_back(RegisterDataDescriptor(MakeDataDescriptor(rhs_id, {})));
}
// Now add the fact.
AddEquationFactRecursive(lhs_dd, static_cast<SpvOp>(fact.opcode()), rhs_dds,
context);
}
DataSynonymAndIdEquationFacts::OperationSet
DataSynonymAndIdEquationFacts::GetEquations(
const protobufs::DataDescriptor* lhs) const {
auto existing = id_equations_.find(lhs);
if (existing == id_equations_.end()) {
return OperationSet();
}
return existing->second;
}
void DataSynonymAndIdEquationFacts::AddEquationFactRecursive(
const protobufs::DataDescriptor& lhs_dd, SpvOp opcode,
const std::vector<const protobufs::DataDescriptor*>& rhs_dds,
opt::IRContext* context) {
assert(synonymous_.Exists(lhs_dd) &&
"The LHS must be known to the equivalence relation.");
for (auto rhs_dd : rhs_dds) {
// Keep release compilers happy.
(void)(rhs_dd);
assert(synonymous_.Exists(*rhs_dd) &&
"The RHS operands must be known to the equivalence relation.");
}
auto lhs_dd_representative = synonymous_.Find(&lhs_dd);
if (id_equations_.count(lhs_dd_representative) == 0) {
// We have not seen an equation with this LHS before, so associate the LHS
// with an initially empty set.
id_equations_.insert({lhs_dd_representative, OperationSet()});
}
{
auto existing_equations = id_equations_.find(lhs_dd_representative);
assert(existing_equations != id_equations_.end() &&
"A set of operations should be present, even if empty.");
Operation new_operation = {opcode, rhs_dds};
if (existing_equations->second.count(new_operation)) {
// This equation is known, so there is nothing further to be done.
return;
}
// Add the equation to the set of known equations.
existing_equations->second.insert(new_operation);
}
// Now try to work out corollaries implied by the new equation and existing
// facts.
switch (opcode) {
case SpvOpConvertSToF:
case SpvOpConvertUToF:
ComputeConversionDataSynonymFacts(*rhs_dds[0], context);
break;
case SpvOpBitcast: {
assert(DataDescriptorsAreWellFormedAndComparable(context, lhs_dd,
*rhs_dds[0]) &&
"Operands of OpBitcast equation fact must have compatible types");
if (!synonymous_.IsEquivalent(lhs_dd, *rhs_dds[0])) {
AddDataSynonymFactRecursive(lhs_dd, *rhs_dds[0], context);
}
} break;
case SpvOpIAdd: {
// Equation form: "a = b + c"
for (const auto& equation : GetEquations(rhs_dds[0])) {
if (equation.opcode == SpvOpISub) {
// Equation form: "a = (d - e) + c"
if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[1])) {
// Equation form: "a = (d - c) + c"
// We can thus infer "a = d"
AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context);
}
if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) {
// Equation form: "a = (c - e) + c"
// We can thus infer "a = -e"
AddEquationFactRecursive(lhs_dd, SpvOpSNegate,
{equation.operands[1]}, context);
}
}
}
for (const auto& equation : GetEquations(rhs_dds[1])) {
if (equation.opcode == SpvOpISub) {
// Equation form: "a = b + (d - e)"
if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[0])) {
// Equation form: "a = b + (d - b)"
// We can thus infer "a = d"
AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context);
}
}
}
break;
}
case SpvOpISub: {
// Equation form: "a = b - c"
for (const auto& equation : GetEquations(rhs_dds[0])) {
if (equation.opcode == SpvOpIAdd) {
// Equation form: "a = (d + e) - c"
if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) {
// Equation form: "a = (c + e) - c"
// We can thus infer "a = e"
AddDataSynonymFactRecursive(lhs_dd, *equation.operands[1], context);
}
if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[1])) {
// Equation form: "a = (d + c) - c"
// We can thus infer "a = d"
AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context);
}
}
if (equation.opcode == SpvOpISub) {
// Equation form: "a = (d - e) - c"
if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) {
// Equation form: "a = (c - e) - c"
// We can thus infer "a = -e"
AddEquationFactRecursive(lhs_dd, SpvOpSNegate,
{equation.operands[1]}, context);
}
}
}
for (const auto& equation : GetEquations(rhs_dds[1])) {
if (equation.opcode == SpvOpIAdd) {
// Equation form: "a = b - (d + e)"
if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[0])) {
// Equation form: "a = b - (b + e)"
// We can thus infer "a = -e"
AddEquationFactRecursive(lhs_dd, SpvOpSNegate,
{equation.operands[1]}, context);
}
if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[0])) {
// Equation form: "a = b - (d + b)"
// We can thus infer "a = -d"
AddEquationFactRecursive(lhs_dd, SpvOpSNegate,
{equation.operands[0]}, context);
}
}
if (equation.opcode == SpvOpISub) {
// Equation form: "a = b - (d - e)"
if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[0])) {
// Equation form: "a = b - (b - e)"
// We can thus infer "a = e"
AddDataSynonymFactRecursive(lhs_dd, *equation.operands[1], context);
}
}
}
break;
}
case SpvOpLogicalNot:
case SpvOpSNegate: {
// Equation form: "a = !b" or "a = -b"
for (const auto& equation : GetEquations(rhs_dds[0])) {
if (equation.opcode == opcode) {
// Equation form: "a = !!b" or "a = -(-b)"
// We can thus infer "a = b"
AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context);
}
}
break;
}
default:
break;
}
}
void DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive(
const protobufs::DataDescriptor& dd1, const protobufs::DataDescriptor& dd2,
opt::IRContext* context) {
assert(DataDescriptorsAreWellFormedAndComparable(context, dd1, dd2));
// Record that the data descriptors provided in the fact are equivalent.
MakeEquivalent(dd1, dd2);
assert(synonymous_.Find(&dd1) == synonymous_.Find(&dd2) &&
"|dd1| and |dd2| must have a single representative");
// Compute various corollary facts.
// |dd1| and |dd2| belong to the same equivalence class so it doesn't matter
// which one we use here.
ComputeConversionDataSynonymFacts(dd1, context);
ComputeCompositeDataSynonymFacts(dd1, dd2, context);
}
void DataSynonymAndIdEquationFacts::ComputeConversionDataSynonymFacts(
const protobufs::DataDescriptor& dd, opt::IRContext* context) {
assert(synonymous_.Exists(dd) &&
"|dd| should've been registered in the equivalence relation");
const auto* representative = synonymous_.Find(&dd);
assert(representative &&
"Representative can't be null for a registered descriptor");
const auto* type =
context->get_type_mgr()->GetType(fuzzerutil::WalkCompositeTypeIndices(
context, fuzzerutil::GetTypeId(context, representative->object()),
representative->index()));
assert(type && "Data descriptor has invalid type");
if ((type->AsVector() && type->AsVector()->element_type()->AsInteger()) ||
type->AsInteger()) {
// If there exist equation facts of the form |%a = opcode %representative|
// and |%b = opcode %representative| where |opcode| is either OpConvertSToF
// or OpConvertUToF, then |a| and |b| are synonymous.
std::vector<const protobufs::DataDescriptor*> convert_s_to_f_lhs;
std::vector<const protobufs::DataDescriptor*> convert_u_to_f_lhs;
for (const auto& fact : id_equations_) {
for (const auto& equation : fact.second) {
if (synonymous_.IsEquivalent(*equation.operands[0], *representative)) {
if (equation.opcode == SpvOpConvertSToF) {
convert_s_to_f_lhs.push_back(fact.first);
} else if (equation.opcode == SpvOpConvertUToF) {
convert_u_to_f_lhs.push_back(fact.first);
}
}
}
}
for (const auto& synonyms :
{std::move(convert_s_to_f_lhs), std::move(convert_u_to_f_lhs)}) {
for (const auto* synonym_a : synonyms) {
for (const auto* synonym_b : synonyms) {
if (!synonymous_.IsEquivalent(*synonym_a, *synonym_b) &&
DataDescriptorsAreWellFormedAndComparable(context, *synonym_a,
*synonym_b)) {
// |synonym_a| and |synonym_b| have compatible types - they are
// synonymous.
AddDataSynonymFactRecursive(*synonym_a, *synonym_b, context);
}
}
}
}
}
}
void DataSynonymAndIdEquationFacts::ComputeCompositeDataSynonymFacts(
const protobufs::DataDescriptor& dd1, const protobufs::DataDescriptor& dd2,
opt::IRContext* context) {
// Check whether this is a synonym about composite objects. If it is,
// we can recursively add synonym facts about their associated sub-components.
// Get the type of the object referred to by the first data descriptor in the
// synonym fact.
uint32_t type_id = fuzzerutil::WalkCompositeTypeIndices(
context, context->get_def_use_mgr()->GetDef(dd1.object())->type_id(),
dd1.index());
auto type = context->get_type_mgr()->GetType(type_id);
auto type_instruction = context->get_def_use_mgr()->GetDef(type_id);
assert(type != nullptr &&
"Invalid data synonym fact: one side has an unknown type.");
// Check whether the type is composite, recording the number of elements
// associated with the composite if so.
uint32_t num_composite_elements;
if (type->AsArray()) {
num_composite_elements =
fuzzerutil::GetArraySize(*type_instruction, context);
} else if (type->AsMatrix()) {
num_composite_elements = type->AsMatrix()->element_count();
} else if (type->AsStruct()) {
num_composite_elements =
fuzzerutil::GetNumberOfStructMembers(*type_instruction);
} else if (type->AsVector()) {
num_composite_elements = type->AsVector()->element_count();
} else {
// The type is not a composite, so return.
return;
}
// If the fact has the form:
// obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n]
// then for each composite index i, we add a fact of the form:
// obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i]
//
// However, to avoid adding a large number of synonym facts e.g. in the case
// of arrays, we bound the number of composite elements to which this is
// applied. Nevertheless, we always add a synonym fact for the final
// components, as this may be an interesting edge case.
// The bound on the number of indices of the composite pair to note as being
// synonymous.
const uint32_t kCompositeElementBound = 10;
for (uint32_t i = 0; i < num_composite_elements;) {
std::vector<uint32_t> extended_indices1 =
fuzzerutil::RepeatedFieldToVector(dd1.index());
extended_indices1.push_back(i);
std::vector<uint32_t> extended_indices2 =
fuzzerutil::RepeatedFieldToVector(dd2.index());
extended_indices2.push_back(i);
AddDataSynonymFactRecursive(
MakeDataDescriptor(dd1.object(), std::move(extended_indices1)),
MakeDataDescriptor(dd2.object(), std::move(extended_indices2)),
context);
if (i < kCompositeElementBound - 1 || i == num_composite_elements - 1) {
// We have not reached the bound yet, or have already skipped ahead to the
// last element, so increment the loop counter as standard.
i++;
} else {
// We have reached the bound, so skip ahead to the last element.
assert(i == kCompositeElementBound - 1);
i = num_composite_elements - 1;
}
}
}
void DataSynonymAndIdEquationFacts::ComputeClosureOfFacts(
opt::IRContext* context, uint32_t maximum_equivalence_class_size) {
// Suppose that obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n] are distinct
// data descriptors that describe objects of the same composite type, and that
// the composite type is comprised of k components.
//
// For example, if m is a mat4x4 and v a vec4, we might consider:
// m[2]: describes the 2nd column of m, a vec4
// v[]: describes all of v, a vec4
//
// Suppose that we know, for every 0 <= i < k, that the fact:
// obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i]
// holds - i.e. that the children of the two data descriptors are synonymous.
//
// Then we can conclude that:
// obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n]
// holds.
//
// For instance, if we have the facts:
// m[2, 0] == v[0]
// m[2, 1] == v[1]
// m[2, 2] == v[2]
// m[2, 3] == v[3]
// then we can conclude that:
// m[2] == v.
//
// This method repeatedly searches the equivalence relation of data
// descriptors, deducing and adding such facts, until a pass over the
// relation leads to no further facts being deduced.
// The method relies on working with pairs of data descriptors, and in
// particular being able to hash and compare such pairs.
using DataDescriptorPair =
std::pair<protobufs::DataDescriptor, protobufs::DataDescriptor>;
struct DataDescriptorPairHash {
std::size_t operator()(const DataDescriptorPair& pair) const {
return DataDescriptorHash()(&pair.first) ^
DataDescriptorHash()(&pair.second);
}
};
struct DataDescriptorPairEquals {
bool operator()(const DataDescriptorPair& first,
const DataDescriptorPair& second) const {
return (DataDescriptorEquals()(&first.first, &second.first) &&
DataDescriptorEquals()(&first.second, &second.second)) ||
(DataDescriptorEquals()(&first.first, &second.second) &&
DataDescriptorEquals()(&first.second, &second.first));
}
};
// This map records, for a given pair of composite data descriptors of the
// same type, all the indices at which the data descriptors are known to be
// synonymous. A pair is a key to this map only if we have observed that
// the pair are synonymous at *some* index, but not at *all* indices.
// Once we find that a pair of data descriptors are equivalent at all indices
// we record the fact that they are synonymous and remove them from the map.
//
// Using the m and v example from above, initially the pair (m[2], v) would
// not be a key to the map. If we find that m[2, 2] == v[2] holds, we would
// add an entry:
// (m[2], v) -> [false, false, true, false]
// to record that they are synonymous at index 2. If we then find that
// m[2, 0] == v[0] holds, we would update this entry to:
// (m[2], v) -> [true, false, true, false]
// If we then find that m[2, 3] == v[3] holds, we would update this entry to:
// (m[2], v) -> [true, false, true, true]
// Finally, if we then find that m[2, 1] == v[1] holds, which would make the
// boolean vector true at every index, we would add the fact:
// m[2] == v
// to the equivalence relation and remove (m[2], v) from the map.
std::unordered_map<DataDescriptorPair, std::vector<bool>,
DataDescriptorPairHash, DataDescriptorPairEquals>
candidate_composite_synonyms;
// We keep looking for new facts until we perform a complete pass over the
// equivalence relation without finding any new facts.
while (closure_computation_required_) {
// We have not found any new facts yet during this pass; we set this to
// 'true' if we do find a new fact.
closure_computation_required_ = false;
// Consider each class in the equivalence relation.
for (auto representative :
synonymous_.GetEquivalenceClassRepresentatives()) {
auto equivalence_class = synonymous_.GetEquivalenceClass(*representative);
if (equivalence_class.size() > maximum_equivalence_class_size) {
// This equivalence class is larger than the maximum size we are willing
// to consider, so we skip it. This potentially leads to missed fact
// deductions, but avoids excessive runtime for closure computation.
continue;
}
// Consider every data descriptor in the equivalence class.
for (auto dd1_it = equivalence_class.begin();
dd1_it != equivalence_class.end(); ++dd1_it) {
// If this data descriptor has no indices then it does not have the form
// obj_1[a_1, ..., a_m, i], so move on.
auto dd1 = *dd1_it;
if (dd1->index_size() == 0) {
continue;
}
// Consider every other data descriptor later in the equivalence class
// (due to symmetry, there is no need to compare with previous data
// descriptors).
auto dd2_it = dd1_it;
for (++dd2_it; dd2_it != equivalence_class.end(); ++dd2_it) {
auto dd2 = *dd2_it;
// If this data descriptor has no indices then it does not have the
// form obj_2[b_1, ..., b_n, i], so move on.
if (dd2->index_size() == 0) {
continue;
}
// At this point we know that:
// - |dd1| has the form obj_1[a_1, ..., a_m, i]
// - |dd2| has the form obj_2[b_1, ..., b_n, j]
assert(dd1->index_size() > 0 && dd2->index_size() > 0 &&
"Control should not reach here if either data descriptor has "
"no indices.");
// We are only interested if i == j.
if (dd1->index(dd1->index_size() - 1) !=
dd2->index(dd2->index_size() - 1)) {
continue;
}
const uint32_t common_final_index = dd1->index(dd1->index_size() - 1);
// Make data descriptors |dd1_prefix| and |dd2_prefix| for
// obj_1[a_1, ..., a_m]
// and
// obj_2[b_1, ..., b_n]
// These are the two data descriptors we might be getting closer to
// deducing as being synonymous, due to knowing that they are
// synonymous when extended by a particular index.
protobufs::DataDescriptor dd1_prefix;
dd1_prefix.set_object(dd1->object());
for (uint32_t i = 0; i < static_cast<uint32_t>(dd1->index_size() - 1);
i++) {
dd1_prefix.add_index(dd1->index(i));
}
protobufs::DataDescriptor dd2_prefix;
dd2_prefix.set_object(dd2->object());
for (uint32_t i = 0; i < static_cast<uint32_t>(dd2->index_size() - 1);
i++) {
dd2_prefix.add_index(dd2->index(i));
}
assert(!DataDescriptorEquals()(&dd1_prefix, &dd2_prefix) &&
"By construction these prefixes should be different.");
// If we already know that these prefixes are synonymous, move on.
if (synonymous_.Exists(dd1_prefix) &&
synonymous_.Exists(dd2_prefix) &&
synonymous_.IsEquivalent(dd1_prefix, dd2_prefix)) {
continue;
}
// Get the type of obj_1
auto dd1_root_type_id =
context->get_def_use_mgr()->GetDef(dd1->object())->type_id();
// Use this type, together with a_1, ..., a_m, to get the type of
// obj_1[a_1, ..., a_m].
auto dd1_prefix_type = fuzzerutil::WalkCompositeTypeIndices(
context, dd1_root_type_id, dd1_prefix.index());
// Similarly, get the type of obj_2 and use it to get the type of
// obj_2[b_1, ..., b_n].
auto dd2_root_type_id =
context->get_def_use_mgr()->GetDef(dd2->object())->type_id();
auto dd2_prefix_type = fuzzerutil::WalkCompositeTypeIndices(
context, dd2_root_type_id, dd2_prefix.index());
// If the types of dd1_prefix and dd2_prefix are not the same, they
// cannot be synonymous.
if (dd1_prefix_type != dd2_prefix_type) {
continue;
}
// At this point, we know we have synonymous data descriptors of the
// form:
// obj_1[a_1, ..., a_m, i]
// obj_2[b_1, ..., b_n, i]
// with the same last_index i, such that:
// obj_1[a_1, ..., a_m]
// and
// obj_2[b_1, ..., b_n]
// have the same type.
// Work out how many components there are in the (common) commposite
// type associated with obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n].
// This depends on whether the composite type is array, matrix, struct
// or vector.
uint32_t num_components_in_composite;
auto composite_type =
context->get_type_mgr()->GetType(dd1_prefix_type);
auto composite_type_instruction =
context->get_def_use_mgr()->GetDef(dd1_prefix_type);
if (composite_type->AsArray()) {
num_components_in_composite =
fuzzerutil::GetArraySize(*composite_type_instruction, context);
if (num_components_in_composite == 0) {
// This indicates that the array has an unknown size, in which
// case we cannot be sure we have matched all of its elements with
// synonymous elements of another array.
continue;
}
} else if (composite_type->AsMatrix()) {
num_components_in_composite =
composite_type->AsMatrix()->element_count();
} else if (composite_type->AsStruct()) {
num_components_in_composite = fuzzerutil::GetNumberOfStructMembers(
*composite_type_instruction);
} else {
assert(composite_type->AsVector());
num_components_in_composite =
composite_type->AsVector()->element_count();
}
// We are one step closer to being able to say that |dd1_prefix| and
// |dd2_prefix| are synonymous.
DataDescriptorPair candidate_composite_synonym(dd1_prefix,
dd2_prefix);
// We look up what we already know about this pair.
auto existing_entry =
candidate_composite_synonyms.find(candidate_composite_synonym);
if (existing_entry == candidate_composite_synonyms.end()) {
// If this is the first time we have seen the pair, we make a vector
// of size |num_components_in_composite| that is 'true' at the
// common final index associated with |dd1| and |dd2|, and 'false'
// everywhere else, and register this vector as being associated
// with the pair.
std::vector<bool> entry;
for (uint32_t i = 0; i < num_components_in_composite; i++) {
entry.push_back(i == common_final_index);
}
candidate_composite_synonyms[candidate_composite_synonym] = entry;
existing_entry =
candidate_composite_synonyms.find(candidate_composite_synonym);
} else {
// We have seen this pair of data descriptors before, and we now
// know that they are synonymous at one further index, so we
// update the entry to record that.
existing_entry->second[common_final_index] = true;
}
assert(existing_entry != candidate_composite_synonyms.end());
// Check whether |dd1_prefix| and |dd2_prefix| are now known to match
// at every sub-component.
bool all_components_match = true;
for (uint32_t i = 0; i < num_components_in_composite; i++) {
if (!existing_entry->second[i]) {
all_components_match = false;
break;
}
}
if (all_components_match) {
// The two prefixes match on all sub-components, so we know that
// they are synonymous. We add this fact *non-recursively*, as we
// have deduced that |dd1_prefix| and |dd2_prefix| are synonymous
// by observing that all their sub-components are already
// synonymous.
assert(DataDescriptorsAreWellFormedAndComparable(
context, dd1_prefix, dd2_prefix));
MakeEquivalent(dd1_prefix, dd2_prefix);
// Now that we know this pair of data descriptors are synonymous,
// there is no point recording how close they are to being
// synonymous.
candidate_composite_synonyms.erase(candidate_composite_synonym);
}
}
}
}
}
}
void DataSynonymAndIdEquationFacts::MakeEquivalent(
const protobufs::DataDescriptor& dd1,
const protobufs::DataDescriptor& dd2) {
// Register the data descriptors if they are not already known to the
// equivalence relation.
RegisterDataDescriptor(dd1);
RegisterDataDescriptor(dd2);
if (synonymous_.IsEquivalent(dd1, dd2)) {
// The data descriptors are already known to be equivalent, so there is
// nothing to do.
return;
}
// We must make the data descriptors equivalent, and also make sure any
// equation facts known about their representatives are merged.
// Record the original equivalence class representatives of the data
// descriptors.
auto dd1_original_representative = synonymous_.Find(&dd1);
auto dd2_original_representative = synonymous_.Find(&dd2);
// Make the data descriptors equivalent.
synonymous_.MakeEquivalent(dd1, dd2);
// As we have updated the equivalence relation, we might be able to deduce
// more facts by performing a closure computation, so we record that such a
// computation is required.
closure_computation_required_ = true;
// At this point, exactly one of |dd1_original_representative| and
// |dd2_original_representative| will be the representative of the combined
// equivalence class. We work out which one of them is still the class
// representative and which one is no longer the class representative.
auto still_representative = synonymous_.Find(dd1_original_representative) ==
dd1_original_representative
? dd1_original_representative
: dd2_original_representative;
auto no_longer_representative =
still_representative == dd1_original_representative
? dd2_original_representative
: dd1_original_representative;
assert(no_longer_representative != still_representative &&
"The current and former representatives cannot be the same.");
// We now need to add all equations about |no_longer_representative| to the
// set of equations known about |still_representative|.
// Get the equations associated with |no_longer_representative|.
auto no_longer_representative_id_equations =
id_equations_.find(no_longer_representative);
if (no_longer_representative_id_equations != id_equations_.end()) {
// There are some equations to transfer. There might not yet be any
// equations about |still_representative|; create an empty set of equations
// if this is the case.
if (!id_equations_.count(still_representative)) {
id_equations_.insert({still_representative, OperationSet()});
}
auto still_representative_id_equations =
id_equations_.find(still_representative);
assert(still_representative_id_equations != id_equations_.end() &&
"At this point there must be a set of equations.");
// Add all the equations known about |no_longer_representative| to the set
// of equations known about |still_representative|.
still_representative_id_equations->second.insert(
no_longer_representative_id_equations->second.begin(),
no_longer_representative_id_equations->second.end());
}
// Delete the no longer-relevant equations about |no_longer_representative|.
id_equations_.erase(no_longer_representative);
}
const protobufs::DataDescriptor*
DataSynonymAndIdEquationFacts::RegisterDataDescriptor(
const protobufs::DataDescriptor& dd) {
return synonymous_.Exists(dd) ? synonymous_.Find(&dd)
: synonymous_.Register(dd);
}
bool DataSynonymAndIdEquationFacts::DataDescriptorsAreWellFormedAndComparable(
opt::IRContext* context, const protobufs::DataDescriptor& dd1,
const protobufs::DataDescriptor& dd2) {
auto end_type_id_1 = fuzzerutil::WalkCompositeTypeIndices(
context, context->get_def_use_mgr()->GetDef(dd1.object())->type_id(),
dd1.index());
auto end_type_id_2 = fuzzerutil::WalkCompositeTypeIndices(
context, context->get_def_use_mgr()->GetDef(dd2.object())->type_id(),
dd2.index());
// The end types of the data descriptors must exist.
if (end_type_id_1 == 0 || end_type_id_2 == 0) {
return false;
}
// Neither end type is allowed to be void.
if (context->get_def_use_mgr()->GetDef(end_type_id_1)->opcode() ==
SpvOpTypeVoid ||
context->get_def_use_mgr()->GetDef(end_type_id_2)->opcode() ==
SpvOpTypeVoid) {
return false;
}
// If the end types are the same, the data descriptors are comparable.
if (end_type_id_1 == end_type_id_2) {
return true;
}
// Otherwise they are only comparable if they are integer scalars or integer
// vectors that differ only in signedness.
// Get both types.
const auto* type_a = context->get_type_mgr()->GetType(end_type_id_1);
const auto* type_b = context->get_type_mgr()->GetType(end_type_id_2);
assert(type_a && type_b && "Data descriptors have invalid type(s)");
// If both types are numerical or vectors of numerical components, then they
// are compatible if they have the same number of components and the same bit
// count per component.
if (type_a->AsVector() && type_b->AsVector()) {
const auto* vector_a = type_a->AsVector();
const auto* vector_b = type_b->AsVector();
if (vector_a->element_count() != vector_b->element_count() ||
vector_a->element_type()->AsBool() ||
vector_b->element_type()->AsBool()) {
// The case where both vectors have boolean elements and the same number
// of components is handled by the direct equality check earlier.
// You can't have multiple identical boolean vector types.
return false;
}
type_a = vector_a->element_type();
type_b = vector_b->element_type();
}
auto get_bit_count_for_numeric_type =
[](const opt::analysis::Type& type) -> uint32_t {
if (const auto* integer = type.AsInteger()) {
return integer->width();
} else if (const auto* floating = type.AsFloat()) {
return floating->width();
} else {
assert(false && "|type| must be a numerical type");
return 0;
}
};
// Checks that both |type_a| and |type_b| are either numerical or vectors of
// numerical components and have the same number of bits.
return (type_a->AsInteger() || type_a->AsFloat()) &&
(type_b->AsInteger() || type_b->AsFloat()) &&
(get_bit_count_for_numeric_type(*type_a) ==
get_bit_count_for_numeric_type(*type_b));
}
std::vector<const protobufs::DataDescriptor*>
DataSynonymAndIdEquationFacts::GetSynonymsForDataDescriptor(
const protobufs::DataDescriptor& data_descriptor) const {
if (synonymous_.Exists(data_descriptor)) {
return synonymous_.GetEquivalenceClass(data_descriptor);
}
return {};
}
std::vector<uint32_t>
DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown() const {
std::vector<uint32_t> result;
for (auto& data_descriptor : synonymous_.GetAllKnownValues()) {
if (data_descriptor->index().empty()) {
result.push_back(data_descriptor->object());
}
}
return result;
}
bool DataSynonymAndIdEquationFacts::IsSynonymous(
const protobufs::DataDescriptor& data_descriptor1,
const protobufs::DataDescriptor& data_descriptor2) const {
return synonymous_.Exists(data_descriptor1) &&
synonymous_.Exists(data_descriptor2) &&
synonymous_.IsEquivalent(data_descriptor1, data_descriptor2);
}
} // namespace fact_manager
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,156 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_
#define SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_
#include <unordered_set>
#include <vector>
#include "source/fuzz/data_descriptor.h"
#include "source/fuzz/equivalence_relation.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
namespace fact_manager {
// The purpose of this class is to group the fields and data used to represent
// facts about data synonyms and id equations.
class DataSynonymAndIdEquationFacts {
public:
// See method in FactManager which delegates to this method.
void AddFact(const protobufs::FactDataSynonym& fact, opt::IRContext* context);
// See method in FactManager which delegates to this method.
void AddFact(const protobufs::FactIdEquation& fact, opt::IRContext* context);
// See method in FactManager which delegates to this method.
std::vector<const protobufs::DataDescriptor*> GetSynonymsForDataDescriptor(
const protobufs::DataDescriptor& data_descriptor) const;
// See method in FactManager which delegates to this method.
std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown() const;
// See method in FactManager which delegates to this method.
bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1,
const protobufs::DataDescriptor& data_descriptor2) const;
// See method in FactManager which delegates to this method.
void ComputeClosureOfFacts(opt::IRContext* context,
uint32_t maximum_equivalence_class_size);
private:
// This helper struct represents the right hand side of an equation as an
// operator applied to a number of data descriptor operands.
struct Operation {
SpvOp opcode;
std::vector<const protobufs::DataDescriptor*> operands;
};
// Hashing for operations, to allow deterministic unordered sets.
struct OperationHash {
size_t operator()(const Operation& operation) const;
};
// Equality for operations, to allow deterministic unordered sets.
struct OperationEquals {
bool operator()(const Operation& first, const Operation& second) const;
};
using OperationSet =
std::unordered_set<Operation, OperationHash, OperationEquals>;
// Adds the synonym |dd1| = |dd2| to the set of managed facts, and recurses
// into sub-components of the data descriptors, if they are composites, to
// record that their components are pairwise-synonymous.
void AddDataSynonymFactRecursive(const protobufs::DataDescriptor& dd1,
const protobufs::DataDescriptor& dd2,
opt::IRContext* context);
// Computes various corollary facts from the data descriptor |dd| if members
// of its equivalence class participate in equation facts with OpConvert*
// opcodes. The descriptor should be registered in the equivalence relation.
void ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd,
opt::IRContext* context);
// Recurses into sub-components of the data descriptors, if they are
// composites, to record that their components are pairwise-synonymous.
void ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1,
const protobufs::DataDescriptor& dd2,
opt::IRContext* context);
// Records the fact that |dd1| and |dd2| are equivalent, and merges the sets
// of equations that are known about them.
void MakeEquivalent(const protobufs::DataDescriptor& dd1,
const protobufs::DataDescriptor& dd2);
// Registers a data descriptor in the equivalence relation if it hasn't been
// registered yet, and returns its representative.
const protobufs::DataDescriptor* RegisterDataDescriptor(
const protobufs::DataDescriptor& dd);
// Returns true if and only if |dd1| and |dd2| are valid data descriptors
// whose associated data have compatible types. Two types are compatible if:
// - they are the same
// - they both are numerical or vectors of numerical components with the same
// number of components and the same bit count per component
static bool DataDescriptorsAreWellFormedAndComparable(
opt::IRContext* context, const protobufs::DataDescriptor& dd1,
const protobufs::DataDescriptor& dd2);
OperationSet GetEquations(const protobufs::DataDescriptor* lhs) const;
// Requires that |lhs_dd| and every element of |rhs_dds| is present in the
// |synonymous_| equivalence relation, but is not necessarily its own
// representative. Records the fact that the equation
// "|lhs_dd| |opcode| |rhs_dds_non_canonical|" holds, and adds any
// corollaries, in the form of data synonym or equation facts, that follow
// from this and other known facts.
void AddEquationFactRecursive(
const protobufs::DataDescriptor& lhs_dd, SpvOp opcode,
const std::vector<const protobufs::DataDescriptor*>& rhs_dds,
opt::IRContext* context);
// The data descriptors that are known to be synonymous with one another are
// captured by this equivalence relation.
EquivalenceRelation<protobufs::DataDescriptor, DataDescriptorHash,
DataDescriptorEquals>
synonymous_;
// When a new synonym fact is added, it may be possible to deduce further
// synonym facts by computing a closure of all known facts. However, this is
// an expensive operation, so it should be performed sparingly and only there
// is some chance of new facts being deduced. This boolean tracks whether a
// closure computation is required - i.e., whether a new fact has been added
// since the last time such a computation was performed.
bool closure_computation_required_ = false;
// Represents a set of equations on data descriptors as a map indexed by
// left-hand-side, mapping a left-hand-side to a set of operations, each of
// which (together with the left-hand-side) defines an equation.
//
// All data descriptors occurring in equations are required to be present in
// the |synonymous_| equivalence relation, and to be their own representatives
// in that relation.
std::unordered_map<const protobufs::DataDescriptor*, OperationSet>
id_equations_;
};
} // namespace fact_manager
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_

View File

@@ -1,31 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fact_manager/dead_block_facts.h"
namespace spvtools {
namespace fuzz {
namespace fact_manager {
void DeadBlockFacts::AddFact(const protobufs::FactBlockIsDead& fact) {
dead_block_ids_.insert(fact.block_id());
}
bool DeadBlockFacts::BlockIsDead(uint32_t block_id) const {
return dead_block_ids_.count(block_id) != 0;
}
} // namespace fact_manager
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,44 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_
#define SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_
#include <unordered_set>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
namespace spvtools {
namespace fuzz {
namespace fact_manager {
// The purpose of this class is to group the fields and data used to represent
// facts about data blocks.
class DeadBlockFacts {
public:
// See method in FactManager which delegates to this method.
void AddFact(const protobufs::FactBlockIsDead& fact);
// See method in FactManager which delegates to this method.
bool BlockIsDead(uint32_t block_id) const;
private:
std::unordered_set<uint32_t> dead_block_ids_;
};
} // namespace fact_manager
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_

View File

@@ -1,244 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "fact_manager.h"
#include <sstream>
#include <unordered_map>
#include "source/fuzz/uniform_buffer_element_descriptor.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
namespace {
std::string ToString(const protobufs::FactConstantUniform& fact) {
std::stringstream stream;
stream << "(" << fact.uniform_buffer_element_descriptor().descriptor_set()
<< ", " << fact.uniform_buffer_element_descriptor().binding() << ")[";
bool first = true;
for (auto index : fact.uniform_buffer_element_descriptor().index()) {
if (first) {
first = false;
} else {
stream << ", ";
}
stream << index;
}
stream << "] == [";
first = true;
for (auto constant_word : fact.constant_word()) {
if (first) {
first = false;
} else {
stream << ", ";
}
stream << constant_word;
}
stream << "]";
return stream.str();
}
std::string ToString(const protobufs::FactDataSynonym& fact) {
std::stringstream stream;
stream << fact.data1() << " = " << fact.data2();
return stream.str();
}
std::string ToString(const protobufs::FactIdEquation& fact) {
std::stringstream stream;
stream << fact.lhs_id();
stream << " " << static_cast<SpvOp>(fact.opcode());
for (auto rhs_id : fact.rhs_id()) {
stream << " " << rhs_id;
}
return stream.str();
}
std::string ToString(const protobufs::Fact& fact) {
switch (fact.fact_case()) {
case protobufs::Fact::kConstantUniformFact:
return ToString(fact.constant_uniform_fact());
case protobufs::Fact::kDataSynonymFact:
return ToString(fact.data_synonym_fact());
case protobufs::Fact::kIdEquationFact:
return ToString(fact.id_equation_fact());
default:
assert(false && "Stringification not supported for this fact.");
return "";
}
}
} // namespace
void FactManager::AddFacts(const MessageConsumer& message_consumer,
const protobufs::FactSequence& initial_facts,
opt::IRContext* context) {
for (auto& fact : initial_facts.fact()) {
if (!AddFact(fact, context)) {
auto message = "Invalid fact " + ToString(fact) + " ignored.";
message_consumer(SPV_MSG_WARNING, nullptr, {}, message.c_str());
}
}
}
bool FactManager::AddFact(const fuzz::protobufs::Fact& fact,
opt::IRContext* context) {
switch (fact.fact_case()) {
case protobufs::Fact::kConstantUniformFact:
return constant_uniform_facts_.AddFact(fact.constant_uniform_fact(),
context);
case protobufs::Fact::kDataSynonymFact:
data_synonym_and_id_equation_facts_.AddFact(fact.data_synonym_fact(),
context);
return true;
case protobufs::Fact::kBlockIsDeadFact:
dead_block_facts_.AddFact(fact.block_is_dead_fact());
return true;
case protobufs::Fact::kFunctionIsLivesafeFact:
livesafe_function_facts_.AddFact(fact.function_is_livesafe_fact());
return true;
default:
assert(false && "Unknown fact type.");
return false;
}
}
void FactManager::AddFactDataSynonym(const protobufs::DataDescriptor& data1,
const protobufs::DataDescriptor& data2,
opt::IRContext* context) {
protobufs::FactDataSynonym fact;
*fact.mutable_data1() = data1;
*fact.mutable_data2() = data2;
data_synonym_and_id_equation_facts_.AddFact(fact, context);
}
std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType(
opt::IRContext* ir_context, uint32_t type_id) const {
return constant_uniform_facts_.GetConstantsAvailableFromUniformsForType(
ir_context, type_id);
}
std::vector<protobufs::UniformBufferElementDescriptor>
FactManager::GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
uint32_t constant_id) const {
return constant_uniform_facts_.GetUniformDescriptorsForConstant(ir_context,
constant_id);
}
uint32_t FactManager::GetConstantFromUniformDescriptor(
opt::IRContext* context,
const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
return constant_uniform_facts_.GetConstantFromUniformDescriptor(
context, uniform_descriptor);
}
std::vector<uint32_t> FactManager::GetTypesForWhichUniformValuesAreKnown()
const {
return constant_uniform_facts_.GetTypesForWhichUniformValuesAreKnown();
}
const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
FactManager::GetConstantUniformFactsAndTypes() const {
return constant_uniform_facts_.GetConstantUniformFactsAndTypes();
}
std::vector<uint32_t> FactManager::GetIdsForWhichSynonymsAreKnown() const {
return data_synonym_and_id_equation_facts_.GetIdsForWhichSynonymsAreKnown();
}
std::vector<const protobufs::DataDescriptor*>
FactManager::GetSynonymsForDataDescriptor(
const protobufs::DataDescriptor& data_descriptor) const {
return data_synonym_and_id_equation_facts_.GetSynonymsForDataDescriptor(
data_descriptor);
}
std::vector<const protobufs::DataDescriptor*> FactManager::GetSynonymsForId(
uint32_t id) const {
return GetSynonymsForDataDescriptor(MakeDataDescriptor(id, {}));
}
bool FactManager::IsSynonymous(
const protobufs::DataDescriptor& data_descriptor1,
const protobufs::DataDescriptor& data_descriptor2) const {
return data_synonym_and_id_equation_facts_.IsSynonymous(data_descriptor1,
data_descriptor2);
}
bool FactManager::BlockIsDead(uint32_t block_id) const {
return dead_block_facts_.BlockIsDead(block_id);
}
void FactManager::AddFactBlockIsDead(uint32_t block_id) {
protobufs::FactBlockIsDead fact;
fact.set_block_id(block_id);
dead_block_facts_.AddFact(fact);
}
bool FactManager::FunctionIsLivesafe(uint32_t function_id) const {
return livesafe_function_facts_.FunctionIsLivesafe(function_id);
}
void FactManager::AddFactFunctionIsLivesafe(uint32_t function_id) {
protobufs::FactFunctionIsLivesafe fact;
fact.set_function_id(function_id);
livesafe_function_facts_.AddFact(fact);
}
bool FactManager::PointeeValueIsIrrelevant(uint32_t pointer_id) const {
return irrelevant_value_facts_.PointeeValueIsIrrelevant(pointer_id);
}
bool FactManager::IdIsIrrelevant(uint32_t result_id) const {
return irrelevant_value_facts_.IdIsIrrelevant(result_id);
}
void FactManager::AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id) {
protobufs::FactPointeeValueIsIrrelevant fact;
fact.set_pointer_id(pointer_id);
irrelevant_value_facts_.AddFact(fact);
}
void FactManager::AddFactIdIsIrrelevant(uint32_t result_id) {
protobufs::FactIdIsIrrelevant fact;
fact.set_result_id(result_id);
irrelevant_value_facts_.AddFact(fact);
}
void FactManager::AddFactIdEquation(uint32_t lhs_id, SpvOp opcode,
const std::vector<uint32_t>& rhs_id,
opt::IRContext* context) {
protobufs::FactIdEquation fact;
fact.set_lhs_id(lhs_id);
fact.set_opcode(opcode);
for (auto an_rhs_id : rhs_id) {
fact.add_rhs_id(an_rhs_id);
}
data_synonym_and_id_equation_facts_.AddFact(fact, context);
}
void FactManager::ComputeClosureOfFacts(
opt::IRContext* ir_context, uint32_t maximum_equivalence_class_size) {
data_synonym_and_id_equation_facts_.ComputeClosureOfFacts(
ir_context, maximum_equivalence_class_size);
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,213 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_
#define SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_
#include <set>
#include <utility>
#include <vector>
#include "source/fuzz/data_descriptor.h"
#include "source/fuzz/fact_manager/constant_uniform_facts.h"
#include "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h"
#include "source/fuzz/fact_manager/dead_block_facts.h"
#include "source/fuzz/fact_manager/irrelevant_value_facts.h"
#include "source/fuzz/fact_manager/livesafe_function_facts.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/opt/constants.h"
namespace spvtools {
namespace fuzz {
// Keeps track of facts about the module being transformed on which the fuzzing
// process can depend. Some initial facts can be provided, for example about
// guarantees on the values of inputs to SPIR-V entry points. Transformations
// may then rely on these facts, can add further facts that they establish.
// Facts are intended to be simple properties that either cannot be deduced from
// the module (such as properties that are guaranteed to hold for entry point
// inputs), or that are established by transformations, likely to be useful for
// future transformations, and not completely trivial to deduce straight from
// the module.
class FactManager {
public:
// Adds all the facts from |facts|, checking them for validity with respect to
// |context|. Warnings about invalid facts are communicated via
// |message_consumer|; such facts are otherwise ignored.
void AddFacts(const MessageConsumer& message_consumer,
const protobufs::FactSequence& facts, opt::IRContext* context);
// Checks the fact for validity with respect to |context|. Returns false,
// with no side effects, if the fact is invalid. Otherwise adds |fact| to the
// fact manager.
bool AddFact(const protobufs::Fact& fact, opt::IRContext* context);
// Record the fact that |data1| and |data2| are synonymous.
void AddFactDataSynonym(const protobufs::DataDescriptor& data1,
const protobufs::DataDescriptor& data2,
opt::IRContext* context);
// Records the fact that |block_id| is dead.
void AddFactBlockIsDead(uint32_t block_id);
// Records the fact that |function_id| is livesafe.
void AddFactFunctionIsLivesafe(uint32_t function_id);
// Records the fact that the value of the pointee associated with |pointer_id|
// is irrelevant: it does not affect the observable behaviour of the module.
void AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id);
// Records a fact that the |result_id| is irrelevant (i.e. it doesn't affect
// the semantics of the module)
void AddFactIdIsIrrelevant(uint32_t result_id);
// Records the fact that |lhs_id| is defined by the equation:
//
// |lhs_id| = |opcode| |rhs_id[0]| ... |rhs_id[N-1]|
//
void AddFactIdEquation(uint32_t lhs_id, SpvOp opcode,
const std::vector<uint32_t>& rhs_id,
opt::IRContext* context);
// Inspects all known facts and adds corollary facts; e.g. if we know that
// a.x == b.x and a.y == b.y, where a and b have vec2 type, we can record
// that a == b holds.
//
// This method is expensive, and should only be called (by applying a
// transformation) at the start of a fuzzer pass that depends on data
// synonym facts, rather than calling it every time a new data synonym fact
// is added.
//
// The parameter |maximum_equivalence_class_size| specifies the size beyond
// which equivalence classes should not be mined for new facts, to avoid
// excessively-long closure computations.
void ComputeClosureOfFacts(opt::IRContext* ir_context,
uint32_t maximum_equivalence_class_size);
// The fact manager is responsible for managing a few distinct categories of
// facts. In principle there could be different fact managers for each kind
// of fact, but in practice providing one 'go to' place for facts is
// convenient. To keep some separation, the public methods of the fact
// manager should be grouped according to the kind of fact to which they
// relate.
//==============================
// Querying facts about uniform constants
// Provides the distinct type ids for which at least one "constant ==
// uniform element" fact is known.
std::vector<uint32_t> GetTypesForWhichUniformValuesAreKnown() const;
// Provides distinct constant ids with type |type_id| for which at least one
// "constant == uniform element" fact is known. If multiple identically-
// valued constants are relevant, only one will appear in the sequence.
std::vector<uint32_t> GetConstantsAvailableFromUniformsForType(
opt::IRContext* ir_context, uint32_t type_id) const;
// Provides details of all uniform elements that are known to be equal to the
// constant associated with |constant_id| in |ir_context|.
std::vector<protobufs::UniformBufferElementDescriptor>
GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
uint32_t constant_id) const;
// Returns the id of a constant whose value is known to match that of
// |uniform_descriptor|, and whose type matches the type of the uniform
// element. If multiple such constant is exist, the one that is returned
// is arbitrary. Returns 0 if no such constant id exists.
uint32_t GetConstantFromUniformDescriptor(
opt::IRContext* context,
const protobufs::UniformBufferElementDescriptor& uniform_descriptor)
const;
// Returns all "constant == uniform element" facts known to the fact
// manager, pairing each fact with id of the type that is associated with
// both the constant and the uniform element.
const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
GetConstantUniformFactsAndTypes() const;
// End of uniform constant facts
//==============================
//==============================
// Querying facts about id synonyms
// Returns every id for which a fact of the form "this id is synonymous with
// this piece of data" is known.
std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown() const;
// Returns the equivalence class of all known synonyms of |id|, or an empty
// set if no synonyms are known.
std::vector<const protobufs::DataDescriptor*> GetSynonymsForId(
uint32_t id) const;
// Returns the equivalence class of all known synonyms of |data_descriptor|,
// or empty if no synonyms are known.
std::vector<const protobufs::DataDescriptor*> GetSynonymsForDataDescriptor(
const protobufs::DataDescriptor& data_descriptor) const;
// Returns true if and ony if |data_descriptor1| and |data_descriptor2| are
// known to be synonymous.
bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1,
const protobufs::DataDescriptor& data_descriptor2) const;
// End of id synonym facts
//==============================
//==============================
// Querying facts about dead blocks
// Returns true if and ony if |block_id| is the id of a block known to be
// dynamically unreachable.
bool BlockIsDead(uint32_t block_id) const;
// End of dead block facts
//==============================
//==============================
// Querying facts about livesafe function
// Returns true if and ony if |function_id| is the id of a function known
// to be livesafe.
bool FunctionIsLivesafe(uint32_t function_id) const;
// End of dead livesafe function facts
//==============================
//==============================
// Querying facts about irrelevant values
// Returns true if and ony if the value of the pointee associated with
// |pointer_id| is irrelevant.
bool PointeeValueIsIrrelevant(uint32_t pointer_id) const;
// Returns true iff there exists a fact that the |result_id| is irrelevant.
bool IdIsIrrelevant(uint32_t result_id) const;
// End of irrelevant value facts
//==============================
private:
// Keep these in alphabetical order.
fact_manager::ConstantUniformFacts constant_uniform_facts_;
fact_manager::DataSynonymAndIdEquationFacts
data_synonym_and_id_equation_facts_;
fact_manager::DeadBlockFacts dead_block_facts_;
fact_manager::LivesafeFunctionFacts livesafe_function_facts_;
fact_manager::IrrelevantValueFacts irrelevant_value_facts_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_

View File

@@ -1,51 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fact_manager/irrelevant_value_facts.h"
#include "source/fuzz/data_descriptor.h"
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
namespace fact_manager {
void IrrelevantValueFacts::AddFact(
const protobufs::FactPointeeValueIsIrrelevant& fact) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550)
// Assert that the id does not participate in DataSynonym facts and is a
// pointer.
pointers_to_irrelevant_pointees_ids_.insert(fact.pointer_id());
}
void IrrelevantValueFacts::AddFact(const protobufs::FactIdIsIrrelevant& fact) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550)
// Assert that the id does not participate in DataSynonym facts and is not a
// pointer.
irrelevant_ids_.insert(fact.result_id());
}
bool IrrelevantValueFacts::PointeeValueIsIrrelevant(uint32_t pointer_id) const {
return pointers_to_irrelevant_pointees_ids_.count(pointer_id) != 0;
}
bool IrrelevantValueFacts::IdIsIrrelevant(uint32_t pointer_id) const {
return irrelevant_ids_.count(pointer_id) != 0;
}
} // namespace fact_manager
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,52 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_
#define SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_
#include <unordered_set>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
namespace fact_manager {
// The purpose of this class is to group the fields and data used to represent
// facts about various irrelevant values in the module.
class IrrelevantValueFacts {
public:
// See method in FactManager which delegates to this method.
void AddFact(const protobufs::FactPointeeValueIsIrrelevant& fact);
// See method in FactManager which delegates to this method.
void AddFact(const protobufs::FactIdIsIrrelevant& fact);
// See method in FactManager which delegates to this method.
bool PointeeValueIsIrrelevant(uint32_t pointer_id) const;
// See method in FactManager which delegates to this method.
bool IdIsIrrelevant(uint32_t pointer_id) const;
private:
std::unordered_set<uint32_t> pointers_to_irrelevant_pointees_ids_;
std::unordered_set<uint32_t> irrelevant_ids_;
};
} // namespace fact_manager
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_

View File

@@ -1,32 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fact_manager/livesafe_function_facts.h"
namespace spvtools {
namespace fuzz {
namespace fact_manager {
void LivesafeFunctionFacts::AddFact(
const protobufs::FactFunctionIsLivesafe& fact) {
livesafe_function_ids_.insert(fact.function_id());
}
bool LivesafeFunctionFacts::FunctionIsLivesafe(uint32_t function_id) const {
return livesafe_function_ids_.count(function_id) != 0;
}
} // namespace fact_manager
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,44 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_
#define SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_
#include <unordered_set>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
namespace spvtools {
namespace fuzz {
namespace fact_manager {
// The purpose of this class is to group the fields and data used to represent
// facts about livesafe functions.
class LivesafeFunctionFacts {
public:
// See method in FactManager which delegates to this method.
void AddFact(const protobufs::FactFunctionIsLivesafe& fact);
// See method in FactManager which delegates to this method.
bool FunctionIsLivesafe(uint32_t function_id) const;
private:
std::unordered_set<uint32_t> livesafe_function_ids_;
};
} // namespace fact_manager
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_

View File

@@ -1,372 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/force_render_red.h"
#include "source/fuzz/fact_manager/fact_manager.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation_context.h"
#include "source/fuzz/transformation_replace_constant_with_uniform.h"
#include "source/fuzz/uniform_buffer_element_descriptor.h"
#include "source/opt/build_module.h"
#include "source/opt/ir_context.h"
#include "source/opt/types.h"
#include "source/util/make_unique.h"
#include "tools/util/cli_consumer.h"
namespace spvtools {
namespace fuzz {
namespace {
// Helper method to find the fragment shader entry point, complaining if there
// is no shader or if there is no fragment entry point.
opt::Function* FindFragmentShaderEntryPoint(opt::IRContext* ir_context,
MessageConsumer message_consumer) {
// Check that this is a fragment shader
bool found_capability_shader = false;
for (auto& capability : ir_context->capabilities()) {
assert(capability.opcode() == SpvOpCapability);
if (capability.GetSingleWordInOperand(0) == SpvCapabilityShader) {
found_capability_shader = true;
break;
}
}
if (!found_capability_shader) {
message_consumer(
SPV_MSG_ERROR, nullptr, {},
"Forcing of red rendering requires the Shader capability.");
return nullptr;
}
opt::Instruction* fragment_entry_point = nullptr;
for (auto& entry_point : ir_context->module()->entry_points()) {
if (entry_point.GetSingleWordInOperand(0) == SpvExecutionModelFragment) {
fragment_entry_point = &entry_point;
break;
}
}
if (fragment_entry_point == nullptr) {
message_consumer(SPV_MSG_ERROR, nullptr, {},
"Forcing of red rendering requires an entry point with "
"the Fragment execution model.");
return nullptr;
}
for (auto& function : *ir_context->module()) {
if (function.result_id() ==
fragment_entry_point->GetSingleWordInOperand(1)) {
return &function;
}
}
assert(
false &&
"A valid module must have a function associate with each entry point.");
return nullptr;
}
// Helper method to check that there is a single vec4 output variable and get a
// pointer to it.
opt::Instruction* FindVec4OutputVariable(opt::IRContext* ir_context,
MessageConsumer message_consumer) {
opt::Instruction* output_variable = nullptr;
for (auto& inst : ir_context->types_values()) {
if (inst.opcode() == SpvOpVariable &&
inst.GetSingleWordInOperand(0) == SpvStorageClassOutput) {
if (output_variable != nullptr) {
message_consumer(SPV_MSG_ERROR, nullptr, {},
"Only one output variable can be handled at present; "
"found multiple.");
return nullptr;
}
output_variable = &inst;
// Do not break, as we want to check for multiple output variables.
}
}
if (output_variable == nullptr) {
message_consumer(SPV_MSG_ERROR, nullptr, {},
"No output variable to which to write red was found.");
return nullptr;
}
auto output_variable_base_type = ir_context->get_type_mgr()
->GetType(output_variable->type_id())
->AsPointer()
->pointee_type()
->AsVector();
if (!output_variable_base_type ||
output_variable_base_type->element_count() != 4 ||
!output_variable_base_type->element_type()->AsFloat()) {
message_consumer(SPV_MSG_ERROR, nullptr, {},
"The output variable must have type vec4.");
return nullptr;
}
return output_variable;
}
// Helper to get the ids of float constants 0.0 and 1.0, creating them if
// necessary.
std::pair<uint32_t, uint32_t> FindOrCreateFloatZeroAndOne(
opt::IRContext* ir_context, opt::analysis::Float* float_type) {
float one = 1.0;
uint32_t one_as_uint;
memcpy(&one_as_uint, &one, sizeof(float));
std::vector<uint32_t> zero_bytes = {0};
std::vector<uint32_t> one_bytes = {one_as_uint};
auto constant_zero = ir_context->get_constant_mgr()->RegisterConstant(
MakeUnique<opt::analysis::FloatConstant>(float_type, zero_bytes));
auto constant_one = ir_context->get_constant_mgr()->RegisterConstant(
MakeUnique<opt::analysis::FloatConstant>(float_type, one_bytes));
auto constant_zero_id = ir_context->get_constant_mgr()
->GetDefiningInstruction(constant_zero)
->result_id();
auto constant_one_id = ir_context->get_constant_mgr()
->GetDefiningInstruction(constant_one)
->result_id();
return std::pair<uint32_t, uint32_t>(constant_zero_id, constant_one_id);
}
std::unique_ptr<TransformationReplaceConstantWithUniform>
MakeConstantUniformReplacement(opt::IRContext* ir_context,
const FactManager& fact_manager,
uint32_t constant_id,
uint32_t greater_than_instruction,
uint32_t in_operand_index) {
return MakeUnique<TransformationReplaceConstantWithUniform>(
MakeIdUseDescriptor(constant_id,
MakeInstructionDescriptor(greater_than_instruction,
SpvOpFOrdGreaterThan, 0),
in_operand_index),
fact_manager.GetUniformDescriptorsForConstant(ir_context, constant_id)[0],
ir_context->TakeNextId(), ir_context->TakeNextId());
}
} // namespace
bool ForceRenderRed(
const spv_target_env& target_env, spv_validator_options validator_options,
const std::vector<uint32_t>& binary_in,
const spvtools::fuzz::protobufs::FactSequence& initial_facts,
std::vector<uint32_t>* binary_out) {
auto message_consumer = spvtools::utils::CLIMessageConsumer;
spvtools::SpirvTools tools(target_env);
if (!tools.IsValid()) {
message_consumer(SPV_MSG_ERROR, nullptr, {},
"Failed to create SPIRV-Tools interface; stopping.");
return false;
}
// Initial binary should be valid.
if (!tools.Validate(&binary_in[0], binary_in.size(), validator_options)) {
message_consumer(SPV_MSG_ERROR, nullptr, {},
"Initial binary is invalid; stopping.");
return false;
}
// Build the module from the input binary.
std::unique_ptr<opt::IRContext> ir_context = BuildModule(
target_env, message_consumer, binary_in.data(), binary_in.size());
assert(ir_context);
// Set up a fact manager with any given initial facts.
FactManager fact_manager;
for (auto& fact : initial_facts.fact()) {
fact_manager.AddFact(fact, ir_context.get());
}
TransformationContext transformation_context(&fact_manager,
validator_options);
auto entry_point_function =
FindFragmentShaderEntryPoint(ir_context.get(), message_consumer);
auto output_variable =
FindVec4OutputVariable(ir_context.get(), message_consumer);
if (entry_point_function == nullptr || output_variable == nullptr) {
return false;
}
opt::analysis::Float temp_float_type(32);
opt::analysis::Float* float_type = ir_context->get_type_mgr()
->GetRegisteredType(&temp_float_type)
->AsFloat();
std::pair<uint32_t, uint32_t> zero_one_float_ids =
FindOrCreateFloatZeroAndOne(ir_context.get(), float_type);
// Make the new exit block
auto new_exit_block_id = ir_context->TakeNextId();
{
auto label = MakeUnique<opt::Instruction>(ir_context.get(), SpvOpLabel, 0,
new_exit_block_id,
opt::Instruction::OperandList());
auto new_exit_block = MakeUnique<opt::BasicBlock>(std::move(label));
new_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
ir_context.get(), SpvOpReturn, 0, 0, opt::Instruction::OperandList()));
entry_point_function->AddBasicBlock(std::move(new_exit_block));
}
// Make the new entry block
{
auto label = MakeUnique<opt::Instruction>(ir_context.get(), SpvOpLabel, 0,
ir_context->TakeNextId(),
opt::Instruction::OperandList());
auto new_entry_block = MakeUnique<opt::BasicBlock>(std::move(label));
// Make an instruction to construct vec4(1.0, 0.0, 0.0, 1.0), representing
// the colour red.
opt::Operand zero_float = {SPV_OPERAND_TYPE_ID, {zero_one_float_ids.first}};
opt::Operand one_float = {SPV_OPERAND_TYPE_ID, {zero_one_float_ids.second}};
opt::Instruction::OperandList op_composite_construct_operands = {
one_float, zero_float, zero_float, one_float};
auto temp_vec4 = opt::analysis::Vector(float_type, 4);
auto vec4_id = ir_context->get_type_mgr()->GetId(&temp_vec4);
auto red = MakeUnique<opt::Instruction>(
ir_context.get(), SpvOpCompositeConstruct, vec4_id,
ir_context->TakeNextId(), op_composite_construct_operands);
auto red_id = red->result_id();
new_entry_block->AddInstruction(std::move(red));
// Make an instruction to store red into the output color.
opt::Operand variable_to_store_into = {SPV_OPERAND_TYPE_ID,
{output_variable->result_id()}};
opt::Operand value_to_be_stored = {SPV_OPERAND_TYPE_ID, {red_id}};
opt::Instruction::OperandList op_store_operands = {variable_to_store_into,
value_to_be_stored};
new_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
ir_context.get(), SpvOpStore, 0, 0, op_store_operands));
// We are going to attempt to construct 'false' as an expression of the form
// 'literal1 > literal2'. If we succeed, we will later replace each literal
// with a uniform of the same value - we can only do that replacement once
// we have added the entry block to the module.
std::unique_ptr<TransformationReplaceConstantWithUniform>
first_greater_then_operand_replacement = nullptr;
std::unique_ptr<TransformationReplaceConstantWithUniform>
second_greater_then_operand_replacement = nullptr;
uint32_t id_guaranteed_to_be_false = 0;
opt::analysis::Bool temp_bool_type;
opt::analysis::Bool* registered_bool_type =
ir_context->get_type_mgr()
->GetRegisteredType(&temp_bool_type)
->AsBool();
auto float_type_id = ir_context->get_type_mgr()->GetId(float_type);
auto types_for_which_uniforms_are_known =
fact_manager.GetTypesForWhichUniformValuesAreKnown();
// Check whether we have any float uniforms.
if (std::find(types_for_which_uniforms_are_known.begin(),
types_for_which_uniforms_are_known.end(),
float_type_id) != types_for_which_uniforms_are_known.end()) {
// We have at least one float uniform; let's see whether we have at least
// two.
auto available_constants =
fact_manager.GetConstantsAvailableFromUniformsForType(
ir_context.get(), float_type_id);
if (available_constants.size() > 1) {
// Grab the float constants associated with the first two known float
// uniforms.
auto first_constant =
ir_context->get_constant_mgr()
->GetConstantFromInst(ir_context->get_def_use_mgr()->GetDef(
available_constants[0]))
->AsFloatConstant();
auto second_constant =
ir_context->get_constant_mgr()
->GetConstantFromInst(ir_context->get_def_use_mgr()->GetDef(
available_constants[1]))
->AsFloatConstant();
// Now work out which of the two constants is larger than the other.
uint32_t larger_constant_index = 0;
uint32_t smaller_constant_index = 0;
if (first_constant->GetFloat() > second_constant->GetFloat()) {
larger_constant_index = 0;
smaller_constant_index = 1;
} else if (first_constant->GetFloat() < second_constant->GetFloat()) {
larger_constant_index = 1;
smaller_constant_index = 0;
}
// Only proceed with these constants if they have turned out to be
// distinct.
if (larger_constant_index != smaller_constant_index) {
// We are in a position to create 'false' as 'literal1 > literal2', so
// reserve an id for this computation; this id will end up being
// guaranteed to be 'false'.
id_guaranteed_to_be_false = ir_context->TakeNextId();
auto smaller_constant = available_constants[smaller_constant_index];
auto larger_constant = available_constants[larger_constant_index];
opt::Instruction::OperandList greater_than_operands = {
{SPV_OPERAND_TYPE_ID, {smaller_constant}},
{SPV_OPERAND_TYPE_ID, {larger_constant}}};
new_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
ir_context.get(), SpvOpFOrdGreaterThan,
ir_context->get_type_mgr()->GetId(registered_bool_type),
id_guaranteed_to_be_false, greater_than_operands));
first_greater_then_operand_replacement =
MakeConstantUniformReplacement(ir_context.get(), fact_manager,
smaller_constant,
id_guaranteed_to_be_false, 0);
second_greater_then_operand_replacement =
MakeConstantUniformReplacement(ir_context.get(), fact_manager,
larger_constant,
id_guaranteed_to_be_false, 1);
}
}
}
if (id_guaranteed_to_be_false == 0) {
auto constant_false = ir_context->get_constant_mgr()->RegisterConstant(
MakeUnique<opt::analysis::BoolConstant>(registered_bool_type, false));
id_guaranteed_to_be_false = ir_context->get_constant_mgr()
->GetDefiningInstruction(constant_false)
->result_id();
}
opt::Operand false_condition = {SPV_OPERAND_TYPE_ID,
{id_guaranteed_to_be_false}};
opt::Operand then_block = {SPV_OPERAND_TYPE_ID,
{entry_point_function->entry()->id()}};
opt::Operand else_block = {SPV_OPERAND_TYPE_ID, {new_exit_block_id}};
opt::Instruction::OperandList op_branch_conditional_operands = {
false_condition, then_block, else_block};
new_entry_block->AddInstruction(
MakeUnique<opt::Instruction>(ir_context.get(), SpvOpBranchConditional,
0, 0, op_branch_conditional_operands));
entry_point_function->InsertBasicBlockBefore(
std::move(new_entry_block), entry_point_function->entry().get());
for (auto& replacement : {first_greater_then_operand_replacement.get(),
second_greater_then_operand_replacement.get()}) {
if (replacement) {
assert(replacement->IsApplicable(ir_context.get(),
transformation_context));
replacement->Apply(ir_context.get(), &transformation_context);
}
}
}
// Write out the module as a binary.
ir_context->module()->ToBinary(binary_out, false);
return true;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,49 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FORCE_RENDER_RED_H_
#define SOURCE_FORCE_RENDER_RED_H_
#include <vector>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "spirv-tools/libspirv.hpp"
namespace spvtools {
namespace fuzz {
// Requires |binary_in| to be a valid SPIR-V module with Shader capability,
// containing an entry point with the Fragment execution model, and a single
// output variable of type vec4.
//
// Turns the body of this entry point into effectively:
//
// output_variable = vec4(1.0, 0.0, 0.0, 1.0);
// if (false) {
// original_body
// }
//
// If suitable facts about values of uniforms are available, the 'false' will
// instead become: 'u > v', where 'u' and 'v' are pieces of uniform data for
// which it is known that 'u < v' holds.
bool ForceRenderRed(
const spv_target_env& target_env, spv_validator_options validator_options,
const std::vector<uint32_t>& binary_in,
const spvtools::fuzz::protobufs::FactSequence& initial_facts,
std::vector<uint32_t>* binary_out);
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FORCE_RENDER_RED_H_

View File

@@ -1,394 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer.h"
#include <cassert>
#include <memory>
#include "source/fuzz/fact_manager/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"
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
#include "source/fuzz/fuzzer_pass_add_dead_continues.h"
#include "source/fuzz/fuzzer_pass_add_equation_instructions.h"
#include "source/fuzz/fuzzer_pass_add_function_calls.h"
#include "source/fuzz/fuzzer_pass_add_global_variables.h"
#include "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h"
#include "source/fuzz/fuzzer_pass_add_loads.h"
#include "source/fuzz/fuzzer_pass_add_local_variables.h"
#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_opphi_synonyms.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"
#include "source/fuzz/fuzzer_pass_adjust_branch_weights.h"
#include "source/fuzz/fuzzer_pass_adjust_function_controls.h"
#include "source/fuzz/fuzzer_pass_adjust_loop_controls.h"
#include "source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h"
#include "source/fuzz/fuzzer_pass_adjust_selection_controls.h"
#include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
#include "source/fuzz/fuzzer_pass_construct_composites.h"
#include "source/fuzz/fuzzer_pass_copy_objects.h"
#include "source/fuzz/fuzzer_pass_donate_modules.h"
#include "source/fuzz/fuzzer_pass_inline_functions.h"
#include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h"
#include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h"
#include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
#include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h"
#include "source/fuzz/fuzzer_pass_merge_blocks.h"
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
#include "source/fuzz/fuzzer_pass_outline_functions.h"
#include "source/fuzz/fuzzer_pass_permute_blocks.h"
#include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
#include "source/fuzz/fuzzer_pass_permute_instructions.h"
#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"
#include "source/fuzz/fuzzer_pass_swap_commutable_operands.h"
#include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h"
#include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/pseudo_random_generator.h"
#include "source/fuzz/transformation_context.h"
#include "source/opt/build_module.h"
#include "source/spirv_fuzzer_options.h"
#include "source/util/make_unique.h"
namespace spvtools {
namespace fuzz {
namespace {
const uint32_t kIdBoundGap = 100;
const uint32_t kTransformationLimit = 500;
const uint32_t kChanceOfApplyingAnotherPass = 85;
// A convenience method to add a fuzzer pass to |passes| with probability 0.5.
// All fuzzer passes take |ir_context|, |transformation_context|,
// |fuzzer_context| and |transformation_sequence_out| as parameters. Extra
// arguments can be provided via |extra_args|.
template <typename T, typename... Args>
void MaybeAddPass(
std::vector<std::unique_ptr<FuzzerPass>>* passes,
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformation_sequence_out,
Args&&... extra_args) {
if (fuzzer_context->ChooseEven()) {
passes->push_back(MakeUnique<T>(ir_context, transformation_context,
fuzzer_context, transformation_sequence_out,
std::forward<Args>(extra_args)...));
}
}
} // namespace
Fuzzer::Fuzzer(spv_target_env target_env, uint32_t seed,
bool validate_after_each_fuzzer_pass,
spv_validator_options validator_options)
: target_env_(target_env),
seed_(seed),
validate_after_each_fuzzer_pass_(validate_after_each_fuzzer_pass),
validator_options_(validator_options) {}
Fuzzer::~Fuzzer() = default;
void Fuzzer::SetMessageConsumer(MessageConsumer consumer) {
consumer_ = std::move(consumer);
}
bool Fuzzer::ApplyPassAndCheckValidity(
FuzzerPass* pass, const opt::IRContext& ir_context,
const spvtools::SpirvTools& tools) const {
pass->Apply();
if (validate_after_each_fuzzer_pass_) {
std::vector<uint32_t> binary_to_validate;
ir_context.module()->ToBinary(&binary_to_validate, false);
if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(),
validator_options_)) {
consumer_(SPV_MSG_INFO, nullptr, {},
"Binary became invalid during fuzzing (set a breakpoint to "
"inspect); stopping.");
return false;
}
}
return true;
}
Fuzzer::FuzzerResultStatus Fuzzer::Run(
const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers,
std::vector<uint32_t>* binary_out,
protobufs::TransformationSequence* transformation_sequence_out) const {
// Check compatibility between the library version being linked with and the
// header files being used.
GOOGLE_PROTOBUF_VERIFY_VERSION;
spvtools::SpirvTools tools(target_env_);
tools.SetMessageConsumer(consumer_);
if (!tools.IsValid()) {
consumer_(SPV_MSG_ERROR, nullptr, {},
"Failed to create SPIRV-Tools interface; stopping.");
return Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface;
}
// Initial binary should be valid.
if (!tools.Validate(&binary_in[0], binary_in.size(), validator_options_)) {
consumer_(SPV_MSG_ERROR, nullptr, {},
"Initial binary is invalid; stopping.");
return Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid;
}
// Build the module from the input binary.
std::unique_ptr<opt::IRContext> ir_context =
BuildModule(target_env_, consumer_, binary_in.data(), binary_in.size());
assert(ir_context);
// Make a PRNG from the seed passed to the fuzzer on creation.
PseudoRandomGenerator random_generator(seed_);
// The fuzzer will introduce new ids into the module. The module's id bound
// gives the smallest id that can be used for this purpose. We add an offset
// to this so that there is a sizeable gap between the ids used in the
// original module and the ids used for fuzzing, as a readability aid.
//
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) consider the
// case where the maximum id bound is reached.
auto minimum_fresh_id = ir_context->module()->id_bound() + kIdBoundGap;
FuzzerContext fuzzer_context(&random_generator, minimum_fresh_id);
FactManager fact_manager;
fact_manager.AddFacts(consumer_, initial_facts, ir_context.get());
TransformationContext transformation_context(&fact_manager,
validator_options_);
// Apply some semantics-preserving passes.
std::vector<std::unique_ptr<FuzzerPass>> passes;
while (passes.empty()) {
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);
MaybeAddPass<FuzzerPassAddCopyMemory>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddDeadBlocks>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddDeadBreaks>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddDeadContinues>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddEquationInstructions>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddFunctionCalls>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddGlobalVariables>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddImageSampleUnusedComponents>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddLoads>(&passes, ir_context.get(),
&transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddLocalVariables>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddLoopPreheaders>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddOpPhiSynonyms>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
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);
MaybeAddPass<FuzzerPassAddSynonyms>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddVectorShuffleInstructions>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassApplyIdSynonyms>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassConstructComposites>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassCopyObjects>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassDonateModules>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out, donor_suppliers);
MaybeAddPass<FuzzerPassInlineFunctions>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassInvertComparisonOperators>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassMakeVectorOperationsDynamic>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassMergeBlocks>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassObfuscateConstants>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassOutlineFunctions>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassPermuteBlocks>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassPermuteFunctionParameters>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassPermuteInstructions>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassPropagateInstructionsUp>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
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);
MaybeAddPass<FuzzerPassReplaceLinearAlgebraInstructions>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassReplaceParamsWithStruct>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassSplitBlocks>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassSwapBranchConditionalOperands>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
}
bool is_first = true;
while (static_cast<uint32_t>(
transformation_sequence_out->transformation_size()) <
kTransformationLimit &&
(is_first ||
fuzzer_context.ChoosePercentage(kChanceOfApplyingAnotherPass))) {
is_first = false;
if (!ApplyPassAndCheckValidity(
passes[fuzzer_context.RandomIndex(passes)].get(), *ir_context,
tools)) {
return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule;
}
}
// Now apply some passes that it does not make sense to apply repeatedly,
// as they do not unlock other passes.
std::vector<std::unique_ptr<FuzzerPass>> final_passes;
MaybeAddPass<FuzzerPassAdjustBranchWeights>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAdjustFunctionControls>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAdjustLoopControls>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAdjustMemoryOperandsMasks>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAdjustSelectionControls>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddNoContractionDecorations>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassInterchangeSignednessOfIntegerOperands>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassInterchangeZeroLikeConstants>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassPermutePhiOperands>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassSwapCommutableOperands>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassToggleAccessChainInstruction>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
for (auto& pass : final_passes) {
if (!ApplyPassAndCheckValidity(pass.get(), *ir_context, tools)) {
return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule;
}
}
// Encode the module as a binary.
ir_context->module()->ToBinary(binary_out, false);
return Fuzzer::FuzzerResultStatus::kComplete;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,101 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_H_
#define SOURCE_FUZZ_FUZZER_H_
#include <memory>
#include <vector>
#include "source/fuzz/fuzzer_pass.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "spirv-tools/libspirv.hpp"
namespace spvtools {
namespace fuzz {
// Transforms a SPIR-V module into a semantically equivalent SPIR-V module by
// running a number of randomized fuzzer passes.
class Fuzzer {
public:
// Possible statuses that can result from running the fuzzer.
enum class FuzzerResultStatus {
kComplete,
kFailedToCreateSpirvToolsInterface,
kFuzzerPassLedToInvalidModule,
kInitialBinaryInvalid,
};
// Constructs a fuzzer from the given target environment |target_env|. |seed|
// is a seed for pseudo-random number generation.
// |validate_after_each_fuzzer_pass| controls whether the validator will be
// invoked after every fuzzer pass is applied.
Fuzzer(spv_target_env target_env, uint32_t seed,
bool validate_after_each_fuzzer_pass,
spv_validator_options validator_options);
// Disables copy/move constructor/assignment operations.
Fuzzer(const Fuzzer&) = delete;
Fuzzer(Fuzzer&&) = delete;
Fuzzer& operator=(const Fuzzer&) = delete;
Fuzzer& operator=(Fuzzer&&) = delete;
~Fuzzer();
// Sets the message consumer to the given |consumer|. The |consumer| will be
// invoked once for each message communicated from the library.
void SetMessageConsumer(MessageConsumer consumer);
// Transforms |binary_in| to |binary_out| by running a number of randomized
// fuzzer passes. Initial facts about the input binary and the context in
// which it will execute are provided via |initial_facts|. A source of donor
// modules to be used by transformations is provided via |donor_suppliers|.
// The transformation sequence that was applied is returned via
// |transformation_sequence_out|.
FuzzerResultStatus Run(
const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers,
std::vector<uint32_t>* binary_out,
protobufs::TransformationSequence* transformation_sequence_out) const;
private:
// Applies |pass|, which must be a pass constructed with |ir_context|, and
// then returns true if and only if |ir_context| is valid. |tools| is used to
// check validity.
bool ApplyPassAndCheckValidity(FuzzerPass* pass,
const opt::IRContext& ir_context,
const spvtools::SpirvTools& tools) const;
// Target environment.
const spv_target_env target_env_;
// Message consumer.
MessageConsumer consumer_;
// Seed for random number generator.
const uint32_t seed_;
// Determines whether the validator should be invoked after every fuzzer pass.
bool validate_after_each_fuzzer_pass_;
// Options to control validation.
spv_validator_options validator_options_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_H_

View File

@@ -1,320 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_context.h"
#include <cmath>
namespace spvtools {
namespace fuzz {
namespace {
// Default <minimum, maximum> pairs of probabilities for applying various
// transformations. All values are percentages. Keep them in alphabetical order.
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};
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadContinue = {5, 80};
const std::pair<uint32_t, uint32_t> kChanceOfAddingEquationInstruction = {5,
90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingGlobalVariable = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingImageSampleUnusedComponents =
{20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingLoad = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingLocalVariable = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingLoopPreheader = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingMatrixType = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = {
5, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingOpPhiSynonym = {5, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingParameters = {5, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingRelaxedDecoration = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingStore = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingSynonyms = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorType = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorShuffle = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingBranchWeights = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingFunctionControl = {20,
70};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingLoopControl = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingMemoryOperandsMask = {20,
90};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingSelectionControl = {20,
90};
const std::pair<uint32_t, uint32_t> kChanceOfCallingFunction = {1, 10};
const std::pair<uint32_t, uint32_t> kChanceOfChoosingStructTypeVsArrayType = {
20, 80};
const std::pair<uint32_t, uint32_t> kChanceOfChoosingWorkgroupStorageClass = {
50, 50};
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> kChanceOfInliningFunction = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfInterchangingZeroLikeConstants = {
10, 90};
const std::pair<uint32_t, uint32_t>
kChanceOfInterchangingSignednessOfIntegerOperands = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfInvertingComparisonOperators = {
20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfMakingDonorLivesafe = {40, 60};
const std::pair<uint32_t, uint32_t> kChanceOfMakingVectorOperationDynamic = {
20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95};
const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfOutliningFunction = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfPermutingInstructions = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfPermutingParameters = {30, 90};
const std::pair<uint32_t, uint32_t> kChanceOfPermutingPhiOperands = {30, 90};
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, 70};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyMemoryWithLoadStore =
{20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyObjectWithStoreLoad =
{20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
const std::pair<uint32_t, uint32_t>
kChanceOfReplacingLinearAlgebraInstructions = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingLoadStoreWithCopyMemory =
{20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithGlobals = {
30, 70};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithStruct = {
20, 40};
const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
const std::pair<uint32_t, uint32_t> kChanceOfSwappingConditionalBranchOperands =
{10, 70};
const std::pair<uint32_t, uint32_t> kChanceOfTogglingAccessChainInstruction = {
20, 90};
// Default limits for various quantities that are chosen during fuzzing.
// Keep them in alphabetical order.
const uint32_t kDefaultMaxEquivalenceClassSizeForDataSynonymFactClosure = 1000;
const uint32_t kDefaultMaxLoopControlPartialCount = 100;
const uint32_t kDefaultMaxLoopControlPeelCount = 100;
const uint32_t kDefaultMaxLoopLimit = 20;
const uint32_t kDefaultMaxNewArraySizeLimit = 100;
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3424):
// think whether there is a better limit on the maximum number of parameters.
const uint32_t kDefaultMaxNumberOfFunctionParameters = 128;
const uint32_t kDefaultMaxNumberOfNewParameters = 15;
const uint32_t kGetDefaultMaxNumberOfParametersReplacedWithStruct = 5;
// Default functions for controlling how deep to go during recursive
// generation/transformation. Keep them in alphabetical order.
const std::function<bool(uint32_t, RandomGenerator*)>
kDefaultGoDeeperInConstantObfuscation =
[](uint32_t current_depth, RandomGenerator* random_generator) -> bool {
double chance = 1.0 / std::pow(3.0, static_cast<float>(current_depth + 1));
return random_generator->RandomDouble() < chance;
};
} // namespace
FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
uint32_t min_fresh_id)
: random_generator_(random_generator),
next_fresh_id_(min_fresh_id),
max_equivalence_class_size_for_data_synonym_fact_closure_(
kDefaultMaxEquivalenceClassSizeForDataSynonymFactClosure),
max_loop_control_partial_count_(kDefaultMaxLoopControlPartialCount),
max_loop_control_peel_count_(kDefaultMaxLoopControlPeelCount),
max_loop_limit_(kDefaultMaxLoopLimit),
max_new_array_size_limit_(kDefaultMaxNewArraySizeLimit),
max_number_of_function_parameters_(kDefaultMaxNumberOfFunctionParameters),
max_number_of_new_parameters_(kDefaultMaxNumberOfNewParameters),
max_number_of_parameters_replaced_with_struct_(
kGetDefaultMaxNumberOfParametersReplacedWithStruct),
go_deeper_in_constant_obfuscation_(
kDefaultGoDeeperInConstantObfuscation) {
chance_of_adding_access_chain_ =
ChooseBetweenMinAndMax(kChanceOfAddingAccessChain);
chance_of_adding_another_struct_field_ =
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_ =
ChooseBetweenMinAndMax(kChanceOfAddingDeadBlock);
chance_of_adding_dead_break_ =
ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak);
chance_of_adding_dead_continue_ =
ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue);
chance_of_adding_equation_instruction_ =
ChooseBetweenMinAndMax(kChanceOfAddingEquationInstruction);
chance_of_adding_global_variable_ =
ChooseBetweenMinAndMax(kChanceOfAddingGlobalVariable);
chance_of_adding_load_ = ChooseBetweenMinAndMax(kChanceOfAddingLoad);
chance_of_adding_loop_preheader_ =
ChooseBetweenMinAndMax(kChanceOfAddingLoopPreheader);
chance_of_adding_image_sample_unused_components_ =
ChooseBetweenMinAndMax(kChanceOfAddingImageSampleUnusedComponents);
chance_of_adding_local_variable_ =
ChooseBetweenMinAndMax(kChanceOfAddingLocalVariable);
chance_of_adding_matrix_type_ =
ChooseBetweenMinAndMax(kChanceOfAddingMatrixType);
chance_of_adding_no_contraction_decoration_ =
ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration);
chance_of_adding_opphi_synonym_ =
ChooseBetweenMinAndMax(kChanceOfAddingOpPhiSynonym);
chance_of_adding_parameters =
ChooseBetweenMinAndMax(kChanceOfAddingParameters);
chance_of_adding_relaxed_decoration_ =
ChooseBetweenMinAndMax(kChanceOfAddingRelaxedDecoration);
chance_of_adding_store_ = ChooseBetweenMinAndMax(kChanceOfAddingStore);
chance_of_adding_vector_shuffle_ =
ChooseBetweenMinAndMax(kChanceOfAddingVectorShuffle);
chance_of_adding_vector_type_ =
ChooseBetweenMinAndMax(kChanceOfAddingVectorType);
chance_of_adjusting_branch_weights_ =
ChooseBetweenMinAndMax(kChanceOfAdjustingBranchWeights);
chance_of_adjusting_function_control_ =
ChooseBetweenMinAndMax(kChanceOfAdjustingFunctionControl);
chance_of_adding_synonyms_ = ChooseBetweenMinAndMax(kChanceOfAddingSynonyms);
chance_of_adjusting_loop_control_ =
ChooseBetweenMinAndMax(kChanceOfAdjustingLoopControl);
chance_of_adjusting_memory_operands_mask_ =
ChooseBetweenMinAndMax(kChanceOfAdjustingMemoryOperandsMask);
chance_of_adjusting_selection_control_ =
ChooseBetweenMinAndMax(kChanceOfAdjustingSelectionControl);
chance_of_calling_function_ =
ChooseBetweenMinAndMax(kChanceOfCallingFunction);
chance_of_choosing_struct_type_vs_array_type_ =
ChooseBetweenMinAndMax(kChanceOfChoosingStructTypeVsArrayType);
chance_of_choosing_workgroup_storage_class_ =
ChooseBetweenMinAndMax(kChanceOfChoosingWorkgroupStorageClass);
chance_of_constructing_composite_ =
ChooseBetweenMinAndMax(kChanceOfConstructingComposite);
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_inlining_function_ =
ChooseBetweenMinAndMax(kChanceOfInliningFunction);
chance_of_interchanging_signedness_of_integer_operands_ =
ChooseBetweenMinAndMax(kChanceOfInterchangingSignednessOfIntegerOperands);
chance_of_interchanging_zero_like_constants_ =
ChooseBetweenMinAndMax(kChanceOfInterchangingZeroLikeConstants);
chance_of_inverting_comparison_operators_ =
ChooseBetweenMinAndMax(kChanceOfInvertingComparisonOperators);
chance_of_making_donor_livesafe_ =
ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe);
chance_of_making_vector_operation_dynamic_ =
ChooseBetweenMinAndMax(kChanceOfMakingVectorOperationDynamic);
chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks);
chance_of_moving_block_down_ =
ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
chance_of_obfuscating_constant_ =
ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant);
chance_of_outlining_function_ =
ChooseBetweenMinAndMax(kChanceOfOutliningFunction);
chance_of_permuting_instructions_ =
ChooseBetweenMinAndMax(kChanceOfPermutingInstructions);
chance_of_permuting_parameters_ =
ChooseBetweenMinAndMax(kChanceOfPermutingParameters);
chance_of_permuting_phi_operands_ =
ChooseBetweenMinAndMax(kChanceOfPermutingPhiOperands);
chance_of_propagating_instructions_up_ =
ChooseBetweenMinAndMax(kChanceOfPropagatingInstructionsUp);
chance_of_pushing_id_through_variable_ =
ChooseBetweenMinAndMax(kChanceOfPushingIdThroughVariable);
chance_of_replacing_add_sub_mul_with_carrying_extended_ =
ChooseBetweenMinAndMax(kChanceOfReplacingAddSubMulWithCarryingExtended);
chance_of_replacing_copy_memory_with_load_store_ =
ChooseBetweenMinAndMax(kChanceOfReplacingCopyMemoryWithLoadStore);
chance_of_replacing_copyobject_with_store_load_ =
ChooseBetweenMinAndMax(kChanceOfReplacingCopyObjectWithStoreLoad);
chance_of_replacing_id_with_synonym_ =
ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
chance_of_replacing_linear_algebra_instructions_ =
ChooseBetweenMinAndMax(kChanceOfReplacingLinearAlgebraInstructions);
chance_of_replacing_load_store_with_copy_memory_ =
ChooseBetweenMinAndMax(kChanceOfReplacingLoadStoreWithCopyMemory);
chance_of_replacing_parameters_with_globals_ =
ChooseBetweenMinAndMax(kChanceOfReplacingParametersWithGlobals);
chance_of_replacing_parameters_with_struct_ =
ChooseBetweenMinAndMax(kChanceOfReplacingParametersWithStruct);
chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
chance_of_swapping_conditional_branch_operands_ =
ChooseBetweenMinAndMax(kChanceOfSwappingConditionalBranchOperands);
chance_of_toggling_access_chain_instruction_ =
ChooseBetweenMinAndMax(kChanceOfTogglingAccessChainInstruction);
}
FuzzerContext::~FuzzerContext() = default;
uint32_t FuzzerContext::GetFreshId() { return next_fresh_id_++; }
std::vector<uint32_t> FuzzerContext::GetFreshIds(const uint32_t count) {
std::vector<uint32_t> fresh_ids(count);
for (uint32_t& fresh_id : fresh_ids) {
fresh_id = next_fresh_id_++;
}
return fresh_ids;
}
bool FuzzerContext::ChooseEven() { return random_generator_->RandomBool(); }
bool FuzzerContext::ChoosePercentage(uint32_t percentage_chance) {
assert(percentage_chance <= 100);
return random_generator_->RandomPercentage() < percentage_chance;
}
uint32_t FuzzerContext::ChooseBetweenMinAndMax(
const std::pair<uint32_t, uint32_t>& min_max) {
assert(min_max.first <= min_max.second);
return min_max.first +
random_generator_->RandomUint32(min_max.second - min_max.first + 1);
}
protobufs::TransformationAddSynonym::SynonymType
FuzzerContext::GetRandomSynonymType() {
// value_count method is guaranteed to return a value greater than 0.
auto result_index = ChooseBetweenMinAndMax(
{0, static_cast<uint32_t>(
protobufs::TransformationAddSynonym::SynonymType_descriptor()
->value_count() -
1)});
auto result = protobufs::TransformationAddSynonym::SynonymType_descriptor()
->value(result_index)
->number();
assert(protobufs::TransformationAddSynonym::SynonymType_IsValid(result) &&
"|result| is not a value of SynonymType");
return static_cast<protobufs::TransformationAddSynonym::SynonymType>(result);
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,445 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_CONTEXT_H_
#define SOURCE_FUZZ_FUZZER_CONTEXT_H_
#include <functional>
#include <utility>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/random_generator.h"
#include "source/opt/function.h"
namespace spvtools {
namespace fuzz {
// Encapsulates all parameters that control the fuzzing process, such as the
// source of randomness and the probabilities with which transformations are
// applied.
class FuzzerContext {
public:
// Constructs a fuzzer context with a given random generator and the minimum
// value that can be used for fresh ids.
FuzzerContext(RandomGenerator* random_generator, uint32_t min_fresh_id);
~FuzzerContext();
// Returns a random boolean.
bool ChooseEven();
// Returns true if and only if a randomly-chosen integer in the range [0, 100]
// is less than |percentage_chance|.
bool ChoosePercentage(uint32_t percentage_chance);
// Returns a random index into |sequence|, which is expected to have a 'size'
// method, and which must be non-empty. Typically 'HasSizeMethod' will be an
// std::vector.
template <typename HasSizeMethod>
uint32_t RandomIndex(const HasSizeMethod& sequence) const {
assert(sequence.size() > 0);
return random_generator_->RandomUint32(
static_cast<uint32_t>(sequence.size()));
}
// Selects a random index into |sequence|, removes the element at that index
// and returns it.
template <typename T>
T RemoveAtRandomIndex(std::vector<T>* sequence) const {
uint32_t index = RandomIndex(*sequence);
T result = sequence->at(index);
sequence->erase(sequence->begin() + index);
return result;
}
// Randomly shuffles a |sequence| between |lo| and |hi| indices inclusively.
// |lo| and |hi| must be valid indices to the |sequence|
template <typename T>
void Shuffle(std::vector<T>* sequence, size_t lo, size_t hi) const {
auto& array = *sequence;
if (array.empty()) {
return;
}
assert(lo <= hi && hi < array.size() && "lo and/or hi indices are invalid");
// i > lo to account for potential infinite loop when lo == 0
for (size_t i = hi; i > lo; --i) {
auto index =
random_generator_->RandomUint32(static_cast<uint32_t>(i - lo + 1));
if (lo + index != i) {
// Introduce std::swap to the scope but don't use it
// directly since there might be a better overload
using std::swap;
swap(array[lo + index], array[i]);
}
}
}
// Ramdomly shuffles a |sequence|
template <typename T>
void Shuffle(std::vector<T>* sequence) const {
if (!sequence->empty()) {
Shuffle(sequence, 0, sequence->size() - 1);
}
}
// Yields an id that is guaranteed not to be used in the module being fuzzed,
// or to have been issued before.
uint32_t GetFreshId();
// Returns a vector of |count| fresh ids.
std::vector<uint32_t> GetFreshIds(const uint32_t count);
// Probabilities associated with applying various transformations.
// Keep them in alphabetical order.
uint32_t GetChanceOfAddingAccessChain() {
return chance_of_adding_access_chain_;
}
uint32_t GetChanceOfAddingAnotherStructField() {
return chance_of_adding_another_struct_field_;
}
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_;
}
uint32_t GetChanceOfAddingDeadBlock() { return chance_of_adding_dead_block_; }
uint32_t GetChanceOfAddingDeadBreak() { return chance_of_adding_dead_break_; }
uint32_t GetChanceOfAddingDeadContinue() {
return chance_of_adding_dead_continue_;
}
uint32_t GetChanceOfAddingEquationInstruction() {
return chance_of_adding_equation_instruction_;
}
uint32_t GetChanceOfAddingGlobalVariable() {
return chance_of_adding_global_variable_;
}
uint32_t GetChanceOfAddingImageSampleUnusedComponents() {
return chance_of_adding_image_sample_unused_components_;
}
uint32_t GetChanceOfAddingLoad() { return chance_of_adding_load_; }
uint32_t GetChanceOfAddingLocalVariable() {
return chance_of_adding_local_variable_;
}
uint32_t GetChanceOfAddingLoopPreheader() {
return chance_of_adding_loop_preheader_;
}
uint32_t GetChanceOfAddingMatrixType() {
return chance_of_adding_matrix_type_;
}
uint32_t GetChanceOfAddingNoContractionDecoration() {
return chance_of_adding_no_contraction_decoration_;
}
uint32_t GetChanceOfAddingOpPhiSynonym() {
return chance_of_adding_opphi_synonym_;
}
uint32_t GetChanceOfAddingParameters() { return chance_of_adding_parameters; }
uint32_t GetChanceOfAddingRelaxedDecoration() {
return chance_of_adding_relaxed_decoration_;
}
uint32_t GetChanceOfAddingStore() { return chance_of_adding_store_; }
uint32_t GetChanceOfAddingSynonyms() { return chance_of_adding_synonyms_; }
uint32_t GetChanceOfAddingVectorShuffle() {
return chance_of_adding_vector_shuffle_;
}
uint32_t GetChanceOfAddingVectorType() {
return chance_of_adding_vector_type_;
}
uint32_t GetChanceOfAdjustingBranchWeights() {
return chance_of_adjusting_branch_weights_;
}
uint32_t GetChanceOfAdjustingFunctionControl() {
return chance_of_adjusting_function_control_;
}
uint32_t GetChanceOfAdjustingLoopControl() {
return chance_of_adjusting_loop_control_;
}
uint32_t GetChanceOfAdjustingMemoryOperandsMask() {
return chance_of_adjusting_memory_operands_mask_;
}
uint32_t GetChanceOfAdjustingSelectionControl() {
return chance_of_adjusting_selection_control_;
}
uint32_t GetChanceOfCallingFunction() { return chance_of_calling_function_; }
uint32_t GetChanceOfChoosingStructTypeVsArrayType() {
return chance_of_choosing_struct_type_vs_array_type_;
}
uint32_t GetChanceOfChoosingWorkgroupStorageClass() {
return chance_of_choosing_workgroup_storage_class_;
}
uint32_t GetChanceOfConstructingComposite() {
return chance_of_constructing_composite_;
}
uint32_t GetChanceOfCopyingObject() { return chance_of_copying_object_; }
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_;
}
uint32_t GetChanceOfInliningFunction() {
return chance_of_inlining_function_;
}
uint32_t GetChanceOfInterchangingSignednessOfIntegerOperands() {
return chance_of_interchanging_signedness_of_integer_operands_;
}
uint32_t GetChanceOfInterchangingZeroLikeConstants() {
return chance_of_interchanging_zero_like_constants_;
}
uint32_t GetChanceOfInvertingComparisonOperators() {
return chance_of_inverting_comparison_operators_;
}
uint32_t ChanceOfMakingDonorLivesafe() {
return chance_of_making_donor_livesafe_;
}
uint32_t GetChanceOfMakingVectorOperationDynamic() {
return chance_of_making_vector_operation_dynamic_;
}
uint32_t GetChanceOfMergingBlocks() { return chance_of_merging_blocks_; }
uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
uint32_t GetChanceOfObfuscatingConstant() {
return chance_of_obfuscating_constant_;
}
uint32_t GetChanceOfOutliningFunction() {
return chance_of_outlining_function_;
}
uint32_t GetChanceOfPermutingInstructions() {
return chance_of_permuting_instructions_;
}
uint32_t GetChanceOfPermutingParameters() {
return chance_of_permuting_parameters_;
}
uint32_t GetChanceOfPermutingPhiOperands() {
return chance_of_permuting_phi_operands_;
}
uint32_t GetChanceOfPropagatingInstructionsUp() {
return chance_of_propagating_instructions_up_;
}
uint32_t GetChanceOfPushingIdThroughVariable() {
return chance_of_pushing_id_through_variable_;
}
uint32_t GetChanceOfReplacingAddSubMulWithCarryingExtended() {
return chance_of_replacing_add_sub_mul_with_carrying_extended_;
}
uint32_t GetChanceOfReplacingCopyMemoryWithLoadStore() {
return chance_of_replacing_copy_memory_with_load_store_;
}
uint32_t GetChanceOfReplacingCopyObjectWithStoreLoad() {
return chance_of_replacing_copyobject_with_store_load_;
}
uint32_t GetChanceOfReplacingIdWithSynonym() {
return chance_of_replacing_id_with_synonym_;
}
uint32_t GetChanceOfReplacingLinearAlgebraInstructions() {
return chance_of_replacing_linear_algebra_instructions_;
}
uint32_t GetChanceOfReplacingLoadStoreWithCopyMemory() {
return chance_of_replacing_load_store_with_copy_memory_;
}
uint32_t GetChanceOfReplacingParametersWithGlobals() {
return chance_of_replacing_parameters_with_globals_;
}
uint32_t GetChanceOfReplacingParametersWithStruct() {
return chance_of_replacing_parameters_with_struct_;
}
uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; }
uint32_t GetChanceOfSwappingConditionalBranchOperands() {
return chance_of_swapping_conditional_branch_operands_;
}
uint32_t GetChanceOfTogglingAccessChainInstruction() {
return chance_of_toggling_access_chain_instruction_;
}
// Other functions to control transformations. Keep them in alphabetical
// order.
uint32_t GetMaximumEquivalenceClassSizeForDataSynonymFactClosure() {
return max_equivalence_class_size_for_data_synonym_fact_closure_;
}
uint32_t GetMaximumNumberOfFunctionParameters() {
return max_number_of_function_parameters_;
}
uint32_t GetMaximumNumberOfParametersReplacedWithStruct() {
return max_number_of_parameters_replaced_with_struct_;
}
std::pair<uint32_t, uint32_t> GetRandomBranchWeights() {
std::pair<uint32_t, uint32_t> branch_weights = {0, 0};
while (branch_weights.first == 0 && branch_weights.second == 0) {
// Using INT32_MAX to do not overflow UINT32_MAX when the branch weights
// are added together.
branch_weights.first = random_generator_->RandomUint32(INT32_MAX);
branch_weights.second = random_generator_->RandomUint32(INT32_MAX);
}
return branch_weights;
}
std::vector<uint32_t> GetRandomComponentsForVectorShuffle(
uint32_t max_component_index) {
// Component count must be in range [2, 4].
std::vector<uint32_t> components(random_generator_->RandomUint32(2) + 2);
for (uint32_t& component : components) {
component = random_generator_->RandomUint32(max_component_index);
}
return components;
}
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_);
}
uint32_t GetRandomLoopControlPeelCount() {
return random_generator_->RandomUint32(max_loop_control_peel_count_);
}
uint32_t GetRandomLoopLimit() {
return random_generator_->RandomUint32(max_loop_limit_);
}
uint32_t GetRandomNumberOfNewParameters(uint32_t num_of_params) {
assert(num_of_params < GetMaximumNumberOfFunctionParameters());
return ChooseBetweenMinAndMax(
{1, std::min(max_number_of_new_parameters_,
GetMaximumNumberOfFunctionParameters() - num_of_params)});
}
uint32_t GetRandomNumberOfParametersReplacedWithStruct(uint32_t num_params) {
assert(num_params != 0 && "A function must have parameters to replace");
return ChooseBetweenMinAndMax(
{1, std::min(num_params,
GetMaximumNumberOfParametersReplacedWithStruct())});
}
uint32_t GetRandomSizeForNewArray() {
// Ensure that the array size is non-zero.
return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1;
}
protobufs::TransformationAddSynonym::SynonymType GetRandomSynonymType();
uint32_t GetRandomUnusedComponentCountForImageSample(
uint32_t max_unused_component_count) {
// Ensure that the number of unused components is non-zero.
return random_generator_->RandomUint32(max_unused_component_count) + 1;
}
bool GoDeeperInConstantObfuscation(uint32_t depth) {
return go_deeper_in_constant_obfuscation_(depth, random_generator_);
}
private:
// The source of randomness.
RandomGenerator* random_generator_;
// The next fresh id to be issued.
uint32_t next_fresh_id_;
// Probabilities associated with applying various transformations.
// Keep them in alphabetical order.
uint32_t chance_of_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_;
uint32_t chance_of_adding_dead_continue_;
uint32_t chance_of_adding_equation_instruction_;
uint32_t chance_of_adding_global_variable_;
uint32_t chance_of_adding_image_sample_unused_components_;
uint32_t chance_of_adding_load_;
uint32_t chance_of_adding_local_variable_;
uint32_t chance_of_adding_loop_preheader_;
uint32_t chance_of_adding_matrix_type_;
uint32_t chance_of_adding_no_contraction_decoration_;
uint32_t chance_of_adding_opphi_synonym_;
uint32_t chance_of_adding_parameters;
uint32_t chance_of_adding_relaxed_decoration_;
uint32_t chance_of_adding_store_;
uint32_t chance_of_adding_synonyms_;
uint32_t chance_of_adding_vector_shuffle_;
uint32_t chance_of_adding_vector_type_;
uint32_t chance_of_adjusting_branch_weights_;
uint32_t chance_of_adjusting_function_control_;
uint32_t chance_of_adjusting_loop_control_;
uint32_t chance_of_adjusting_memory_operands_mask_;
uint32_t chance_of_adjusting_selection_control_;
uint32_t chance_of_calling_function_;
uint32_t chance_of_choosing_struct_type_vs_array_type_;
uint32_t chance_of_choosing_workgroup_storage_class_;
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_inlining_function_;
uint32_t chance_of_interchanging_signedness_of_integer_operands_;
uint32_t chance_of_interchanging_zero_like_constants_;
uint32_t chance_of_inverting_comparison_operators_;
uint32_t chance_of_making_donor_livesafe_;
uint32_t chance_of_making_vector_operation_dynamic_;
uint32_t chance_of_merging_blocks_;
uint32_t chance_of_moving_block_down_;
uint32_t chance_of_obfuscating_constant_;
uint32_t chance_of_outlining_function_;
uint32_t chance_of_permuting_instructions_;
uint32_t chance_of_permuting_parameters_;
uint32_t chance_of_permuting_phi_operands_;
uint32_t chance_of_propagating_instructions_up_;
uint32_t chance_of_pushing_id_through_variable_;
uint32_t chance_of_replacing_add_sub_mul_with_carrying_extended_;
uint32_t chance_of_replacing_copy_memory_with_load_store_;
uint32_t chance_of_replacing_copyobject_with_store_load_;
uint32_t chance_of_replacing_id_with_synonym_;
uint32_t chance_of_replacing_linear_algebra_instructions_;
uint32_t chance_of_replacing_load_store_with_copy_memory_;
uint32_t chance_of_replacing_parameters_with_globals_;
uint32_t chance_of_replacing_parameters_with_struct_;
uint32_t chance_of_splitting_block_;
uint32_t chance_of_swapping_conditional_branch_operands_;
uint32_t chance_of_toggling_access_chain_instruction_;
// Limits associated with various quantities for which random values are
// chosen during fuzzing.
// Keep them in alphabetical order.
uint32_t max_equivalence_class_size_for_data_synonym_fact_closure_;
uint32_t max_loop_control_partial_count_;
uint32_t max_loop_control_peel_count_;
uint32_t max_loop_limit_;
uint32_t max_new_array_size_limit_;
uint32_t max_number_of_function_parameters_;
uint32_t max_number_of_new_parameters_;
uint32_t max_number_of_parameters_replaced_with_struct_;
// Functions to determine with what probability to go deeper when generating
// or mutating constructs recursively.
const std::function<bool(uint32_t, RandomGenerator*)>&
go_deeper_in_constant_obfuscation_;
// Requires |min_max.first| <= |min_max.second|, and returns a value in the
// range [ |min_max.first|, |min_max.second| ]
uint32_t ChooseBetweenMinAndMax(const std::pair<uint32_t, uint32_t>& min_max);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_CONTEXT_H_

View File

@@ -1,715 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass.h"
#include <set>
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/id_use_descriptor.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_add_constant_boolean.h"
#include "source/fuzz/transformation_add_constant_composite.h"
#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"
#include "source/fuzz/transformation_add_type_function.h"
#include "source/fuzz/transformation_add_type_int.h"
#include "source/fuzz/transformation_add_type_matrix.h"
#include "source/fuzz/transformation_add_type_pointer.h"
#include "source/fuzz/transformation_add_type_struct.h"
#include "source/fuzz/transformation_add_type_vector.h"
namespace spvtools {
namespace fuzz {
FuzzerPass::FuzzerPass(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: ir_context_(ir_context),
transformation_context_(transformation_context),
fuzzer_context_(fuzzer_context),
transformations_(transformations) {}
FuzzerPass::~FuzzerPass() = default;
std::vector<opt::Instruction*> FuzzerPass::FindAvailableInstructions(
opt::Function* function, opt::BasicBlock* block,
const opt::BasicBlock::iterator& inst_it,
std::function<bool(opt::IRContext*, opt::Instruction*)>
instruction_is_relevant) const {
// TODO(afd) The following is (relatively) simple, but may end up being
// prohibitively inefficient, as it walks the whole dominator tree for
// every instruction that is considered.
std::vector<opt::Instruction*> result;
// Consider all global declarations
for (auto& global : GetIRContext()->module()->types_values()) {
if (instruction_is_relevant(GetIRContext(), &global)) {
result.push_back(&global);
}
}
// Consider all function parameters
function->ForEachParam(
[this, &instruction_is_relevant, &result](opt::Instruction* param) {
if (instruction_is_relevant(GetIRContext(), param)) {
result.push_back(param);
}
});
// Consider all previous instructions in this block
for (auto prev_inst_it = block->begin(); prev_inst_it != inst_it;
++prev_inst_it) {
if (instruction_is_relevant(GetIRContext(), &*prev_inst_it)) {
result.push_back(&*prev_inst_it);
}
}
// Walk the dominator tree to consider all instructions from dominating
// blocks
auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function);
for (auto next_dominator = dominator_analysis->ImmediateDominator(block);
next_dominator != nullptr;
next_dominator =
dominator_analysis->ImmediateDominator(next_dominator)) {
for (auto& dominating_inst : *next_dominator) {
if (instruction_is_relevant(GetIRContext(), &dominating_inst)) {
result.push_back(&dominating_inst);
}
}
}
return result;
}
void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
std::function<
void(opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor)>
action) {
// Consider every block in every function.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// We now consider every instruction in the block, randomly deciding
// whether to apply a transformation before it.
// In order for transformations to insert new instructions, they need to
// be able to identify the instruction to insert before. We describe an
// instruction via its opcode, 'opc', a base instruction 'base' that has a
// result id, and the number of instructions with opcode 'opc' that we
// should skip when searching from 'base' for the desired instruction.
// (An instruction that has a result id is represented by its own opcode,
// itself as 'base', and a skip-count of 0.)
std::vector<std::tuple<uint32_t, SpvOp, uint32_t>>
base_opcode_skip_triples;
// The initial base instruction is the block label.
uint32_t base = block.id();
// Counts the number of times we have seen each opcode since we reset the
// base instruction.
std::map<SpvOp, uint32_t> skip_count;
// Consider every instruction in the block. The label is excluded: it is
// only necessary to consider it as a base in case the first instruction
// in the block does not have a result id.
for (auto inst_it = block.begin(); inst_it != block.end(); ++inst_it) {
if (inst_it->HasResultId()) {
// In the case that the instruction has a result id, we use the
// instruction as its own base, and clear the skip counts we have
// collected.
base = inst_it->result_id();
skip_count.clear();
}
const SpvOp opcode = inst_it->opcode();
// Invoke the provided function, which might apply a transformation.
action(&function, &block, inst_it,
MakeInstructionDescriptor(
base, opcode,
skip_count.count(opcode) ? skip_count.at(opcode) : 0));
if (!inst_it->HasResultId()) {
skip_count[opcode] =
skip_count.count(opcode) ? skip_count.at(opcode) + 1 : 1;
}
}
}
}
}
uint32_t FuzzerPass::FindOrCreateBoolType() {
if (auto existing_id = fuzzerutil::MaybeGetBoolType(GetIRContext())) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeBoolean(result));
return result;
}
uint32_t FuzzerPass::FindOrCreateIntegerType(uint32_t width, bool is_signed) {
opt::analysis::Integer int_type(width, is_signed);
auto existing_id = GetIRContext()->get_type_mgr()->GetId(&int_type);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeInt(result, width, is_signed));
return result;
}
uint32_t FuzzerPass::FindOrCreateFloatType(uint32_t width) {
opt::analysis::Float float_type(width);
auto existing_id = GetIRContext()->get_type_mgr()->GetId(&float_type);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeFloat(result, width));
return result;
}
uint32_t FuzzerPass::FindOrCreateFunctionType(
uint32_t return_type_id, const std::vector<uint32_t>& argument_id) {
// FindFunctionType has a sigle argument for OpTypeFunction operands
// so we will have to copy them all in this vector
std::vector<uint32_t> type_ids(argument_id.size() + 1);
type_ids[0] = return_type_id;
std::copy(argument_id.begin(), argument_id.end(), type_ids.begin() + 1);
// Check if type exists
auto existing_id = fuzzerutil::FindFunctionType(GetIRContext(), type_ids);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddTypeFunction(result, return_type_id, argument_id));
return result;
}
uint32_t FuzzerPass::FindOrCreateVectorType(uint32_t component_type_id,
uint32_t component_count) {
assert(component_count >= 2 && component_count <= 4 &&
"Precondition: component count must be in range [2, 4].");
opt::analysis::Type* component_type =
GetIRContext()->get_type_mgr()->GetType(component_type_id);
assert(component_type && "Precondition: the component type must exist.");
opt::analysis::Vector vector_type(component_type, component_count);
auto existing_id = GetIRContext()->get_type_mgr()->GetId(&vector_type);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddTypeVector(result, component_type_id, component_count));
return result;
}
uint32_t FuzzerPass::FindOrCreateMatrixType(uint32_t column_count,
uint32_t row_count) {
assert(column_count >= 2 && column_count <= 4 &&
"Precondition: column count must be in range [2, 4].");
assert(row_count >= 2 && row_count <= 4 &&
"Precondition: row count must be in range [2, 4].");
uint32_t column_type_id =
FindOrCreateVectorType(FindOrCreateFloatType(32), row_count);
opt::analysis::Type* column_type =
GetIRContext()->get_type_mgr()->GetType(column_type_id);
opt::analysis::Matrix matrix_type(column_type, column_count);
auto existing_id = GetIRContext()->get_type_mgr()->GetId(&matrix_type);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddTypeMatrix(result, column_type_id, column_count));
return result;
}
uint32_t FuzzerPass::FindOrCreateStructType(
const std::vector<uint32_t>& component_type_ids) {
if (auto existing_id =
fuzzerutil::MaybeGetStructType(GetIRContext(), component_type_ids)) {
return existing_id;
}
auto new_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeStruct(new_id, component_type_ids));
return new_id;
}
uint32_t FuzzerPass::FindOrCreatePointerType(uint32_t base_type_id,
SpvStorageClass storage_class) {
// We do not use the type manager here, due to problems related to isomorphic
// but distinct structs not being regarded as different.
auto existing_id = fuzzerutil::MaybeGetPointerType(
GetIRContext(), base_type_id, storage_class);
if (existing_id) {
return existing_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddTypePointer(result, storage_class, base_type_id));
return result;
}
uint32_t FuzzerPass::FindOrCreatePointerToIntegerType(
uint32_t width, bool is_signed, SpvStorageClass storage_class) {
return FindOrCreatePointerType(FindOrCreateIntegerType(width, is_signed),
storage_class);
}
uint32_t FuzzerPass::FindOrCreateIntegerConstant(
const std::vector<uint32_t>& words, uint32_t width, bool is_signed,
bool is_irrelevant) {
auto int_type_id = FindOrCreateIntegerType(width, is_signed);
if (auto constant_id = fuzzerutil::MaybeGetScalarConstant(
GetIRContext(), *GetTransformationContext(), words, int_type_id,
is_irrelevant)) {
return constant_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddConstantScalar(result, int_type_id,
words, is_irrelevant));
return result;
}
uint32_t FuzzerPass::FindOrCreateFloatConstant(
const std::vector<uint32_t>& words, uint32_t width, bool is_irrelevant) {
auto float_type_id = FindOrCreateFloatType(width);
if (auto constant_id = fuzzerutil::MaybeGetScalarConstant(
GetIRContext(), *GetTransformationContext(), words, float_type_id,
is_irrelevant)) {
return constant_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddConstantScalar(result, float_type_id,
words, is_irrelevant));
return result;
}
uint32_t FuzzerPass::FindOrCreateBoolConstant(bool value, bool is_irrelevant) {
auto bool_type_id = FindOrCreateBoolType();
if (auto constant_id = fuzzerutil::MaybeGetScalarConstant(
GetIRContext(), *GetTransformationContext(), {value ? 1u : 0u},
bool_type_id, is_irrelevant)) {
return constant_id;
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddConstantBoolean(result, value, is_irrelevant));
return result;
}
uint32_t FuzzerPass::FindOrCreateConstant(const std::vector<uint32_t>& words,
uint32_t type_id,
bool is_irrelevant) {
assert(type_id && "Constant's type id can't be 0.");
const auto* type = GetIRContext()->get_type_mgr()->GetType(type_id);
assert(type && "Type does not exist.");
if (type->AsBool()) {
assert(words.size() == 1);
return FindOrCreateBoolConstant(words[0], is_irrelevant);
} else if (const auto* integer = type->AsInteger()) {
return FindOrCreateIntegerConstant(words, integer->width(),
integer->IsSigned(), is_irrelevant);
} else if (const auto* floating = type->AsFloat()) {
return FindOrCreateFloatConstant(words, floating->width(), is_irrelevant);
}
// This assertion will fail in debug build but not in release build
// so we return 0 to make compiler happy.
assert(false && "Constant type is not supported");
return 0;
}
uint32_t FuzzerPass::FindOrCreateCompositeConstant(
const std::vector<uint32_t>& component_ids, uint32_t type_id,
bool is_irrelevant) {
if (auto existing_constant = fuzzerutil::MaybeGetCompositeConstant(
GetIRContext(), *GetTransformationContext(), component_ids, type_id,
is_irrelevant)) {
return existing_constant;
}
uint32_t result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddConstantComposite(
result, type_id, component_ids, is_irrelevant));
return result;
}
uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) {
for (auto& inst : GetIRContext()->types_values()) {
if (inst.opcode() == SpvOpUndef && inst.type_id() == type_id) {
return inst.result_id();
}
}
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddGlobalUndef(result, type_id));
return result;
}
uint32_t FuzzerPass::FindOrCreateNullConstant(uint32_t type_id) {
// Find existing declaration
opt::analysis::NullConstant null_constant(
GetIRContext()->get_type_mgr()->GetType(type_id));
auto existing_constant =
GetIRContext()->get_constant_mgr()->FindConstant(&null_constant);
// Return if found
if (existing_constant) {
return GetIRContext()
->get_constant_mgr()
->GetDefiningInstruction(existing_constant)
->result_id();
}
// Create new if not found
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddConstantNull(result, type_id));
return result;
}
std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
FuzzerPass::GetAvailableBasicTypesAndPointers(
SpvStorageClass storage_class) const {
// Records all of the basic types available in the module.
std::set<uint32_t> basic_types;
// For each basic type, records all the associated pointer types that target
// the basic type and that have |storage_class| as their storage class.
std::map<uint32_t, std::vector<uint32_t>> basic_type_to_pointers;
for (auto& inst : GetIRContext()->types_values()) {
// For each basic type that we come across, record type, and the fact that
// we cannot yet have seen any pointers that use the basic type as its
// pointee type.
//
// For pointer types with basic pointee types, associate the pointer type
// with the basic type.
switch (inst.opcode()) {
case SpvOpTypeBool:
case SpvOpTypeFloat:
case SpvOpTypeInt:
case SpvOpTypeMatrix:
case SpvOpTypeVector:
// These are all basic types.
basic_types.insert(inst.result_id());
basic_type_to_pointers.insert({inst.result_id(), {}});
break;
case SpvOpTypeArray:
// An array type is basic if its base type is basic.
if (basic_types.count(inst.GetSingleWordInOperand(0))) {
basic_types.insert(inst.result_id());
basic_type_to_pointers.insert({inst.result_id(), {}});
}
break;
case SpvOpTypeStruct: {
// A struct type is basic if all of its members are basic.
bool all_members_are_basic_types = true;
for (uint32_t i = 0; i < inst.NumInOperands(); i++) {
if (!basic_types.count(inst.GetSingleWordInOperand(i))) {
all_members_are_basic_types = false;
break;
}
}
if (all_members_are_basic_types) {
basic_types.insert(inst.result_id());
basic_type_to_pointers.insert({inst.result_id(), {}});
}
break;
}
case SpvOpTypePointer: {
// We are interested in the pointer if its pointee type is basic and it
// has the right storage class.
auto pointee_type = inst.GetSingleWordInOperand(1);
if (inst.GetSingleWordInOperand(0) == storage_class &&
basic_types.count(pointee_type)) {
// The pointer has the desired storage class, and its pointee type is
// a basic type, so we are interested in it. Associate it with its
// basic type.
basic_type_to_pointers.at(pointee_type).push_back(inst.result_id());
}
break;
}
default:
break;
}
}
return {{basic_types.begin(), basic_types.end()}, basic_type_to_pointers};
}
uint32_t FuzzerPass::FindOrCreateZeroConstant(
uint32_t scalar_or_composite_type_id, bool is_irrelevant) {
auto type_instruction =
GetIRContext()->get_def_use_mgr()->GetDef(scalar_or_composite_type_id);
assert(type_instruction && "The type instruction must exist.");
switch (type_instruction->opcode()) {
case SpvOpTypeBool:
return FindOrCreateBoolConstant(false, is_irrelevant);
case SpvOpTypeFloat: {
auto width = type_instruction->GetSingleWordInOperand(0);
auto num_words = (width + 32 - 1) / 32;
return FindOrCreateFloatConstant(std::vector<uint32_t>(num_words, 0),
width, is_irrelevant);
}
case SpvOpTypeInt: {
auto width = type_instruction->GetSingleWordInOperand(0);
auto num_words = (width + 32 - 1) / 32;
return FindOrCreateIntegerConstant(
std::vector<uint32_t>(num_words, 0), width,
type_instruction->GetSingleWordInOperand(1), is_irrelevant);
}
case SpvOpTypeArray: {
auto component_type_id = type_instruction->GetSingleWordInOperand(0);
auto num_components =
fuzzerutil::GetArraySize(*type_instruction, GetIRContext());
return FindOrCreateCompositeConstant(
std::vector<uint32_t>(
num_components,
FindOrCreateZeroConstant(component_type_id, is_irrelevant)),
scalar_or_composite_type_id, is_irrelevant);
}
case SpvOpTypeMatrix:
case SpvOpTypeVector: {
auto component_type_id = type_instruction->GetSingleWordInOperand(0);
auto num_components = type_instruction->GetSingleWordInOperand(1);
return FindOrCreateCompositeConstant(
std::vector<uint32_t>(
num_components,
FindOrCreateZeroConstant(component_type_id, is_irrelevant)),
scalar_or_composite_type_id, is_irrelevant);
}
case SpvOpTypeStruct: {
std::vector<uint32_t> field_zero_ids;
for (uint32_t index = 0; index < type_instruction->NumInOperands();
index++) {
field_zero_ids.push_back(FindOrCreateZeroConstant(
type_instruction->GetSingleWordInOperand(index), is_irrelevant));
}
return FindOrCreateCompositeConstant(
field_zero_ids, scalar_or_composite_type_id, is_irrelevant);
}
default:
assert(false && "Unknown type.");
return 0;
}
}
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>>*
uses_to_replace) {
// Only consider this use if it is in a block
if (!GetIRContext()->get_instr_block(use_inst)) {
return;
}
// Get the index of the operand restricted to input operands.
uint32_t in_operand_index =
fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
auto id_use_descriptor =
MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, in_operand_index);
uses_to_replace->emplace_back(
std::make_pair(id_use_descriptor, replacement_id));
}
opt::BasicBlock* FuzzerPass::GetOrCreateSimpleLoopPreheader(
uint32_t header_id) {
auto header_block = fuzzerutil::MaybeFindBlock(GetIRContext(), header_id);
assert(header_block && header_block->IsLoopHeader() &&
"|header_id| should be the label id of a loop header");
auto predecessors = GetIRContext()->cfg()->preds(header_id);
assert(predecessors.size() >= 2 &&
"The block |header_id| should be reachable.");
auto function = header_block->GetParent();
if (predecessors.size() == 2) {
// The header has a single out-of-loop predecessor, which could be a
// preheader.
opt::BasicBlock* maybe_preheader;
if (GetIRContext()->GetDominatorAnalysis(function)->Dominates(
header_id, predecessors[0])) {
// The first predecessor is the back-edge block, because the header
// dominates it, so the second one is out of the loop.
maybe_preheader = &*function->FindBlock(predecessors[1]);
} else {
// The first predecessor is out of the loop.
maybe_preheader = &*function->FindBlock(predecessors[0]);
}
// |maybe_preheader| is a preheader if it branches unconditionally to
// the header. We also require it not to be a loop header.
if (maybe_preheader->terminator()->opcode() == SpvOpBranch &&
!maybe_preheader->IsLoopHeader()) {
return maybe_preheader;
}
}
// We need to add a preheader.
// Get a fresh id for the preheader.
uint32_t preheader_id = GetFuzzerContext()->GetFreshId();
// Get a fresh id for each OpPhi instruction, if there is more than one
// out-of-loop predecessor.
std::vector<uint32_t> phi_ids;
if (predecessors.size() > 2) {
header_block->ForEachPhiInst(
[this, &phi_ids](opt::Instruction* /* unused */) {
phi_ids.push_back(GetFuzzerContext()->GetFreshId());
});
}
// Add the preheader.
ApplyTransformation(
TransformationAddLoopPreheader(header_id, preheader_id, phi_ids));
// Make the newly-created preheader the new entry block.
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

@@ -1,330 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_H_
#define SOURCE_FUZZ_FUZZER_PASS_H_
#include <functional>
#include <vector>
#include "source/fuzz/fuzzer_context.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 {
// Interface for applying a pass of transformations to a module.
class FuzzerPass {
public:
FuzzerPass(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
virtual ~FuzzerPass();
// Applies the pass to the module |ir_context_|, assuming and updating
// information from |transformation_context_|, and using |fuzzer_context_| to
// guide the process. Appends to |transformations_| all transformations that
// were applied during the pass.
virtual void Apply() = 0;
protected:
opt::IRContext* GetIRContext() const { return ir_context_; }
TransformationContext* GetTransformationContext() const {
return transformation_context_;
}
FuzzerContext* GetFuzzerContext() const { return fuzzer_context_; }
protobufs::TransformationSequence* GetTransformations() const {
return transformations_;
}
// Returns all instructions that are *available* at |inst_it|, which is
// required to be inside block |block| of function |function| - that is, all
// instructions at global scope and all instructions that strictly dominate
// |inst_it|.
//
// Filters said instructions to return only those that satisfy the
// |instruction_is_relevant| predicate. This, for instance, could ignore all
// instructions that have a particular decoration.
std::vector<opt::Instruction*> FindAvailableInstructions(
opt::Function* function, opt::BasicBlock* block,
const opt::BasicBlock::iterator& inst_it,
std::function<bool(opt::IRContext*, opt::Instruction*)>
instruction_is_relevant) const;
// A helper method that iterates through each instruction in each block, at
// all times tracking an instruction descriptor that allows the latest
// instruction to be located even if it has no result id.
//
// 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
// transformation passes.
//
// The function |action| is invoked for each instruction |inst_it| in block
// |block| of function |function| that is encountered. The
// |instruction_descriptor| parameter to the function object allows |inst_it|
// to be identified.
//
// In most intended use cases, the job of |action| is to randomly decide
// whether to try to apply some transformation, and then - if selected - to
// attempt to apply it.
void ForEachInstructionWithInstructionDescriptor(
std::function<
void(opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor)>
action);
// A generic helper for applying a transformation that should be applicable
// by construction, and adding it to the sequence of applied transformations.
void ApplyTransformation(const Transformation& transformation) {
assert(transformation.IsApplicable(GetIRContext(),
*GetTransformationContext()) &&
"Transformation should be applicable by construction.");
transformation.Apply(GetIRContext(), GetTransformationContext());
*GetTransformations()->add_transformation() = transformation.ToMessage();
}
// A generic helper for applying a transformation only if it is applicable.
// If it is applicable, the transformation is applied and then added to the
// sequence of applied transformations and the function returns true.
// Otherwise, the function returns false.
bool MaybeApplyTransformation(const Transformation& transformation) {
if (transformation.IsApplicable(GetIRContext(),
*GetTransformationContext())) {
transformation.Apply(GetIRContext(), GetTransformationContext());
*GetTransformations()->add_transformation() = transformation.ToMessage();
return true;
}
return false;
}
// Returns the id of an OpTypeBool instruction. If such an instruction does
// not exist, a transformation is applied to add it.
uint32_t FindOrCreateBoolType();
// Returns the id of an OpTypeInt instruction, with width and signedness
// specified by |width| and |is_signed|, respectively. If such an instruction
// does not exist, a transformation is applied to add it.
uint32_t FindOrCreateIntegerType(uint32_t width, bool is_signed);
// Returns the id of an OpTypeFloat instruction, with width specified by
// |width|. If such an instruction does not exist, a transformation is
// applied to add it.
uint32_t FindOrCreateFloatType(uint32_t width);
// Returns the id of an OpTypeFunction %<return_type_id> %<...argument_id>
// instruction. If such an instruction doesn't exist, a transformation
// is applied to create a new one.
uint32_t FindOrCreateFunctionType(uint32_t return_type_id,
const std::vector<uint32_t>& argument_id);
// Returns the id of an OpTypeVector instruction, with |component_type_id|
// (which must already exist) as its base type, and |component_count|
// elements (which must be in the range [2, 4]). If such an instruction does
// not exist, a transformation is applied to add it.
uint32_t FindOrCreateVectorType(uint32_t component_type_id,
uint32_t component_count);
// Returns the id of an OpTypeMatrix instruction, with |column_count| columns
// and |row_count| rows (each of which must be in the range [2, 4]). If the
// float and vector types required to build this matrix type or the matrix
// type itself do not exist, transformations are applied to add them.
uint32_t FindOrCreateMatrixType(uint32_t column_count, uint32_t row_count);
// Returns the id of an OpTypeStruct instruction with |component_type_ids| as
// type ids for struct's components. If no such a struct type exists,
// transformations are applied to add it. |component_type_ids| may not contain
// a result id of an OpTypeFunction.
uint32_t FindOrCreateStructType(
const std::vector<uint32_t>& component_type_ids);
// Returns the id of a pointer type with base type |base_type_id| (which must
// already exist) and storage class |storage_class|. A transformation is
// applied to add the pointer if it does not already exist.
uint32_t FindOrCreatePointerType(uint32_t base_type_id,
SpvStorageClass storage_class);
// Returns the id of an OpTypePointer instruction, with a integer base
// type of width and signedness specified by |width| and |is_signed|,
// respectively. If the pointer type or required integer base type do not
// exist, transformations are applied to add them.
uint32_t FindOrCreatePointerToIntegerType(uint32_t width, bool is_signed,
SpvStorageClass storage_class);
// Returns the id of an OpConstant instruction, with a integer type of
// width and signedness specified by |width| and |is_signed|, respectively,
// with |words| as its value. If either the required integer type or the
// constant do not exist, transformations are applied to add them.
// The returned id either participates in IdIsIrrelevant fact or not,
// depending on the |is_irrelevant| parameter.
uint32_t FindOrCreateIntegerConstant(const std::vector<uint32_t>& words,
uint32_t width, bool is_signed,
bool is_irrelevant);
// Returns the id of an OpConstant instruction, with a floating-point
// type of width specified by |width|, with |words| as its value. If either
// the required floating-point type or the constant do not exist,
// transformations are applied to add them. The returned id either
// participates in IdIsIrrelevant fact or not, depending on the
// |is_irrelevant| parameter.
uint32_t FindOrCreateFloatConstant(const std::vector<uint32_t>& words,
uint32_t width, bool is_irrelevant);
// Returns the id of an OpConstantTrue or OpConstantFalse instruction,
// according to |value|. If either the required instruction or the bool
// type do not exist, transformations are applied to add them.
// The returned id either participates in IdIsIrrelevant fact or not,
// depending on the |is_irrelevant| parameter.
uint32_t FindOrCreateBoolConstant(bool value, bool is_irrelevant);
// Returns the id of an OpConstant instruction of type with |type_id|
// that consists of |words|. If that instruction doesn't exist,
// transformations are applied to add it. |type_id| must be a valid
// result id of either scalar or boolean OpType* instruction that exists
// in the module. The returned id either participates in IdIsIrrelevant fact
// or not, depending on the |is_irrelevant| parameter.
uint32_t FindOrCreateConstant(const std::vector<uint32_t>& words,
uint32_t type_id, bool is_irrelevant);
// Returns the id of an OpConstantComposite instruction of type with |type_id|
// that consists of |component_ids|. If that instruction doesn't exist,
// transformations are applied to add it. |type_id| must be a valid
// result id of an OpType* instruction that represents a composite type
// (i.e. a vector, matrix, struct or array).
// The returned id either participates in IdIsIrrelevant fact or not,
// depending on the |is_irrelevant| parameter.
uint32_t FindOrCreateCompositeConstant(
const std::vector<uint32_t>& component_ids, uint32_t type_id,
bool is_irrelevant);
// Returns the result id of an instruction of the form:
// %id = OpUndef %|type_id|
// If no such instruction exists, a transformation is applied to add it.
uint32_t FindOrCreateGlobalUndef(uint32_t type_id);
// Returns the id of an OpNullConstant instruction of type |type_id|. If
// that instruction doesn't exist, it is added through a transformation.
// |type_id| must be a valid result id of an OpType* instruction that exists
// in the module.
uint32_t FindOrCreateNullConstant(uint32_t type_id);
// Define a *basic type* to be an integer, boolean or floating-point type,
// or a matrix, vector, struct or fixed-size array built from basic types. In
// particular, a basic type cannot contain an opaque type (such as an image),
// or a runtime-sized array.
//
// Yields a pair, (basic_type_ids, basic_type_ids_to_pointers), such that:
// - basic_type_ids captures every basic type declared in the module.
// - basic_type_ids_to_pointers maps every such basic type to the sequence
// of all pointer types that have storage class |storage_class| and the
// given basic type as their pointee type. The sequence may be empty for
// some basic types if no pointers to those types are defined for the given
// storage class, and the sequence will have multiple elements if there are
// repeated pointer declarations for the same basic type and storage class.
std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
GetAvailableBasicTypesAndPointers(SpvStorageClass storage_class) const;
// Given a type id, |scalar_or_composite_type_id|, which must correspond to
// some scalar or composite type, returns the result id of an instruction
// defining a constant of the given type that is zero or false at everywhere.
// If such an instruction does not yet exist, transformations are applied to
// add it. The returned id either participates in IdIsIrrelevant fact or not,
// depending on the |is_irrelevant| parameter.
//
// Examples:
// --------------+-------------------------------
// TYPE | RESULT is id corresponding to
// --------------+-------------------------------
// bool | false
// --------------+-------------------------------
// bvec4 | (false, false, false, false)
// --------------+-------------------------------
// float | 0.0
// --------------+-------------------------------
// vec2 | (0.0, 0.0)
// --------------+-------------------------------
// int[3] | [0, 0, 0]
// --------------+-------------------------------
// struct S { |
// int i; | S(0, false, (0u, 0u))
// bool b; |
// uint2 u; |
// } |
// --------------+-------------------------------
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
// index |use_index|, only if the instruction is in a basic block.
// If the instruction is not in a basic block, it does nothing.
void MaybeAddUseToReplace(
opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
uses_to_replace);
// Returns the preheader of the loop with header |header_id|, which satisfies
// all of the following conditions:
// - It is the only out-of-loop predecessor of the header
// - It unconditionally branches to the header
// - It is not a loop header itself
// If such preheader does not exist, a new one is added and returned.
// Requires |header_id| to be the label id of a loop header block that is
// 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_;
FuzzerContext* fuzzer_context_;
protobufs::TransformationSequence* transformations_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_H_

View File

@@ -1,192 +0,0 @@
// 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_access_chains.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_access_chain.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddAccessChains::FuzzerPassAddAccessChains(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddAccessChains::~FuzzerPassAddAccessChains() = default;
void FuzzerPassAddAccessChains::Apply() {
ForEachInstructionWithInstructionDescriptor(
[this](opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor)
-> void {
assert(inst_it->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");
// Check whether it is legitimate to insert an access chain
// instruction before this instruction.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpAccessChain,
inst_it)) {
return;
}
// Randomly decide whether to try inserting a load here.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingAccessChain())) {
return;
}
// Get all of the pointers that are currently in scope, excluding
// explicitly null and undefined pointers.
std::vector<opt::Instruction*> relevant_pointer_instructions =
FindAvailableInstructions(
function, block, inst_it,
[](opt::IRContext* context,
opt::Instruction* instruction) -> bool {
if (!instruction->result_id() || !instruction->type_id()) {
// A pointer needs both a result and type id.
return false;
}
switch (instruction->opcode()) {
case SpvOpConstantNull:
case SpvOpUndef:
// Do not allow making an access chain from a null or
// undefined pointer. (We can eliminate these cases
// before actually checking that the instruction is a
// pointer.)
return false;
default:
break;
}
// If the instruction has pointer type, we can legitimately
// make an access chain from it.
return context->get_def_use_mgr()
->GetDef(instruction->type_id())
->opcode() == SpvOpTypePointer;
});
// At this point, |relevant_instructions| contains all the pointers
// we might think of making an access chain from.
if (relevant_pointer_instructions.empty()) {
return;
}
auto chosen_pointer =
relevant_pointer_instructions[GetFuzzerContext()->RandomIndex(
relevant_pointer_instructions)];
std::vector<uint32_t> index_ids;
// Each index accessing a non-struct composite will be clamped, thus
// needing a pair of fresh ids
std::vector<std::pair<uint32_t, uint32_t>> fresh_ids_for_clamping;
auto pointer_type = GetIRContext()->get_def_use_mgr()->GetDef(
chosen_pointer->type_id());
uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1);
while (true) {
auto subobject_type =
GetIRContext()->get_def_use_mgr()->GetDef(subobject_type_id);
if (!spvOpcodeIsComposite(subobject_type->opcode())) {
break;
}
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfGoingDeeperWhenMakingAccessChain())) {
break;
}
uint32_t bound;
switch (subobject_type->opcode()) {
case SpvOpTypeArray:
bound = fuzzerutil::GetArraySize(*subobject_type, GetIRContext());
break;
case SpvOpTypeMatrix:
case SpvOpTypeVector:
bound = subobject_type->GetSingleWordInOperand(1);
break;
case SpvOpTypeStruct:
bound = fuzzerutil::GetNumberOfStructMembers(*subobject_type);
break;
default:
assert(false && "Not a composite type opcode.");
// Set the bound to a value in order to keep release compilers
// happy.
bound = 0;
break;
}
if (bound == 0) {
// It is possible for a composite type to legitimately have zero
// sub-components, at least in the case of a struct, which
// can have no fields.
break;
}
uint32_t index_value =
GetFuzzerContext()->GetRandomIndexForAccessChain(bound);
switch (subobject_type->opcode()) {
case SpvOpTypeArray:
case SpvOpTypeMatrix:
case SpvOpTypeVector: {
// The index will be clamped
bool is_signed = GetFuzzerContext()->ChooseEven();
// Make the constant ready for clamping. We need:
// - an OpTypeBool to be present in the module
// - an OpConstant with the same type as the index and value
// the maximum value for an index
// - a new pair of fresh ids for the clamping instructions
FindOrCreateBoolType();
FindOrCreateIntegerConstant({bound - 1}, 32, is_signed, false);
std::pair<uint32_t, uint32_t> fresh_pair_of_ids = {
GetFuzzerContext()->GetFreshId(),
GetFuzzerContext()->GetFreshId()};
fresh_ids_for_clamping.emplace_back(fresh_pair_of_ids);
index_ids.push_back(FindOrCreateIntegerConstant(
{index_value}, 32, is_signed, false));
subobject_type_id = subobject_type->GetSingleWordInOperand(0);
} break;
case SpvOpTypeStruct:
index_ids.push_back(FindOrCreateIntegerConstant(
{index_value}, 32, GetFuzzerContext()->ChooseEven(), false));
subobject_type_id =
subobject_type->GetSingleWordInOperand(index_value);
break;
default:
assert(false && "Not a composite type opcode.");
}
}
// The transformation we are about to create will only apply if a
// pointer suitable for the access chain's result type exists, so we
// create one if it does not.
FindOrCreatePointerType(subobject_type_id,
static_cast<SpvStorageClass>(
pointer_type->GetSingleWordInOperand(0)));
// Apply the transformation to add an access chain.
ApplyTransformation(TransformationAccessChain(
GetFuzzerContext()->GetFreshId(), chosen_pointer->result_id(),
index_ids, instruction_descriptor, fresh_ids_for_clamping));
});
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,41 +0,0 @@
// 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_FUZZER_PASS_ADD_ACCESS_CHAINS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_ACCESS_CHAINS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Fuzzer pass that randomly adds access chains based on pointers available in
// the module. Other passes can use these access chains, e.g. by loading from
// them.
class FuzzerPassAddAccessChains : public FuzzerPass {
public:
FuzzerPassAddAccessChains(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddAccessChains();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_ACCESS_CHAINS_H_

View File

@@ -1,236 +0,0 @@
// 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

@@ -1,45 +0,0 @@
// 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

@@ -1,139 +0,0 @@
// 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_types.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_add_type_array.h"
#include "source/fuzz/transformation_add_type_struct.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddCompositeTypes::FuzzerPassAddCompositeTypes(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddCompositeTypes::~FuzzerPassAddCompositeTypes() = default;
void FuzzerPassAddCompositeTypes::Apply() {
MaybeAddMissingVectorTypes();
MaybeAddMissingMatrixTypes();
// Randomly interleave between adding struct and array composite types
while (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingArrayOrStructType())) {
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfChoosingStructTypeVsArrayType())) {
AddNewStructType();
} else {
AddNewArrayType();
}
}
}
void FuzzerPassAddCompositeTypes::MaybeAddMissingVectorTypes() {
// Functions to lazily supply scalar base types on demand if we decide to
// create vectors with the relevant base types.
std::function<uint32_t()> bool_type_supplier = [this]() -> uint32_t {
return FindOrCreateBoolType();
};
std::function<uint32_t()> float_type_supplier = [this]() -> uint32_t {
return FindOrCreateFloatType(32);
};
std::function<uint32_t()> int_type_supplier = [this]() -> uint32_t {
return FindOrCreateIntegerType(32, true);
};
std::function<uint32_t()> uint_type_supplier = [this]() -> uint32_t {
return FindOrCreateIntegerType(32, false);
};
// Consider each of the base types with which we can make vectors.
for (auto& base_type_supplier : {bool_type_supplier, float_type_supplier,
int_type_supplier, uint_type_supplier}) {
// Consider each valid vector size.
for (uint32_t size = 2; size <= 4; size++) {
// Randomly decide whether to create (if it does not already exist) a
// vector with this size and base type.
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingVectorType())) {
FindOrCreateVectorType(base_type_supplier(), size);
}
}
}
}
void FuzzerPassAddCompositeTypes::MaybeAddMissingMatrixTypes() {
// Consider every valid matrix dimension.
for (uint32_t columns = 2; columns <= 4; columns++) {
for (uint32_t rows = 2; rows <= 4; rows++) {
// Randomly decide whether to create (if it does not already exist) a
// matrix with these dimensions. As matrices can only have floating-point
// base type, we do not need to consider multiple base types as in the
// case for vectors.
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingMatrixType())) {
FindOrCreateMatrixType(columns, rows);
}
}
}
}
void FuzzerPassAddCompositeTypes::AddNewArrayType() {
ApplyTransformation(TransformationAddTypeArray(
GetFuzzerContext()->GetFreshId(), ChooseScalarOrCompositeType(),
FindOrCreateIntegerConstant(
{GetFuzzerContext()->GetRandomSizeForNewArray()}, 32, false, false)));
}
void FuzzerPassAddCompositeTypes::AddNewStructType() {
std::vector<uint32_t> field_type_ids;
do {
field_type_ids.push_back(ChooseScalarOrCompositeType());
} while (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingAnotherStructField()));
ApplyTransformation(TransformationAddTypeStruct(
GetFuzzerContext()->GetFreshId(), field_type_ids));
}
uint32_t FuzzerPassAddCompositeTypes::ChooseScalarOrCompositeType() {
// Gather up all the possibly-relevant types.
std::vector<uint32_t> candidates;
for (auto& inst : GetIRContext()->types_values()) {
switch (inst.opcode()) {
case SpvOpTypeArray:
case SpvOpTypeBool:
case SpvOpTypeFloat:
case SpvOpTypeInt:
case SpvOpTypeMatrix:
case SpvOpTypeStruct:
case SpvOpTypeVector:
candidates.push_back(inst.result_id());
break;
default:
break;
}
}
assert(!candidates.empty() &&
"This function should only be called if there is at least one scalar "
"or composite type available.");
// Return one of these types at random.
return candidates[GetFuzzerContext()->RandomIndex(candidates)];
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,61 +0,0 @@
// 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_FUZZER_PASS_ADD_COMPOSITE_TYPES_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_TYPES_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Fuzzer pass that randomly adds missing vector and matrix types, and new
// array and struct types, to the module.
class FuzzerPassAddCompositeTypes : public FuzzerPass {
public:
FuzzerPassAddCompositeTypes(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddCompositeTypes();
void Apply() override;
private:
// Creates an array of a random size with a random existing base type and adds
// it to the module.
void AddNewArrayType();
// Creates a struct with fields of random existing types and adds it to the
// module.
void AddNewStructType();
// For each vector type not already present in the module, randomly decides
// whether to add it to the module.
void MaybeAddMissingVectorTypes();
// For each matrix type not already present in the module, randomly decides
// whether to add it to the module.
void MaybeAddMissingMatrixTypes();
// Returns the id of a scalar or composite type declared in the module,
// chosen randomly.
uint32_t ChooseScalarOrCompositeType();
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_TYPES_H_

View File

@@ -1,82 +0,0 @@
// 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_copy_memory.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_add_copy_memory.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddCopyMemory::FuzzerPassAddCopyMemory(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddCopyMemory::~FuzzerPassAddCopyMemory() = default;
void FuzzerPassAddCopyMemory::Apply() {
ForEachInstructionWithInstructionDescriptor(
[this](opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor) {
// Check that we can insert an OpCopyMemory before this instruction.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCopyMemory,
inst_it)) {
return;
}
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingCopyMemory())) {
return;
}
// Get all instructions available before |inst_it| according to the
// domination rules.
auto instructions = FindAvailableInstructions(
function, block, inst_it,
TransformationAddCopyMemory::IsInstructionSupported);
if (instructions.empty()) {
return;
}
const auto* inst =
instructions[GetFuzzerContext()->RandomIndex(instructions)];
// Decide whether to create global or local variable.
auto storage_class = GetFuzzerContext()->ChooseEven()
? SpvStorageClassPrivate
: SpvStorageClassFunction;
auto pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
GetIRContext(), inst->type_id());
// Create a pointer type with |storage_class| if needed.
FindOrCreatePointerType(pointee_type_id, storage_class);
ApplyTransformation(TransformationAddCopyMemory(
instruction_descriptor, GetFuzzerContext()->GetFreshId(),
inst->result_id(), storage_class,
FindOrCreateZeroConstant(pointee_type_id, false)));
});
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,40 +0,0 @@
// 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.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_COPY_MEMORY_INSTRUCTIONS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_COPY_MEMORY_INSTRUCTIONS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Randomly decides whether to add OpCopyMemory before some instruction in the
// module.
class FuzzerPassAddCopyMemory : public FuzzerPass {
public:
FuzzerPassAddCopyMemory(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddCopyMemory() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_COPY_MEMORY_INSTRUCTIONS_H_

View File

@@ -1,67 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_add_dead_block.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddDeadBlocks::FuzzerPassAddDeadBlocks(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddDeadBlocks::~FuzzerPassAddDeadBlocks() = default;
void FuzzerPassAddDeadBlocks::Apply() {
// We iterate over all blocks in the module collecting up those at which we
// might add a branch to a new dead block. We then loop over all such
// candidates and actually apply transformations. This separation is to
// avoid modifying the module as we traverse it.
std::vector<TransformationAddDeadBlock> candidate_transformations;
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingDeadBlock())) {
continue;
}
// Make sure the module contains a boolean constant equal to
// |condition_value|.
bool condition_value = GetFuzzerContext()->ChooseEven();
FindOrCreateBoolConstant(condition_value, false);
// We speculatively create a transformation, and then apply it (below) if
// it turns out to be applicable. This avoids duplicating the logic for
// applicability checking.
//
// It means that fresh ids for transformations that turn out not to be
// applicable end up being unused.
candidate_transformations.emplace_back(TransformationAddDeadBlock(
GetFuzzerContext()->GetFreshId(), block.id(), condition_value));
}
}
// Apply all those transformations that are in fact applicable.
for (auto& transformation : candidate_transformations) {
MaybeApplyTransformation(transformation);
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,40 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_BLOCKS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_BLOCKS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Fuzzer pass to add dynamically unreachable blocks to the module. Future
// passes can then manipulate such blocks.
class FuzzerPassAddDeadBlocks : public FuzzerPass {
public:
FuzzerPassAddDeadBlocks(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddDeadBlocks();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_BLOCKS_H_

View File

@@ -1,122 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_add_dead_break.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddDeadBreaks::FuzzerPassAddDeadBreaks(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddDeadBreaks::~FuzzerPassAddDeadBreaks() = default;
void FuzzerPassAddDeadBreaks::Apply() {
// We first collect up lots of possibly-applicable transformations.
std::vector<TransformationAddDeadBreak> candidate_transformations;
// We consider each function separately.
for (auto& function : *GetIRContext()->module()) {
// For a given function, we find all the merge blocks in that function.
std::vector<opt::BasicBlock*> merge_blocks;
for (auto& block : function) {
auto maybe_merge_id = block.MergeBlockIdIfAny();
if (maybe_merge_id) {
auto merge_block =
fuzzerutil::MaybeFindBlock(GetIRContext(), maybe_merge_id);
assert(merge_block && "Merge block can't be null");
merge_blocks.push_back(merge_block);
}
}
// We rather aggressively consider the possibility of adding a break from
// every block in the function to every merge block. Many of these will be
// inapplicable as they would be illegal. That's OK - we later discard the
// ones that turn out to be no good.
for (auto& block : function) {
for (auto* merge_block : merge_blocks) {
// Populate this vector with ids that are available at the branch point
// of this basic block. We will use these ids to update OpPhi
// instructions later.
std::vector<uint32_t> phi_ids;
// Determine how we need to adjust OpPhi instructions' operands
// for this transformation to be valid.
//
// If |block| has a branch to |merge_block|, the latter must have all of
// its OpPhi instructions set up correctly - we don't need to adjust
// anything.
if (!block.IsSuccessor(merge_block)) {
merge_block->ForEachPhiInst([this, &phi_ids](opt::Instruction* phi) {
// Add an additional operand for OpPhi instruction.
//
// We mark the constant as irrelevant so that we can replace it with
// a more interesting value later.
phi_ids.push_back(FindOrCreateZeroConstant(phi->type_id(), true));
});
}
// Make sure the module has a required boolean constant to be used in
// OpBranchConditional instruction.
auto break_condition = GetFuzzerContext()->ChooseEven();
FindOrCreateBoolConstant(break_condition, false);
auto candidate_transformation = TransformationAddDeadBreak(
block.id(), merge_block->id(), break_condition, std::move(phi_ids));
if (candidate_transformation.IsApplicable(
GetIRContext(), *GetTransformationContext())) {
// Only consider a transformation as a candidate if it is applicable.
candidate_transformations.push_back(
std::move(candidate_transformation));
}
}
}
}
// Go through the candidate transformations that were accumulated,
// probabilistically deciding whether to consider each one further and
// applying the still-applicable ones that are considered further.
//
// We iterate through the candidate transformations in a random order by
// repeatedly removing a random candidate transformation from the sequence
// until no candidate transformations remain. This is done because
// transformations can potentially disable one another, so that iterating
// through them in order would lead to a higher probability of
// transformations appearing early in the sequence being applied compared
// with later transformations.
while (!candidate_transformations.empty()) {
// Choose a random index into the sequence of remaining candidate
// transformations.
auto index = GetFuzzerContext()->RandomIndex(candidate_transformations);
// Remove the transformation at the chosen index from the sequence.
auto transformation = std::move(candidate_transformations[index]);
candidate_transformations.erase(candidate_transformations.begin() + index);
// Probabilistically decide whether to try to apply it vs. ignore it, in the
// case that it is applicable.
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingDeadBreak())) {
MaybeApplyTransformation(transformation);
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,39 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_BREAKS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_BREAKS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass for adding dead break edges to the module.
class FuzzerPassAddDeadBreaks : public FuzzerPass {
public:
FuzzerPassAddDeadBreaks(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddDeadBreaks();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_BREAKS_H_

View File

@@ -1,89 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_add_dead_continues.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_add_dead_continue.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddDeadContinues::FuzzerPassAddDeadContinues(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddDeadContinues::~FuzzerPassAddDeadContinues() = default;
void FuzzerPassAddDeadContinues::Apply() {
// Consider every block in every function.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// Get the label id of the continue target of the innermost loop.
auto continue_block_id =
block.IsLoopHeader()
? block.ContinueBlockId()
: GetIRContext()->GetStructuredCFGAnalysis()->LoopContinueBlock(
block.id());
// This transformation is not applicable if current block is not inside a
// loop.
if (continue_block_id == 0) {
continue;
}
auto* continue_block =
fuzzerutil::MaybeFindBlock(GetIRContext(), continue_block_id);
assert(continue_block && "Continue block is null");
// Analyze return type of each OpPhi instruction in the continue target
// and provide an id for the transformation if needed.
std::vector<uint32_t> phi_ids;
// Check whether current block has an edge to the continue target.
// If this is the case, we don't need to do anything.
if (!block.IsSuccessor(continue_block)) {
continue_block->ForEachPhiInst([this, &phi_ids](opt::Instruction* phi) {
// Add an additional operand for OpPhi instruction.
//
// We mark the constant as irrelevant so that we can replace it with a
// more interesting value later.
phi_ids.push_back(FindOrCreateZeroConstant(phi->type_id(), true));
});
}
// Make sure the module contains a boolean constant equal to
// |condition_value|.
bool condition_value = GetFuzzerContext()->ChooseEven();
FindOrCreateBoolConstant(condition_value, false);
// Make a transformation to add a dead continue from this node; if the
// node turns out to be inappropriate (e.g. by not being in a loop) the
// precondition for the transformation will fail and it will be ignored.
auto candidate_transformation = TransformationAddDeadContinue(
block.id(), condition_value, std::move(phi_ids));
// Probabilistically decide whether to apply the transformation in the
// case that it is applicable.
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingDeadContinue())) {
MaybeApplyTransformation(candidate_transformation);
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,39 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_CONTINUES_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_CONTINUES_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass for adding dead continue edges to the module.
class FuzzerPassAddDeadContinues : public FuzzerPass {
public:
FuzzerPassAddDeadContinues(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddDeadContinues();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_CONTINUES_H_

View File

@@ -1,413 +0,0 @@
// 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_equation_instructions.h"
#include <vector>
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_equation_instruction.h"
namespace spvtools {
namespace fuzz {
namespace {
bool IsBitWidthSupported(opt::IRContext* ir_context, uint32_t bit_width) {
switch (bit_width) {
case 32:
return true;
case 64:
return ir_context->get_feature_mgr()->HasCapability(
SpvCapabilityFloat64) &&
ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt64);
case 16:
return ir_context->get_feature_mgr()->HasCapability(
SpvCapabilityFloat16) &&
ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt16);
default:
return false;
}
}
} // namespace
FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddEquationInstructions::~FuzzerPassAddEquationInstructions() =
default;
void FuzzerPassAddEquationInstructions::Apply() {
ForEachInstructionWithInstructionDescriptor(
[this](opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor) {
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingEquationInstruction())) {
return;
}
// Check that it is OK to add an equation instruction before the given
// instruction in principle - e.g. check that this does not lead to
// inserting before an OpVariable or OpPhi instruction. We use OpIAdd
// as an example opcode for this check, to be representative of *some*
// opcode that defines an equation, even though we may choose a
// different opcode below.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpIAdd, inst_it)) {
return;
}
// Get all available instructions with result ids and types that are not
// OpUndef.
std::vector<opt::Instruction*> available_instructions =
FindAvailableInstructions(
function, block, inst_it,
[this](opt::IRContext*, opt::Instruction* instruction) -> bool {
return instruction->result_id() && instruction->type_id() &&
instruction->opcode() != SpvOpUndef &&
!GetTransformationContext()
->GetFactManager()
->IdIsIrrelevant(instruction->result_id());
});
// Try the opcodes for which we know how to make ids at random until
// something works.
std::vector<SpvOp> candidate_opcodes = {
SpvOpIAdd, SpvOpISub, SpvOpLogicalNot, SpvOpSNegate,
SpvOpConvertUToF, SpvOpConvertSToF, SpvOpBitcast};
do {
auto opcode =
GetFuzzerContext()->RemoveAtRandomIndex(&candidate_opcodes);
switch (opcode) {
case SpvOpConvertSToF:
case SpvOpConvertUToF: {
std::vector<const opt::Instruction*> candidate_instructions;
for (const auto* inst :
GetIntegerInstructions(available_instructions)) {
const auto* type =
GetIRContext()->get_type_mgr()->GetType(inst->type_id());
assert(type && "|inst| has invalid type");
if (const auto* vector_type = type->AsVector()) {
type = vector_type->element_type();
}
if (IsBitWidthSupported(GetIRContext(),
type->AsInteger()->width())) {
candidate_instructions.push_back(inst);
}
}
if (candidate_instructions.empty()) {
break;
}
const auto* operand =
candidate_instructions[GetFuzzerContext()->RandomIndex(
candidate_instructions)];
const auto* type =
GetIRContext()->get_type_mgr()->GetType(operand->type_id());
assert(type && "Operand has invalid type");
// Make sure a result type exists in the module.
if (const auto* vector = type->AsVector()) {
// We store element count in a separate variable since the
// call FindOrCreate* functions below might invalidate
// |vector| pointer.
const auto element_count = vector->element_count();
FindOrCreateVectorType(
FindOrCreateFloatType(
vector->element_type()->AsInteger()->width()),
element_count);
} else {
FindOrCreateFloatType(type->AsInteger()->width());
}
ApplyTransformation(TransformationEquationInstruction(
GetFuzzerContext()->GetFreshId(), opcode,
{operand->result_id()}, instruction_descriptor));
return;
}
case SpvOpBitcast: {
const auto candidate_instructions =
GetNumericalInstructions(available_instructions);
if (!candidate_instructions.empty()) {
const auto* operand_inst =
candidate_instructions[GetFuzzerContext()->RandomIndex(
candidate_instructions)];
const auto* operand_type =
GetIRContext()->get_type_mgr()->GetType(
operand_inst->type_id());
assert(operand_type && "Operand instruction has invalid type");
// Make sure a result type exists in the module.
//
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3539):
// The only constraint on the types of OpBitcast's parameters
// is that they must have the same number of bits. Consider
// improving the code below to support this in full.
if (const auto* vector = operand_type->AsVector()) {
// We store element count in a separate variable since the
// call FindOrCreate* functions below might invalidate
// |vector| pointer.
const auto element_count = vector->element_count();
uint32_t element_type_id;
if (const auto* int_type =
vector->element_type()->AsInteger()) {
element_type_id = FindOrCreateFloatType(int_type->width());
} else {
assert(vector->element_type()->AsFloat() &&
"Vector must have numerical elements");
element_type_id = FindOrCreateIntegerType(
vector->element_type()->AsFloat()->width(),
GetFuzzerContext()->ChooseEven());
}
FindOrCreateVectorType(element_type_id, element_count);
} else if (const auto* int_type = operand_type->AsInteger()) {
FindOrCreateFloatType(int_type->width());
} else {
assert(operand_type->AsFloat() &&
"Operand is not a scalar of numerical type");
FindOrCreateIntegerType(operand_type->AsFloat()->width(),
GetFuzzerContext()->ChooseEven());
}
ApplyTransformation(TransformationEquationInstruction(
GetFuzzerContext()->GetFreshId(), opcode,
{operand_inst->result_id()}, instruction_descriptor));
return;
}
} break;
case SpvOpIAdd:
case SpvOpISub: {
// Instructions of integer (scalar or vector) result type are
// suitable for these opcodes.
auto integer_instructions =
GetIntegerInstructions(available_instructions);
if (!integer_instructions.empty()) {
// There is at least one such instruction, so pick one at random
// for the LHS of an equation.
auto lhs = integer_instructions.at(
GetFuzzerContext()->RandomIndex(integer_instructions));
// For the RHS, we can use any instruction with an integer
// scalar/vector result type of the same number of components
// and the same bit-width for the underlying integer type.
// Work out the element count and bit-width.
auto lhs_type =
GetIRContext()->get_type_mgr()->GetType(lhs->type_id());
uint32_t lhs_element_count;
uint32_t lhs_bit_width;
if (lhs_type->AsVector()) {
lhs_element_count = lhs_type->AsVector()->element_count();
lhs_bit_width = lhs_type->AsVector()
->element_type()
->AsInteger()
->width();
} else {
lhs_element_count = 1;
lhs_bit_width = lhs_type->AsInteger()->width();
}
// Get all the instructions that match on element count and
// bit-width.
auto candidate_rhs_instructions = RestrictToElementBitWidth(
RestrictToVectorWidth(integer_instructions,
lhs_element_count),
lhs_bit_width);
// Choose a RHS instruction at random; there is guaranteed to
// be at least one choice as the LHS will be available.
auto rhs = candidate_rhs_instructions.at(
GetFuzzerContext()->RandomIndex(
candidate_rhs_instructions));
// Add the equation instruction.
ApplyTransformation(TransformationEquationInstruction(
GetFuzzerContext()->GetFreshId(), opcode,
{lhs->result_id(), rhs->result_id()},
instruction_descriptor));
return;
}
break;
}
case SpvOpLogicalNot: {
// Choose any available instruction of boolean scalar/vector
// result type and equate its negation with a fresh id.
auto boolean_instructions =
GetBooleanInstructions(available_instructions);
if (!boolean_instructions.empty()) {
ApplyTransformation(TransformationEquationInstruction(
GetFuzzerContext()->GetFreshId(), opcode,
{boolean_instructions
.at(GetFuzzerContext()->RandomIndex(
boolean_instructions))
->result_id()},
instruction_descriptor));
return;
}
break;
}
case SpvOpSNegate: {
// Similar to OpLogicalNot, but for signed integer negation.
auto integer_instructions =
GetIntegerInstructions(available_instructions);
if (!integer_instructions.empty()) {
ApplyTransformation(TransformationEquationInstruction(
GetFuzzerContext()->GetFreshId(), opcode,
{integer_instructions
.at(GetFuzzerContext()->RandomIndex(
integer_instructions))
->result_id()},
instruction_descriptor));
return;
}
break;
}
default:
assert(false && "Unexpected opcode.");
break;
}
} while (!candidate_opcodes.empty());
// Reaching here means that we did not manage to apply any
// transformation at this point of the module.
});
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::GetIntegerInstructions(
const std::vector<opt::Instruction*>& instructions) const {
std::vector<opt::Instruction*> result;
for (auto& inst : instructions) {
auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
if (type->AsInteger() ||
(type->AsVector() && type->AsVector()->element_type()->AsInteger())) {
result.push_back(inst);
}
}
return result;
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::GetFloatInstructions(
const std::vector<opt::Instruction*>& instructions) const {
std::vector<opt::Instruction*> result;
for (auto& inst : instructions) {
auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
if (type->AsFloat() ||
(type->AsVector() && type->AsVector()->element_type()->AsFloat())) {
result.push_back(inst);
}
}
return result;
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::GetBooleanInstructions(
const std::vector<opt::Instruction*>& instructions) const {
std::vector<opt::Instruction*> result;
for (auto& inst : instructions) {
auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
if (type->AsBool() ||
(type->AsVector() && type->AsVector()->element_type()->AsBool())) {
result.push_back(inst);
}
}
return result;
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::RestrictToVectorWidth(
const std::vector<opt::Instruction*>& instructions,
uint32_t vector_width) const {
std::vector<opt::Instruction*> result;
for (auto& inst : instructions) {
auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
// Get the vector width of |inst|, which is 1 if |inst| is a scalar and is
// otherwise derived from its vector type.
uint32_t other_vector_width =
type->AsVector() ? type->AsVector()->element_count() : 1;
// Keep |inst| if the vector widths match.
if (vector_width == other_vector_width) {
result.push_back(inst);
}
}
return result;
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::RestrictToElementBitWidth(
const std::vector<opt::Instruction*>& instructions,
uint32_t bit_width) const {
std::vector<opt::Instruction*> result;
for (auto& inst : instructions) {
const opt::analysis::Type* type =
GetIRContext()->get_type_mgr()->GetType(inst->type_id());
if (type->AsVector()) {
type = type->AsVector()->element_type();
}
assert((type->AsInteger() || type->AsFloat()) &&
"Precondition: all input instructions must "
"have integer or float scalar or vector type.");
if ((type->AsInteger() && type->AsInteger()->width() == bit_width) ||
(type->AsFloat() && type->AsFloat()->width() == bit_width)) {
result.push_back(inst);
}
}
return result;
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::GetNumericalInstructions(
const std::vector<opt::Instruction*>& instructions) const {
std::vector<opt::Instruction*> result;
for (auto* inst : instructions) {
const auto* type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
assert(type && "Instruction has invalid type");
if (const auto* vector_type = type->AsVector()) {
type = vector_type->element_type();
}
if (!type->AsInteger() && !type->AsFloat()) {
// Only numerical scalars or vectors of numerical components are
// supported.
continue;
}
if (!IsBitWidthSupported(GetIRContext(), type->AsInteger()
? type->AsInteger()->width()
: type->AsFloat()->width())) {
continue;
}
result.push_back(inst);
}
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,80 +0,0 @@
// 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_FUZZER_PASS_ADD_EQUATION_INSTRUCTIONS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_EQUATION_INSTRUCTIONS_H_
#include <vector>
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Fuzzer pass that sprinkles instructions through the module that define
// equations using various arithmetic and logical operators.
class FuzzerPassAddEquationInstructions : public FuzzerPass {
public:
FuzzerPassAddEquationInstructions(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddEquationInstructions();
void Apply() override;
private:
// Yields those instructions in |instructions| that have integer scalar or
// vector result type.
std::vector<opt::Instruction*> GetIntegerInstructions(
const std::vector<opt::Instruction*>& instructions) const;
// Returns only instructions, that have either a scalar floating-point or a
// vector type.
std::vector<opt::Instruction*> GetFloatInstructions(
const std::vector<opt::Instruction*>& instructions) const;
// Yields those instructions in |instructions| that have boolean scalar or
// vector result type.
std::vector<opt::Instruction*> GetBooleanInstructions(
const std::vector<opt::Instruction*>& instructions) const;
// Yields those instructions in |instructions| that have a scalar numerical or
// a vector of numerical components type. Only 16, 32 and 64-bit numericals
// are supported if both OpTypeInt and OpTypeFloat instructions can be created
// with the specified width (e.g. for 16-bit types both Float16 and Int16
// capabilities must be present).
std::vector<opt::Instruction*> GetNumericalInstructions(
const std::vector<opt::Instruction*>& instructions) const;
// Requires that |instructions| are scalars or vectors of some type. Returns
// only those instructions whose width is |width|. If |width| is 1 this means
// the scalars.
std::vector<opt::Instruction*> RestrictToVectorWidth(
const std::vector<opt::Instruction*>& instructions,
uint32_t vector_width) const;
// Requires that |instructions| are integer or float scalars or vectors.
// Returns only those instructions for which the bit-width of the underlying
// integer or floating-point type is |bit_width|.
std::vector<opt::Instruction*> RestrictToElementBitWidth(
const std::vector<opt::Instruction*>& instructions,
uint32_t bit_width) const;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_EQUATION_INSTRUCTIONS_H_

View File

@@ -1,199 +0,0 @@
// 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_function_calls.h"
#include "source/fuzz/call_graph.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_add_global_variable.h"
#include "source/fuzz/transformation_add_local_variable.h"
#include "source/fuzz/transformation_function_call.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddFunctionCalls::FuzzerPassAddFunctionCalls(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddFunctionCalls::~FuzzerPassAddFunctionCalls() = default;
void FuzzerPassAddFunctionCalls::Apply() {
ForEachInstructionWithInstructionDescriptor(
[this](opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor)
-> void {
// Check whether it is legitimate to insert a function call before the
// instruction.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpFunctionCall,
inst_it)) {
return;
}
// Randomly decide whether to try inserting a function call here.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfCallingFunction())) {
return;
}
// Compute the module's call graph - we don't cache it since it may
// change each time we apply a transformation. If this proves to be
// a bottleneck the call graph data structure could be made updatable.
CallGraph call_graph(GetIRContext());
// Gather all the non-entry point functions different from this
// function. It is important to ignore entry points as a function
// cannot be an entry point and the target of an OpFunctionCall
// instruction. We ignore this function to avoid direct recursion.
std::vector<opt::Function*> candidate_functions;
for (auto& other_function : *GetIRContext()->module()) {
if (&other_function != function &&
!fuzzerutil::FunctionIsEntryPoint(GetIRContext(),
other_function.result_id())) {
candidate_functions.push_back(&other_function);
}
}
// Choose a function to call, at random, by considering candidate
// functions until a suitable one is found.
opt::Function* chosen_function = nullptr;
while (!candidate_functions.empty()) {
opt::Function* candidate_function =
GetFuzzerContext()->RemoveAtRandomIndex(&candidate_functions);
if (!GetTransformationContext()->GetFactManager()->BlockIsDead(
block->id()) &&
!GetTransformationContext()->GetFactManager()->FunctionIsLivesafe(
candidate_function->result_id())) {
// Unless in a dead block, only livesafe functions can be invoked
continue;
}
if (call_graph.GetIndirectCallees(candidate_function->result_id())
.count(function->result_id())) {
// Calling this function could lead to indirect recursion
continue;
}
chosen_function = candidate_function;
break;
}
if (!chosen_function) {
// No suitable function was found to call. (This can happen, for
// instance, if the current function is the only function in the
// module.)
return;
}
ApplyTransformation(TransformationFunctionCall(
GetFuzzerContext()->GetFreshId(), chosen_function->result_id(),
ChooseFunctionCallArguments(*chosen_function, function, block,
inst_it),
instruction_descriptor));
});
}
std::vector<uint32_t> FuzzerPassAddFunctionCalls::ChooseFunctionCallArguments(
const opt::Function& callee, opt::Function* caller_function,
opt::BasicBlock* caller_block,
const opt::BasicBlock::iterator& caller_inst_it) {
auto available_pointers = FindAvailableInstructions(
caller_function, caller_block, caller_inst_it,
[this, caller_block](opt::IRContext* /*unused*/, opt::Instruction* inst) {
if (inst->opcode() != SpvOpVariable ||
inst->opcode() != SpvOpFunctionParameter) {
// Function parameters and variables are the only
// kinds of pointer that can be used as actual
// parameters.
return false;
}
return GetTransformationContext()->GetFactManager()->BlockIsDead(
caller_block->id()) ||
GetTransformationContext()
->GetFactManager()
->PointeeValueIsIrrelevant(inst->result_id());
});
std::unordered_map<uint32_t, std::vector<uint32_t>> type_id_to_result_id;
for (const auto* inst : available_pointers) {
type_id_to_result_id[inst->type_id()].push_back(inst->result_id());
}
std::vector<uint32_t> result;
for (const auto* param :
fuzzerutil::GetParameters(GetIRContext(), callee.result_id())) {
const auto* param_type =
GetIRContext()->get_type_mgr()->GetType(param->type_id());
assert(param_type && "Parameter has invalid type");
if (!param_type->AsPointer()) {
// We mark the constant as irrelevant so that we can replace it with a
// more interesting value later.
result.push_back(FindOrCreateZeroConstant(param->type_id(), true));
continue;
}
if (type_id_to_result_id.count(param->type_id())) {
// Use an existing pointer if there are any.
const auto& candidates = type_id_to_result_id[param->type_id()];
result.push_back(candidates[GetFuzzerContext()->RandomIndex(candidates)]);
continue;
}
// Make a new variable, at function or global scope depending on the storage
// class of the pointer.
// Get a fresh id for the new variable.
uint32_t fresh_variable_id = GetFuzzerContext()->GetFreshId();
// The id of this variable is what we pass as the parameter to
// the call.
result.push_back(fresh_variable_id);
type_id_to_result_id[param->type_id()].push_back(fresh_variable_id);
// Now bring the variable into existence.
auto storage_class = param_type->AsPointer()->storage_class();
auto pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
GetIRContext(), param->type_id());
if (storage_class == SpvStorageClassFunction) {
// Add a new zero-initialized local variable to the current
// function, noting that its pointee value is irrelevant.
ApplyTransformation(TransformationAddLocalVariable(
fresh_variable_id, param->type_id(), caller_function->result_id(),
FindOrCreateZeroConstant(pointee_type_id, false), true));
} else {
assert((storage_class == SpvStorageClassPrivate ||
storage_class == SpvStorageClassWorkgroup) &&
"Only Function, Private and Workgroup storage classes are "
"supported at present.");
// Add a new global variable to the module, zero-initializing it if
// it has Private storage class, and noting that its pointee value is
// irrelevant.
ApplyTransformation(TransformationAddGlobalVariable(
fresh_variable_id, param->type_id(), storage_class,
storage_class == SpvStorageClassPrivate
? FindOrCreateZeroConstant(pointee_type_id, false)
: 0,
true));
}
}
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,50 +0,0 @@
// 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_FUZZER_PASS_ADD_FUNCTION_CALLS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_FUNCTION_CALLS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Fuzzer pass that adds calls at random to (a) livesafe functions, from
// anywhere, and (b) any functions, from dead blocks.
class FuzzerPassAddFunctionCalls : public FuzzerPass {
public:
FuzzerPassAddFunctionCalls(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddFunctionCalls();
void Apply() override;
private:
// Randomly chooses suitable arguments to invoke |callee| right before
// instruction |caller_inst_it| of block |caller_block| in |caller_function|,
// based on both existing available instructions and the addition of new
// instructions to the module.
std::vector<uint32_t> ChooseFunctionCallArguments(
const opt::Function& callee, opt::Function* caller_function,
opt::BasicBlock* caller_block,
const opt::BasicBlock::iterator& caller_inst_it);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_FUNCTION_CALLS_H_

View File

@@ -1,95 +0,0 @@
// 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_global_variables.h"
#include "source/fuzz/transformation_add_global_variable.h"
#include "source/fuzz/transformation_add_type_pointer.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddGlobalVariables::FuzzerPassAddGlobalVariables(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddGlobalVariables::~FuzzerPassAddGlobalVariables() = default;
void FuzzerPassAddGlobalVariables::Apply() {
SpvStorageClass variable_storage_class = SpvStorageClassPrivate;
for (auto& entry_point : GetIRContext()->module()->entry_points()) {
// If the execution model of some entry point is GLCompute,
// then the variable storage class may be Workgroup.
if (entry_point.GetSingleWordInOperand(0) == SpvExecutionModelGLCompute) {
variable_storage_class =
GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfChoosingWorkgroupStorageClass())
? SpvStorageClassWorkgroup
: SpvStorageClassPrivate;
break;
}
}
auto basic_type_ids_and_pointers =
GetAvailableBasicTypesAndPointers(variable_storage_class);
// These are the basic types that are available to this fuzzer pass.
auto& basic_types = basic_type_ids_and_pointers.first;
// These are the pointers to those basic types that are *initially* available
// to the fuzzer pass. The fuzzer pass might add pointer types in cases where
// none are available for a given basic type.
auto& basic_type_to_pointers = basic_type_ids_and_pointers.second;
// Probabilistically keep adding global variables.
while (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingGlobalVariable())) {
// Choose a random basic type; the new variable's type will be a pointer to
// this basic type.
uint32_t basic_type =
basic_types[GetFuzzerContext()->RandomIndex(basic_types)];
uint32_t pointer_type_id;
std::vector<uint32_t>& available_pointers_to_basic_type =
basic_type_to_pointers.at(basic_type);
// Determine whether there is at least one pointer to this basic type.
if (available_pointers_to_basic_type.empty()) {
// There is not. Make one, to use here, and add it to the available
// pointers for the basic type so that future variables can potentially
// use it.
pointer_type_id = GetFuzzerContext()->GetFreshId();
available_pointers_to_basic_type.push_back(pointer_type_id);
ApplyTransformation(TransformationAddTypePointer(
pointer_type_id, variable_storage_class, basic_type));
} else {
// There is - grab one.
pointer_type_id =
available_pointers_to_basic_type[GetFuzzerContext()->RandomIndex(
available_pointers_to_basic_type)];
}
ApplyTransformation(TransformationAddGlobalVariable(
GetFuzzerContext()->GetFreshId(), pointer_type_id,
variable_storage_class,
variable_storage_class == SpvStorageClassPrivate
? FindOrCreateZeroConstant(basic_type, false)
: 0,
true));
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,40 +0,0 @@
// 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_FUZZER_PASS_ADD_GLOBAL_VARIABLES_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_GLOBAL_VARIABLES_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Fuzzer pass that randomly adds global variables, with Private storage class,
// to the module.
class FuzzerPassAddGlobalVariables : public FuzzerPass {
public:
FuzzerPassAddGlobalVariables(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddGlobalVariables();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_GLOBAL_VARIABLES_H_

View File

@@ -1,200 +0,0 @@
// Copyright (c) 2020 André Perez Maselco
//
// 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_image_sample_unused_components.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_add_image_sample_unused_components.h"
#include "source/fuzz/transformation_composite_construct.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddImageSampleUnusedComponents::
FuzzerPassAddImageSampleUnusedComponents(
opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddImageSampleUnusedComponents::
~FuzzerPassAddImageSampleUnusedComponents() = default;
void FuzzerPassAddImageSampleUnusedComponents::Apply() {
// SPIR-V module to help understand the transformation.
//
// OpCapability Shader
// %1 = OpExtInstImport "GLSL.std.450"
// OpMemoryModel Logical GLSL450
// OpEntryPoint Fragment %15 "main" %12 %14
// OpExecutionMode %15 OriginUpperLeft
//
// ; Decorations
// OpDecorate %12 Location 0 ; Input color variable location
// OpDecorate %13 DescriptorSet 0 ; Image coordinate variable
// descriptor set OpDecorate %13 Binding 0 ; Image coordinate
// variable binding OpDecorate %14 Location 0 ; Fragment color
// variable location
//
// ; Types
// %2 = OpTypeVoid
// %3 = OpTypeFunction %2
// %4 = OpTypeFloat 32
// %5 = OpTypeVector %4 2
// %6 = OpTypeVector %4 4
// %7 = OpTypeImage %4 2D 0 0 0 1 Rgba32f
// %8 = OpTypeSampledImage %7
// %9 = OpTypePointer Input %5
// %10 = OpTypePointer UniformConstant %8
// %11 = OpTypePointer Output %6
//
// ; Variables
// %12 = OpVariable %9 Input ; Input image coordinate variable
// %13 = OpVariable %10 UniformConstant ; Image variable
// %14 = OpVariable %11 Output ; Fragment color variable
//
// ; main function
// %15 = OpFunction %2 None %3
// %16 = OpLabel
// %17 = OpLoad %5 %12
// %18 = OpLoad %8 %13
// %19 = OpImageSampleImplicitLod %6 %18 %17
// OpStore %14 %19
// OpReturn
// OpFunctionEnd
GetIRContext()->module()->ForEachInst([this](opt::Instruction* instruction) {
// |instruction| %19 = OpImageSampleImplicitLod %6 %18 %17
if (!spvOpcodeIsImageSample(instruction->opcode())) {
return;
}
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfAddingImageSampleUnusedComponents())) {
return;
}
// Gets image sample coordinate information.
// |coordinate_instruction| %17 = OpLoad %5 %12
uint32_t coordinate_id = instruction->GetSingleWordInOperand(1);
auto coordinate_instruction =
GetIRContext()->get_def_use_mgr()->GetDef(coordinate_id);
auto coordinate_type = GetIRContext()->get_type_mgr()->GetType(
coordinate_instruction->type_id());
// If the coordinate is a 4-dimensional vector, then no unused components
// may be added.
if (coordinate_type->AsVector() &&
coordinate_type->AsVector()->element_count() == 4) {
return;
}
// If the coordinate is a scalar, then at most 3 unused components may be
// added. If the coordinate is a vector, then the maximum number of unused
// components depends on the vector size.
// For the sample module, the coordinate type instruction is %5 =
// OpTypeVector %4 2, thus |max_unused_component_count| = 4 - 2 = 2.
uint32_t max_unused_component_count =
coordinate_type->AsInteger() || coordinate_type->AsFloat()
? 3
: 4 - coordinate_type->AsVector()->element_count();
// |unused_component_count| may be 1 or 2.
uint32_t unused_component_count =
GetFuzzerContext()->GetRandomUnusedComponentCountForImageSample(
max_unused_component_count);
// Gets a type for the zero-unused components.
uint32_t zero_constant_type_id;
switch (unused_component_count) {
case 1:
// If the coordinate is an integer or float, then the unused components
// type is the same as the coordinate. If the coordinate is a vector,
// then the unused components type is the same as the vector components
// type.
zero_constant_type_id =
coordinate_type->AsInteger() || coordinate_type->AsFloat()
? coordinate_instruction->type_id()
: GetIRContext()->get_type_mgr()->GetId(
coordinate_type->AsVector()->element_type());
break;
case 2:
case 3:
// If the coordinate is an integer or float, then the unused components
// type is the same as the coordinate. If the coordinate is a vector,
// then the unused components type is the same as the coordinate
// components type.
// |zero_constant_type_id| %5 = OpTypeVector %4 2
zero_constant_type_id =
coordinate_type->AsInteger() || coordinate_type->AsFloat()
? FindOrCreateVectorType(coordinate_instruction->type_id(),
unused_component_count)
: FindOrCreateVectorType(
GetIRContext()->get_type_mgr()->GetId(
coordinate_type->AsVector()->element_type()),
unused_component_count);
break;
default:
assert(false && "Should be unreachable.");
zero_constant_type_id = 0;
break;
}
// Gets |coordinate_type| again because the module may have changed due to
// the use of FindOrCreateVectorType above.
coordinate_type = GetIRContext()->get_type_mgr()->GetType(
coordinate_instruction->type_id());
// If the new vector type with unused components does not exist, then create
// it. |coordinate_with_unused_components_type_id| %6 = OpTypeVector %4 4
uint32_t coordinate_with_unused_components_type_id =
coordinate_type->AsInteger() || coordinate_type->AsFloat()
? FindOrCreateVectorType(coordinate_instruction->type_id(),
1 + unused_component_count)
: FindOrCreateVectorType(
GetIRContext()->get_type_mgr()->GetId(
coordinate_type->AsVector()->element_type()),
coordinate_type->AsVector()->element_count() +
unused_component_count);
// Inserts an OpCompositeConstruct instruction which
// represents the coordinate with unused components.
// |coordinate_with_unused_components_id|
// %22 = OpCompositeConstruct %6 %17 %21
uint32_t coordinate_with_unused_components_id =
GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationCompositeConstruct(
coordinate_with_unused_components_type_id,
{coordinate_instruction->result_id(),
// FindOrCreateZeroConstant
// %20 = OpConstant %4 0
// %21 = OpConstantComposite %5 %20 %20
FindOrCreateZeroConstant(zero_constant_type_id, false)},
MakeInstructionDescriptor(GetIRContext(), instruction),
coordinate_with_unused_components_id));
// Tries to add unused components to the image sample coordinate.
// %19 = OpImageSampleImplicitLod %6 %18 %22
ApplyTransformation(TransformationAddImageSampleUnusedComponents(
coordinate_with_unused_components_id,
MakeInstructionDescriptor(GetIRContext(), instruction)));
});
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,41 +0,0 @@
// Copyright (c) 2020 André Perez Maselco
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_IMAGE_SAMPLE_UNUSED_COMPONENTS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_IMAGE_SAMPLE_UNUSED_COMPONENTS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// This fuzzer pass searches for image sample instructions in the module and
// randomly applies the transformation to add unused components to the image
// sample coordinate.
class FuzzerPassAddImageSampleUnusedComponents : public FuzzerPass {
public:
FuzzerPassAddImageSampleUnusedComponents(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddImageSampleUnusedComponents();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_IMAGE_SAMPLE_UNUSED_COMPONENTS_H_

View File

@@ -1,96 +0,0 @@
// 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_loads.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_load.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddLoads::FuzzerPassAddLoads(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddLoads::~FuzzerPassAddLoads() = default;
void FuzzerPassAddLoads::Apply() {
ForEachInstructionWithInstructionDescriptor(
[this](opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor)
-> void {
assert(inst_it->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");
// Check whether it is legitimate to insert a load before this
// instruction.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, inst_it)) {
return;
}
// Randomly decide whether to try inserting a load here.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingLoad())) {
return;
}
std::vector<opt::Instruction*> relevant_instructions =
FindAvailableInstructions(
function, block, inst_it,
[](opt::IRContext* context,
opt::Instruction* instruction) -> bool {
if (!instruction->result_id() || !instruction->type_id()) {
return false;
}
switch (instruction->opcode()) {
case SpvOpConstantNull:
case SpvOpUndef:
// Do not allow loading from a null or undefined pointer;
// this might be OK if the block is dead, but for now we
// conservatively avoid it.
return false;
default:
break;
}
return context->get_def_use_mgr()
->GetDef(instruction->type_id())
->opcode() == SpvOpTypePointer;
});
// At this point, |relevant_instructions| contains all the pointers
// we might think of loading from.
if (relevant_instructions.empty()) {
return;
}
// Choose a pointer at random, and create and apply a loading
// transformation based on it.
ApplyTransformation(TransformationLoad(
GetFuzzerContext()->GetFreshId(),
relevant_instructions[GetFuzzerContext()->RandomIndex(
relevant_instructions)]
->result_id(),
instruction_descriptor));
});
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,39 +0,0 @@
// 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_FUZZER_PASS_ADD_LOADS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOADS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Fuzzer pass that adds stores, at random, from pointers in the module.
class FuzzerPassAddLoads : public FuzzerPass {
public:
FuzzerPassAddLoads(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddLoads();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_LOADS_H_

View File

@@ -1,80 +0,0 @@
// 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_local_variables.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_add_local_variable.h"
#include "source/fuzz/transformation_add_type_pointer.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddLocalVariables::FuzzerPassAddLocalVariables(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddLocalVariables::~FuzzerPassAddLocalVariables() = default;
void FuzzerPassAddLocalVariables::Apply() {
auto basic_type_ids_and_pointers =
GetAvailableBasicTypesAndPointers(SpvStorageClassFunction);
// These are the basic types that are available to this fuzzer pass.
auto& basic_types = basic_type_ids_and_pointers.first;
// These are the pointers to those basic types that are *initially* available
// to the fuzzer pass. The fuzzer pass might add pointer types in cases where
// none are available for a given basic type.
auto& basic_type_to_pointers = basic_type_ids_and_pointers.second;
// Consider every function in the module.
for (auto& function : *GetIRContext()->module()) {
// Probabilistically keep adding random variables to this function.
while (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingLocalVariable())) {
// Choose a random basic type; the new variable's type will be a pointer
// to this basic type.
uint32_t basic_type =
basic_types[GetFuzzerContext()->RandomIndex(basic_types)];
uint32_t pointer_type;
std::vector<uint32_t>& available_pointers_to_basic_type =
basic_type_to_pointers.at(basic_type);
// Determine whether there is at least one pointer to this basic type.
if (available_pointers_to_basic_type.empty()) {
// There is not. Make one, to use here, and add it to the available
// pointers for the basic type so that future variables can potentially
// use it.
pointer_type = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypePointer(
pointer_type, SpvStorageClassFunction, basic_type));
available_pointers_to_basic_type.push_back(pointer_type);
} else {
// There is - grab one.
pointer_type =
available_pointers_to_basic_type[GetFuzzerContext()->RandomIndex(
available_pointers_to_basic_type)];
}
ApplyTransformation(TransformationAddLocalVariable(
GetFuzzerContext()->GetFreshId(), pointer_type, function.result_id(),
FindOrCreateZeroConstant(basic_type, false), true));
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,40 +0,0 @@
// 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_FUZZER_PASS_ADD_LOCAL_VARIABLES_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOCAL_VARIABLES_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Fuzzer pass that randomly adds local variables, with Function storage class,
// to the module.
class FuzzerPassAddLocalVariables : public FuzzerPass {
public:
FuzzerPassAddLocalVariables(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddLocalVariables();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_LOCAL_VARIABLES_H_

View File

@@ -1,66 +0,0 @@
// 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_loop_preheaders.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_add_loop_preheader.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddLoopPreheaders::FuzzerPassAddLoopPreheaders(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddLoopPreheaders::~FuzzerPassAddLoopPreheaders() = default;
void FuzzerPassAddLoopPreheaders::Apply() {
for (auto& function : *GetIRContext()->module()) {
// Keep track of all the loop headers we want to add a preheader to.
std::vector<uint32_t> loop_header_ids_to_consider;
for (auto& block : function) {
// We only care about loop headers.
if (!block.IsLoopHeader()) {
continue;
}
// Randomly decide whether to consider this header.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingLoopPreheader())) {
continue;
}
// We exclude loop headers with just one predecessor (the back-edge block)
// because they are unreachable.
if (GetIRContext()->cfg()->preds(block.id()).size() < 2) {
continue;
}
loop_header_ids_to_consider.push_back(block.id());
}
for (uint32_t header_id : loop_header_ids_to_consider) {
// If not already present, add a preheader which is not also a loop
// header.
GetOrCreateSimpleLoopPreheader(header_id);
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,43 +0,0 @@
// 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_FUZZER_PASS_ADD_LOOP_PREHEADERS_H
#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOOP_PREHEADERS_H
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass that randomly adds simple loop preheaders to loops that do not
// have one. A simple loop preheader is a block that:
// - is the only out-of-loop predecessor of the header
// - branches unconditionally to the header
// - is not a loop header itself
class FuzzerPassAddLoopPreheaders : public FuzzerPass {
public:
FuzzerPassAddLoopPreheaders(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddLoopPreheaders();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_LOOP_PREHEADERS_H

View File

@@ -1,57 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
#include "source/fuzz/transformation_add_no_contraction_decoration.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddNoContractionDecorations::FuzzerPassAddNoContractionDecorations(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddNoContractionDecorations::
~FuzzerPassAddNoContractionDecorations() = default;
void FuzzerPassAddNoContractionDecorations::Apply() {
// Consider every instruction in every block in every function.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
for (auto& inst : block) {
// Restrict attention to arithmetic instructions (as defined in the
// SPIR-V specification).
if (TransformationAddNoContractionDecoration::IsArithmetic(
inst.opcode())) {
// Randomly choose whether to apply the NoContraction decoration to
// this arithmetic instruction.
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfAddingNoContractionDecoration())) {
TransformationAddNoContractionDecoration transformation(
inst.result_id());
ApplyTransformation(transformation);
}
}
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,39 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A pass that applies the NoContraction decoration to arithmetic instructions.
class FuzzerPassAddNoContractionDecorations : public FuzzerPass {
public:
FuzzerPassAddNoContractionDecorations(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddNoContractionDecorations() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_

View File

@@ -1,297 +0,0 @@
// 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_opphi_synonyms.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_add_opphi_synonym.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddOpPhiSynonyms::FuzzerPassAddOpPhiSynonyms(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddOpPhiSynonyms::~FuzzerPassAddOpPhiSynonyms() = default;
void FuzzerPassAddOpPhiSynonyms::Apply() {
// Get a list of synonymous ids with the same type that can be used in the
// same OpPhi instruction.
auto equivalence_classes = GetIdEquivalenceClasses();
// Make a list of references, to avoid copying sets unnecessarily.
std::vector<std::set<uint32_t>*> equivalence_class_pointers;
for (auto& set : equivalence_classes) {
equivalence_class_pointers.push_back(&set);
}
// Keep a list of transformations to apply at the end.
std::vector<TransformationAddOpPhiSynonym> transformations_to_apply;
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// Randomly decide whether to consider this block.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingOpPhiSynonym())) {
continue;
}
// The block must have at least one predecessor.
size_t num_preds = GetIRContext()->cfg()->preds(block.id()).size();
if (num_preds == 0) {
continue;
}
std::set<uint32_t>* chosen_equivalence_class = nullptr;
if (num_preds > 1) {
// If the block has more than one predecessor, prioritise sets with at
// least 2 ids available at some predecessor.
chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly(
equivalence_class_pointers, block.id(), 2);
}
// If a set was not already chosen, choose one with at least one available
// id.
if (!chosen_equivalence_class) {
chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly(
equivalence_class_pointers, block.id(), 1);
}
// If no suitable set was found, we cannot apply the transformation to
// this block.
if (!chosen_equivalence_class) {
continue;
}
// Initialise the map from predecessor labels to ids.
std::map<uint32_t, uint32_t> preds_to_ids;
// Keep track of the ids used and of the id of a predecessor with at least
// two ids to choose from. This is to ensure that, if possible, at least
// two distinct ids will be used.
std::set<uint32_t> ids_chosen;
uint32_t pred_with_alternatives = 0;
// Choose an id for each predecessor.
for (uint32_t pred_id : GetIRContext()->cfg()->preds(block.id())) {
auto suitable_ids = GetSuitableIds(*chosen_equivalence_class, pred_id);
assert(!suitable_ids.empty() &&
"We must be able to find at least one suitable id because the "
"equivalence class was chosen among suitable ones.");
// If this predecessor has more than one id to choose from and it is the
// first one of this kind that we found, remember its id.
if (suitable_ids.size() > 1 && !pred_with_alternatives) {
pred_with_alternatives = pred_id;
}
uint32_t chosen_id =
suitable_ids[GetFuzzerContext()->RandomIndex(suitable_ids)];
// Add this id to the set of ids chosen.
ids_chosen.emplace(chosen_id);
// Add the pair (predecessor, chosen id) to the map.
preds_to_ids[pred_id] = chosen_id;
}
// If:
// - the block has more than one predecessor
// - at least one predecessor has more than one alternative
// - the same id has been chosen by all the predecessors
// then choose another one for the predecessor with more than one
// alternative.
if (num_preds > 1 && pred_with_alternatives != 0 &&
ids_chosen.size() == 1) {
auto suitable_ids =
GetSuitableIds(*chosen_equivalence_class, pred_with_alternatives);
uint32_t chosen_id =
GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids);
if (chosen_id == preds_to_ids[pred_with_alternatives]) {
chosen_id = GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids);
}
preds_to_ids[pred_with_alternatives] = chosen_id;
}
// Add the transformation to the list of transformations to apply.
transformations_to_apply.emplace_back(block.id(), preds_to_ids,
GetFuzzerContext()->GetFreshId());
}
}
// Apply the transformations.
for (const auto& transformation : transformations_to_apply) {
ApplyTransformation(transformation);
}
}
std::vector<std::set<uint32_t>>
FuzzerPassAddOpPhiSynonyms::GetIdEquivalenceClasses() {
std::vector<std::set<uint32_t>> id_equivalence_classes;
// Keep track of all the ids that have already be assigned to a class.
std::set<uint32_t> already_in_a_class;
for (const auto& pair : GetIRContext()->get_def_use_mgr()->id_to_defs()) {
// Exclude ids that have already been assigned to a class.
if (already_in_a_class.count(pair.first)) {
continue;
}
// Exclude irrelevant ids.
if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
pair.first)) {
continue;
}
// Exclude ids having a type that is not allowed by the transformation.
if (!TransformationAddOpPhiSynonym::CheckTypeIsAllowed(
GetIRContext(), pair.second->type_id())) {
continue;
}
// We need a new equivalence class for this id.
std::set<uint32_t> new_equivalence_class;
// Add this id to the class.
new_equivalence_class.emplace(pair.first);
already_in_a_class.emplace(pair.first);
// Add all the synonyms with the same type to this class.
for (auto synonym :
GetTransformationContext()->GetFactManager()->GetSynonymsForId(
pair.first)) {
// The synonym must be a plain id - it cannot be an indexed access into a
// composite.
if (synonym->index_size() > 0) {
continue;
}
// The synonym must not be irrelevant.
if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
synonym->object())) {
continue;
}
auto synonym_def =
GetIRContext()->get_def_use_mgr()->GetDef(synonym->object());
// The synonym must exist and have the same type as the id we are
// considering.
if (!synonym_def || synonym_def->type_id() != pair.second->type_id()) {
continue;
}
// We can add this synonym to the new equivalence class.
new_equivalence_class.emplace(synonym->object());
already_in_a_class.emplace(synonym->object());
}
// Add the new equivalence class to the list of equivalence classes.
id_equivalence_classes.emplace_back(std::move(new_equivalence_class));
}
return id_equivalence_classes;
}
bool FuzzerPassAddOpPhiSynonyms::EquivalenceClassIsSuitableForBlock(
const std::set<uint32_t>& equivalence_class, uint32_t block_id,
uint32_t distinct_ids_required) {
bool at_least_one_id_for_each_pred = true;
// Keep a set of the suitable ids found.
std::set<uint32_t> suitable_ids_found;
// Loop through all the predecessors of the block.
for (auto pred_id : GetIRContext()->cfg()->preds(block_id)) {
// Find the last instruction in the predecessor block.
auto last_instruction =
GetIRContext()->get_instr_block(pred_id)->terminator();
// Initially assume that there is not a suitable id for this predecessor.
bool at_least_one_suitable_id_found = false;
for (uint32_t id : equivalence_class) {
if (fuzzerutil::IdIsAvailableBeforeInstruction(GetIRContext(),
last_instruction, id)) {
// We have found a suitable id.
at_least_one_suitable_id_found = true;
suitable_ids_found.emplace(id);
// If we have already found enough distinct suitable ids, we don't need
// to check the remaining ones for this predecessor.
if (suitable_ids_found.size() >= distinct_ids_required) {
break;
}
}
}
// If no suitable id was found for this predecessor, this equivalence class
// is not suitable and we don't need to check the other predecessors.
if (!at_least_one_suitable_id_found) {
at_least_one_id_for_each_pred = false;
break;
}
}
// The equivalence class is suitable if at least one suitable id was found for
// each predecessor and we have found at least |distinct_ids_required|
// distinct suitable ids in general.
return at_least_one_id_for_each_pred &&
suitable_ids_found.size() >= distinct_ids_required;
}
std::vector<uint32_t> FuzzerPassAddOpPhiSynonyms::GetSuitableIds(
const std::set<uint32_t>& ids, uint32_t pred_id) {
// Initialise an empty vector of suitable ids.
std::vector<uint32_t> suitable_ids;
// Get the predecessor block.
auto predecessor = fuzzerutil::MaybeFindBlock(GetIRContext(), pred_id);
// Loop through the ids to find the suitable ones.
for (uint32_t id : ids) {
if (fuzzerutil::IdIsAvailableBeforeInstruction(
GetIRContext(), predecessor->terminator(), id)) {
suitable_ids.push_back(id);
}
}
return suitable_ids;
}
std::set<uint32_t>*
FuzzerPassAddOpPhiSynonyms::MaybeFindSuitableEquivalenceClassRandomly(
const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id,
uint32_t distinct_ids_required) {
auto remaining_candidates = candidates;
while (!remaining_candidates.empty()) {
// Choose one set randomly and return it if it is suitable.
auto chosen =
GetFuzzerContext()->RemoveAtRandomIndex(&remaining_candidates);
if (EquivalenceClassIsSuitableForBlock(*chosen, block_id,
distinct_ids_required)) {
return chosen;
}
}
// No suitable sets were found.
return nullptr;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,73 +0,0 @@
// 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_FUZZER_PASS_ADD_OPPHI_SYNONYMS_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass to add OpPhi instructions which can take the values of ids that
// have been marked as synonymous. This instruction will itself be marked as
// synonymous with the others.
class FuzzerPassAddOpPhiSynonyms : public FuzzerPass {
public:
FuzzerPassAddOpPhiSynonyms(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddOpPhiSynonyms() override;
void Apply() override;
// Computes the equivalence classes for the non-pointer and non-irrelevant ids
// in the module, where two ids are considered equivalent iff they have been
// declared synonymous and they have the same type.
std::vector<std::set<uint32_t>> GetIdEquivalenceClasses();
// Returns true iff |equivalence_class| contains at least
// |distinct_ids_required| ids so that all of these ids are available at the
// end of at least one predecessor of the block with label |block_id|.
// Assumes that the block has at least one predecessor.
bool EquivalenceClassIsSuitableForBlock(
const std::set<uint32_t>& equivalence_class, uint32_t block_id,
uint32_t distinct_ids_required);
// Returns a vector with the ids that are available to use at the end of the
// block with id |pred_id|, selected among the given |ids|. Assumes that
// |pred_id| is the label of a block and all ids in |ids| exist in the module.
std::vector<uint32_t> GetSuitableIds(const std::set<uint32_t>& ids,
uint32_t pred_id);
private:
// Randomly chooses one of the equivalence classes in |candidates|, so that it
// satisfies all of the following conditions:
// - For each of the predecessors of the |block_id| block, there is at least
// one id in the chosen equivalence class that is available at the end of
// it.
// - There are at least |distinct_ids_required| ids available at the end of
// some predecessor.
// Returns nullptr if no equivalence class in |candidates| satisfies the
// requirements.
std::set<uint32_t>* MaybeFindSuitableEquivalenceClassRandomly(
const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id,
uint32_t distinct_ids_required);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_

View File

@@ -1,147 +0,0 @@
// 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_parameters.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_add_parameter.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddParameters::FuzzerPassAddParameters(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddParameters::~FuzzerPassAddParameters() = default;
void FuzzerPassAddParameters::Apply() {
// Compute type candidates for the new parameter.
std::vector<uint32_t> type_candidates;
for (const auto& type_inst : GetIRContext()->module()->GetTypes()) {
const auto* type =
GetIRContext()->get_type_mgr()->GetType(type_inst->result_id());
assert(type && "Type instruction is not registered in the type manager");
if (TransformationAddParameter::IsParameterTypeSupported(*type)) {
type_candidates.push_back(type_inst->result_id());
}
}
if (type_candidates.empty()) {
// The module contains no suitable types to use in new parameters.
return;
}
// Iterate over all functions in the module.
for (const auto& function : *GetIRContext()->module()) {
// Skip all entry-point functions - we don't want to change those.
if (fuzzerutil::FunctionIsEntryPoint(GetIRContext(),
function.result_id())) {
continue;
}
if (GetNumberOfParameters(function) >=
GetFuzzerContext()->GetMaximumNumberOfFunctionParameters()) {
continue;
}
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingParameters())) {
continue;
}
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(),
current_type_id, std::move(call_parameter_ids),
GetFuzzerContext()->GetFreshId()));
}
}
}
uint32_t FuzzerPassAddParameters::GetNumberOfParameters(
const opt::Function& function) const {
const auto* type = GetIRContext()->get_type_mgr()->GetType(
function.DefInst().GetSingleWordInOperand(1));
assert(type && type->AsFunction());
return static_cast<uint32_t>(type->AsFunction()->param_types().size());
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,47 +0,0 @@
// 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.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_PARAMETERS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_PARAMETERS_H_
#include <vector>
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Randomly decides for each non-entry-point function in the module whether to
// add new parameters to it. If so, randomly determines the number of parameters
// to add, their type and creates constants used to initialize them.
class FuzzerPassAddParameters : public FuzzerPass {
public:
FuzzerPassAddParameters(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddParameters() override;
void Apply() override;
private:
// Returns number of parameters of |function|.
uint32_t GetNumberOfParameters(const opt::Function& function) const;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_PARAMETERS_H_

View File

@@ -1,55 +0,0 @@
// 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_relaxed_decorations.h"
#include "source/fuzz/transformation_add_relaxed_decoration.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddRelaxedDecorations::FuzzerPassAddRelaxedDecorations(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddRelaxedDecorations::~FuzzerPassAddRelaxedDecorations() = default;
void FuzzerPassAddRelaxedDecorations::Apply() {
// Consider every instruction in every block in every function.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
for (auto& inst : block) {
// Randomly choose whether to apply the RelaxedPrecision decoration
// to this instruction.
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingRelaxedDecoration())) {
TransformationAddRelaxedDecoration transformation(inst.result_id());
// Restrict attention to numeric instructions (returning 32-bit
// floats or ints according to SPIR-V documentation) in dead blocks.
if (transformation.IsApplicable(GetIRContext(),
*GetTransformationContext())) {
transformation.Apply(GetIRContext(), GetTransformationContext());
*GetTransformations()->add_transformation() =
transformation.ToMessage();
}
}
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,39 +0,0 @@
// 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_RELAXED_DECORATIONS_H
#define SPIRV_TOOLS_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A pass that applies the Relaxed decoration to numeric instructions.
class FuzzerPassAddRelaxedDecorations : public FuzzerPass {
public:
FuzzerPassAddRelaxedDecorations(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddRelaxedDecorations() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SPIRV_TOOLS_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H

View File

@@ -1,132 +0,0 @@
// 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_stores.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_store.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddStores::FuzzerPassAddStores(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddStores::~FuzzerPassAddStores() = default;
void FuzzerPassAddStores::Apply() {
ForEachInstructionWithInstructionDescriptor(
[this](opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor)
-> void {
assert(inst_it->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");
// Check whether it is legitimate to insert a store before this
// instruction.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore,
inst_it)) {
return;
}
// Randomly decide whether to try inserting a store here.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingStore())) {
return;
}
// Look for pointers we might consider storing to.
std::vector<opt::Instruction*> relevant_pointers =
FindAvailableInstructions(
function, block, inst_it,
[this, block](opt::IRContext* context,
opt::Instruction* instruction) -> bool {
if (!instruction->result_id() || !instruction->type_id()) {
return false;
}
auto type_inst = context->get_def_use_mgr()->GetDef(
instruction->type_id());
if (type_inst->opcode() != SpvOpTypePointer) {
// Not a pointer.
return false;
}
if (instruction->IsReadOnlyPointer()) {
// Read only: cannot store to it.
return false;
}
switch (instruction->opcode()) {
case SpvOpConstantNull:
case SpvOpUndef:
// Do not allow storing to a null or undefined pointer;
// this might be OK if the block is dead, but for now we
// conservatively avoid it.
return false;
default:
break;
}
return GetTransformationContext()
->GetFactManager()
->BlockIsDead(block->id()) ||
GetTransformationContext()
->GetFactManager()
->PointeeValueIsIrrelevant(
instruction->result_id());
});
// At this point, |relevant_pointers| contains all the pointers we might
// think of storing to.
if (relevant_pointers.empty()) {
return;
}
auto pointer = relevant_pointers[GetFuzzerContext()->RandomIndex(
relevant_pointers)];
std::vector<opt::Instruction*> relevant_values =
FindAvailableInstructions(
function, block, inst_it,
[pointer](opt::IRContext* context,
opt::Instruction* instruction) -> bool {
if (!instruction->result_id() || !instruction->type_id()) {
return false;
}
return instruction->type_id() ==
context->get_def_use_mgr()
->GetDef(pointer->type_id())
->GetSingleWordInOperand(1);
});
if (relevant_values.empty()) {
return;
}
// Choose a value at random, and create and apply a storing
// transformation based on it and the pointer.
ApplyTransformation(TransformationStore(
pointer->result_id(),
relevant_values[GetFuzzerContext()->RandomIndex(relevant_values)]
->result_id(),
instruction_descriptor));
});
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,41 +0,0 @@
// 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_FUZZER_PASS_ADD_STORES_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_STORES_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Fuzzer pass that adds stores, at random, through pointers in the module,
// either (a) from dead blocks, or (b) through pointers whose pointee values
// are known not to affect the module's overall behaviour.
class FuzzerPassAddStores : public FuzzerPass {
public:
FuzzerPassAddStores(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddStores();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_STORES_H_

View File

@@ -1,127 +0,0 @@
// 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_synonyms.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_add_synonym.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddSynonyms::FuzzerPassAddSynonyms(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddSynonyms::~FuzzerPassAddSynonyms() = default;
void FuzzerPassAddSynonyms::Apply() {
ForEachInstructionWithInstructionDescriptor(
[this](opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor) {
// Skip |inst_it| if we can't insert anything above it. OpIAdd is just
// a representative of some instruction that might be produced by the
// transformation.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpIAdd, inst_it)) {
return;
}
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingSynonyms())) {
return;
}
auto synonym_type = GetFuzzerContext()->GetRandomSynonymType();
// Select all instructions that can be used to create a synonym to.
auto available_instructions = FindAvailableInstructions(
function, block, inst_it,
[synonym_type, this](opt::IRContext* ir_context,
opt::Instruction* inst) {
// Check that we can create a synonym to |inst| as described by
// the |synonym_type| and insert it before |inst_it|.
return TransformationAddSynonym::IsInstructionValid(
ir_context, *GetTransformationContext(), inst, synonym_type);
});
if (available_instructions.empty()) {
return;
}
const auto* existing_synonym =
available_instructions[GetFuzzerContext()->RandomIndex(
available_instructions)];
// Make sure the module contains all instructions required to apply the
// transformation.
switch (synonym_type) {
case protobufs::TransformationAddSynonym::ADD_ZERO:
case protobufs::TransformationAddSynonym::SUB_ZERO:
case protobufs::TransformationAddSynonym::LOGICAL_OR:
// Create a zero constant to be used as an operand of the synonymous
// instruction.
FindOrCreateZeroConstant(existing_synonym->type_id(), false);
break;
case protobufs::TransformationAddSynonym::MUL_ONE:
case protobufs::TransformationAddSynonym::LOGICAL_AND: {
const auto* existing_synonym_type =
GetIRContext()->get_type_mgr()->GetType(
existing_synonym->type_id());
assert(existing_synonym_type && "Instruction has invalid type");
if (const auto* vector = existing_synonym_type->AsVector()) {
auto element_type_id =
GetIRContext()->get_type_mgr()->GetId(vector->element_type());
assert(element_type_id && "Vector's element type is invalid");
auto one_word = vector->element_type()->AsFloat()
? fuzzerutil::FloatToWord(1)
: 1u;
FindOrCreateCompositeConstant(
std::vector<uint32_t>(
vector->element_count(),
FindOrCreateConstant({one_word}, element_type_id, false)),
existing_synonym->type_id(), false);
} else {
FindOrCreateConstant(
{existing_synonym_type->AsFloat() ? fuzzerutil::FloatToWord(1)
: 1u},
existing_synonym->type_id(), false);
}
} break;
default:
// This assertion will fail if some SynonymType is missing from the
// switch statement.
assert(
!TransformationAddSynonym::IsAdditionalConstantRequired(
synonym_type) &&
"|synonym_type| requires an additional constant to be present "
"in the module");
break;
}
ApplyTransformation(TransformationAddSynonym(
existing_synonym->result_id(), synonym_type,
GetFuzzerContext()->GetFreshId(), instruction_descriptor));
});
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,40 +0,0 @@
// 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.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_SYNONYMS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_SYNONYMS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Sprinkles instructions through the module that produce ids, synonymous to
// some other instructions.
class FuzzerPassAddSynonyms : public FuzzerPass {
public:
FuzzerPassAddSynonyms(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddSynonyms() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_SYNONYMS_H_

View File

@@ -1,145 +0,0 @@
// Copyright (c) 2020 André Perez Maselco
//
// 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_vector_shuffle_instructions.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_vector_shuffle.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddVectorShuffleInstructions::FuzzerPassAddVectorShuffleInstructions(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddVectorShuffleInstructions::
~FuzzerPassAddVectorShuffleInstructions() = default;
void FuzzerPassAddVectorShuffleInstructions::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 OpVectorShuffle instruction.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingVectorShuffle())) {
return;
}
// It must be valid to insert an OpVectorShuffle instruction
// before |instruction_iterator|.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
SpvOpVectorShuffle, instruction_iterator)) {
return;
}
// Looks for vectors that we might consider to use as OpVectorShuffle
// operands.
std::vector<opt::Instruction*> vector_instructions =
FindAvailableInstructions(
function, block, instruction_iterator,
[this, instruction_descriptor](
opt::IRContext* ir_context,
opt::Instruction* instruction) -> bool {
if (!instruction->result_id() || !instruction->type_id()) {
return false;
}
if (!ir_context->get_type_mgr()
->GetType(instruction->type_id())
->AsVector()) {
return false;
}
if (!GetTransformationContext()
->GetFactManager()
->IdIsIrrelevant(instruction->result_id()) &&
!fuzzerutil::CanMakeSynonymOf(ir_context,
*GetTransformationContext(),
instruction)) {
// If the id is irrelevant, we can use it since it will not
// participate in DataSynonym fact. Otherwise, we should be
// able to produce a synonym out of the id.
return false;
}
return fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context,
FindInstruction(instruction_descriptor, ir_context),
instruction->result_id());
});
// If there are no vector instructions, then return.
if (vector_instructions.empty()) {
return;
}
auto vector_1_instruction =
vector_instructions[GetFuzzerContext()->RandomIndex(
vector_instructions)];
auto vector_1_type = GetIRContext()
->get_type_mgr()
->GetType(vector_1_instruction->type_id())
->AsVector();
auto vector_2_instruction =
GetFuzzerContext()->RemoveAtRandomIndex(&vector_instructions);
auto vector_2_type = GetIRContext()
->get_type_mgr()
->GetType(vector_2_instruction->type_id())
->AsVector();
// |vector_1| and |vector_2| must have the same element type as each
// other. The loop is guaranteed to terminate because each iteration
// removes on possible choice for |vector_2|, and there is at least one
// choice that will cause the loop to exit - namely |vector_1|.
while (vector_1_type->element_type() != vector_2_type->element_type()) {
vector_2_instruction =
GetFuzzerContext()->RemoveAtRandomIndex(&vector_instructions);
vector_2_type = GetIRContext()
->get_type_mgr()
->GetType(vector_2_instruction->type_id())
->AsVector();
}
// Gets components and creates the appropriate result vector type.
std::vector<uint32_t> components =
GetFuzzerContext()->GetRandomComponentsForVectorShuffle(
vector_1_type->element_count() +
vector_2_type->element_count());
FindOrCreateVectorType(GetIRContext()->get_type_mgr()->GetId(
vector_1_type->element_type()),
static_cast<uint32_t>(components.size()));
// Applies the vector shuffle transformation.
ApplyTransformation(TransformationVectorShuffle(
instruction_descriptor, GetFuzzerContext()->GetFreshId(),
vector_1_instruction->result_id(),
vector_2_instruction->result_id(), components));
});
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,39 +0,0 @@
// Copyright (c) 2020 André Perez Maselco
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_VECTOR_SHUFFLE_INSTRUCTIONS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_VECTOR_SHUFFLE_INSTRUCTIONS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Adds OpVectorShuffle instructions to the module.
class FuzzerPassAddVectorShuffleInstructions : public FuzzerPass {
public:
FuzzerPassAddVectorShuffleInstructions(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddVectorShuffleInstructions();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_VECTOR_SHUFFLE_INSTRUCTIONS_H_

View File

@@ -1,48 +0,0 @@
// Copyright (c) 2020 André Perez Maselco
//
// 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_adjust_branch_weights.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_adjust_branch_weights.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAdjustBranchWeights::FuzzerPassAdjustBranchWeights(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAdjustBranchWeights::~FuzzerPassAdjustBranchWeights() = default;
void FuzzerPassAdjustBranchWeights::Apply() {
// For all OpBranchConditional instructions,
// randomly applies the transformation.
GetIRContext()->module()->ForEachInst([this](opt::Instruction* instruction) {
if (instruction->opcode() == SpvOpBranchConditional &&
GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAdjustingBranchWeights())) {
ApplyTransformation(TransformationAdjustBranchWeights(
MakeInstructionDescriptor(GetIRContext(), instruction),
GetFuzzerContext()->GetRandomBranchWeights()));
}
});
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,41 +0,0 @@
// Copyright (c) 2020 André Perez Maselco
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_BRANCH_WEIGHTS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_BRANCH_WEIGHTS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// This fuzzer pass searches for branch conditional instructions
// and randomly chooses which of these instructions will have their weights
// adjusted.
class FuzzerPassAdjustBranchWeights : public FuzzerPass {
public:
FuzzerPassAdjustBranchWeights(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAdjustBranchWeights();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_BRANCH_WEIGHTS_H_

View File

@@ -1,71 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_adjust_function_controls.h"
#include "source/fuzz/transformation_set_function_control.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAdjustFunctionControls::FuzzerPassAdjustFunctionControls(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAdjustFunctionControls::~FuzzerPassAdjustFunctionControls() = default;
void FuzzerPassAdjustFunctionControls::Apply() {
// Consider every function in the module.
for (auto& function : *GetIRContext()->module()) {
// Randomly decide whether to adjust this function's controls.
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAdjustingFunctionControl())) {
// Grab the function control mask for the function in its present form.
uint32_t existing_function_control_mask =
function.DefInst().GetSingleWordInOperand(0);
// For the new mask, we first randomly select one of three basic masks:
// None, Inline or DontInline. These are always valid (and are mutually
// exclusive).
std::vector<uint32_t> basic_function_control_masks = {
SpvFunctionControlMaskNone, SpvFunctionControlInlineMask,
SpvFunctionControlDontInlineMask};
uint32_t new_function_control_mask =
basic_function_control_masks[GetFuzzerContext()->RandomIndex(
basic_function_control_masks)];
// We now consider the Pure and Const mask bits. If these are already
// set on the function then it's OK to keep them, but also interesting
// to consider dropping them, so we decide randomly in each case.
for (auto mask_bit :
{SpvFunctionControlPureMask, SpvFunctionControlConstMask}) {
if ((existing_function_control_mask & mask_bit) &&
GetFuzzerContext()->ChooseEven()) {
new_function_control_mask |= mask_bit;
}
}
// Create and add a transformation.
TransformationSetFunctionControl transformation(
function.DefInst().result_id(), new_function_control_mask);
ApplyTransformation(transformation);
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,39 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_
#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A pass that adjusts the function controls on OpFunction instructions.
class FuzzerPassAdjustFunctionControls : public FuzzerPass {
public:
FuzzerPassAdjustFunctionControls(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAdjustFunctionControls() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_

View File

@@ -1,118 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_adjust_loop_controls.h"
#include "source/fuzz/transformation_set_loop_control.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAdjustLoopControls::FuzzerPassAdjustLoopControls(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAdjustLoopControls::~FuzzerPassAdjustLoopControls() = default;
void FuzzerPassAdjustLoopControls::Apply() {
// Consider every merge instruction in the module (via looking through all
// functions and blocks).
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
if (auto merge_inst = block.GetMergeInst()) {
// Ignore the instruction if it is not a loop merge.
if (merge_inst->opcode() != SpvOpLoopMerge) {
continue;
}
// Decide randomly whether to adjust this loop merge.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAdjustingLoopControl())) {
continue;
}
uint32_t existing_mask = merge_inst->GetSingleWordOperand(
TransformationSetLoopControl::kLoopControlMaskInOperandIndex);
// First, set the new mask to one of None, Unroll or DontUnroll.
std::vector<uint32_t> basic_masks = {SpvLoopControlMaskNone,
SpvLoopControlUnrollMask,
SpvLoopControlDontUnrollMask};
uint32_t new_mask =
basic_masks[GetFuzzerContext()->RandomIndex(basic_masks)];
// For the loop controls that depend on guarantees about what the loop
// does, check which of these were present in the existing mask and
// randomly decide whether to keep them. They are just hints, so
// removing them should not change the semantics of the module.
for (auto mask_bit :
{SpvLoopControlDependencyInfiniteMask,
SpvLoopControlDependencyLengthMask,
SpvLoopControlMinIterationsMask, SpvLoopControlMaxIterationsMask,
SpvLoopControlIterationMultipleMask}) {
if ((existing_mask & mask_bit) && GetFuzzerContext()->ChooseEven()) {
// The mask bits we are considering are not available in all SPIR-V
// versions. However, we only include a mask bit if it was present
// in the original loop control mask, and we work under the
// assumption that we are transforming a valid module, thus we don't
// need to actually check whether the SPIR-V version being used
// supports these loop control mask bits.
new_mask |= mask_bit;
}
}
// We use 0 for peel count and partial count in the case that we choose
// not to set these controls.
uint32_t peel_count = 0;
uint32_t partial_count = 0;
// PeelCount and PartialCount are not compatible with DontUnroll, so
// we check whether DontUnroll is set.
if (!(new_mask & SpvLoopControlDontUnrollMask)) {
// If PeelCount is supported by this SPIR-V version, randomly choose
// whether to set it. If it was set in the original mask and is not
// selected for setting here, that amounts to dropping it.
if (TransformationSetLoopControl::PeelCountIsSupported(
GetIRContext()) &&
GetFuzzerContext()->ChooseEven()) {
new_mask |= SpvLoopControlPeelCountMask;
// The peel count is chosen randomly - if PeelCount was already set
// this will overwrite whatever peel count was previously used.
peel_count = GetFuzzerContext()->GetRandomLoopControlPeelCount();
}
// Similar, but for PartialCount.
if (TransformationSetLoopControl::PartialCountIsSupported(
GetIRContext()) &&
GetFuzzerContext()->ChooseEven()) {
new_mask |= SpvLoopControlPartialCountMask;
partial_count =
GetFuzzerContext()->GetRandomLoopControlPartialCount();
}
}
// Apply the transformation and add it to the output transformation
// sequence.
TransformationSetLoopControl transformation(block.id(), new_mask,
peel_count, partial_count);
ApplyTransformation(transformation);
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,39 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_
#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A pass that adjusts the loop controls on OpLoopMerge instructions.
class FuzzerPassAdjustLoopControls : public FuzzerPass {
public:
FuzzerPassAdjustLoopControls(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAdjustLoopControls() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_

View File

@@ -1,109 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_set_memory_operands_mask.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAdjustMemoryOperandsMasks::FuzzerPassAdjustMemoryOperandsMasks(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAdjustMemoryOperandsMasks::~FuzzerPassAdjustMemoryOperandsMasks() =
default;
void FuzzerPassAdjustMemoryOperandsMasks::Apply() {
// Consider every block in every function.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// Consider every instruction in this block, using an explicit iterator so
// that when we find an instruction of interest we can search backwards to
// create an id descriptor for it.
for (auto inst_it = block.cbegin(); inst_it != block.cend(); ++inst_it) {
if (!TransformationSetMemoryOperandsMask::IsMemoryAccess(*inst_it)) {
// We are only interested in memory access instructions.
continue;
}
std::vector<uint32_t> indices_of_available_masks_to_adjust;
// All memory instructions have at least one memory operands mask.
indices_of_available_masks_to_adjust.push_back(0);
// From SPIR-V 1.4 onwards, OpCopyMemory and OpCopyMemorySized have a
// second mask.
switch (inst_it->opcode()) {
case SpvOpCopyMemory:
case SpvOpCopyMemorySized:
if (TransformationSetMemoryOperandsMask::
MultipleMemoryOperandMasksAreSupported(GetIRContext())) {
indices_of_available_masks_to_adjust.push_back(1);
}
break;
default:
break;
}
// Consider the available masks
for (auto mask_index : indices_of_available_masks_to_adjust) {
// Randomly decide whether to adjust this mask.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfAdjustingMemoryOperandsMask())) {
continue;
}
// Get the existing mask, using None if there was no mask present at
// all.
auto existing_mask_in_operand_index =
TransformationSetMemoryOperandsMask::GetInOperandIndexForMask(
*inst_it, mask_index);
auto existing_mask =
existing_mask_in_operand_index < inst_it->NumInOperands()
? inst_it->GetSingleWordInOperand(
existing_mask_in_operand_index)
: static_cast<uint32_t>(SpvMemoryAccessMaskNone);
// There are two things we can do to a mask:
// - add Volatile if not already present
// - toggle Nontemporal
// The following ensures that we do at least one of these
bool add_volatile = !(existing_mask & SpvMemoryAccessVolatileMask) &&
GetFuzzerContext()->ChooseEven();
bool toggle_nontemporal =
!add_volatile || GetFuzzerContext()->ChooseEven();
// These bitwise operations use '|' to add Volatile if desired, and
// '^' to toggle Nontemporal if desired.
uint32_t new_mask =
(existing_mask | (add_volatile ? SpvMemoryAccessVolatileMask
: SpvMemoryAccessMaskNone)) ^
(toggle_nontemporal ? SpvMemoryAccessNontemporalMask
: SpvMemoryAccessMaskNone);
TransformationSetMemoryOperandsMask transformation(
MakeInstructionDescriptor(block, inst_it), new_mask, mask_index);
ApplyTransformation(transformation);
}
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,40 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_MEMORY_OPERANDS_MASKS_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_MEMORY_OPERANDS_MASKS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass to adjust the memory operand masks in memory access
// instructions.
class FuzzerPassAdjustMemoryOperandsMasks : public FuzzerPass {
public:
FuzzerPassAdjustMemoryOperandsMasks(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAdjustMemoryOperandsMasks();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_MEMORY_OPERANDS_MASKS_H_

View File

@@ -1,73 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_adjust_selection_controls.h"
#include "source/fuzz/transformation_set_selection_control.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAdjustSelectionControls::FuzzerPassAdjustSelectionControls(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAdjustSelectionControls::~FuzzerPassAdjustSelectionControls() =
default;
void FuzzerPassAdjustSelectionControls::Apply() {
// Consider every merge instruction in the module (via looking through all
// functions and blocks).
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
if (auto merge_inst = block.GetMergeInst()) {
// Ignore the instruction if it is not a selection merge.
if (merge_inst->opcode() != SpvOpSelectionMerge) {
continue;
}
// Choose randomly whether to change the selection control for this
// instruction.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAdjustingSelectionControl())) {
continue;
}
// The choices to change the selection control to are the set of valid
// controls, minus the current control.
std::vector<uint32_t> choices;
for (auto control :
{SpvSelectionControlMaskNone, SpvSelectionControlFlattenMask,
SpvSelectionControlDontFlattenMask}) {
if (control == merge_inst->GetSingleWordOperand(1)) {
continue;
}
choices.push_back(control);
}
// Apply the transformation and add it to the output transformation
// sequence.
TransformationSetSelectionControl transformation(
block.id(), choices[GetFuzzerContext()->RandomIndex(choices)]);
ApplyTransformation(transformation);
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,39 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_
#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A pass that adjusts the selection controls on OpSelectionMerge instructions.
class FuzzerPassAdjustSelectionControls : public FuzzerPass {
public:
FuzzerPassAdjustSelectionControls(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAdjustSelectionControls() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_

View File

@@ -1,192 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
#include "source/fuzz/data_descriptor.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/id_use_descriptor.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_composite_extract.h"
#include "source/fuzz/transformation_compute_data_synonym_fact_closure.h"
#include "source/fuzz/transformation_replace_id_with_synonym.h"
namespace spvtools {
namespace fuzz {
FuzzerPassApplyIdSynonyms::FuzzerPassApplyIdSynonyms(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassApplyIdSynonyms::~FuzzerPassApplyIdSynonyms() = default;
void FuzzerPassApplyIdSynonyms::Apply() {
// Compute a closure of data synonym facts, to enrich the pool of synonyms
// that are available.
ApplyTransformation(TransformationComputeDataSynonymFactClosure(
GetFuzzerContext()
->GetMaximumEquivalenceClassSizeForDataSynonymFactClosure()));
for (auto id_with_known_synonyms : GetTransformationContext()
->GetFactManager()
->GetIdsForWhichSynonymsAreKnown()) {
// Gather up all uses of |id_with_known_synonym| as a regular id, and
// subsequently iterate over these uses. We use this separation because,
// when considering a given use, we might apply a transformation that will
// invalidate the def-use manager.
std::vector<std::pair<opt::Instruction*, uint32_t>> uses;
GetIRContext()->get_def_use_mgr()->ForEachUse(
id_with_known_synonyms,
[&uses](opt::Instruction* use_inst, uint32_t use_index) -> void {
// We only gather up regular id uses; e.g. we do not include a use of
// the id as the scope for an atomic operation.
if (use_inst->GetOperand(use_index).type == SPV_OPERAND_TYPE_ID) {
uses.emplace_back(
std::pair<opt::Instruction*, uint32_t>(use_inst, use_index));
}
});
for (const auto& use : uses) {
auto use_inst = use.first;
auto use_index = use.second;
auto block_containing_use = GetIRContext()->get_instr_block(use_inst);
// The use might not be in a block; e.g. it could be a decoration.
if (!block_containing_use) {
continue;
}
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfReplacingIdWithSynonym())) {
continue;
}
// |use_index| is the absolute index of the operand. We require
// the index of the operand restricted to input operands only.
uint32_t use_in_operand_index =
fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
if (!TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
GetIRContext(), use_inst, use_in_operand_index)) {
continue;
}
std::vector<const protobufs::DataDescriptor*> synonyms_to_try;
for (const auto* data_descriptor :
GetTransformationContext()->GetFactManager()->GetSynonymsForId(
id_with_known_synonyms)) {
protobufs::DataDescriptor descriptor_for_this_id =
MakeDataDescriptor(id_with_known_synonyms, {});
if (DataDescriptorEquals()(data_descriptor, &descriptor_for_this_id)) {
// Exclude the fact that the id is synonymous with itself.
continue;
}
if (DataDescriptorsHaveCompatibleTypes(
use_inst->opcode(), use_in_operand_index,
descriptor_for_this_id, *data_descriptor)) {
synonyms_to_try.push_back(data_descriptor);
}
}
while (!synonyms_to_try.empty()) {
auto synonym_to_try =
GetFuzzerContext()->RemoveAtRandomIndex(&synonyms_to_try);
// If the synonym's |index_size| is zero, the synonym represents an id.
// Otherwise it represents some element of a composite structure, in
// which case we need to be able to add an extract instruction to get
// that element out.
if (synonym_to_try->index_size() > 0 &&
!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCompositeExtract,
use_inst) &&
use_inst->opcode() != SpvOpPhi) {
// We cannot insert an extract before this instruction, so this
// synonym is no good.
continue;
}
if (!fuzzerutil::IdIsAvailableAtUse(GetIRContext(), use_inst,
use_in_operand_index,
synonym_to_try->object())) {
continue;
}
// We either replace the use with an id known to be synonymous (when
// the synonym's |index_size| is 0), or an id that will hold the result
// of extracting a synonym from a composite (when the synonym's
// |index_size| is > 0).
uint32_t id_with_which_to_replace_use;
if (synonym_to_try->index_size() == 0) {
id_with_which_to_replace_use = synonym_to_try->object();
} else {
id_with_which_to_replace_use = GetFuzzerContext()->GetFreshId();
opt::Instruction* instruction_to_insert_before = nullptr;
if (use_inst->opcode() != SpvOpPhi) {
instruction_to_insert_before = use_inst;
} else {
auto parent_block_id =
use_inst->GetSingleWordInOperand(use_in_operand_index + 1);
auto parent_block_instruction =
GetIRContext()->get_def_use_mgr()->GetDef(parent_block_id);
auto parent_block =
GetIRContext()->get_instr_block(parent_block_instruction);
instruction_to_insert_before = parent_block->GetMergeInst()
? parent_block->GetMergeInst()
: parent_block->terminator();
}
assert(!GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
synonym_to_try->object()) &&
"Irrelevant ids can't participate in DataSynonym facts");
ApplyTransformation(TransformationCompositeExtract(
MakeInstructionDescriptor(GetIRContext(),
instruction_to_insert_before),
id_with_which_to_replace_use, synonym_to_try->object(),
fuzzerutil::RepeatedFieldToVector(synonym_to_try->index())));
}
ApplyTransformation(TransformationReplaceIdWithSynonym(
MakeIdUseDescriptorFromUse(GetIRContext(), use_inst,
use_in_operand_index),
id_with_which_to_replace_use));
break;
}
}
}
}
bool FuzzerPassApplyIdSynonyms::DataDescriptorsHaveCompatibleTypes(
SpvOp opcode, uint32_t use_in_operand_index,
const protobufs::DataDescriptor& dd1,
const protobufs::DataDescriptor& dd2) {
auto base_object_type_id_1 =
fuzzerutil::GetTypeId(GetIRContext(), dd1.object());
auto base_object_type_id_2 =
fuzzerutil::GetTypeId(GetIRContext(), dd2.object());
assert(base_object_type_id_1 && base_object_type_id_2 &&
"Data descriptors are invalid");
auto type_id_1 = fuzzerutil::WalkCompositeTypeIndices(
GetIRContext(), base_object_type_id_1, dd1.index());
auto type_id_2 = fuzzerutil::WalkCompositeTypeIndices(
GetIRContext(), base_object_type_id_2, dd2.index());
assert(type_id_1 && type_id_2 && "Data descriptors have invalid types");
return TransformationReplaceIdWithSynonym::TypesAreCompatible(
GetIRContext(), opcode, use_in_operand_index, type_id_1, type_id_2);
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,51 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_
#define SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_
#include "source/fuzz/fuzzer_pass.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
// A pass that replaces ids with other ids, or accesses into structures, that
// are known to hold the same values.
class FuzzerPassApplyIdSynonyms : public FuzzerPass {
public:
FuzzerPassApplyIdSynonyms(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassApplyIdSynonyms() override;
void Apply() override;
private:
// Returns true if uses of |dd1| can be replaced with |dd2| and vice-versa
// with respect to the type. Concretely, returns true if |dd1| and |dd2| have
// the same type or both |dd1| and |dd2| are either a numerical or a vector
// type of integral components with possibly different signedness.
bool DataDescriptorsHaveCompatibleTypes(SpvOp opcode,
uint32_t use_in_operand_index,
const protobufs::DataDescriptor& dd1,
const protobufs::DataDescriptor& dd2);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_

View File

@@ -1,385 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_construct_composites.h"
#include <memory>
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_composite_construct.h"
namespace spvtools {
namespace fuzz {
FuzzerPassConstructComposites::FuzzerPassConstructComposites(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassConstructComposites::~FuzzerPassConstructComposites() = default;
void FuzzerPassConstructComposites::Apply() {
// Gather up the ids of all composite types.
std::vector<uint32_t> composite_type_ids;
for (auto& inst : GetIRContext()->types_values()) {
if (fuzzerutil::IsCompositeType(
GetIRContext()->get_type_mgr()->GetType(inst.result_id()))) {
composite_type_ids.push_back(inst.result_id());
}
}
ForEachInstructionWithInstructionDescriptor(
[this, &composite_type_ids](
opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor)
-> void {
// Check whether it is legitimate to insert a composite construction
// before the instruction.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
SpvOpCompositeConstruct, inst_it)) {
return;
}
// Randomly decide whether to try inserting an object copy here.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfConstructingComposite())) {
return;
}
// For each instruction that is available at this program point (i.e. an
// instruction that is global or whose definition strictly dominates the
// program point) and suitable for making a synonym of, associate it
// with the id of its result type.
TypeIdToInstructions type_id_to_available_instructions;
auto available_instructions = FindAvailableInstructions(
function, block, inst_it,
[this](opt::IRContext* ir_context, opt::Instruction* inst) {
if (!inst->result_id() || !inst->type_id()) {
return false;
}
// If the id is irrelevant, we can use it since it will not
// participate in DataSynonym fact. Otherwise, we should be able
// to produce a synonym out of the id.
return GetTransformationContext()
->GetFactManager()
->IdIsIrrelevant(inst->result_id()) ||
fuzzerutil::CanMakeSynonymOf(
ir_context, *GetTransformationContext(), inst);
});
for (auto instruction : available_instructions) {
RecordAvailableInstruction(instruction,
&type_id_to_available_instructions);
}
// At this point, |composite_type_ids| captures all the composite types
// we could try to create, while |type_id_to_available_instructions|
// captures all the available result ids we might use, organized by
// type.
// Now we try to find a composite that we can construct. We might not
// manage, if there is a paucity of available ingredients in the module
// (e.g. if our only available composite was a boolean vector and we had
// no instructions generating boolean result types available).
//
// If we succeed, |chosen_composite_type| will end up being non-zero,
// and |constructor_arguments| will end up giving us result ids suitable
// for constructing a composite of that type. Otherwise these variables
// will remain 0 and null respectively.
uint32_t chosen_composite_type = 0;
std::vector<uint32_t> constructor_arguments;
// Initially, all composite type ids are available for us to try. Keep
// trying until we run out of options.
auto composites_to_try_constructing = composite_type_ids;
while (!composites_to_try_constructing.empty()) {
// Remove a composite type from the composite types left for us to
// try.
auto next_composite_to_try_constructing =
GetFuzzerContext()->RemoveAtRandomIndex(
&composites_to_try_constructing);
// Now try to construct a composite of this type, using an appropriate
// helper method depending on the kind of composite type.
auto composite_type_inst = GetIRContext()->get_def_use_mgr()->GetDef(
next_composite_to_try_constructing);
switch (composite_type_inst->opcode()) {
case SpvOpTypeArray:
constructor_arguments = FindComponentsToConstructArray(
*composite_type_inst, type_id_to_available_instructions);
break;
case SpvOpTypeMatrix:
constructor_arguments = FindComponentsToConstructMatrix(
*composite_type_inst, type_id_to_available_instructions);
break;
case SpvOpTypeStruct:
constructor_arguments = FindComponentsToConstructStruct(
*composite_type_inst, type_id_to_available_instructions);
break;
case SpvOpTypeVector:
constructor_arguments = FindComponentsToConstructVector(
*composite_type_inst, type_id_to_available_instructions);
break;
default:
assert(false &&
"The space of possible composite types should be covered "
"by the above cases.");
break;
}
if (!constructor_arguments.empty()) {
// We succeeded! Note the composite type we finally settled on, and
// exit from the loop.
chosen_composite_type = next_composite_to_try_constructing;
break;
}
}
if (!chosen_composite_type) {
// We did not manage to make a composite; return 0 to indicate that no
// instructions were added.
assert(constructor_arguments.empty());
return;
}
assert(!constructor_arguments.empty());
// Make and apply a transformation.
ApplyTransformation(TransformationCompositeConstruct(
chosen_composite_type, constructor_arguments,
instruction_descriptor, GetFuzzerContext()->GetFreshId()));
});
}
void FuzzerPassConstructComposites::RecordAvailableInstruction(
opt::Instruction* inst,
TypeIdToInstructions* type_id_to_available_instructions) {
if (type_id_to_available_instructions->count(inst->type_id()) == 0) {
(*type_id_to_available_instructions)[inst->type_id()] = {};
}
type_id_to_available_instructions->at(inst->type_id()).push_back(inst);
}
std::vector<uint32_t>
FuzzerPassConstructComposites::FindComponentsToConstructArray(
const opt::Instruction& array_type_instruction,
const TypeIdToInstructions& type_id_to_available_instructions) {
assert(array_type_instruction.opcode() == SpvOpTypeArray &&
"Precondition: instruction must be an array type.");
// Get the element type for the array.
auto element_type_id = array_type_instruction.GetSingleWordInOperand(0);
// Get all instructions at our disposal that compute something of this element
// type.
auto available_instructions =
type_id_to_available_instructions.find(element_type_id);
if (available_instructions == type_id_to_available_instructions.cend()) {
// If there are not any instructions available that compute the element type
// of the array then we are not in a position to construct a composite with
// this array type.
return {};
}
uint32_t array_length =
GetIRContext()
->get_def_use_mgr()
->GetDef(array_type_instruction.GetSingleWordInOperand(1))
->GetSingleWordInOperand(0);
std::vector<uint32_t> result;
for (uint32_t index = 0; index < array_length; index++) {
result.push_back(available_instructions
->second[GetFuzzerContext()->RandomIndex(
available_instructions->second)]
->result_id());
}
return result;
}
std::vector<uint32_t>
FuzzerPassConstructComposites::FindComponentsToConstructMatrix(
const opt::Instruction& matrix_type_instruction,
const TypeIdToInstructions& type_id_to_available_instructions) {
assert(matrix_type_instruction.opcode() == SpvOpTypeMatrix &&
"Precondition: instruction must be a matrix type.");
// Get the element type for the matrix.
auto element_type_id = matrix_type_instruction.GetSingleWordInOperand(0);
// Get all instructions at our disposal that compute something of this element
// type.
auto available_instructions =
type_id_to_available_instructions.find(element_type_id);
if (available_instructions == type_id_to_available_instructions.cend()) {
// If there are not any instructions available that compute the element type
// of the matrix then we are not in a position to construct a composite with
// this matrix type.
return {};
}
std::vector<uint32_t> result;
for (uint32_t index = 0;
index < matrix_type_instruction.GetSingleWordInOperand(1); index++) {
result.push_back(available_instructions
->second[GetFuzzerContext()->RandomIndex(
available_instructions->second)]
->result_id());
}
return result;
}
std::vector<uint32_t>
FuzzerPassConstructComposites::FindComponentsToConstructStruct(
const opt::Instruction& struct_type_instruction,
const TypeIdToInstructions& type_id_to_available_instructions) {
assert(struct_type_instruction.opcode() == SpvOpTypeStruct &&
"Precondition: instruction must be a struct type.");
std::vector<uint32_t> result;
// Consider the type of each field of the struct.
for (uint32_t in_operand_index = 0;
in_operand_index < struct_type_instruction.NumInOperands();
in_operand_index++) {
auto element_type_id =
struct_type_instruction.GetSingleWordInOperand(in_operand_index);
// Find the instructions at our disposal that compute something of the field
// type.
auto available_instructions =
type_id_to_available_instructions.find(element_type_id);
if (available_instructions == type_id_to_available_instructions.cend()) {
// If there are no such instructions, we cannot construct a composite of
// this struct type.
return {};
}
result.push_back(available_instructions
->second[GetFuzzerContext()->RandomIndex(
available_instructions->second)]
->result_id());
}
return result;
}
std::vector<uint32_t>
FuzzerPassConstructComposites::FindComponentsToConstructVector(
const opt::Instruction& vector_type_instruction,
const TypeIdToInstructions& type_id_to_available_instructions) {
assert(vector_type_instruction.opcode() == SpvOpTypeVector &&
"Precondition: instruction must be a vector type.");
// Get details of the type underlying the vector, and the width of the vector,
// for convenience.
auto element_type_id = vector_type_instruction.GetSingleWordInOperand(0);
auto element_type = GetIRContext()->get_type_mgr()->GetType(element_type_id);
auto element_count = vector_type_instruction.GetSingleWordInOperand(1);
// Collect a mapping, from type id to width, for scalar/vector types that are
// smaller in width than |vector_type|, but that have the same underlying
// type. For example, if |vector_type| is vec4, the mapping will be:
// { float -> 1, vec2 -> 2, vec3 -> 3 }
// The mapping will have missing entries if some of these types do not exist.
std::map<uint32_t, uint32_t> smaller_vector_type_id_to_width;
// Add the underlying type. This id must exist, in order for |vector_type| to
// exist.
smaller_vector_type_id_to_width[element_type_id] = 1;
// Now add every vector type with width at least 2, and less than the width of
// |vector_type|.
for (uint32_t width = 2; width < element_count; width++) {
opt::analysis::Vector smaller_vector_type(element_type, width);
auto smaller_vector_type_id =
GetIRContext()->get_type_mgr()->GetId(&smaller_vector_type);
// We might find that there is no declared type of this smaller width.
// For example, a module can declare vec4 without having declared vec2 or
// vec3.
if (smaller_vector_type_id) {
smaller_vector_type_id_to_width[smaller_vector_type_id] = width;
}
}
// Now we know the types that are available to us, we set about populating a
// vector of the right length. We do this by deciding, with no order in mind,
// which instructions we will use to populate the vector, and subsequently
// randomly choosing an order. This is to avoid biasing construction of
// vectors with smaller vectors to the left and scalars to the right. That is
// a concern because, e.g. in the case of populating a vec4, if we populate
// the constructor instructions left-to-right, we can always choose a vec3 to
// construct the first three elements, but can only choose a vec3 to construct
// the last three elements if we chose a float to construct the first element
// (otherwise there will not be space left for a vec3).
uint32_t vector_slots_used = 0;
// The instructions we will use to construct the vector, in no particular
// order at this stage.
std::vector<opt::Instruction*> instructions_to_use;
while (vector_slots_used < element_count) {
std::vector<opt::Instruction*> instructions_to_choose_from;
for (auto& entry : smaller_vector_type_id_to_width) {
if (entry.second >
std::min(element_count - 1, element_count - vector_slots_used)) {
continue;
}
auto available_instructions =
type_id_to_available_instructions.find(entry.first);
if (available_instructions == type_id_to_available_instructions.cend()) {
continue;
}
instructions_to_choose_from.insert(instructions_to_choose_from.end(),
available_instructions->second.begin(),
available_instructions->second.end());
}
if (instructions_to_choose_from.empty()) {
// We may get unlucky and find that there are not any instructions to
// choose from. In this case we give up constructing a composite of this
// vector type. It might be that we could construct the composite in
// another manner, so we could opt to retry a few times here, but it is
// simpler to just give up on the basis that this will not happen
// frequently.
return {};
}
auto instruction_to_use =
instructions_to_choose_from[GetFuzzerContext()->RandomIndex(
instructions_to_choose_from)];
instructions_to_use.push_back(instruction_to_use);
auto chosen_type =
GetIRContext()->get_type_mgr()->GetType(instruction_to_use->type_id());
if (chosen_type->AsVector()) {
assert(chosen_type->AsVector()->element_type() == element_type);
assert(chosen_type->AsVector()->element_count() < element_count);
assert(chosen_type->AsVector()->element_count() <=
element_count - vector_slots_used);
vector_slots_used += chosen_type->AsVector()->element_count();
} else {
assert(chosen_type == element_type);
vector_slots_used += 1;
}
}
assert(vector_slots_used == element_count);
std::vector<uint32_t> result;
std::vector<uint32_t> operands;
while (!instructions_to_use.empty()) {
auto index = GetFuzzerContext()->RandomIndex(instructions_to_use);
result.push_back(instructions_to_use[index]->result_id());
instructions_to_use.erase(instructions_to_use.begin() + index);
}
assert(result.size() > 1);
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,80 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_
#define SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_
#include "source/fuzz/fuzzer_pass.h"
#include <map>
#include <vector>
namespace spvtools {
namespace fuzz {
// A fuzzer pass for constructing composite objects from smaller objects.
class FuzzerPassConstructComposites : public FuzzerPass {
public:
FuzzerPassConstructComposites(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassConstructComposites();
void Apply() override;
private:
// Used to map a type id to relevant instructions whose result type matches
// the type id.
typedef std::map<uint32_t, std::vector<opt::Instruction*>>
TypeIdToInstructions;
// Considers all instructions that are available at |inst| - instructions
// whose results could be packed into a composite - and updates
// |type_id_to_available_instructions| so that each such instruction is
// associated with its the id of its result type.
void RecordAvailableInstruction(
opt::Instruction* inst,
TypeIdToInstructions* type_id_to_available_instructions);
// Requires that |array_type_instruction| has opcode OpTypeArray.
// Attempts to find suitable instruction result ids from the values of
// |type_id_to_available_instructions| that would allow a composite of type
// |array_type_instruction| to be constructed. Returns said ids if they can
// be found and an empty vector otherwise.
std::vector<uint32_t> FindComponentsToConstructArray(
const opt::Instruction& array_type_instruction,
const TypeIdToInstructions& type_id_to_available_instructions);
// Similar to FindComponentsToConstructArray, but for matrices.
std::vector<uint32_t> FindComponentsToConstructMatrix(
const opt::Instruction& matrix_type_instruction,
const TypeIdToInstructions& type_id_to_available_instructions);
// Similar to FindComponentsToConstructArray, but for structs.
std::vector<uint32_t> FindComponentsToConstructStruct(
const opt::Instruction& struct_type_instruction,
const TypeIdToInstructions& type_id_to_available_instructions);
// Similar to FindComponentsToConstructArray, but for vectors.
std::vector<uint32_t> FindComponentsToConstructVector(
const opt::Instruction& vector_type_instruction,
const TypeIdToInstructions& type_id_to_available_instructions);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_

View File

@@ -1,83 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_copy_objects.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation_add_synonym.h"
namespace spvtools {
namespace fuzz {
FuzzerPassCopyObjects::FuzzerPassCopyObjects(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassCopyObjects::~FuzzerPassCopyObjects() = default;
void FuzzerPassCopyObjects::Apply() {
ForEachInstructionWithInstructionDescriptor(
[this](opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor)
-> void {
assert(inst_it->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");
// Check whether it is legitimate to insert a copy before this
// instruction.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCopyObject,
inst_it)) {
return;
}
// Randomly decide whether to try inserting an object copy here.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfCopyingObject())) {
return;
}
const auto relevant_instructions = FindAvailableInstructions(
function, block, inst_it,
[this](opt::IRContext* ir_context, opt::Instruction* inst) {
return TransformationAddSynonym::IsInstructionValid(
ir_context, *GetTransformationContext(), inst,
protobufs::TransformationAddSynonym::COPY_OBJECT);
});
// At this point, |relevant_instructions| contains all the instructions
// we might think of copying.
if (relevant_instructions.empty()) {
return;
}
// Choose a copyable instruction at random, and create and apply an
// object copying transformation based on it.
ApplyTransformation(TransformationAddSynonym(
relevant_instructions[GetFuzzerContext()->RandomIndex(
relevant_instructions)]
->result_id(),
protobufs::TransformationAddSynonym::COPY_OBJECT,
GetFuzzerContext()->GetFreshId(), instruction_descriptor));
});
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,39 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_COPY_OBJECTS_H_
#define SOURCE_FUZZ_FUZZER_PASS_COPY_OBJECTS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass for adding adding copies of objects to the module.
class FuzzerPassCopyObjects : public FuzzerPass {
public:
FuzzerPassCopyObjects(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassCopyObjects();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_COPY_OBJECTS_H_

File diff suppressed because it is too large Load Diff

View File

@@ -1,170 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_
#define SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_
#include <vector>
#include "source/fuzz/fuzzer_pass.h"
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass that randomly adds code from other SPIR-V modules to the module
// being transformed.
class FuzzerPassDonateModules : public FuzzerPass {
public:
FuzzerPassDonateModules(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations,
const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers);
~FuzzerPassDonateModules();
void Apply() override;
// Donates the global declarations and functions of |donor_ir_context| into
// the fuzzer pass's IR context. |make_livesafe| dictates whether the
// functions of the donated module will be made livesafe (see
// FactFunctionIsLivesafe).
void DonateSingleModule(opt::IRContext* donor_ir_context, bool make_livesafe);
private:
// Adapts a storage class coming from a donor module so that it will work
// in a recipient module, e.g. by changing Uniform to Private.
static SpvStorageClass AdaptStorageClass(SpvStorageClass donor_storage_class);
// Identifies all external instruction set imports in |donor_ir_context| and
// populates |original_id_to_donated_id| with a mapping from the donor's id
// for such an import to a corresponding import in the recipient. Aborts if
// no such corresponding import is available.
void HandleExternalInstructionImports(
opt::IRContext* donor_ir_context,
std::map<uint32_t, uint32_t>* original_id_to_donated_id);
// Considers all types, globals, constants and undefs in |donor_ir_context|.
// For each instruction, uses |original_to_donated_id| to map its result id to
// either (1) the id of an existing identical instruction in the recipient, or
// (2) to a fresh id, in which case the instruction is also added to the
// recipient (with any operand ids that it uses being remapped via
// |original_id_to_donated_id|).
void HandleTypesAndValues(
opt::IRContext* donor_ir_context,
std::map<uint32_t, uint32_t>* original_id_to_donated_id);
// Helper method for HandleTypesAndValues, to handle a single type/value.
void HandleTypeOrValue(
const opt::Instruction& type_or_value,
std::map<uint32_t, uint32_t>* original_id_to_donated_id);
// Assumes that |donor_ir_context| does not exhibit recursion. Considers the
// functions in |donor_ir_context|'s call graph in a reverse-topologically-
// sorted order (leaves-to-root), adding each function to the recipient
// module, rewritten to use fresh ids and using |original_id_to_donated_id| to
// remap ids. The |make_livesafe| argument captures whether the functions in
// the module are required to be made livesafe before being added to the
// recipient.
void HandleFunctions(opt::IRContext* donor_ir_context,
std::map<uint32_t, uint32_t>* original_id_to_donated_id,
bool make_livesafe);
// During donation we will have to ignore some instructions, e.g. because they
// use opcodes that we cannot support or because they reference the ids of
// instructions that have not been donated. This function encapsulates the
// logic for deciding which whether instruction |instruction| from
// |donor_ir_context| can be donated.
bool CanDonateInstruction(
opt::IRContext* donor_ir_context, const opt::Instruction& instruction,
const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
const std::set<uint32_t>& skipped_instructions) const;
// We treat the OpArrayLength instruction specially. In the donor shader this
// instruction yields the length of a runtime array that is the final member
// of a struct. During donation, we will have converted the runtime array
// type, and the associated struct field, into a fixed-size array.
//
// Instead of donating this instruction, we turn it into an OpCopyObject
// instruction that copies the size of the fixed-size array.
void HandleOpArrayLength(
const opt::Instruction& instruction,
std::map<uint32_t, uint32_t>* original_id_to_donated_id,
std::vector<protobufs::Instruction>* donated_instructions) const;
// The instruction |instruction| is required to be an instruction that cannot
// be easily donated, either because it uses an unsupported opcode, has an
// unsupported result type, or uses id operands that could not be donated.
//
// If |instruction| generates a result id, the function attempts to add a
// substitute for |instruction| to |donated_instructions| that has the correct
// result type. If this cannot be done, the instruction's result id is added
// to |skipped_instructions|. The mapping from donor ids to recipient ids is
// managed by |original_id_to_donated_id|.
void HandleDifficultInstruction(
const opt::Instruction& instruction,
std::map<uint32_t, uint32_t>* original_id_to_donated_id,
std::vector<protobufs::Instruction>* donated_instructions,
std::set<uint32_t>* skipped_instructions);
// Adds an instruction based in |instruction| to |donated_instructions| in a
// form ready for donation. The original instruction comes from
// |donor_ir_context|, and |original_id_to_donated_id| maps ids from
// |donor_ir_context| to corresponding ids in the recipient module.
void PrepareInstructionForDonation(
const opt::Instruction& instruction, opt::IRContext* donor_ir_context,
std::map<uint32_t, uint32_t>* original_id_to_donated_id,
std::vector<protobufs::Instruction>* donated_instructions);
// Tries to create a protobufs::LoopLimiterInfo given a loop header basic
// block. Returns true if successful and outputs loop limiter into the |out|
// variable. Otherwise, returns false. |out| contains an undefined value when
// this function returns false.
bool CreateLoopLimiterInfo(
opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header,
const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
protobufs::LoopLimiterInfo* out);
// Requires that |donated_instructions| represents a prepared version of the
// instructions of |function_to_donate| (which comes from |donor_ir_context|)
// ready for donation, and |original_id_to_donated_id| maps ids from
// |donor_ir_context| to their corresponding ids in the recipient module.
//
// Attempts to add a livesafe version of the function, based on
// |donated_instructions|, to the recipient module. Returns true if the
// donation was successful, false otherwise.
bool MaybeAddLivesafeFunction(
const opt::Function& function_to_donate, opt::IRContext* donor_ir_context,
const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
const std::vector<protobufs::Instruction>& donated_instructions);
// Returns true if and only if |instruction| is a scalar, vector, matrix,
// array or struct; i.e. it is not an opaque type.
bool IsBasicType(const opt::Instruction& instruction) const;
// Returns the ids of all functions in |context| in a topological order in
// relation to the call graph of |context|, which is assumed to be recursion-
// free.
static std::vector<uint32_t> GetFunctionsInCallGraphTopologicalOrder(
opt::IRContext* context);
// Functions that supply SPIR-V modules
std::vector<fuzzerutil::ModuleSupplier> donor_suppliers_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_

View File

@@ -1,104 +0,0 @@
// Copyright (c) 2020 André Perez Maselco
//
// 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_inline_functions.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_inline_function.h"
#include "source/fuzz/transformation_split_block.h"
namespace spvtools {
namespace fuzz {
FuzzerPassInlineFunctions::FuzzerPassInlineFunctions(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassInlineFunctions::~FuzzerPassInlineFunctions() = default;
void FuzzerPassInlineFunctions::Apply() {
// |function_call_instructions| are the instructions that will be inlined.
// First, they will be collected and then do the inlining in another loop.
// This avoids changing the module while it is being inspected.
std::vector<opt::Instruction*> function_call_instructions;
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
for (auto& instruction : block) {
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfInliningFunction())) {
continue;
}
// |instruction| must be suitable for inlining.
if (!TransformationInlineFunction::IsSuitableForInlining(
GetIRContext(), &instruction)) {
continue;
}
function_call_instructions.push_back(&instruction);
}
}
}
// Once the function calls have been collected, it's time to actually create
// and apply the inlining transformations.
for (auto& function_call_instruction : function_call_instructions) {
// If |function_call_instruction| is not the penultimate instruction in its
// block or its block termination instruction is not OpBranch, then try to
// split |function_call_block| such that the conditions are met.
auto* function_call_block =
GetIRContext()->get_instr_block(function_call_instruction);
if ((function_call_instruction != &*--function_call_block->tail() ||
function_call_block->terminator()->opcode() != SpvOpBranch) &&
!MaybeApplyTransformation(TransformationSplitBlock(
MakeInstructionDescriptor(GetIRContext(),
function_call_instruction->NextNode()),
GetFuzzerContext()->GetFreshId()))) {
continue;
}
auto* called_function = fuzzerutil::FindFunction(
GetIRContext(), function_call_instruction->GetSingleWordInOperand(0));
// Mapping the called function instructions.
std::map<uint32_t, uint32_t> result_id_map;
for (auto& called_function_block : *called_function) {
// The called function entry block label will not be inlined.
if (&called_function_block != &*called_function->entry()) {
result_id_map[called_function_block.GetLabelInst()->result_id()] =
GetFuzzerContext()->GetFreshId();
}
for (auto& instruction_to_inline : called_function_block) {
// The instructions are mapped to fresh ids.
if (instruction_to_inline.HasResultId()) {
result_id_map[instruction_to_inline.result_id()] =
GetFuzzerContext()->GetFreshId();
}
}
}
// Applies the inline function transformation.
ApplyTransformation(TransformationInlineFunction(
function_call_instruction->result_id(), result_id_map));
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,41 +0,0 @@
// Copyright (c) 2020 André Perez Maselco
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_
#define SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Looks for OpFunctionCall instructions and randomly decides which ones to
// inline. If the instructions of the called function are going to be inlined,
// then a mapping, between their result ids and suitable ids, is done.
class FuzzerPassInlineFunctions : public FuzzerPass {
public:
FuzzerPassInlineFunctions(opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassInlineFunctions() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_

View File

@@ -1,156 +0,0 @@
// 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 "fuzzer_pass_interchange_signedness_of_integer_operands.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/id_use_descriptor.h"
#include "source/fuzz/transformation_record_synonymous_constants.h"
#include "source/fuzz/transformation_replace_id_with_synonym.h"
namespace spvtools {
namespace fuzz {
FuzzerPassInterchangeSignednessOfIntegerOperands::
FuzzerPassInterchangeSignednessOfIntegerOperands(
opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassInterchangeSignednessOfIntegerOperands::
~FuzzerPassInterchangeSignednessOfIntegerOperands() = default;
void FuzzerPassInterchangeSignednessOfIntegerOperands::Apply() {
// Make vector keeping track of all the uses we want to replace.
// This is a vector of pairs, where the first element is an id use descriptor
// identifying the use of a constant id and the second is the id that should
// be used to replace it.
std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>> uses_to_replace;
for (auto constant : GetIRContext()->GetConstants()) {
uint32_t constant_id = constant->result_id();
// We want to record the synonymity of an integer constant with another
// constant with opposite signedness, and this can only be done if they are
// not irrelevant.
if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
constant_id)) {
continue;
}
uint32_t toggled_id =
FindOrCreateToggledIntegerConstant(constant->result_id());
if (!toggled_id) {
// Not an integer constant
continue;
}
assert(!GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
toggled_id) &&
"FindOrCreateToggledConstant can't produce an irrelevant id");
// Record synonymous constants
ApplyTransformation(
TransformationRecordSynonymousConstants(constant_id, toggled_id));
// Find all the uses of the constant and, for each, probabilistically
// decide whether to replace it.
GetIRContext()->get_def_use_mgr()->ForEachUse(
constant_id,
[this, toggled_id, &uses_to_replace](opt::Instruction* use_inst,
uint32_t use_index) -> void {
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfInterchangingSignednessOfIntegerOperands())) {
MaybeAddUseToReplace(use_inst, use_index, toggled_id,
&uses_to_replace);
}
});
}
// Replace the ids if it is allowed.
for (auto use_to_replace : uses_to_replace) {
MaybeApplyTransformation(TransformationReplaceIdWithSynonym(
use_to_replace.first, use_to_replace.second));
}
}
uint32_t FuzzerPassInterchangeSignednessOfIntegerOperands::
FindOrCreateToggledIntegerConstant(uint32_t id) {
// |id| must not be a specialization constant because we do not know the value
// of specialization constants.
if (opt::IsSpecConstantInst(
GetIRContext()->get_def_use_mgr()->GetDef(id)->opcode())) {
return 0;
}
auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(id);
// This pass only toggles integer constants.
if (!constant->AsIntConstant() &&
(!constant->AsVectorConstant() ||
!constant->AsVectorConstant()->component_type()->AsInteger())) {
return 0;
}
if (auto integer = constant->AsIntConstant()) {
auto type = integer->type()->AsInteger();
// Find or create and return the toggled constant.
return FindOrCreateIntegerConstant(std::vector<uint32_t>(integer->words()),
type->width(), !type->IsSigned(), false);
}
// The constant is an integer vector.
// Find the component type.
auto component_type =
constant->AsVectorConstant()->component_type()->AsInteger();
// Find or create the toggled component type.
uint32_t toggled_component_type = FindOrCreateIntegerType(
component_type->width(), !component_type->IsSigned());
// Get the information about the toggled components. We need to extract this
// information now because the analyses might be invalidated, which would make
// the constant and component_type variables invalid.
std::vector<std::vector<uint32_t>> component_words;
for (auto component : constant->AsVectorConstant()->GetComponents()) {
component_words.push_back(component->AsIntConstant()->words());
}
uint32_t width = component_type->width();
bool is_signed = !component_type->IsSigned();
std::vector<uint32_t> toggled_components;
// Find or create the toggled components.
for (auto words : component_words) {
toggled_components.push_back(
FindOrCreateIntegerConstant(words, width, is_signed, false));
}
// Find or create the required toggled vector type.
uint32_t toggled_type = FindOrCreateVectorType(
toggled_component_type, (uint32_t)toggled_components.size());
// Find or create and return the toggled vector constant.
return FindOrCreateCompositeConstant(toggled_components, toggled_type, false);
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,51 +0,0 @@
// 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_FUZZER_PASS_INTERCHANGE_SIGNEDNESS_OF_INTEGER_OPERANDS_
#define SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_SIGNEDNESS_OF_INTEGER_OPERANDS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A pass that:
// - Finds all the integer constant (scalar and vector) definitions in the
// module and adds the definitions of the integer with the same data words but
// opposite signedness. If the synonym is already in the module, it does not
// add a new one.
// - For each use of an integer constant where its signedness does not matter,
// decides whether to change it to the id of the toggled constant.
class FuzzerPassInterchangeSignednessOfIntegerOperands : public FuzzerPass {
public:
FuzzerPassInterchangeSignednessOfIntegerOperands(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassInterchangeSignednessOfIntegerOperands() override;
void Apply() override;
private:
// Given the id of an integer constant (scalar or vector), it finds or creates
// the corresponding toggled constant (the integer with the same data words
// but opposite signedness). Returns the id of the toggled instruction if the
// constant is an integer scalar or vector, 0 otherwise.
uint32_t FindOrCreateToggledIntegerConstant(uint32_t id);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_SIGNEDNESS_OF_INTEGER_OPERANDS_

View File

@@ -1,116 +0,0 @@
// Copyright (c) 2020 Stefano Milizia
// 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_interchange_zero_like_constants.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/id_use_descriptor.h"
#include "source/fuzz/transformation_record_synonymous_constants.h"
#include "source/fuzz/transformation_replace_id_with_synonym.h"
namespace spvtools {
namespace fuzz {
FuzzerPassInterchangeZeroLikeConstants::FuzzerPassInterchangeZeroLikeConstants(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassInterchangeZeroLikeConstants::
~FuzzerPassInterchangeZeroLikeConstants() = default;
uint32_t FuzzerPassInterchangeZeroLikeConstants::FindOrCreateToggledConstant(
opt::Instruction* declaration) {
// |declaration| must not be a specialization constant because we do not know
// the value of specialization constants.
if (opt::IsSpecConstantInst(declaration->opcode())) {
return 0;
}
auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
declaration->result_id());
// This pass only toggles zero-like constants
if (!constant->IsZero()) {
return 0;
}
if (constant->AsScalarConstant()) {
return FindOrCreateNullConstant(declaration->type_id());
} else if (constant->AsNullConstant()) {
// Add declaration of equivalent scalar constant
auto kind = constant->type()->kind();
if (kind == opt::analysis::Type::kBool ||
kind == opt::analysis::Type::kInteger ||
kind == opt::analysis::Type::kFloat) {
return FindOrCreateZeroConstant(declaration->type_id(), false);
}
}
return 0;
}
void FuzzerPassInterchangeZeroLikeConstants::Apply() {
// Make vector keeping track of all the uses we want to replace.
// This is a vector of pairs, where the first element is an id use descriptor
// identifying the use of a constant id and the second is the id that should
// be used to replace it.
std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>> uses_to_replace;
for (auto constant : GetIRContext()->GetConstants()) {
uint32_t constant_id = constant->result_id();
if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
constant_id)) {
continue;
}
uint32_t toggled_id = FindOrCreateToggledConstant(constant);
if (!toggled_id) {
// Not a zero-like constant
continue;
}
assert(!GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
toggled_id) &&
"FindOrCreateToggledConstant can't produce an irrelevant id");
// Record synonymous constants
ApplyTransformation(
TransformationRecordSynonymousConstants(constant_id, toggled_id));
// Find all the uses of the constant and, for each, probabilistically
// decide whether to replace it.
GetIRContext()->get_def_use_mgr()->ForEachUse(
constant_id,
[this, toggled_id, &uses_to_replace](opt::Instruction* use_inst,
uint32_t use_index) -> void {
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfInterchangingZeroLikeConstants())) {
MaybeAddUseToReplace(use_inst, use_index, toggled_id,
&uses_to_replace);
}
});
}
// Replace the ids if it is allowed.
for (auto use_to_replace : uses_to_replace) {
MaybeApplyTransformation(TransformationReplaceIdWithSynonym(
use_to_replace.first, use_to_replace.second));
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,53 +0,0 @@
// Copyright (c) 2020 Stefano Milizia
// 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_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_
#define SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A pass that:
// - Finds all the zero-like constant definitions in the module and adds the
// definitions of the corresponding synonym, recording the fact that they
// are synonymous. If the synonym is already in the module, it does not
// add a new one.
// - For each use of a zero-like constant, decides whether to change it to the
// id of the toggled constant.
class FuzzerPassInterchangeZeroLikeConstants : public FuzzerPass {
public:
FuzzerPassInterchangeZeroLikeConstants(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassInterchangeZeroLikeConstants() override;
void Apply() override;
private:
// Given the declaration of a zero-like constant, it finds or creates the
// corresponding toggled constant (a scalar constant of value 0 becomes a
// null constant of the same type and vice versa).
// Returns the id of the toggled instruction if the constant is zero-like,
// 0 otherwise.
uint32_t FindOrCreateToggledConstant(opt::Instruction* declaration);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_

View File

@@ -1,52 +0,0 @@
// 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_invert_comparison_operators.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_invert_comparison_operator.h"
namespace spvtools {
namespace fuzz {
FuzzerPassInvertComparisonOperators::FuzzerPassInvertComparisonOperators(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassInvertComparisonOperators::~FuzzerPassInvertComparisonOperators() =
default;
void FuzzerPassInvertComparisonOperators::Apply() {
GetIRContext()->module()->ForEachInst([this](const opt::Instruction* inst) {
if (!TransformationInvertComparisonOperator::IsInversionSupported(
inst->opcode())) {
return;
}
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfInvertingComparisonOperators())) {
return;
}
ApplyTransformation(TransformationInvertComparisonOperator(
inst->result_id(), GetFuzzerContext()->GetFreshId()));
});
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,40 +0,0 @@
// 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.
#ifndef SOURCE_FUZZ_FUZZER_PASS_INVERT_COMPARISON_OPERATORS_H_
#define SOURCE_FUZZ_FUZZER_PASS_INVERT_COMPARISON_OPERATORS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Iterates over different comparison operators in the module (>=, <, > etc.)
// and randomly decides whether to invert each one or not.
class FuzzerPassInvertComparisonOperators : public FuzzerPass {
public:
FuzzerPassInvertComparisonOperators(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassInvertComparisonOperators() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_INVERT_COMPARISON_OPERATORS_H_

View File

@@ -1,71 +0,0 @@
// Copyright (c) 2020 André Perez Maselco
//
// 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_make_vector_operations_dynamic.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_make_vector_operation_dynamic.h"
namespace spvtools {
namespace fuzz {
FuzzerPassMakeVectorOperationsDynamic::FuzzerPassMakeVectorOperationsDynamic(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassMakeVectorOperationsDynamic::
~FuzzerPassMakeVectorOperationsDynamic() = default;
void FuzzerPassMakeVectorOperationsDynamic::Apply() {
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
for (auto& instruction : block) {
// Randomly decide whether to try applying the transformation.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfMakingVectorOperationDynamic())) {
continue;
}
// |instruction| must be a vector operation.
if (!TransformationMakeVectorOperationDynamic::IsVectorOperation(
GetIRContext(), &instruction)) {
continue;
}
// Make sure |instruction| has only one indexing operand.
assert(instruction.NumInOperands() ==
(instruction.opcode() == SpvOpCompositeExtract ? 2 : 3) &&
"FuzzerPassMakeVectorOperationsDynamic: the composite "
"instruction must have "
"only one indexing operand.");
// Applies the make vector operation dynamic transformation.
ApplyTransformation(TransformationMakeVectorOperationDynamic(
instruction.result_id(),
FindOrCreateIntegerConstant(
{instruction.GetSingleWordInOperand(
instruction.opcode() == SpvOpCompositeExtract ? 1 : 2)},
32, GetFuzzerContext()->ChooseEven(), false)));
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,40 +0,0 @@
// Copyright (c) 2020 André Perez Maselco
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_
#define SOURCE_FUZZ_FUZZER_PASS_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Looks for OpCompositeExtract/Insert instructions on vectors, and replaces
// them with OpVectorExtract/InsertDynamic.
class FuzzerPassMakeVectorOperationsDynamic : public FuzzerPass {
public:
FuzzerPassMakeVectorOperationsDynamic(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassMakeVectorOperationsDynamic() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_

View File

@@ -1,63 +0,0 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_merge_blocks.h"
#include <vector>
#include "source/fuzz/transformation_merge_blocks.h"
namespace spvtools {
namespace fuzz {
FuzzerPassMergeBlocks::FuzzerPassMergeBlocks(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassMergeBlocks::~FuzzerPassMergeBlocks() = default;
void FuzzerPassMergeBlocks::Apply() {
// First we populate a sequence of transformations that we might consider
// applying.
std::vector<TransformationMergeBlocks> potential_transformations;
// We do this by considering every block of every function.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// We probabilistically decide to ignore some blocks.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfMergingBlocks())) {
continue;
}
// For other blocks, we add a transformation to merge the block into its
// predecessor if that transformation would be applicable.
TransformationMergeBlocks transformation(block.id());
if (transformation.IsApplicable(GetIRContext(),
*GetTransformationContext())) {
potential_transformations.push_back(transformation);
}
}
}
while (!potential_transformations.empty()) {
auto transformation =
GetFuzzerContext()->RemoveAtRandomIndex(&potential_transformations);
MaybeApplyTransformation(transformation);
}
}
} // namespace fuzz
} // namespace spvtools

Some files were not shown because too many files have changed in this diff Show More