Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2020-07-17 20:30:31 -07:00
parent 8d5c7eae3a
commit c851c522f6
71 changed files with 1778 additions and 578 deletions

View File

@@ -1 +1 @@
"v2020.4-dev", "SPIRV-Tools v2020.4-dev 38bba44706260cfb807097c58ca926c64c3a13d2"
"v2020.4-dev", "SPIRV-Tools v2020.4-dev 8d5c7eae3a89ee8b898c9771119abb8a3f977898"

View File

@@ -110,6 +110,16 @@ static const int kInstRayTracingOutLaunchIdX = kInstCommonOutCnt;
static const int kInstRayTracingOutLaunchIdY = kInstCommonOutCnt + 1;
static const int kInstRayTracingOutLaunchIdZ = kInstCommonOutCnt + 2;
// Mesh Shader Output Record Offsets
static const int kInstMeshOutGlobalInvocationIdX = kInstCommonOutCnt;
static const int kInstMeshOutGlobalInvocationIdY = kInstCommonOutCnt + 1;
static const int kInstMeshOutGlobalInvocationIdZ = kInstCommonOutCnt + 2;
// Task Shader Output Record Offsets
static const int kInstTaskOutGlobalInvocationIdX = kInstCommonOutCnt;
static const int kInstTaskOutGlobalInvocationIdY = kInstCommonOutCnt + 1;
static const int kInstTaskOutGlobalInvocationIdZ = kInstCommonOutCnt + 2;
// Size of Common and Stage-specific Members
static const int kInstStageOutCnt = kInstCommonOutCnt + 3;

View File

@@ -677,6 +677,12 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableReplayValidation(
SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetRandomSeed(
spv_fuzzer_options options, uint32_t seed);
// Sets the range of transformations that should be applied during replay: 0
// means all transformations, +N means the first N transformations, -N means all
// except the final N transformations.
SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetReplayRange(
spv_fuzzer_options options, int32_t replay_range);
// Sets the maximum number of steps that the shrinker should take before giving
// up.
SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit(

View File

@@ -227,6 +227,11 @@ class FuzzerOptions {
spvFuzzerOptionsSetRandomSeed(options_, seed);
}
// See spvFuzzerOptionsSetReplayRange.
void set_replay_range(int32_t replay_range) {
spvFuzzerOptionsSetReplayRange(options_, replay_range);
}
// See spvFuzzerOptionsSetShrinkerStepLimit.
void set_shrinker_step_limit(uint32_t shrinker_step_limit) {
spvFuzzerOptionsSetShrinkerStepLimit(options_, shrinker_step_limit);

View File

@@ -47,6 +47,7 @@ if(SPIRV_BUILD_FUZZER)
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_no_contraction_decorations.h
@@ -63,6 +64,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_copy_objects.h
fuzzer_pass_donate_modules.h
fuzzer_pass_invert_comparison_operators.h
fuzzer_pass_interchange_zero_like_constants.h
fuzzer_pass_merge_blocks.h
fuzzer_pass_obfuscate_constants.h
fuzzer_pass_outline_functions.h
@@ -103,6 +105,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_add_no_contraction_decoration.h
transformation_add_parameter.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
@@ -117,7 +120,6 @@ if(SPIRV_BUILD_FUZZER)
transformation_composite_extract.h
transformation_compute_data_synonym_fact_closure.h
transformation_context.h
transformation_copy_object.h
transformation_equation_instruction.h
transformation_function_call.h
transformation_invert_comparison_operator.h
@@ -164,6 +166,7 @@ if(SPIRV_BUILD_FUZZER)
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_no_contraction_decorations.cpp
@@ -180,6 +183,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_copy_objects.cpp
fuzzer_pass_donate_modules.cpp
fuzzer_pass_invert_comparison_operators.cpp
fuzzer_pass_interchange_zero_like_constants.cpp
fuzzer_pass_merge_blocks.cpp
fuzzer_pass_obfuscate_constants.cpp
fuzzer_pass_outline_functions.cpp
@@ -219,6 +223,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_add_no_contraction_decoration.cpp
transformation_add_parameter.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
@@ -233,7 +238,6 @@ if(SPIRV_BUILD_FUZZER)
transformation_composite_extract.cpp
transformation_compute_data_synonym_fact_closure.cpp
transformation_context.cpp
transformation_copy_object.cpp
transformation_equation_instruction.cpp
transformation_function_call.cpp
transformation_invert_comparison_operator.cpp

View File

@@ -457,6 +457,18 @@ class FactManager::DataSynonymAndIdEquationFacts {
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,
@@ -588,9 +600,13 @@ void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive(
// 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 SpvOpIAdd: {
// Equation form: "a = b + c"
for (auto equation : GetEquations(rhs_dds[0])) {
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])) {
@@ -606,7 +622,7 @@ void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive(
}
}
}
for (auto equation : GetEquations(rhs_dds[1])) {
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])) {
@@ -620,7 +636,7 @@ void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive(
}
case SpvOpISub: {
// Equation form: "a = b - c"
for (auto equation : GetEquations(rhs_dds[0])) {
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])) {
@@ -646,7 +662,7 @@ void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive(
}
}
for (auto equation : GetEquations(rhs_dds[1])) {
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])) {
@@ -676,7 +692,7 @@ void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive(
case SpvOpLogicalNot:
case SpvOpSNegate: {
// Equation form: "a = !b" or "a = -b"
for (auto equation : GetEquations(rhs_dds[0])) {
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"
@@ -698,7 +714,69 @@ void FactManager::DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive(
// Record that the data descriptors provided in the fact are equivalent.
MakeEquivalent(dd1, dd2);
// We now check whether this is a synonym about composite objects. If it is,
// Compute various corollary facts.
ComputeConversionDataSynonymFacts(dd1, context);
ComputeCompositeDataSynonymFacts(dd1, dd2, context);
}
void FactManager::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 FactManager::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

View File

@@ -18,7 +18,6 @@
#include <memory>
#include <sstream>
#include "fuzzer_pass_adjust_memory_operands_masks.h"
#include "source/fuzz/fact_manager.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_pass_add_access_chains.h"
@@ -36,15 +35,18 @@
#include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
#include "source/fuzz/fuzzer_pass_add_parameters.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_interchange_zero_like_constants.h"
#include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
#include "source/fuzz/fuzzer_pass_merge_blocks.h"
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
@@ -241,6 +243,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
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);
@@ -326,6 +331,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassAddNoContractionDecorations>(
&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);

View File

@@ -43,6 +43,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = {
5, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingParameters = {5, 70};
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};
@@ -63,6 +64,8 @@ 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> kChanceOfGoingDeeperWhenMakingAccessChain =
{50, 95};
const std::pair<uint32_t, uint32_t> kChanceOfInterchangingZeroLikeConstants = {
10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfInvertingComparisonOperators = {
20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfMakingDonorLivesafe = {40, 60};
@@ -160,6 +163,7 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
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_ =
@@ -179,6 +183,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
chance_of_going_deeper_when_making_access_chain_ =
ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
chance_of_interchanging_zero_like_constants_ =
ChooseBetweenMinAndMax(kChanceOfInterchangingZeroLikeConstants);
chance_of_inverting_comparison_operators_ =
ChooseBetweenMinAndMax(kChanceOfInvertingComparisonOperators);
chance_of_making_donor_livesafe_ =
@@ -237,5 +243,21 @@ uint32_t FuzzerContext::ChooseBetweenMinAndMax(
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

@@ -18,6 +18,7 @@
#include <functional>
#include <utility>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/random_generator.h"
#include "source/opt/function.h"
@@ -143,6 +144,7 @@ class FuzzerContext {
}
uint32_t GetChanceOfAddingParameters() { return chance_of_adding_parameters; }
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_;
}
@@ -181,6 +183,9 @@ class FuzzerContext {
uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
return chance_of_going_deeper_when_making_access_chain_;
}
uint32_t GetChanceOfInterchangingZeroLikeConstants() {
return chance_of_interchanging_zero_like_constants_;
}
uint32_t GetChanceOfInvertingComparisonOperators() {
return chance_of_inverting_comparison_operators_;
}
@@ -274,6 +279,7 @@ class FuzzerContext {
// 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.
@@ -307,6 +313,7 @@ class FuzzerContext {
uint32_t chance_of_adding_no_contraction_decoration_;
uint32_t chance_of_adding_parameters;
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_;
@@ -321,6 +328,7 @@ class FuzzerContext {
uint32_t chance_of_copying_object_;
uint32_t chance_of_donating_additional_module_;
uint32_t chance_of_going_deeper_when_making_access_chain_;
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_merging_blocks_;

View File

@@ -20,6 +20,7 @@
#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_type_boolean.h"
@@ -163,8 +164,9 @@ uint32_t FuzzerPass::FindOrCreateBoolType() {
}
uint32_t FuzzerPass::FindOrCreateIntegerType(uint32_t width, bool is_signed) {
if (auto existing_id =
fuzzerutil::MaybeGetIntegerType(GetIRContext(), width, 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();
@@ -173,7 +175,9 @@ uint32_t FuzzerPass::FindOrCreateIntegerType(uint32_t width, bool is_signed) {
}
uint32_t FuzzerPass::FindOrCreateFloatType(uint32_t width) {
if (auto existing_id = fuzzerutil::MaybeGetFloatType(GetIRContext(), 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();
@@ -203,8 +207,14 @@ uint32_t FuzzerPass::FindOrCreateFunctionType(
uint32_t FuzzerPass::FindOrCreateVectorType(uint32_t component_type_id,
uint32_t component_count) {
if (auto existing_id = fuzzerutil::MaybeGetVectorType(
GetIRContext(), component_type_id, 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();
@@ -344,6 +354,24 @@ uint32_t FuzzerPass::FindOrCreateConstant(const std::vector<uint32_t>& words,
return 0;
}
uint32_t FuzzerPass::FindOrCreateCompositeConstant(
const std::vector<uint32_t>& component_ids, uint32_t type_id) {
assert(type_id && "|type_id| can't be 0");
const auto* type_inst = GetIRContext()->get_def_use_mgr()->GetDef(type_id);
assert(type_inst && "|type_id| is invalid");
std::vector<const opt::analysis::Constant*> constants;
for (auto id : component_ids) {
assert(id && "Component's id can't be 0");
const auto* constant =
GetIRContext()->get_constant_mgr()->FindDeclaredConstant(id);
assert(constant && "Component's id is invalid");
constants.push_back(constant);
}
return FindOrCreateCompositeConstant(*type_inst, constants, component_ids);
}
uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) {
for (auto& inst : GetIRContext()->types_values()) {
if (inst.opcode() == SpvOpUndef && inst.type_id() == type_id) {
@@ -355,6 +383,27 @@ uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t 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 {

View File

@@ -103,6 +103,20 @@ class FuzzerPass {
*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();
@@ -183,11 +197,21 @@ class FuzzerPass {
uint32_t FindOrCreateConstant(const std::vector<uint32_t>& words,
uint32_t type_id);
// Returns the id of an OpConstantComposite
uint32_t FindOrCreateCompositeConstant(
const std::vector<uint32_t>& component_ids, uint32_t type_id);
// 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),

View File

@@ -59,11 +59,7 @@ void FuzzerPassAddDeadBlocks::Apply() {
}
// Apply all those transformations that are in fact applicable.
for (auto& transformation : candidate_transformations) {
if (transformation.IsApplicable(GetIRContext(),
*GetTransformationContext())) {
transformation.Apply(GetIRContext(), GetTransformationContext());
*GetTransformations()->add_transformation() = transformation.ToMessage();
}
MaybeApplyTransformation(transformation);
}
}

View File

@@ -114,12 +114,9 @@ void FuzzerPassAddDeadBreaks::Apply() {
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 (transformation.IsApplicable(GetIRContext(),
*GetTransformationContext()) &&
GetFuzzerContext()->ChoosePercentage(
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingDeadBreak())) {
transformation.Apply(GetIRContext(), GetTransformationContext());
*GetTransformations()->add_transformation() = transformation.ToMessage();
MaybeApplyTransformation(transformation);
}
}
}

View File

@@ -80,14 +80,9 @@ void FuzzerPassAddDeadContinues::Apply() {
block.id(), condition_value, std::move(phi_ids));
// Probabilistically decide whether to apply the transformation in the
// case that it is applicable.
if (candidate_transformation.IsApplicable(GetIRContext(),
*GetTransformationContext()) &&
GetFuzzerContext()->ChoosePercentage(
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingDeadContinue())) {
candidate_transformation.Apply(GetIRContext(),
GetTransformationContext());
*GetTransformations()->add_transformation() =
candidate_transformation.ToMessage();
MaybeApplyTransformation(candidate_transformation);
}
}
}

View File

@@ -64,12 +64,106 @@ void FuzzerPassAddEquationInstructions::Apply() {
// 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};
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: {
auto candidate_instructions =
GetIntegerInstructions(available_instructions);
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()) {
FindOrCreateVectorType(
FindOrCreateFloatType(
vector->element_type()->AsInteger()->width()),
vector->element_count());
} else {
FindOrCreateFloatType(type->AsInteger()->width());
}
ApplyTransformation(TransformationEquationInstruction(
GetFuzzerContext()->GetFreshId(), opcode,
{operand->result_id()}, instruction_descriptor));
return;
}
case SpvOpBitcast: {
std::vector<const opt::Instruction*> candidate_instructions;
for (const auto* inst : available_instructions) {
const auto* type =
GetIRContext()->get_type_mgr()->GetType(inst->type_id());
assert(type && "Instruction has invalid type");
if ((type->AsVector() &&
(type->AsVector()->element_type()->AsInteger() ||
type->AsVector()->element_type()->AsFloat())) ||
type->AsInteger() || type->AsFloat()) {
// We support OpBitcast for only scalars or vectors of
// numerical type.
candidate_instructions.push_back(inst);
}
}
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()) {
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,
vector->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
@@ -181,6 +275,20 @@ FuzzerPassAddEquationInstructions::GetIntegerInstructions(
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 {
@@ -225,10 +333,11 @@ FuzzerPassAddEquationInstructions::RestrictToElementBitWidth(
if (type->AsVector()) {
type = type->AsVector()->element_type();
}
assert(type->AsInteger() &&
assert((type->AsInteger() || type->AsFloat()) &&
"Precondition: all input instructions must "
"have integer scalar or vector type.");
if (type->AsInteger()->width() == bit_width) {
"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);
}
}

View File

@@ -41,6 +41,11 @@ class FuzzerPassAddEquationInstructions : public FuzzerPass {
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(
@@ -53,9 +58,9 @@ class FuzzerPassAddEquationInstructions : public FuzzerPass {
const std::vector<opt::Instruction*>& instructions,
uint32_t vector_width) const;
// Requires that |instructions| are integer scalars or vectors. Returns only
// those instructions for which the bit-width of the underlying integer type
// is |bit_width|.
// 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;

View File

@@ -32,7 +32,16 @@ FuzzerPassAddParameters::FuzzerPassAddParameters(
FuzzerPassAddParameters::~FuzzerPassAddParameters() = default;
void FuzzerPassAddParameters::Apply() {
const auto& type_candidates = ComputeTypeCandidates();
// 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.
@@ -71,32 +80,6 @@ void FuzzerPassAddParameters::Apply() {
}
}
std::vector<uint32_t> FuzzerPassAddParameters::ComputeTypeCandidates() const {
std::vector<uint32_t> result;
for (const auto* type_inst : GetIRContext()->module()->GetTypes()) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
// the number of types we support here is limited by the number of types
// supported by |FindOrCreateZeroConstant|.
switch (type_inst->opcode()) {
case SpvOpTypeBool:
case SpvOpTypeInt:
case SpvOpTypeFloat:
case SpvOpTypeArray:
case SpvOpTypeMatrix:
case SpvOpTypeVector:
case SpvOpTypeStruct: {
result.push_back(type_inst->result_id());
} break;
default:
// Ignore other types.
break;
}
}
return result;
}
uint32_t FuzzerPassAddParameters::GetNumberOfParameters(
const opt::Function& function) const {
const auto* type = GetIRContext()->get_type_mgr()->GetType(

View File

@@ -37,10 +37,6 @@ class FuzzerPassAddParameters : public FuzzerPass {
void Apply() override;
private:
// Uses types, defined in the module, to compute a vector of their ids, which
// will be used as type ids of new parameters.
std::vector<uint32_t> ComputeTypeCandidates() const;
// Returns number of parameters of |function|.
uint32_t GetNumberOfParameters(const opt::Function& function) const;
};

View File

@@ -0,0 +1,126 @@
// 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](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, 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());
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)),
existing_synonym->type_id());
} else {
FindOrCreateConstant(
{existing_synonym_type->AsFloat() ? fuzzerutil::FloatToWord(1)
: 1u},
existing_synonym->type_id());
}
} 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

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

@@ -73,10 +73,9 @@ void FuzzerPassApplyIdSynonyms::Apply() {
continue;
}
// |use_index| is the absolute index of the operand. We require
// the index of the operand restricted to input operands only, so
// we subtract the number of non-input operands from |use_index|.
// the index of the operand restricted to input operands only.
uint32_t use_in_operand_index =
use_index - use_inst->NumOperands() + use_inst->NumInOperands();
fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
if (!TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
GetIRContext(), use_inst, use_in_operand_index)) {
continue;

View File

@@ -15,7 +15,8 @@
#include "source/fuzz/fuzzer_pass_copy_objects.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_copy_object.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation_add_synonym.h"
namespace spvtools {
namespace fuzz {
@@ -65,11 +66,12 @@ void FuzzerPassCopyObjects::Apply() {
// Choose a copyable instruction at random, and create and apply an
// object copying transformation based on it.
ApplyTransformation(TransformationCopyObject(
ApplyTransformation(TransformationAddSynonym(
relevant_instructions[GetFuzzerContext()->RandomIndex(
relevant_instructions)]
->result_id(),
instruction_descriptor, GetFuzzerContext()->GetFreshId()));
protobufs::TransformationAddSynonym::COPY_OBJECT,
GetFuzzerContext()->GetFreshId(), instruction_descriptor));
});
}

View File

@@ -0,0 +1,119 @@
// 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) {
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());
}
}
return 0;
}
void FuzzerPassInterchangeZeroLikeConstants::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));
}
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();
uint32_t toggled_id = FindOrCreateToggledConstant(constant);
if (!toggled_id) {
// Not a zero-like constant
continue;
}
// 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
for (auto use_to_replace : uses_to_replace) {
MaybeApplyTransformation(TransformationReplaceIdWithSynonym(
use_to_replace.first, use_to_replace.second));
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,63 @@
// 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);
// Given an id use (described by an instruction and an index) and an id with
// which the original one should be replaced, adds a pair (with the elements
// being the corresponding id use descriptor and the replacement id) to
// |uses_to_replace| if the use is in an instruction block, otherwise 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);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_

View File

@@ -56,11 +56,7 @@ void FuzzerPassMergeBlocks::Apply() {
uint32_t index = GetFuzzerContext()->RandomIndex(potential_transformations);
auto transformation = potential_transformations.at(index);
potential_transformations.erase(potential_transformations.begin() + index);
if (transformation.IsApplicable(GetIRContext(),
*GetTransformationContext())) {
transformation.Apply(GetIRContext(), GetTransformationContext());
*GetTransformations()->add_transformation() = transformation.ToMessage();
}
MaybeApplyTransformation(transformation);
}
}

View File

@@ -477,28 +477,18 @@ void FuzzerPassObfuscateConstants::Apply() {
skipped_opcode_count.clear();
}
switch (inst.opcode()) {
case SpvOpPhi:
// The instruction must not be an OpPhi, as we cannot insert
// instructions before an OpPhi.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902):
// there is scope for being less conservative.
break;
case SpvOpVariable:
// The instruction must not be an OpVariable, the only id that an
// OpVariable uses is an initializer id, which has to remain
// constant.
break;
default:
// Consider each operand of the instruction, and add a constant id
// use for the operand if relevant.
for (uint32_t in_operand_index = 0;
in_operand_index < inst.NumInOperands(); in_operand_index++) {
MaybeAddConstantIdUse(inst, in_operand_index,
base_instruction_result_id,
skipped_opcode_count, &constant_uses);
}
break;
// The instruction must not be an OpVariable, the only id that an
// OpVariable uses is an initializer id, which has to remain
// constant.
if (inst.opcode() != SpvOpVariable) {
// Consider each operand of the instruction, and add a constant id
// use for the operand if relevant.
for (uint32_t in_operand_index = 0;
in_operand_index < inst.NumInOperands(); in_operand_index++) {
MaybeAddConstantIdUse(inst, in_operand_index,
base_instruction_result_id,
skipped_opcode_count, &constant_uses);
}
}
if (!inst.HasResultId()) {

View File

@@ -89,11 +89,7 @@ void FuzzerPassOutlineFunctions::Apply() {
/*new_callee_result_id*/ GetFuzzerContext()->GetFreshId(),
/*input_id_to_fresh_id*/ std::move(input_id_to_fresh_id),
/*output_id_to_fresh_id*/ std::move(output_id_to_fresh_id));
if (transformation.IsApplicable(GetIRContext(),
*GetTransformationContext())) {
transformation.Apply(GetIRContext(), GetTransformationContext());
*GetTransformations()->add_transformation() = transformation.ToMessage();
}
MaybeApplyTransformation(transformation);
}
}

View File

@@ -67,12 +67,7 @@ void FuzzerPassPermuteBlocks::Apply() {
// down indefinitely.
while (true) {
TransformationMoveBlockDown transformation(*id);
if (transformation.IsApplicable(GetIRContext(),
*GetTransformationContext())) {
transformation.Apply(GetIRContext(), GetTransformationContext());
*GetTransformations()->add_transformation() =
transformation.ToMessage();
} else {
if (!MaybeApplyTransformation(transformation)) {
break;
}
}

View File

@@ -45,6 +45,7 @@ void FuzzerPassReplaceLinearAlgebraInstructions::Apply() {
instruction->opcode() != SpvOpMatrixTimesScalar &&
instruction->opcode() != SpvOpVectorTimesMatrix &&
instruction->opcode() != SpvOpMatrixTimesVector &&
instruction->opcode() != SpvOpMatrixTimesMatrix &&
instruction->opcode() != SpvOpDot) {
return;
}

View File

@@ -58,7 +58,7 @@ void FuzzerPassReplaceParameterWithGlobal::Apply() {
param->type_id());
assert(param_type && "Parameter has invalid type");
return TransformationReplaceParameterWithGlobal::
CanReplaceFunctionParameterType(*param_type);
IsParameterTypeSupported(*param_type);
})) {
continue;
}
@@ -71,8 +71,9 @@ void FuzzerPassReplaceParameterWithGlobal::Apply() {
param_type =
GetIRContext()->get_type_mgr()->GetType(replaced_param->type_id());
assert(param_type && "Parameter has invalid type");
} while (!TransformationReplaceParameterWithGlobal::
CanReplaceFunctionParameterType(*param_type));
} while (
!TransformationReplaceParameterWithGlobal::IsParameterTypeSupported(
*param_type));
assert(replaced_param && "Unable to find a parameter to replace");

View File

@@ -96,11 +96,7 @@ void FuzzerPassSplitBlocks::Apply() {
// If the position we have chosen turns out to be a valid place to split
// the block, we apply the split. Otherwise the block just doesn't get
// split.
if (transformation.IsApplicable(GetIRContext(),
*GetTransformationContext())) {
transformation.Apply(GetIRContext(), GetTransformationContext());
*GetTransformations()->add_transformation() = transformation.ToMessage();
}
MaybeApplyTransformation(transformation);
}
}

View File

@@ -156,6 +156,25 @@ void AddUnreachableEdgeAndUpdateOpPhis(
}
}
bool BlockIsBackEdge(opt::IRContext* context, uint32_t block_id,
uint32_t loop_header_id) {
auto block = context->cfg()->block(block_id);
auto loop_header = context->cfg()->block(loop_header_id);
// |block| and |loop_header| must be defined, |loop_header| must be in fact
// loop header and |block| must branch to it.
if (!(block && loop_header && loop_header->IsLoopHeader() &&
block->IsSuccessor(loop_header))) {
return false;
}
// |block_id| must be reachable and be dominated by |loop_header|.
opt::DominatorAnalysis* dominator_analysis =
context->GetDominatorAnalysis(loop_header->GetParent());
return dominator_analysis->IsReachable(block_id) &&
dominator_analysis->Dominates(loop_header_id, block_id);
}
bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id,
uint32_t maybe_loop_header_id) {
// We deem a block to be part of a loop's continue construct if the loop's
@@ -533,6 +552,12 @@ uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id,
return 0;
}
uint32_t InOperandIndexFromOperandIndex(const opt::Instruction& inst,
uint32_t absolute_index) {
// Subtract the number of non-input operands from the index
return absolute_index - inst.NumOperands() + inst.NumInOperands();
}
bool IsNullConstantSupported(const opt::analysis::Type& type) {
return type.AsBool() || type.AsInteger() || type.AsFloat() ||
type.AsMatrix() || type.AsVector() || type.AsArray() ||
@@ -1013,45 +1038,6 @@ void AddStructType(opt::IRContext* ir_context, uint32_t result_id,
UpdateModuleIdBound(ir_context, result_id);
}
uint32_t FindOrCreateIntegerType(opt::IRContext* ir_context, uint32_t result_id,
uint32_t width, bool is_signed) {
if (auto existing_id = MaybeGetIntegerType(ir_context, width, is_signed)) {
return existing_id;
}
AddIntegerType(ir_context, result_id, width, is_signed);
return result_id;
}
uint32_t FindOrCreateFloatType(opt::IRContext* ir_context, uint32_t result_id,
uint32_t width) {
if (auto existing_id = MaybeGetFloatType(ir_context, width)) {
return existing_id;
}
AddFloatType(ir_context, result_id, width);
return result_id;
}
uint32_t FindOrCreateVectorType(opt::IRContext* ir_context, uint32_t result_id,
uint32_t component_type_id,
uint32_t element_count) {
if (auto existing_id =
MaybeGetVectorType(ir_context, component_type_id, element_count)) {
return existing_id;
}
AddVectorType(ir_context, result_id, component_type_id, element_count);
return result_id;
}
uint32_t FindOrCreateStructType(
opt::IRContext* ir_context, uint32_t result_id,
const std::vector<uint32_t>& component_type_ids) {
if (auto existing_id = MaybeGetStructType(ir_context, component_type_ids)) {
return existing_id;
}
AddStructType(ir_context, result_id, component_type_ids);
return result_id;
}
} // namespace fuzzerutil
} // namespace fuzz

View File

@@ -64,6 +64,12 @@ void AddUnreachableEdgeAndUpdateOpPhis(
bool condition_value,
const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids);
// Returns true if and only if |loop_header_id| is a loop header and
// |block_id| is a reachable block branching to and dominated by
// |loop_header_id|.
bool BlockIsBackEdge(opt::IRContext* context, uint32_t block_id,
uint32_t loop_header_id);
// Returns true if and only if |maybe_loop_header_id| is a loop header and
// |block_id| is in the continue construct of the associated loop.
bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id,
@@ -207,6 +213,11 @@ SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context,
uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id,
SpvStorageClass storage_class);
// Given an instruction |inst| and an operand absolute index |absolute_index|,
// returns the index of the operand restricted to the input operands.
uint32_t InOperandIndexFromOperandIndex(const opt::Instruction& inst,
uint32_t absolute_index);
// Returns true if and only if |type| is one of the types for which it is legal
// to have an OpConstantNull value.
bool IsNullConstantSupported(const opt::analysis::Type& type);
@@ -369,38 +380,12 @@ void AddVectorType(opt::IRContext* ir_context, uint32_t result_id,
void AddStructType(opt::IRContext* ir_context, uint32_t result_id,
const std::vector<uint32_t>& component_type_ids);
// Returns a result id of an OpTypeInt instruction in the module. Creates a new
// instruction with |result_id|, if no required OpTypeInt is present in the
// module, and returns |result_id|. Updates module's id bound to accommodate for
// |result_id|.
uint32_t FindOrCreateIntegerType(opt::IRContext* ir_context, uint32_t result_id,
uint32_t width, bool is_signed);
// Returns a result id of an OpTypeFloat instruction in the module. Creates a
// new instruction with |result_id|, if no required OpTypeFloat is present in
// the module, and returns |result_id|. Updates module's id bound
// to accommodate for |result_id|.
uint32_t FindOrCreateFloatType(opt::IRContext* ir_context, uint32_t result_id,
uint32_t width);
// Returns a result id of an OpTypeVector instruction in the module. Creates a
// new instruction with |result_id|, if no required OpTypeVector is present in
// the module, and returns |result_id|. |component_type_id| must be a valid
// result id of an OpTypeInt, OpTypeFloat or OpTypeBool instruction in the
// module. |element_count| must be in the range [2, 4]. Updates module's id
// bound to accommodate for |result_id|.
uint32_t FindOrCreateVectorType(opt::IRContext* ir_context, uint32_t result_id,
uint32_t component_type_id,
uint32_t element_count);
// Returns a result id of an OpTypeStruct instruction in the module. Creates a
// new instruction with |result_id|, if no required OpTypeStruct is present in
// the module, and returns |result_id|. Updates module's id bound
// to accommodate for |result_id|. |component_type_ids| may not contain a result
// id of an OpTypeFunction.
uint32_t FindOrCreateStructType(
opt::IRContext* ir_context, uint32_t result_id,
const std::vector<uint32_t>& component_type_ids);
// Returns a bit pattern that represents a floating-point |value|.
inline uint32_t FloatToWord(float value) {
uint32_t result;
memcpy(&result, &value, sizeof(uint32_t));
return result;
}
} // namespace fuzzerutil

View File

@@ -39,6 +39,7 @@
// where warnings are ignored.
#include "google/protobuf/util/json_util.h"
#include "google/protobuf/util/message_differencer.h"
#include "source/fuzz/protobufs/spvtoolsfuzz.pb.h"
#if defined(__clang__)

View File

@@ -341,51 +341,51 @@ message Transformation {
TransformationAddTypePointer add_type_pointer = 10;
TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11;
TransformationAddDeadContinue add_dead_continue = 12;
TransformationCopyObject copy_object = 13;
TransformationReplaceIdWithSynonym replace_id_with_synonym = 14;
TransformationSetSelectionControl set_selection_control = 15;
TransformationCompositeConstruct composite_construct = 16;
TransformationSetLoopControl set_loop_control = 17;
TransformationSetFunctionControl set_function_control = 18;
TransformationAddNoContractionDecoration add_no_contraction_decoration = 19;
TransformationSetMemoryOperandsMask set_memory_operands_mask = 20;
TransformationCompositeExtract composite_extract = 21;
TransformationVectorShuffle vector_shuffle = 22;
TransformationOutlineFunction outline_function = 23;
TransformationMergeBlocks merge_blocks = 24;
TransformationAddTypeVector add_type_vector = 25;
TransformationAddTypeArray add_type_array = 26;
TransformationAddTypeMatrix add_type_matrix = 27;
TransformationAddTypeStruct add_type_struct = 28;
TransformationAddTypeFunction add_type_function = 29;
TransformationAddConstantComposite add_constant_composite = 30;
TransformationAddGlobalVariable add_global_variable = 31;
TransformationAddGlobalUndef add_global_undef = 32;
TransformationAddFunction add_function = 33;
TransformationAddDeadBlock add_dead_block = 34;
TransformationAddLocalVariable add_local_variable = 35;
TransformationLoad load = 36;
TransformationStore store = 37;
TransformationFunctionCall function_call = 38;
TransformationAccessChain access_chain = 39;
TransformationEquationInstruction equation_instruction = 40;
TransformationSwapCommutableOperands swap_commutable_operands = 41;
TransformationPermuteFunctionParameters permute_function_parameters = 42;
TransformationToggleAccessChainInstruction toggle_access_chain_instruction = 43;
TransformationAddConstantNull add_constant_null = 44;
TransformationComputeDataSynonymFactClosure compute_data_synonym_fact_closure = 45;
TransformationAdjustBranchWeights adjust_branch_weights = 46;
TransformationPushIdThroughVariable push_id_through_variable = 47;
TransformationAddSpecConstantOp add_spec_constant_op = 48;
TransformationReplaceLinearAlgebraInstruction replace_linear_algebra_instruction = 49;
TransformationSwapConditionalBranchOperands swap_conditional_branch_operands = 50;
TransformationPermutePhiOperands permute_phi_operands = 51;
TransformationAddParameter add_parameter = 52;
TransformationAddCopyMemory add_copy_memory = 53;
TransformationInvertComparisonOperator invert_comparison_operator = 54;
TransformationAddImageSampleUnusedComponents add_image_sample_unused_components = 55;
TransformationReplaceParameterWithGlobal replace_parameter_with_global = 56;
TransformationRecordSynonymousConstants record_synonymous_constants = 57;
TransformationReplaceIdWithSynonym replace_id_with_synonym = 13;
TransformationSetSelectionControl set_selection_control = 14;
TransformationCompositeConstruct composite_construct = 15;
TransformationSetLoopControl set_loop_control = 16;
TransformationSetFunctionControl set_function_control = 17;
TransformationAddNoContractionDecoration add_no_contraction_decoration = 18;
TransformationSetMemoryOperandsMask set_memory_operands_mask = 19;
TransformationCompositeExtract composite_extract = 20;
TransformationVectorShuffle vector_shuffle = 21;
TransformationOutlineFunction outline_function = 22;
TransformationMergeBlocks merge_blocks = 23;
TransformationAddTypeVector add_type_vector = 24;
TransformationAddTypeArray add_type_array = 25;
TransformationAddTypeMatrix add_type_matrix = 26;
TransformationAddTypeStruct add_type_struct = 27;
TransformationAddTypeFunction add_type_function = 28;
TransformationAddConstantComposite add_constant_composite = 29;
TransformationAddGlobalVariable add_global_variable = 30;
TransformationAddGlobalUndef add_global_undef = 31;
TransformationAddFunction add_function = 32;
TransformationAddDeadBlock add_dead_block = 33;
TransformationAddLocalVariable add_local_variable = 34;
TransformationLoad load = 35;
TransformationStore store = 36;
TransformationFunctionCall function_call = 37;
TransformationAccessChain access_chain = 38;
TransformationEquationInstruction equation_instruction = 39;
TransformationSwapCommutableOperands swap_commutable_operands = 40;
TransformationPermuteFunctionParameters permute_function_parameters = 41;
TransformationToggleAccessChainInstruction toggle_access_chain_instruction = 42;
TransformationAddConstantNull add_constant_null = 43;
TransformationComputeDataSynonymFactClosure compute_data_synonym_fact_closure = 44;
TransformationAdjustBranchWeights adjust_branch_weights = 45;
TransformationPushIdThroughVariable push_id_through_variable = 46;
TransformationAddSpecConstantOp add_spec_constant_op = 47;
TransformationReplaceLinearAlgebraInstruction replace_linear_algebra_instruction = 48;
TransformationSwapConditionalBranchOperands swap_conditional_branch_operands = 49;
TransformationPermutePhiOperands permute_phi_operands = 50;
TransformationAddParameter add_parameter = 51;
TransformationAddCopyMemory add_copy_memory = 52;
TransformationInvertComparisonOperator invert_comparison_operator = 53;
TransformationAddImageSampleUnusedComponents add_image_sample_unused_components = 54;
TransformationReplaceParameterWithGlobal replace_parameter_with_global = 55;
TransformationRecordSynonymousConstants record_synonymous_constants = 56;
TransformationAddSynonym add_synonym = 57;
// Add additional option using the next available number.
}
}
@@ -705,6 +705,54 @@ message TransformationAddSpecConstantOp {
}
message TransformationAddSynonym {
// Adds a |synonymous_instruction| before |insert_before| instruction with
// and creates a fact that |result_id| and the result id of |synonymous_instruction|
// are synonymous.
// Result id of the first synonym.
uint32 result_id = 1;
// Type of the synonym to apply. Some types might produce instructions
// with commutative operands. Such types do not specify the order of the
// operands since we have a special transformation to swap commutable operands.
//
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3499):
// Consider adding more types here.
enum SynonymType {
// New synonym is derived by adding zero to the |result_id|.
ADD_ZERO = 0;
// New synonym is derived by subtracting zero from the |result_id|.
SUB_ZERO = 1;
// New synonym is derived by multiplying |result_id| by one.
MUL_ONE = 2;
// New synonym is derived by applying OpCopyObject instruction to |result_id|.
COPY_OBJECT = 3;
// New synonym is derived by applying OpLogicalOr to |result_id| with the second
// operand being 'false'.
LOGICAL_OR = 4;
// New synonym is derived by applying OpLogicalAnd to |result_id| with the second
// operand being 'true'.
LOGICAL_AND = 5;
}
// Type of the synonym to create. See SynonymType for more details.
SynonymType synonym_type = 2;
// Fresh result id for a created synonym.
uint32 synonym_fresh_id = 3;
// An instruction to insert a new synonym before.
InstructionDescriptor insert_before = 4;
}
message TransformationAddTypeArray {
// Adds an array type of the given element type and size to the module
@@ -896,23 +944,6 @@ message TransformationComputeDataSynonymFactClosure {
}
message TransformationCopyObject {
// A transformation that introduces an OpCopyObject instruction to make a
// copy of an object.
// Id of the object to be copied
uint32 object = 1;
// A descriptor for an instruction in a block before which the new
// OpCopyObject instruction should be inserted
InstructionDescriptor instruction_to_insert_before = 2;
// A fresh id for the copied object
uint32 fresh_id = 3;
}
message TransformationEquationInstruction {
// A transformation that adds an instruction to the module that defines an
@@ -1230,13 +1261,13 @@ message TransformationReplaceLinearAlgebraInstruction {
// OpMatrixTimesScalar
// OpVectorTimesMatrix
// OpMatrixTimesVector
// OpMatrixTimesMatrix
// OpDot
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3354):
// Right now we only support certain operations. When this issue is addressed
// the supporting comments can be removed.
// To be supported in the future:
// OpTranspose
// OpMatrixTimesMatrix
// OpOuterProduct
InstructionDescriptor instruction_descriptor = 2;

View File

@@ -65,12 +65,20 @@ Replayer::ReplayerResultStatus Replayer::Run(
const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
const protobufs::TransformationSequence& transformation_sequence_in,
std::vector<uint32_t>* binary_out,
uint32_t num_transformations_to_apply, 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;
if (num_transformations_to_apply >
static_cast<uint32_t>(transformation_sequence_in.transformation_size())) {
impl_->consumer(SPV_MSG_ERROR, nullptr, {},
"The number of transformations to be replayed must not "
"exceed the size of the transformation sequence.");
return Replayer::ReplayerResultStatus::kTooManyTransformationsRequested;
}
spvtools::SpirvTools tools(impl_->target_env);
if (!tools.IsValid()) {
impl_->consumer(SPV_MSG_ERROR, nullptr, {},
@@ -104,7 +112,13 @@ Replayer::ReplayerResultStatus Replayer::Run(
impl_->validator_options);
// Consider the transformation proto messages in turn.
uint32_t counter = 0;
for (auto& message : transformation_sequence_in.transformation()) {
if (counter >= num_transformations_to_apply) {
break;
}
counter++;
auto transformation = Transformation::FromMessage(message);
// Check whether the transformation can be applied.

View File

@@ -34,6 +34,7 @@ class Replayer {
kFailedToCreateSpirvToolsInterface,
kInitialBinaryInvalid,
kReplayValidationFailure,
kTooManyTransformationsRequested,
};
// Constructs a replayer from the given target environment.
@@ -52,16 +53,17 @@ class Replayer {
// invoked once for each message communicated from the library.
void SetMessageConsumer(MessageConsumer consumer);
// Transforms |binary_in| to |binary_out| by attempting to apply the
// transformations from |transformation_sequence_in|. Initial facts about the
// input binary and the context in which it will execute are provided via
// |initial_facts|. The transformations that were successfully applied are
// returned via |transformation_sequence_out|.
// Transforms |binary_in| to |binary_out| by attempting to apply the first
// |num_transformations_to_apply| transformations from
// |transformation_sequence_in|. Initial facts about the input binary and the
// context in which it will execute are provided via |initial_facts|. The
// transformations that were successfully applied are returned via
// |transformation_sequence_out|.
ReplayerResultStatus Run(
const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
const protobufs::TransformationSequence& transformation_sequence_in,
std::vector<uint32_t>* binary_out,
uint32_t num_transformations_to_apply, std::vector<uint32_t>* binary_out,
protobufs::TransformationSequence* transformation_sequence_out) const;
private:

View File

@@ -124,6 +124,7 @@ Shrinker::ShrinkerResultStatus Shrinker::Run(
if (Replayer(impl_->target_env, impl_->validate_during_replay,
impl_->validator_options)
.Run(binary_in, initial_facts, transformation_sequence_in,
transformation_sequence_in.transformation_size(),
&current_best_binary, &current_best_transformations) !=
Replayer::ReplayerResultStatus::kComplete) {
return ShrinkerResultStatus::kReplayFailed;
@@ -196,6 +197,7 @@ Shrinker::ShrinkerResultStatus Shrinker::Run(
if (Replayer(impl_->target_env, impl_->validate_during_replay,
impl_->validator_options)
.Run(binary_in, initial_facts, transformations_with_chunk_removed,
transformations_with_chunk_removed.transformation_size(),
&next_binary, &next_transformation_sequence) !=
Replayer::ReplayerResultStatus::kComplete) {
// Replay should not fail; if it does, we need to abort shrinking.

View File

@@ -34,6 +34,7 @@
#include "source/fuzz/transformation_add_no_contraction_decoration.h"
#include "source/fuzz/transformation_add_parameter.h"
#include "source/fuzz/transformation_add_spec_constant_op.h"
#include "source/fuzz/transformation_add_synonym.h"
#include "source/fuzz/transformation_add_type_array.h"
#include "source/fuzz/transformation_add_type_boolean.h"
#include "source/fuzz/transformation_add_type_float.h"
@@ -47,7 +48,6 @@
#include "source/fuzz/transformation_composite_construct.h"
#include "source/fuzz/transformation_composite_extract.h"
#include "source/fuzz/transformation_compute_data_synonym_fact_closure.h"
#include "source/fuzz/transformation_copy_object.h"
#include "source/fuzz/transformation_equation_instruction.h"
#include "source/fuzz/transformation_function_call.h"
#include "source/fuzz/transformation_invert_comparison_operator.h"
@@ -131,6 +131,8 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
case protobufs::Transformation::TransformationCase::kAddSpecConstantOp:
return MakeUnique<TransformationAddSpecConstantOp>(
message.add_spec_constant_op());
case protobufs::Transformation::TransformationCase::kAddSynonym:
return MakeUnique<TransformationAddSynonym>(message.add_synonym());
case protobufs::Transformation::TransformationCase::kAddTypeArray:
return MakeUnique<TransformationAddTypeArray>(message.add_type_array());
case protobufs::Transformation::TransformationCase::kAddTypeBoolean:
@@ -165,8 +167,6 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
kComputeDataSynonymFactClosure:
return MakeUnique<TransformationComputeDataSynonymFactClosure>(
message.compute_data_synonym_fact_closure());
case protobufs::Transformation::TransformationCase::kCopyObject:
return MakeUnique<TransformationCopyObject>(message.copy_object());
case protobufs::Transformation::TransformationCase::kEquationInstruction:
return MakeUnique<TransformationEquationInstruction>(
message.equation_instruction());

View File

@@ -74,9 +74,9 @@ bool TransformationAccessChain::IsApplicable(
switch (pointer->opcode()) {
case SpvOpConstantNull:
case SpvOpUndef:
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3185): When
// fuzzing for real we would like an 'assert(false)' here. But we also
// want to be able to write negative unit tests.
assert(
false &&
"Access chains should not be created from null/undefined pointers");
return false;
default:
break;

View File

@@ -85,12 +85,10 @@ bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow(
// loop as part of the loop, but it is not legal to jump from a loop's
// continue construct to the loop's merge (except from the back-edge block),
// so we need to check for this case.
//
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2577): We do not
// currently allow a dead break from a back edge block, but we could and
// ultimately should.
return !fuzzerutil::BlockIsInLoopContinueConstruct(
ir_context, message_.from_block(), containing_construct);
ir_context, message_.from_block(), containing_construct) ||
fuzzerutil::BlockIsBackEdge(ir_context, message_.from_block(),
containing_construct);
}
// Case (3) holds if and only if |to_block| is the merge block for this
@@ -102,7 +100,9 @@ bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow(
message_.to_block() ==
ir_context->cfg()->block(containing_loop_header)->MergeBlockId()) {
return !fuzzerutil::BlockIsInLoopContinueConstruct(
ir_context, message_.from_block(), containing_loop_header);
ir_context, message_.from_block(), containing_loop_header) ||
fuzzerutil::BlockIsBackEdge(ir_context, message_.from_block(),
containing_loop_header);
}
return false;
}

View File

@@ -56,7 +56,7 @@ bool TransformationAddParameter::IsApplicable(
const auto* initializer_type =
ir_context->get_type_mgr()->GetType(initializer_inst->type_id());
if (!initializer_type || initializer_type->AsVoid()) {
if (!initializer_type || !IsParameterTypeSupported(*initializer_type)) {
return false;
}
@@ -139,5 +139,28 @@ protobufs::Transformation TransformationAddParameter::ToMessage() const {
return result;
}
bool TransformationAddParameter::IsParameterTypeSupported(
const opt::analysis::Type& type) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
// Think about other type instructions we can add here.
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(),
[](const opt::analysis::Type* element_type) {
return IsParameterTypeSupported(*element_type);
});
default:
return false;
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -35,7 +35,8 @@ class TransformationAddParameter : public Transformation {
// - |function_id| must be a valid result id of some non-entry-point function
// in the module.
// - |initializer_id| must be a valid result id of some instruction in the
// module. Instruction's type may not be OpTypeVoid.
// module. Instruction's type must be supported by this transformation
// as specified by IsParameterTypeSupported function.
// - |parameter_fresh_id| and |function_type_fresh_id| are fresh ids and are
// not equal.
bool IsApplicable(
@@ -52,6 +53,10 @@ class TransformationAddParameter : public Transformation {
protobufs::Transformation ToMessage() const override;
// Returns true if the type of the parameter is supported by this
// transformation.
static bool IsParameterTypeSupported(const opt::analysis::Type& type);
private:
protobufs::TransformationAddParameter message_;
};

View File

@@ -0,0 +1,299 @@
// 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/transformation_add_synonym.h"
#include <utility>
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
namespace spvtools {
namespace fuzz {
TransformationAddSynonym::TransformationAddSynonym(
protobufs::TransformationAddSynonym message)
: message_(std::move(message)) {}
TransformationAddSynonym::TransformationAddSynonym(
uint32_t result_id,
protobufs::TransformationAddSynonym::SynonymType synonym_type,
uint32_t synonym_fresh_id,
const protobufs::InstructionDescriptor& insert_before) {
message_.set_result_id(result_id);
message_.set_synonym_type(synonym_type);
message_.set_synonym_fresh_id(synonym_fresh_id);
*message_.mutable_insert_before() = insert_before;
}
bool TransformationAddSynonym::IsApplicable(
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
assert(protobufs::TransformationAddSynonym::SynonymType_IsValid(
message_.synonym_type()) &&
"Synonym type is invalid");
// |synonym_fresh_id| must be fresh.
if (!fuzzerutil::IsFreshId(ir_context, message_.synonym_fresh_id())) {
return false;
}
// Check that |message_.result_id| is valid.
auto* synonym = ir_context->get_def_use_mgr()->GetDef(message_.result_id());
if (!synonym) {
return false;
}
// Check that we can apply |synonym_type| to |result_id|.
if (!IsInstructionValid(ir_context, synonym, message_.synonym_type())) {
return false;
}
// Check that |insert_before| is valid.
auto* insert_before_inst =
FindInstruction(message_.insert_before(), ir_context);
if (!insert_before_inst) {
return false;
}
// Check that we can insert |message._synonymous_instruction| before
// |message_.insert_before| instruction. We use OpIAdd to represent some
// instruction that can produce a synonym.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpIAdd,
insert_before_inst)) {
return false;
}
// A constant instruction must be present in the module if required.
if (IsAdditionalConstantRequired(message_.synonym_type()) &&
MaybeGetConstantId(ir_context) == 0) {
return false;
}
// Domination rules must be satisfied.
return fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, insert_before_inst, message_.result_id());
}
void TransformationAddSynonym::Apply(
opt::IRContext* ir_context,
TransformationContext* transformation_context) const {
// Add a synonymous instruction.
FindInstruction(message_.insert_before(), ir_context)
->InsertBefore(MakeSynonymousInstruction(ir_context));
fuzzerutil::UpdateModuleIdBound(ir_context, message_.synonym_fresh_id());
ir_context->InvalidateAnalysesExceptFor(
opt::IRContext::Analysis::kAnalysisNone);
// Propagate PointeeValueIsIrrelevant fact.
const auto* new_synonym_type = ir_context->get_type_mgr()->GetType(
fuzzerutil::GetTypeId(ir_context, message_.synonym_fresh_id()));
assert(new_synonym_type && "New synonym should have a valid type");
if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
message_.result_id()) &&
new_synonym_type->AsPointer()) {
transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
message_.synonym_fresh_id());
}
// Mark two ids as synonymous.
transformation_context->GetFactManager()->AddFactDataSynonym(
MakeDataDescriptor(message_.result_id(), {}),
MakeDataDescriptor(message_.synonym_fresh_id(), {}), ir_context);
}
protobufs::Transformation TransformationAddSynonym::ToMessage() const {
protobufs::Transformation result;
*result.mutable_add_synonym() = message_;
return result;
}
bool TransformationAddSynonym::IsInstructionValid(
opt::IRContext* ir_context, opt::Instruction* inst,
protobufs::TransformationAddSynonym::SynonymType synonym_type) {
// Instruction must have a result id, type id. We skip OpUndef and
// OpConstantNull.
if (!inst || !inst->result_id() || !inst->type_id() ||
inst->opcode() == SpvOpUndef || inst->opcode() == SpvOpConstantNull) {
return false;
}
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177):
// We can't create a synonym of an irrelevant id.
switch (synonym_type) {
case protobufs::TransformationAddSynonym::ADD_ZERO:
case protobufs::TransformationAddSynonym::SUB_ZERO:
case protobufs::TransformationAddSynonym::MUL_ONE: {
// The instruction must be either scalar or vector of integers or floats.
const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id());
assert(type && "Instruction's result id is invalid");
if (const auto* vector = type->AsVector()) {
return vector->element_type()->AsInteger() ||
vector->element_type()->AsFloat();
}
return type->AsInteger() || type->AsFloat();
}
case protobufs::TransformationAddSynonym::COPY_OBJECT:
return fuzzerutil::CanMakeSynonymOf(ir_context, inst);
case protobufs::TransformationAddSynonym::LOGICAL_AND:
case protobufs::TransformationAddSynonym::LOGICAL_OR: {
// The instruction must be either a scalar or a vector of booleans.
const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id());
assert(type && "Instruction's result id is invalid");
return (type->AsVector() && type->AsVector()->element_type()->AsBool()) ||
type->AsBool();
}
default:
assert(false && "Synonym type is not supported");
return false;
}
}
std::unique_ptr<opt::Instruction>
TransformationAddSynonym::MakeSynonymousInstruction(
opt::IRContext* ir_context) const {
auto synonym_type_id =
fuzzerutil::GetTypeId(ir_context, message_.result_id());
assert(synonym_type_id && "Synonym has invalid type id");
switch (message_.synonym_type()) {
case protobufs::TransformationAddSynonym::SUB_ZERO:
case protobufs::TransformationAddSynonym::MUL_ONE:
case protobufs::TransformationAddSynonym::ADD_ZERO: {
const auto* synonym_type =
ir_context->get_type_mgr()->GetType(synonym_type_id);
assert(synonym_type && "Synonym has invalid type");
// Compute instruction's opcode based on the type of the operand.
// We have already checked that the operand is either a scalar or a vector
// of either integers or floats.
auto is_integral =
(synonym_type->AsVector() &&
synonym_type->AsVector()->element_type()->AsInteger()) ||
synonym_type->AsInteger();
auto opcode = SpvOpNop;
switch (message_.synonym_type()) {
case protobufs::TransformationAddSynonym::SUB_ZERO:
opcode = is_integral ? SpvOpISub : SpvOpFSub;
break;
case protobufs::TransformationAddSynonym::MUL_ONE:
opcode = is_integral ? SpvOpIMul : SpvOpFMul;
break;
case protobufs::TransformationAddSynonym::ADD_ZERO:
opcode = is_integral ? SpvOpIAdd : SpvOpFAdd;
break;
default:
assert(false && "Unreachable");
break;
}
return MakeUnique<opt::Instruction>(
ir_context, opcode, synonym_type_id, message_.synonym_fresh_id(),
opt::Instruction::OperandList{
{SPV_OPERAND_TYPE_ID, {message_.result_id()}},
{SPV_OPERAND_TYPE_ID, {MaybeGetConstantId(ir_context)}}});
}
case protobufs::TransformationAddSynonym::COPY_OBJECT:
return MakeUnique<opt::Instruction>(
ir_context, SpvOpCopyObject, synonym_type_id,
message_.synonym_fresh_id(),
opt::Instruction::OperandList{
{SPV_OPERAND_TYPE_ID, {message_.result_id()}}});
case protobufs::TransformationAddSynonym::LOGICAL_OR:
case protobufs::TransformationAddSynonym::LOGICAL_AND: {
auto opcode = message_.synonym_type() ==
protobufs::TransformationAddSynonym::LOGICAL_OR
? SpvOpLogicalOr
: SpvOpLogicalAnd;
return MakeUnique<opt::Instruction>(
ir_context, opcode, synonym_type_id, message_.synonym_fresh_id(),
opt::Instruction::OperandList{
{SPV_OPERAND_TYPE_ID, {message_.result_id()}},
{SPV_OPERAND_TYPE_ID, {MaybeGetConstantId(ir_context)}}});
}
default:
assert(false && "Unhandled synonym type");
return nullptr;
}
}
uint32_t TransformationAddSynonym::MaybeGetConstantId(
opt::IRContext* ir_context) const {
assert(IsAdditionalConstantRequired(message_.synonym_type()) &&
"Synonym type doesn't require an additional constant");
auto synonym_type_id =
fuzzerutil::GetTypeId(ir_context, message_.result_id());
assert(synonym_type_id && "Synonym has invalid type id");
switch (message_.synonym_type()) {
case protobufs::TransformationAddSynonym::ADD_ZERO:
case protobufs::TransformationAddSynonym::SUB_ZERO:
case protobufs::TransformationAddSynonym::LOGICAL_OR:
return fuzzerutil::MaybeGetZeroConstant(ir_context, synonym_type_id);
case protobufs::TransformationAddSynonym::MUL_ONE:
case protobufs::TransformationAddSynonym::LOGICAL_AND: {
auto synonym_type = ir_context->get_type_mgr()->GetType(synonym_type_id);
assert(synonym_type && "Synonym has invalid type");
if (const auto* vector = synonym_type->AsVector()) {
auto element_type_id =
ir_context->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;
if (auto scalar_one_id = fuzzerutil::MaybeGetScalarConstant(
ir_context, {one_word}, element_type_id)) {
return fuzzerutil::MaybeGetCompositeConstant(
ir_context,
std::vector<uint32_t>(vector->element_count(), scalar_one_id),
synonym_type_id);
}
return 0;
} else {
return fuzzerutil::MaybeGetScalarConstant(
ir_context,
{synonym_type->AsFloat() ? fuzzerutil::FloatToWord(1) : 1u},
synonym_type_id);
}
}
default:
// The assertion at the beginning of the function will fail in the debug
// mode.
return 0;
}
}
bool TransformationAddSynonym::IsAdditionalConstantRequired(
protobufs::TransformationAddSynonym::SynonymType synonym_type) {
switch (synonym_type) {
case protobufs::TransformationAddSynonym::ADD_ZERO:
case protobufs::TransformationAddSynonym::SUB_ZERO:
case protobufs::TransformationAddSynonym::LOGICAL_OR:
case protobufs::TransformationAddSynonym::MUL_ONE:
case protobufs::TransformationAddSynonym::LOGICAL_AND:
return true;
default:
return false;
}
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -0,0 +1,86 @@
// 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_TRANSFORMATION_ADD_SYNONYM_H_
#define SOURCE_FUZZ_TRANSFORMATION_ADD_SYNONYM_H_
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation.h"
#include "source/fuzz/transformation_context.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
class TransformationAddSynonym : public Transformation {
public:
explicit TransformationAddSynonym(
protobufs::TransformationAddSynonym message);
TransformationAddSynonym(
uint32_t result_id,
protobufs::TransformationAddSynonym::SynonymType synonym_type,
uint32_t synonym_fresh_id,
const protobufs::InstructionDescriptor& insert_before);
// - |result_id| must be a valid result id of some instruction in the module.
// - |synonym_type| is a type of the synonymous instruction that will be
// created.
// - |synonym_fresh_id| is a fresh id.
// - |insert_before| must be a valid instruction descriptor and we must be
// able to insert a new synonymous instruction before |insert_before|.
// - |result_id| must be available before |insert_before|.
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// Creates a new synonymous instruction according to the |synonym_type| with
// result id |synonym_fresh_id|.
// Inserts that instruction before |insert_before| and creates a fact
// that the |synonym_fresh_id| and the |result_id| are synonymous.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
protobufs::Transformation ToMessage() const override;
// Returns true if we can create a synonym of |inst| according to the
// |synonym_type|.
static bool IsInstructionValid(
opt::IRContext* ir_context, opt::Instruction* inst,
protobufs::TransformationAddSynonym::SynonymType synonym_type);
// Returns true if |synonym_type| requires an additional constant instruction
// to be present in the module.
static bool IsAdditionalConstantRequired(
protobufs::TransformationAddSynonym::SynonymType synonym_type);
private:
// Returns a new instruction which is synonymous to |message_.result_id|.
std::unique_ptr<opt::Instruction> MakeSynonymousInstruction(
opt::IRContext* ir_context) const;
// Returns a result id of a constant instruction that is required to be
// present in some synonym types (e.g. returns a result id of a zero constant
// for ADD_ZERO synonym type). Returns 0 if no such instruction is present in
// the module. This method should only be called when
// IsAdditionalConstantRequired returns true.
uint32_t MaybeGetConstantId(opt::IRContext* ir_context) const;
protobufs::TransformationAddSynonym message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_SYNONYM_H_

View File

@@ -18,10 +18,8 @@ namespace spvtools {
namespace fuzz {
TransformationContext::TransformationContext(
FactManager* transformation_context,
spv_validator_options validator_options)
: fact_manager_(transformation_context),
validator_options_(validator_options) {}
FactManager* fact_manager, spv_validator_options validator_options)
: fact_manager_(fact_manager), validator_options_(validator_options) {}
TransformationContext::~TransformationContext() = default;

View File

@@ -1,115 +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/transformation_copy_object.h"
#include "source/fuzz/data_descriptor.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/opt/instruction.h"
#include "source/util/make_unique.h"
namespace spvtools {
namespace fuzz {
TransformationCopyObject::TransformationCopyObject(
const protobufs::TransformationCopyObject& message)
: message_(message) {}
TransformationCopyObject::TransformationCopyObject(
uint32_t object,
const protobufs::InstructionDescriptor& instruction_to_insert_before,
uint32_t fresh_id) {
message_.set_object(object);
*message_.mutable_instruction_to_insert_before() =
instruction_to_insert_before;
message_.set_fresh_id(fresh_id);
}
bool TransformationCopyObject::IsApplicable(
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
// We require the id for the object copy to be unused.
return false;
}
// The id of the object to be copied must exist
auto object_inst = ir_context->get_def_use_mgr()->GetDef(message_.object());
if (!object_inst) {
return false;
}
if (!fuzzerutil::CanMakeSynonymOf(ir_context, object_inst)) {
return false;
}
auto insert_before =
FindInstruction(message_.instruction_to_insert_before(), ir_context);
if (!insert_before) {
// The instruction before which the copy should be inserted was not found.
return false;
}
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCopyObject,
insert_before)) {
return false;
}
// |message_object| must be available directly before the point where we want
// to add the copy.
return fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
message_.object());
}
void TransformationCopyObject::Apply(
opt::IRContext* ir_context,
TransformationContext* transformation_context) const {
auto object_inst = ir_context->get_def_use_mgr()->GetDef(message_.object());
assert(object_inst && "The object to be copied must exist.");
auto insert_before_inst =
FindInstruction(message_.instruction_to_insert_before(), ir_context);
auto destination_block = ir_context->get_instr_block(insert_before_inst);
assert(destination_block && "The base instruction must be in a block.");
auto insert_before = fuzzerutil::GetIteratorForInstruction(
destination_block, insert_before_inst);
assert(insert_before != destination_block->end() &&
"There must be an instruction before which the copy can be inserted.");
opt::Instruction::OperandList operands = {
{SPV_OPERAND_TYPE_ID, {message_.object()}}};
insert_before->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOp::SpvOpCopyObject, object_inst->type_id(),
message_.fresh_id(), operands));
fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
ir_context->InvalidateAnalysesExceptFor(
opt::IRContext::Analysis::kAnalysisNone);
transformation_context->GetFactManager()->AddFactDataSynonym(
MakeDataDescriptor(message_.object(), {}),
MakeDataDescriptor(message_.fresh_id(), {}), ir_context);
if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
message_.object())) {
transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
message_.fresh_id());
}
}
protobufs::Transformation TransformationCopyObject::ToMessage() const {
protobufs::Transformation result;
*result.mutable_copy_object() = message_;
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@@ -1,77 +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_TRANSFORMATION_COPY_OBJECT_H_
#define SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation.h"
#include "source/fuzz/transformation_context.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
class TransformationCopyObject : public Transformation {
public:
explicit TransformationCopyObject(
const protobufs::TransformationCopyObject& message);
TransformationCopyObject(
uint32_t object,
const protobufs::InstructionDescriptor& instruction_to_insert_before,
uint32_t fresh_id);
// - |message_.fresh_id| must not be used by the module.
// - |message_.object| must be a result id that is a legitimate operand for
// OpCopyObject. In particular, it must be the id of an instruction that
// has a result type
// - |message_.object| must not be the target of any decoration.
// TODO(afd): consider copying decorations along with objects.
// - |message_.base_instruction_id| must be the result id of an instruction
// 'base' in some block 'blk'.
// - 'blk' must contain an instruction 'inst' located |message_.offset|
// instructions after 'base' (if |message_.offset| = 0 then 'inst' =
// 'base').
// - It must be legal to insert an OpCopyObject instruction directly
// before 'inst'.
// - |message_.object| must be available directly before 'inst'.
// - |message_.object| must not be a null pointer or undefined pointer (so as
// to make it legal to load from copied pointers).
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// - A new instruction,
// %|message_.fresh_id| = OpCopyObject %ty %|message_.object|
// is added directly before the instruction at |message_.insert_after_id| +
// |message_|.offset, where %ty is the type of |message_.object|.
// - The fact that |message_.fresh_id| and |message_.object| are synonyms
// is added to the fact manager in |transformation_context|.
// - If |message_.object| is a pointer whose pointee value is known to be
// irrelevant, the analogous fact is added to the fact manager in
// |transformation_context| about |message_.fresh_id|.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
protobufs::Transformation ToMessage() const override;
private:
protobufs::TransformationCopyObject message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_

View File

@@ -42,6 +42,7 @@ bool TransformationEquationInstruction::IsApplicable(
if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
return false;
}
// The instruction to insert before must exist.
auto insert_before =
FindInstruction(message_.instruction_to_insert_before(), ir_context);
@@ -64,7 +65,7 @@ bool TransformationEquationInstruction::IsApplicable(
}
}
return MaybeGetResultType(ir_context) != 0;
return MaybeGetResultTypeId(ir_context) != 0;
}
void TransformationEquationInstruction::Apply(
@@ -82,7 +83,8 @@ void TransformationEquationInstruction::Apply(
FindInstruction(message_.instruction_to_insert_before(), ir_context)
->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, static_cast<SpvOp>(message_.opcode()),
MaybeGetResultType(ir_context), message_.fresh_id(), in_operands));
MaybeGetResultTypeId(ir_context), message_.fresh_id(),
std::move(in_operands)));
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
@@ -97,12 +99,108 @@ protobufs::Transformation TransformationEquationInstruction::ToMessage() const {
return result;
}
uint32_t TransformationEquationInstruction::MaybeGetResultType(
uint32_t TransformationEquationInstruction::MaybeGetResultTypeId(
opt::IRContext* ir_context) const {
switch (static_cast<SpvOp>(message_.opcode())) {
auto opcode = static_cast<SpvOp>(message_.opcode());
switch (opcode) {
case SpvOpConvertUToF:
case SpvOpConvertSToF: {
if (message_.in_operand_id_size() != 1) {
return 0;
}
const auto* type = ir_context->get_type_mgr()->GetType(
fuzzerutil::GetTypeId(ir_context, message_.in_operand_id(0)));
if (!type) {
return 0;
}
if (const auto* vector = type->AsVector()) {
if (!vector->element_type()->AsInteger()) {
return 0;
}
if (auto element_type_id = fuzzerutil::MaybeGetFloatType(
ir_context, vector->element_type()->AsInteger()->width())) {
return fuzzerutil::MaybeGetVectorType(ir_context, element_type_id,
vector->element_count());
}
return 0;
} else {
if (!type->AsInteger()) {
return 0;
}
return fuzzerutil::MaybeGetFloatType(ir_context,
type->AsInteger()->width());
}
}
case SpvOpBitcast: {
if (message_.in_operand_id_size() != 1) {
return 0;
}
const auto* operand_inst =
ir_context->get_def_use_mgr()->GetDef(message_.in_operand_id(0));
if (!operand_inst) {
return 0;
}
const auto* operand_type =
ir_context->get_type_mgr()->GetType(operand_inst->type_id());
if (!operand_type) {
return 0;
}
// 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()) {
uint32_t component_type_id;
if (const auto* int_type = vector->element_type()->AsInteger()) {
component_type_id =
fuzzerutil::MaybeGetFloatType(ir_context, int_type->width());
} else if (const auto* float_type = vector->element_type()->AsFloat()) {
component_type_id = fuzzerutil::MaybeGetIntegerType(
ir_context, float_type->width(), true);
if (component_type_id == 0 ||
fuzzerutil::MaybeGetVectorType(ir_context, component_type_id,
vector->element_count()) == 0) {
component_type_id = fuzzerutil::MaybeGetIntegerType(
ir_context, float_type->width(), false);
}
} else {
assert(false && "Only vectors of numerical components are supported");
return 0;
}
if (component_type_id == 0) {
return 0;
}
return fuzzerutil::MaybeGetVectorType(ir_context, component_type_id,
vector->element_count());
} else if (const auto* int_type = operand_type->AsInteger()) {
return fuzzerutil::MaybeGetFloatType(ir_context, int_type->width());
} else if (const auto* float_type = operand_type->AsFloat()) {
if (auto existing_id = fuzzerutil::MaybeGetIntegerType(
ir_context, float_type->width(), true)) {
return existing_id;
}
return fuzzerutil::MaybeGetIntegerType(ir_context, float_type->width(),
false);
} else {
assert(false &&
"Operand is not a scalar or a vector of numerical type");
return 0;
}
}
case SpvOpIAdd:
case SpvOpISub: {
if (message_.in_operand_id().size() != 2) {
if (message_.in_operand_id_size() != 2) {
return 0;
}
uint32_t first_operand_width = 0;
@@ -175,7 +273,6 @@ uint32_t TransformationEquationInstruction::MaybeGetResultType(
}
return operand_inst->type_id();
}
default:
assert(false && "Inappropriate opcode for equation instruction.");
return 0;

View File

@@ -63,11 +63,9 @@ class TransformationEquationInstruction : public Transformation {
protobufs::Transformation ToMessage() const override;
private:
// A helper that, in one fell swoop, checks that |message_.opcode| and the ids
// in |message_.in_operand_id| are compatible, and that the module contains
// an appropriate result type id. If all is well, the result type id is
// returned. Otherwise, 0 is returned.
uint32_t MaybeGetResultType(opt::IRContext* ir_context) const;
// Returns type id for the equation instruction. Returns 0 if result type does
// not exist.
uint32_t MaybeGetResultTypeId(opt::IRContext* ir_context) const;
protobufs::TransformationEquationInstruction message_;
};

View File

@@ -94,6 +94,18 @@ bool TransformationInvertComparisonOperator::IsInversionSupported(
case SpvOpULessThanEqual:
case SpvOpIEqual:
case SpvOpINotEqual:
case SpvOpFOrdEqual:
case SpvOpFUnordEqual:
case SpvOpFOrdNotEqual:
case SpvOpFUnordNotEqual:
case SpvOpFOrdLessThan:
case SpvOpFUnordLessThan:
case SpvOpFOrdLessThanEqual:
case SpvOpFUnordLessThanEqual:
case SpvOpFOrdGreaterThan:
case SpvOpFUnordGreaterThan:
case SpvOpFOrdGreaterThanEqual:
case SpvOpFUnordGreaterThanEqual:
return true;
default:
return false;
@@ -124,6 +136,30 @@ SpvOp TransformationInvertComparisonOperator::InvertOpcode(SpvOp opcode) {
return SpvOpINotEqual;
case SpvOpINotEqual:
return SpvOpIEqual;
case SpvOpFOrdEqual:
return SpvOpFUnordNotEqual;
case SpvOpFUnordEqual:
return SpvOpFOrdNotEqual;
case SpvOpFOrdNotEqual:
return SpvOpFUnordEqual;
case SpvOpFUnordNotEqual:
return SpvOpFOrdEqual;
case SpvOpFOrdLessThan:
return SpvOpFUnordGreaterThanEqual;
case SpvOpFUnordLessThan:
return SpvOpFOrdGreaterThanEqual;
case SpvOpFOrdLessThanEqual:
return SpvOpFUnordGreaterThan;
case SpvOpFUnordLessThanEqual:
return SpvOpFOrdGreaterThan;
case SpvOpFOrdGreaterThan:
return SpvOpFUnordLessThanEqual;
case SpvOpFUnordGreaterThan:
return SpvOpFOrdLessThanEqual;
case SpvOpFOrdGreaterThanEqual:
return SpvOpFUnordLessThan;
case SpvOpFUnordGreaterThanEqual:
return SpvOpFOrdLessThan;
default:
// The program will fail in the debug mode because of the assertion
// at the beginning of the function.

View File

@@ -127,22 +127,20 @@ void TransformationPushIdThroughVariable::Apply(
message_.initializer_id());
}
// Stores value id to variable id.
FindInstruction(message_.instruction_descriptor(), ir_context)
->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpStore, 0, 0,
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {message_.variable_id()}},
{SPV_OPERAND_TYPE_ID, {message_.value_id()}}})));
// Loads variable id to value synonym id.
// First, insert the OpLoad instruction before |instruction_descriptor| and
// then insert the OpStore instruction before the OpLoad instruction.
fuzzerutil::UpdateModuleIdBound(ir_context, message_.value_synonym_id());
FindInstruction(message_.instruction_descriptor(), ir_context)
->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpLoad, value_instruction->type_id(),
message_.value_synonym_id(),
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {message_.variable_id()}}})));
{{SPV_OPERAND_TYPE_ID, {message_.variable_id()}}})))
->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpStore, 0, 0,
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {message_.variable_id()}},
{SPV_OPERAND_TYPE_ID, {message_.value_id()}}})));
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);

View File

@@ -243,22 +243,15 @@ bool TransformationReplaceBooleanConstantWithConstantBinary::IsApplicable(
return false;
}
switch (instruction->opcode()) {
case SpvOpPhi:
// The instruction must not be an OpPhi, as we cannot insert a binary
// operator instruction before an OpPhi.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902): there is
// scope for being less conservative.
return false;
case SpvOpVariable:
// The instruction must not be an OpVariable, because (a) we cannot insert
// a binary operator before an OpVariable, but in any case (b) the
// constant we would be replacing is the initializer constant of the
// OpVariable, and this cannot be the result of a binary operation.
return false;
default:
return true;
// The instruction must not be an OpVariable, because (a) we cannot insert
// a binary operator before an OpVariable, but in any case (b) the
// constant we would be replacing is the initializer constant of the
// OpVariable, and this cannot be the result of a binary operation.
if (instruction->opcode() == SpvOpVariable) {
return false;
}
return true;
}
void TransformationReplaceBooleanConstantWithConstantBinary::Apply(
@@ -281,11 +274,22 @@ TransformationReplaceBooleanConstantWithConstantBinary::ApplyWithResult(
opt::Instruction* result = binary_instruction.get();
auto instruction_containing_constant_use =
FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
auto instruction_before_which_to_insert = instruction_containing_constant_use;
// If |instruction_before_which_to_insert| is an OpPhi instruction,
// then |binary_instruction| will be inserted into the parent block associated
// with the OpPhi variable operand.
if (instruction_containing_constant_use->opcode() == SpvOpPhi) {
instruction_before_which_to_insert =
ir_context->cfg()
->block(instruction_containing_constant_use->GetSingleWordInOperand(
message_.id_use_descriptor().in_operand_index() + 1))
->terminator();
}
// We want to insert the new instruction before the instruction that contains
// the use of the boolean, but we need to go backwards one more instruction if
// the using instruction is preceded by a merge instruction.
auto instruction_before_which_to_insert = instruction_containing_constant_use;
{
opt::Instruction* previous_node =
instruction_before_which_to_insert->PreviousNode();
@@ -294,6 +298,7 @@ TransformationReplaceBooleanConstantWithConstantBinary::ApplyWithResult(
instruction_before_which_to_insert = previous_node;
}
}
instruction_before_which_to_insert->InsertBefore(
std::move(binary_instruction));
instruction_containing_constant_use->SetInOperand(

View File

@@ -49,6 +49,7 @@ bool TransformationReplaceLinearAlgebraInstruction::IsApplicable(
instruction->opcode() != SpvOpMatrixTimesScalar &&
instruction->opcode() != SpvOpVectorTimesMatrix &&
instruction->opcode() != SpvOpMatrixTimesVector &&
instruction->opcode() != SpvOpMatrixTimesMatrix &&
instruction->opcode() != SpvOpDot) {
return false;
}
@@ -88,6 +89,9 @@ void TransformationReplaceLinearAlgebraInstruction::Apply(
case SpvOpMatrixTimesVector:
ReplaceOpMatrixTimesVector(ir_context, linear_algebra_instruction);
break;
case SpvOpMatrixTimesMatrix:
ReplaceOpMatrixTimesMatrix(ir_context, linear_algebra_instruction);
break;
case SpvOpDot:
ReplaceOpDot(ir_context, linear_algebra_instruction);
break;
@@ -178,6 +182,37 @@ uint32_t TransformationReplaceLinearAlgebraInstruction::GetRequiredFreshIdCount(
return 3 * matrix_column_count * matrix_row_count +
2 * matrix_column_count - matrix_row_count;
}
case SpvOpMatrixTimesMatrix: {
// For each matrix 2 column, 1 OpCompositeExtract, 1 OpCompositeConstruct,
// |3 * matrix_1_row_count * matrix_1_column_count| OpCompositeExtract,
// |matrix_1_row_count * matrix_1_column_count| OpFMul,
// |matrix_1_row_count * (matrix_1_column_count - 1)| OpFAdd instructions
// will be inserted.
auto matrix_1_instruction = ir_context->get_def_use_mgr()->GetDef(
instruction->GetSingleWordInOperand(0));
uint32_t matrix_1_column_count =
ir_context->get_type_mgr()
->GetType(matrix_1_instruction->type_id())
->AsMatrix()
->element_count();
uint32_t matrix_1_row_count =
ir_context->get_type_mgr()
->GetType(matrix_1_instruction->type_id())
->AsMatrix()
->element_type()
->AsVector()
->element_count();
auto matrix_2_instruction = ir_context->get_def_use_mgr()->GetDef(
instruction->GetSingleWordInOperand(1));
uint32_t matrix_2_column_count =
ir_context->get_type_mgr()
->GetType(matrix_2_instruction->type_id())
->AsMatrix()
->element_count();
return matrix_2_column_count *
(2 + matrix_1_row_count * (5 * matrix_1_column_count - 1));
}
case SpvOpDot:
// For each pair of vector components, 2 OpCompositeExtract and 1 OpFMul
// will be inserted. The first two OpFMul instructions will result the
@@ -560,6 +595,151 @@ void TransformationReplaceLinearAlgebraInstruction::ReplaceOpMatrixTimesVector(
ir_context, message_.fresh_ids(message_.fresh_ids().size() - 1));
}
void TransformationReplaceLinearAlgebraInstruction::ReplaceOpMatrixTimesMatrix(
opt::IRContext* ir_context,
opt::Instruction* linear_algebra_instruction) const {
// Gets matrix 1 information.
auto matrix_1_instruction = ir_context->get_def_use_mgr()->GetDef(
linear_algebra_instruction->GetSingleWordInOperand(0));
uint32_t matrix_1_column_count =
ir_context->get_type_mgr()
->GetType(matrix_1_instruction->type_id())
->AsMatrix()
->element_count();
auto matrix_1_column_type = ir_context->get_type_mgr()
->GetType(matrix_1_instruction->type_id())
->AsMatrix()
->element_type();
auto matrix_1_column_component_type =
matrix_1_column_type->AsVector()->element_type();
uint32_t matrix_1_row_count =
matrix_1_column_type->AsVector()->element_count();
// Gets matrix 2 information.
auto matrix_2_instruction = ir_context->get_def_use_mgr()->GetDef(
linear_algebra_instruction->GetSingleWordInOperand(1));
uint32_t matrix_2_column_count =
ir_context->get_type_mgr()
->GetType(matrix_2_instruction->type_id())
->AsMatrix()
->element_count();
auto matrix_2_column_type = ir_context->get_type_mgr()
->GetType(matrix_2_instruction->type_id())
->AsMatrix()
->element_type();
uint32_t fresh_id_index = 0;
std::vector<uint32_t> result_column_ids(matrix_2_column_count);
for (uint32_t i = 0; i < matrix_2_column_count; i++) {
// Extracts matrix 2 column.
uint32_t matrix_2_column_id = message_.fresh_ids(fresh_id_index++);
linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpCompositeExtract,
ir_context->get_type_mgr()->GetId(matrix_2_column_type),
matrix_2_column_id,
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {matrix_2_instruction->result_id()}},
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}})));
std::vector<uint32_t> column_component_ids(matrix_1_row_count);
for (uint32_t j = 0; j < matrix_1_row_count; j++) {
std::vector<uint32_t> float_multiplication_ids(matrix_1_column_count);
for (uint32_t k = 0; k < matrix_1_column_count; k++) {
// Extracts matrix 1 column.
uint32_t matrix_1_column_id = message_.fresh_ids(fresh_id_index++);
linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpCompositeExtract,
ir_context->get_type_mgr()->GetId(matrix_1_column_type),
matrix_1_column_id,
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {matrix_1_instruction->result_id()}},
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {k}}})));
// Extracts matrix 1 column component.
uint32_t matrix_1_column_component_id =
message_.fresh_ids(fresh_id_index++);
linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpCompositeExtract,
ir_context->get_type_mgr()->GetId(matrix_1_column_component_type),
matrix_1_column_component_id,
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {matrix_1_column_id}},
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {j}}})));
// Extracts matrix 2 column component.
uint32_t matrix_2_column_component_id =
message_.fresh_ids(fresh_id_index++);
linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpCompositeExtract,
ir_context->get_type_mgr()->GetId(matrix_1_column_component_type),
matrix_2_column_component_id,
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {matrix_2_column_id}},
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {k}}})));
// Multiplies corresponding matrix 1 and matrix 2 column components.
float_multiplication_ids[k] = message_.fresh_ids(fresh_id_index++);
linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpFMul,
ir_context->get_type_mgr()->GetId(matrix_1_column_component_type),
float_multiplication_ids[k],
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {matrix_1_column_component_id}},
{SPV_OPERAND_TYPE_ID, {matrix_2_column_component_id}}})));
}
// Adds the multiplication results.
std::vector<uint32_t> float_add_ids;
uint32_t float_add_id = message_.fresh_ids(fresh_id_index++);
float_add_ids.push_back(float_add_id);
linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpFAdd,
ir_context->get_type_mgr()->GetId(matrix_1_column_component_type),
float_add_id,
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {float_multiplication_ids[0]}},
{SPV_OPERAND_TYPE_ID, {float_multiplication_ids[1]}}})));
for (uint32_t k = 2; k < float_multiplication_ids.size(); k++) {
float_add_id = message_.fresh_ids(fresh_id_index++);
float_add_ids.push_back(float_add_id);
linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpFAdd,
ir_context->get_type_mgr()->GetId(matrix_1_column_component_type),
float_add_id,
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {float_multiplication_ids[k]}},
{SPV_OPERAND_TYPE_ID, {float_add_ids[k - 2]}}})));
}
column_component_ids[j] = float_add_ids.back();
}
// Inserts the resulting matrix column.
opt::Instruction::OperandList in_operands;
for (auto& column_component_id : column_component_ids) {
in_operands.push_back({SPV_OPERAND_TYPE_ID, {column_component_id}});
}
result_column_ids[i] = message_.fresh_ids(fresh_id_index++);
linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpCompositeConstruct,
ir_context->get_type_mgr()->GetId(matrix_1_column_type),
result_column_ids[i], opt::Instruction::OperandList(in_operands)));
}
// The OpMatrixTimesMatrix instruction is changed to an OpCompositeConstruct
// instruction.
linear_algebra_instruction->SetOpcode(SpvOpCompositeConstruct);
linear_algebra_instruction->SetInOperand(0, {result_column_ids[0]});
linear_algebra_instruction->SetInOperand(1, {result_column_ids[1]});
for (uint32_t i = 2; i < result_column_ids.size(); i++) {
linear_algebra_instruction->AddOperand(
{SPV_OPERAND_TYPE_ID, {result_column_ids[i]}});
}
fuzzerutil::UpdateModuleIdBound(
ir_context, message_.fresh_ids(message_.fresh_ids().size() - 1));
}
void TransformationReplaceLinearAlgebraInstruction::ReplaceOpDot(
opt::IRContext* ir_context,
opt::Instruction* linear_algebra_instruction) const {

View File

@@ -68,6 +68,10 @@ class TransformationReplaceLinearAlgebraInstruction : public Transformation {
void ReplaceOpMatrixTimesVector(opt::IRContext* ir_context,
opt::Instruction* instruction) const;
// Replaces an OpMatrixTimesMatrix instruction.
void ReplaceOpMatrixTimesMatrix(opt::IRContext* ir_context,
opt::Instruction* instruction) const;
// Replaces an OpDot instruction.
void ReplaceOpDot(opt::IRContext* ir_context,
opt::Instruction* instruction) const;

View File

@@ -76,7 +76,7 @@ bool TransformationReplaceParameterWithGlobal::IsApplicable(
const auto* param_type =
ir_context->get_type_mgr()->GetType(param_inst->type_id());
assert(param_type && "Parameter has invalid type");
if (!CanReplaceFunctionParameterType(*param_type)) {
if (!IsParameterTypeSupported(*param_type)) {
return false;
}
@@ -217,7 +217,7 @@ protobufs::Transformation TransformationReplaceParameterWithGlobal::ToMessage()
return result;
}
bool TransformationReplaceParameterWithGlobal::CanReplaceFunctionParameterType(
bool TransformationReplaceParameterWithGlobal::IsParameterTypeSupported(
const opt::analysis::Type& type) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
// Think about other type instructions we can add here.
@@ -230,12 +230,11 @@ bool TransformationReplaceParameterWithGlobal::CanReplaceFunctionParameterType(
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(),
[](const opt::analysis::Type* element_type) {
return CanReplaceFunctionParameterType(*element_type);
});
return std::all_of(type.AsStruct()->element_types().begin(),
type.AsStruct()->element_types().end(),
[](const opt::analysis::Type* element_type) {
return IsParameterTypeSupported(*element_type);
});
default:
return false;
}

View File

@@ -55,7 +55,7 @@ class TransformationReplaceParameterWithGlobal : public Transformation {
// Returns true if the type of the parameter is supported by this
// transformation.
static bool CanReplaceFunctionParameterType(const opt::analysis::Type& type);
static bool IsParameterTypeSupported(const opt::analysis::Type& type);
private:
protobufs::TransformationReplaceParameterWithGlobal message_;

View File

@@ -375,20 +375,17 @@ bool DebugInfoManager::IsDebugDeclared(uint32_t variable_id) {
}
void DebugInfoManager::KillDebugDeclares(uint32_t variable_id) {
bool done = false;
while (!done) {
Instruction* kill_inst = nullptr;
auto dbg_decl_itr = var_id_to_dbg_decl_.find(variable_id);
if (dbg_decl_itr != var_id_to_dbg_decl_.end()) {
for (auto dbg_decl : dbg_decl_itr->second) {
kill_inst = dbg_decl;
break;
}
auto dbg_decl_itr = var_id_to_dbg_decl_.find(variable_id);
if (dbg_decl_itr != var_id_to_dbg_decl_.end()) {
// We intentionally copy the list of DebugDeclare instructions because
// context()->KillInst(dbg_decl) will update |var_id_to_dbg_decl_|. If we
// directly use |dbg_decl_itr->second|, it accesses a dangling pointer.
auto copy_dbg_decls = dbg_decl_itr->second;
for (auto* dbg_decl : copy_dbg_decls) {
context()->KillInst(dbg_decl);
}
if (kill_inst)
context()->KillInst(kill_inst);
else
done = true;
var_id_to_dbg_decl_.erase(dbg_decl_itr);
}
}

View File

@@ -55,12 +55,25 @@ bool DominatorAnalysisBase::Dominates(Instruction* a, Instruction* b) const {
return tree_.Dominates(bb_a, bb_b);
}
Instruction* current_inst = a;
while ((current_inst = current_inst->NextNode())) {
if (current_inst == b) {
const Instruction* current = a;
const Instruction* other = b;
if (tree_.IsPostDominator()) {
std::swap(current, other);
}
// We handle OpLabel instructions explicitly since they are not stored in the
// instruction list.
if (current->opcode() == SpvOpLabel) {
return true;
}
while ((current = current->NextNode())) {
if (current == other) {
return true;
}
}
return false;
}

View File

@@ -174,7 +174,9 @@ void InstrumentPass::GenStageStreamWriteCode(uint32_t stage_idx,
context()->GetBuiltinInputVarId(SpvBuiltInInstanceIndex),
kInstVertOutInstanceIndex, base_offset_id, builder);
} break;
case SpvExecutionModelGLCompute: {
case SpvExecutionModelGLCompute:
case SpvExecutionModelTaskNV:
case SpvExecutionModelMeshNV: {
// Load and store GlobalInvocationId.
uint32_t load_id = GenVarLoad(
context()->GetBuiltinInputVarId(SpvBuiltInGlobalInvocationId),
@@ -914,6 +916,7 @@ bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) {
stage != SpvExecutionModelGLCompute &&
stage != SpvExecutionModelTessellationControl &&
stage != SpvExecutionModelTessellationEvaluation &&
stage != SpvExecutionModelTaskNV && stage != SpvExecutionModelMeshNV &&
stage != SpvExecutionModelRayGenerationNV &&
stage != SpvExecutionModelIntersectionNV &&
stage != SpvExecutionModelAnyHitNV &&

View File

@@ -22,6 +22,7 @@ const uint32_t kDefaultStepLimit = 250;
spv_fuzzer_options_t::spv_fuzzer_options_t()
: has_random_seed(false),
random_seed(0),
replay_range(0),
replay_validation_enabled(false),
shrinker_step_limit(kDefaultStepLimit),
fuzzer_pass_validation_enabled(false) {}
@@ -45,6 +46,11 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetRandomSeed(
options->random_seed = seed;
}
SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetReplayRange(
spv_fuzzer_options options, int32_t replay_range) {
options->replay_range = replay_range;
}
SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit(
spv_fuzzer_options options, uint32_t shrinker_step_limit) {
options->shrinker_step_limit = shrinker_step_limit;

View File

@@ -29,6 +29,9 @@ struct spv_fuzzer_options_t {
bool has_random_seed;
uint32_t random_seed;
// See spvFuzzerOptionsSetReplayRange.
int32_t replay_range;
// See spvFuzzerOptionsEnableReplayValidation.
bool replay_validation_enabled;

View File

@@ -58,15 +58,9 @@ void BasicBlock::RegisterSuccessors(
for (auto& block : next_blocks) {
block->predecessors_.push_back(this);
successors_.push_back(block);
if (block->reachable_ == false) block->set_reachable(reachable_);
}
}
void BasicBlock::RegisterBranchInstruction(SpvOp branch_instruction) {
if (branch_instruction == SpvOpUnreachable) reachable_ = false;
return;
}
bool BasicBlock::dominates(const BasicBlock& other) const {
return (this == &other) ||
!(other.dom_end() ==

View File

@@ -106,9 +106,6 @@ class BasicBlock {
/// Returns the immedate post dominator of this basic block
const BasicBlock* immediate_post_dominator() const;
/// Ends the block without a successor
void RegisterBranchInstruction(SpvOp branch_instruction);
/// Returns the label instruction for the block, or nullptr if not set.
const Instruction* label() const { return label_; }

View File

@@ -130,7 +130,6 @@ spv_result_t Function::RegisterBlock(uint32_t block_id, bool is_definition) {
undefined_blocks_.erase(block_id);
current_block_ = &inserted_block->second;
ordered_blocks_.push_back(current_block_);
if (IsFirstBlock(block_id)) current_block_->set_reachable(true);
} else if (success) { // Block doesn't exsist but this is not a definition
undefined_blocks_.insert(block_id);
}
@@ -138,8 +137,7 @@ spv_result_t Function::RegisterBlock(uint32_t block_id, bool is_definition) {
return SPV_SUCCESS;
}
void Function::RegisterBlockEnd(std::vector<uint32_t> next_list,
SpvOp branch_instruction) {
void Function::RegisterBlockEnd(std::vector<uint32_t> next_list) {
assert(
current_block_ &&
"RegisterBlockEnd can only be called when parsing a binary in a block");
@@ -174,7 +172,6 @@ void Function::RegisterBlockEnd(std::vector<uint32_t> next_list,
}
}
current_block_->RegisterBranchInstruction(branch_instruction);
current_block_->RegisterSuccessors(next_blocks);
current_block_ = nullptr;
return;

View File

@@ -97,9 +97,7 @@ class Function {
/// Registers the end of the block
///
/// @param[in] successors_list A list of ids to the block's successors
/// @param[in] branch_instruction the branch instruction that ended the block
void RegisterBlockEnd(std::vector<uint32_t> successors_list,
SpvOp branch_instruction);
void RegisterBlockEnd(std::vector<uint32_t> successors_list);
/// Registers the end of the function. This is idempotent.
void RegisterFunctionEnd();

View File

@@ -368,6 +368,10 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
// Catch undefined forward references before performing further checks.
if (auto error = ValidateForwardDecls(*vstate)) return error;
// Calculate reachability after all the blocks are parsed, but early that it
// can be relied on in subsequent pases.
ReachabilityPass(*vstate);
// ID usage needs be handled in its own iteration of the instructions,
// between the two others. It depends on the first loop to have been
// finished, so that all instructions have been registered. And the following

View File

@@ -197,6 +197,9 @@ spv_result_t FunctionPass(ValidationState_t& _, const Instruction* inst);
/// Validates correctness of miscellaneous instructions.
spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst);
/// Calculates the reachability of basic blocks.
void ReachabilityPass(ValidationState_t& _);
/// Validates execution limitations.
///
/// Verifies execution models are allowed for all functionality they contain.

View File

@@ -1062,7 +1062,7 @@ spv_result_t CfgPass(ValidationState_t& _, const Instruction* inst) {
uint32_t target = inst->GetOperandAs<uint32_t>(0);
CFG_ASSERT(FirstBlockAssert, target);
_.current_function().RegisterBlockEnd({target}, opcode);
_.current_function().RegisterBlockEnd({target});
} break;
case SpvOpBranchConditional: {
uint32_t tlabel = inst->GetOperandAs<uint32_t>(1);
@@ -1070,7 +1070,7 @@ spv_result_t CfgPass(ValidationState_t& _, const Instruction* inst) {
CFG_ASSERT(FirstBlockAssert, tlabel);
CFG_ASSERT(FirstBlockAssert, flabel);
_.current_function().RegisterBlockEnd({tlabel, flabel}, opcode);
_.current_function().RegisterBlockEnd({tlabel, flabel});
} break;
case SpvOpSwitch: {
@@ -1080,7 +1080,7 @@ spv_result_t CfgPass(ValidationState_t& _, const Instruction* inst) {
CFG_ASSERT(FirstBlockAssert, target);
cases.push_back(target);
}
_.current_function().RegisterBlockEnd({cases}, opcode);
_.current_function().RegisterBlockEnd({cases});
} break;
case SpvOpReturn: {
const uint32_t return_type = _.current_function().GetResultTypeId();
@@ -1090,13 +1090,13 @@ spv_result_t CfgPass(ValidationState_t& _, const Instruction* inst) {
return _.diag(SPV_ERROR_INVALID_CFG, inst)
<< "OpReturn can only be called from a function with void "
<< "return type.";
_.current_function().RegisterBlockEnd(std::vector<uint32_t>(), opcode);
_.current_function().RegisterBlockEnd(std::vector<uint32_t>());
break;
}
case SpvOpKill:
case SpvOpReturnValue:
case SpvOpUnreachable:
_.current_function().RegisterBlockEnd(std::vector<uint32_t>(), opcode);
_.current_function().RegisterBlockEnd(std::vector<uint32_t>());
if (opcode == SpvOpKill) {
_.current_function().RegisterExecutionModelLimitation(
SpvExecutionModelFragment,
@@ -1109,6 +1109,27 @@ spv_result_t CfgPass(ValidationState_t& _, const Instruction* inst) {
return SPV_SUCCESS;
}
void ReachabilityPass(ValidationState_t& _) {
for (auto& f : _.functions()) {
std::vector<BasicBlock*> stack;
auto entry = f.first_block();
// Skip function declarations.
if (entry) stack.push_back(entry);
while (!stack.empty()) {
auto block = stack.back();
stack.pop_back();
if (block->reachable()) continue;
block->set_reachable(true);
for (auto succ : *block->successors()) {
stack.push_back(succ);
}
}
}
}
spv_result_t ControlFlowPass(ValidationState_t& _, const Instruction* inst) {
switch (inst->opcode()) {
case SpvOpPhi:

View File

@@ -208,8 +208,8 @@ uint32_t NumConsumedComponents(ValidationState_t& _, const Instruction* type) {
// 4 * location + component.
spv_result_t GetLocationsForVariable(
ValidationState_t& _, const Instruction* entry_point,
const Instruction* variable, std::vector<bool>* locations,
std::vector<bool>* output_index1_locations) {
const Instruction* variable, std::unordered_set<uint32_t>* locations,
std::unordered_set<uint32_t>* output_index1_locations) {
const bool is_fragment = entry_point->GetOperandAs<SpvExecutionModel>(0) ==
SpvExecutionModelFragment;
const bool is_output =
@@ -356,17 +356,13 @@ spv_result_t GetLocationsForVariable(
auto locs = locations;
if (has_index && index == 1) locs = output_index1_locations;
if (end > locs->size()) {
locs->resize(end, false);
}
for (uint32_t i = start; i < end; ++i) {
if (locs->at(i)) {
if (!locs->insert(i).second) {
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
<< "Entry-point has conflicting " << storage_class
<< " location assignment at location " << i / 4
<< ", component " << i % 4;
}
(*locs)[i] = true;
}
}
} else {
@@ -425,17 +421,13 @@ spv_result_t GetLocationsForVariable(
start += component;
end = location * 4 + component + num_components;
}
if (end > locations->size()) {
locations->resize(end, false);
}
for (uint32_t l = start; l < end; ++l) {
if (locations->at(l)) {
if (!locations->insert(l).second) {
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
<< "Entry-point has conflicting " << storage_class
<< " location assignment at location " << l / 4
<< ", component " << l % 4;
}
(*locations)[l] = true;
}
}
}
@@ -445,10 +437,10 @@ spv_result_t GetLocationsForVariable(
spv_result_t ValidateLocations(ValidationState_t& _,
const Instruction* entry_point) {
// Reserve space for 16 locations with 4 components each.
std::vector<bool> input_locations(16 * 4, false);
std::vector<bool> output_locations_index0(16 * 4, false);
std::vector<bool> output_locations_index1(16 * 4, false);
// Locations are stored as a combined location and component values.
std::unordered_set<uint32_t> input_locations;
std::unordered_set<uint32_t> output_locations_index0;
std::unordered_set<uint32_t> output_locations_index1;
for (uint32_t i = 3; i < entry_point->operands().size(); ++i) {
auto interface_id = entry_point->GetOperandAs<uint32_t>(i);
auto interface_var = _.FindDef(interface_id);

View File

@@ -501,10 +501,6 @@ spv_result_t ValidateMemoryModel(ValidationState_t& _,
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Addressing model must be Logical for WebGPU environment.";
}
if (_.memory_model() != SpvMemoryModelVulkanKHR) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Memory model must be VulkanKHR for WebGPU environment.";
}
}
if (spvIsOpenCLEnv(_.context()->target_env)) {