mirror of
https://github.com/openharmony/third_party_spirv-tools.git
synced 2026-07-19 15:13:44 -04:00
spirv-fuzz: Add fuzzer pass to perform module donation (#3117)
This change adds a fuzzer pass that allows code from other SPIR-V modules to be donated into the module under transformation. It also changes the command-line options of the tools so that, in fuzzing mode, a file must be specified that contains the names of available donor modules.
This commit is contained in:
committed by
GitHub
parent
94c3a3ce91
commit
ced7a09ab6
@@ -47,6 +47,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
fuzzer_pass_apply_id_synonyms.h
|
||||
fuzzer_pass_construct_composites.h
|
||||
fuzzer_pass_copy_objects.h
|
||||
fuzzer_pass_donate_modules.h
|
||||
fuzzer_pass_merge_blocks.h
|
||||
fuzzer_pass_obfuscate_constants.h
|
||||
fuzzer_pass_outline_functions.h
|
||||
@@ -115,6 +116,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||
fuzzer_pass_apply_id_synonyms.cpp
|
||||
fuzzer_pass_construct_composites.cpp
|
||||
fuzzer_pass_copy_objects.cpp
|
||||
fuzzer_pass_donate_modules.cpp
|
||||
fuzzer_pass_merge_blocks.cpp
|
||||
fuzzer_pass_obfuscate_constants.cpp
|
||||
fuzzer_pass_outline_functions.cpp
|
||||
|
||||
+14
-3
@@ -31,6 +31,7 @@
|
||||
#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_merge_blocks.h"
|
||||
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
|
||||
#include "source/fuzz/fuzzer_pass_outline_functions.h"
|
||||
@@ -52,15 +53,21 @@ const uint32_t kTransformationLimit = 500;
|
||||
|
||||
const uint32_t kChanceOfApplyingAnotherPass = 85;
|
||||
|
||||
template <typename T>
|
||||
// A convenience method to add a fuzzer pass to |passes| with probability 0.5.
|
||||
// All fuzzer passes take |ir_context|, |fact_manager|, |fuzzer_context| and
|
||||
// |transformation_sequence_out| as parameters. Extra arguments can be provided
|
||||
// via |extra_args|.
|
||||
template <typename T, typename... Args>
|
||||
void MaybeAddPass(
|
||||
std::vector<std::unique_ptr<FuzzerPass>>* passes,
|
||||
opt::IRContext* ir_context, FactManager* fact_manager,
|
||||
FuzzerContext* fuzzer_context,
|
||||
protobufs::TransformationSequence* transformation_sequence_out) {
|
||||
protobufs::TransformationSequence* transformation_sequence_out,
|
||||
Args&&... extra_args) {
|
||||
if (fuzzer_context->ChooseEven()) {
|
||||
passes->push_back(MakeUnique<T>(ir_context, fact_manager, fuzzer_context,
|
||||
transformation_sequence_out));
|
||||
transformation_sequence_out,
|
||||
std::forward<Args>(extra_args)...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +121,7 @@ bool Fuzzer::Impl::ApplyPassAndCheckValidity(
|
||||
Fuzzer::FuzzerResultStatus Fuzzer::Run(
|
||||
const std::vector<uint32_t>& binary_in,
|
||||
const protobufs::FactSequence& initial_facts,
|
||||
const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers,
|
||||
std::vector<uint32_t>* binary_out,
|
||||
protobufs::TransformationSequence* transformation_sequence_out) const {
|
||||
// Check compatibility between the library version being linked with and the
|
||||
@@ -184,6 +192,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
|
||||
MaybeAddPass<FuzzerPassCopyObjects>(&passes, ir_context.get(),
|
||||
&fact_manager, &fuzzer_context,
|
||||
transformation_sequence_out);
|
||||
MaybeAddPass<FuzzerPassDonateModules>(
|
||||
&passes, ir_context.get(), &fact_manager, &fuzzer_context,
|
||||
transformation_sequence_out, donor_suppliers);
|
||||
MaybeAddPass<FuzzerPassMergeBlocks>(&passes, ir_context.get(),
|
||||
&fact_manager, &fuzzer_context,
|
||||
transformation_sequence_out);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
|
||||
#include "spirv-tools/libspirv.hpp"
|
||||
|
||||
@@ -57,11 +58,14 @@ class Fuzzer {
|
||||
|
||||
// Transforms |binary_in| to |binary_out| by running a number of randomized
|
||||
// fuzzer passes. Initial facts about the input binary and the context in
|
||||
// which it will execute are provided via |initial_facts|. The transformation
|
||||
// sequence that was applied is returned via |transformation_sequence_out|.
|
||||
// which it will execute are provided via |initial_facts|. A source of donor
|
||||
// modules to be used by transformations is provided via |donor_suppliers|.
|
||||
// The transformation sequence that was applied is returned via
|
||||
// |transformation_sequence_out|.
|
||||
FuzzerResultStatus Run(
|
||||
const std::vector<uint32_t>& binary_in,
|
||||
const protobufs::FactSequence& initial_facts,
|
||||
const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers,
|
||||
std::vector<uint32_t>* binary_out,
|
||||
protobufs::TransformationSequence* transformation_sequence_out) const;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfAdjustingSelectionControl = {20,
|
||||
90};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfConstructingComposite = {20, 50};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfDonatingAdditionalModule = {5, 50};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
|
||||
@@ -83,6 +84,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
|
||||
chance_of_constructing_composite_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfConstructingComposite);
|
||||
chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
|
||||
chance_of_donating_additional_module_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
|
||||
chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks);
|
||||
chance_of_moving_block_down_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
|
||||
|
||||
@@ -81,6 +81,9 @@ class FuzzerContext {
|
||||
return chance_of_constructing_composite_;
|
||||
}
|
||||
uint32_t GetChanceOfCopyingObject() { return chance_of_copying_object_; }
|
||||
uint32_t GetChanceOfDonatingAdditionalModule() {
|
||||
return chance_of_donating_additional_module_;
|
||||
}
|
||||
uint32_t GetChanceOfMergingBlocks() { return chance_of_merging_blocks_; }
|
||||
uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
|
||||
uint32_t GetChanceOfObfuscatingConstant() {
|
||||
@@ -123,6 +126,7 @@ class FuzzerContext {
|
||||
uint32_t chance_of_adjusting_selection_control_;
|
||||
uint32_t chance_of_constructing_composite_;
|
||||
uint32_t chance_of_copying_object_;
|
||||
uint32_t chance_of_donating_additional_module_;
|
||||
uint32_t chance_of_merging_blocks_;
|
||||
uint32_t chance_of_moving_block_down_;
|
||||
uint32_t chance_of_obfuscating_constant_;
|
||||
|
||||
@@ -89,6 +89,16 @@ class FuzzerPass {
|
||||
const protobufs::InstructionDescriptor& instruction_descriptor)>
|
||||
maybe_apply_transformation);
|
||||
|
||||
// A generic helper for applying a transforamtion that should be appplicable
|
||||
// by construction, and adding it to the sequence of applied transformations.
|
||||
template <typename TransformationType>
|
||||
void ApplyTransformation(const TransformationType& transformation) {
|
||||
assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
|
||||
"Transformation should be applicable by construction.");
|
||||
transformation.Apply(GetIRContext(), GetFactManager());
|
||||
*GetTransformations()->add_transformation() = transformation.ToMessage();
|
||||
}
|
||||
|
||||
private:
|
||||
opt::IRContext* ir_context_;
|
||||
FactManager* fact_manager_;
|
||||
|
||||
@@ -0,0 +1,597 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "source/fuzz/fuzzer_pass_donate_modules.h"
|
||||
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
|
||||
#include "source/fuzz/instruction_message.h"
|
||||
#include "source/fuzz/transformation_add_constant_boolean.h"
|
||||
#include "source/fuzz/transformation_add_constant_composite.h"
|
||||
#include "source/fuzz/transformation_add_constant_scalar.h"
|
||||
#include "source/fuzz/transformation_add_function.h"
|
||||
#include "source/fuzz/transformation_add_global_undef.h"
|
||||
#include "source/fuzz/transformation_add_global_variable.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"
|
||||
#include "source/fuzz/transformation_add_type_function.h"
|
||||
#include "source/fuzz/transformation_add_type_int.h"
|
||||
#include "source/fuzz/transformation_add_type_matrix.h"
|
||||
#include "source/fuzz/transformation_add_type_pointer.h"
|
||||
#include "source/fuzz/transformation_add_type_struct.h"
|
||||
#include "source/fuzz/transformation_add_type_vector.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
FuzzerPassDonateModules::FuzzerPassDonateModules(
|
||||
opt::IRContext* ir_context, FactManager* fact_manager,
|
||||
FuzzerContext* fuzzer_context,
|
||||
protobufs::TransformationSequence* transformations,
|
||||
const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers)
|
||||
: FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations),
|
||||
donor_suppliers_(donor_suppliers) {}
|
||||
|
||||
FuzzerPassDonateModules::~FuzzerPassDonateModules() = default;
|
||||
|
||||
void FuzzerPassDonateModules::Apply() {
|
||||
// If there are no donor suppliers, this fuzzer pass is a no-op.
|
||||
if (donor_suppliers_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Donate at least one module, and probabilistically decide when to stop
|
||||
// donating modules.
|
||||
do {
|
||||
// Choose a donor supplier at random, and get the module that it provides.
|
||||
std::unique_ptr<opt::IRContext> donor_ir_context = donor_suppliers_.at(
|
||||
GetFuzzerContext()->RandomIndex(donor_suppliers_))();
|
||||
assert(donor_ir_context != nullptr && "Supplying of donor failed");
|
||||
// Donate the supplied module.
|
||||
DonateSingleModule(donor_ir_context.get());
|
||||
} while (GetFuzzerContext()->ChoosePercentage(
|
||||
GetFuzzerContext()->GetChanceOfDonatingAdditionalModule()));
|
||||
}
|
||||
|
||||
void FuzzerPassDonateModules::DonateSingleModule(
|
||||
opt::IRContext* donor_ir_context) {
|
||||
// The ids used by the donor module may very well clash with ids defined in
|
||||
// the recipient module. Furthermore, some instructions defined in the donor
|
||||
// module will be equivalent to instructions defined in the recipient module,
|
||||
// and it is not always legal to re-declare equivalent instructions. For
|
||||
// example, OpTypeVoid cannot be declared twice.
|
||||
//
|
||||
// To handle this, we maintain a mapping from an id used in the donor module
|
||||
// to the corresponding id that will be used by the donated code when it
|
||||
// appears in the recipient module.
|
||||
//
|
||||
// This mapping is populated in two ways:
|
||||
// (1) by mapping a donor instruction's result id to the id of some equivalent
|
||||
// existing instruction in the recipient (e.g. this has to be done for
|
||||
// OpTypeVoid)
|
||||
// (2) by mapping a donor instruction's result id to a freshly chosen id that
|
||||
// is guaranteed to be different from any id already used by the recipient
|
||||
// (or from any id already chosen to handle a previous donor id)
|
||||
std::map<uint32_t, uint32_t> original_id_to_donated_id;
|
||||
|
||||
HandleExternalInstructionImports(donor_ir_context,
|
||||
&original_id_to_donated_id);
|
||||
HandleTypesAndValues(donor_ir_context, &original_id_to_donated_id);
|
||||
HandleFunctions(donor_ir_context, &original_id_to_donated_id);
|
||||
|
||||
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3115) Handle some
|
||||
// kinds of decoration.
|
||||
}
|
||||
|
||||
SpvStorageClass FuzzerPassDonateModules::AdaptStorageClass(
|
||||
SpvStorageClass donor_storage_class) {
|
||||
switch (donor_storage_class) {
|
||||
case SpvStorageClassFunction:
|
||||
case SpvStorageClassPrivate:
|
||||
// We leave these alone
|
||||
return donor_storage_class;
|
||||
case SpvStorageClassInput:
|
||||
case SpvStorageClassOutput:
|
||||
case SpvStorageClassUniform:
|
||||
case SpvStorageClassUniformConstant:
|
||||
case SpvStorageClassPushConstant:
|
||||
// We change these to Private
|
||||
return SpvStorageClassPrivate;
|
||||
default:
|
||||
// Handle other cases on demand.
|
||||
assert(false && "Currently unsupported storage class.");
|
||||
return SpvStorageClassMax;
|
||||
}
|
||||
}
|
||||
|
||||
void FuzzerPassDonateModules::HandleExternalInstructionImports(
|
||||
opt::IRContext* donor_ir_context,
|
||||
std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
|
||||
// Consider every external instruction set import in the donor module.
|
||||
for (auto& donor_import : donor_ir_context->module()->ext_inst_imports()) {
|
||||
const auto& donor_import_name_words = donor_import.GetInOperand(0).words;
|
||||
// Look for an identical import in the recipient module.
|
||||
for (auto& existing_import : GetIRContext()->module()->ext_inst_imports()) {
|
||||
const auto& existing_import_name_words =
|
||||
existing_import.GetInOperand(0).words;
|
||||
if (donor_import_name_words == existing_import_name_words) {
|
||||
// A matching import has found. Map the result id for the donor import
|
||||
// to the id of the existing import, so that when donor instructions
|
||||
// rely on the import they will be rewritten to use the existing import.
|
||||
original_id_to_donated_id->insert(
|
||||
{donor_import.result_id(), existing_import.result_id()});
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3116): At present
|
||||
// we do not handle donation of instruction imports, i.e. we do not allow
|
||||
// the donor to import instruction sets that the recipient did not already
|
||||
// import. It might be a good idea to allow this, but it requires some
|
||||
// thought.
|
||||
assert(original_id_to_donated_id->count(donor_import.result_id()) &&
|
||||
"Donation of imports is not yet supported.");
|
||||
}
|
||||
}
|
||||
|
||||
void FuzzerPassDonateModules::HandleTypesAndValues(
|
||||
opt::IRContext* donor_ir_context,
|
||||
std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
|
||||
// Consider every type/global/constant/undef in the module.
|
||||
for (auto& type_or_value : donor_ir_context->module()->types_values()) {
|
||||
// Each such instruction generates a result id, and as part of donation we
|
||||
// need to associate the donor's result id with a new result id. That new
|
||||
// result id will either be the id of some existing instruction, or a fresh
|
||||
// id. This variable captures it.
|
||||
uint32_t new_result_id;
|
||||
|
||||
// Decide how to handle each kind of instruction on a case-by-case basis.
|
||||
//
|
||||
// Because the donor module is required to be valid, when we encounter a
|
||||
// type comprised of component types (e.g. an aggregate or pointer), we know
|
||||
// that its component types will have been considered previously, and that
|
||||
// |original_id_to_donated_id| will already contain an entry for them.
|
||||
switch (type_or_value.opcode()) {
|
||||
case SpvOpTypeVoid: {
|
||||
// Void has to exist already in order for us to have an entry point.
|
||||
// Get the existing id of void.
|
||||
opt::analysis::Void void_type;
|
||||
new_result_id = GetIRContext()->get_type_mgr()->GetId(&void_type);
|
||||
assert(new_result_id &&
|
||||
"The module being transformed will always have 'void' type "
|
||||
"declared.");
|
||||
} break;
|
||||
case SpvOpTypeBool: {
|
||||
// Bool cannot be declared multiple times, so use its existing id if
|
||||
// present, or add a declaration of Bool with a fresh id if not.
|
||||
opt::analysis::Bool bool_type;
|
||||
auto bool_type_id = GetIRContext()->get_type_mgr()->GetId(&bool_type);
|
||||
if (bool_type_id) {
|
||||
new_result_id = bool_type_id;
|
||||
} else {
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
ApplyTransformation(TransformationAddTypeBoolean(new_result_id));
|
||||
}
|
||||
} break;
|
||||
case SpvOpTypeInt: {
|
||||
// Int cannot be declared multiple times with the same width and
|
||||
// signedness, so check whether an existing identical Int type is
|
||||
// present and use its id if so. Otherwise add a declaration of the
|
||||
// Int type used by the donor, with a fresh id.
|
||||
const uint32_t width = type_or_value.GetSingleWordInOperand(0);
|
||||
const bool is_signed =
|
||||
static_cast<bool>(type_or_value.GetSingleWordInOperand(1));
|
||||
opt::analysis::Integer int_type(width, is_signed);
|
||||
auto int_type_id = GetIRContext()->get_type_mgr()->GetId(&int_type);
|
||||
if (int_type_id) {
|
||||
new_result_id = int_type_id;
|
||||
} else {
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
ApplyTransformation(
|
||||
TransformationAddTypeInt(new_result_id, width, is_signed));
|
||||
}
|
||||
} break;
|
||||
case SpvOpTypeFloat: {
|
||||
// Similar to SpvOpTypeInt.
|
||||
const uint32_t width = type_or_value.GetSingleWordInOperand(0);
|
||||
opt::analysis::Float float_type(width);
|
||||
auto float_type_id = GetIRContext()->get_type_mgr()->GetId(&float_type);
|
||||
if (float_type_id) {
|
||||
new_result_id = float_type_id;
|
||||
} else {
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
ApplyTransformation(TransformationAddTypeFloat(new_result_id, width));
|
||||
}
|
||||
} break;
|
||||
case SpvOpTypeVector: {
|
||||
// It is not legal to have two Vector type declarations with identical
|
||||
// element types and element counts, so check whether an existing
|
||||
// identical Vector type is present and use its id if so. Otherwise add
|
||||
// a declaration of the Vector type used by the donor, with a fresh id.
|
||||
|
||||
// When considering the vector's component type id, we look up the id
|
||||
// use in the donor to find the id to which this has been remapped.
|
||||
uint32_t component_type_id = original_id_to_donated_id->at(
|
||||
type_or_value.GetSingleWordInOperand(0));
|
||||
auto component_type =
|
||||
GetIRContext()->get_type_mgr()->GetType(component_type_id);
|
||||
assert(component_type && "The base type should be registered.");
|
||||
auto component_count = type_or_value.GetSingleWordInOperand(1);
|
||||
opt::analysis::Vector vector_type(component_type, component_count);
|
||||
auto vector_type_id =
|
||||
GetIRContext()->get_type_mgr()->GetId(&vector_type);
|
||||
if (vector_type_id) {
|
||||
new_result_id = vector_type_id;
|
||||
} else {
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
ApplyTransformation(TransformationAddTypeVector(
|
||||
new_result_id, component_type_id, component_count));
|
||||
}
|
||||
} break;
|
||||
case SpvOpTypeMatrix: {
|
||||
// Similar to SpvOpTypeVector.
|
||||
uint32_t column_type_id = original_id_to_donated_id->at(
|
||||
type_or_value.GetSingleWordInOperand(0));
|
||||
auto column_type =
|
||||
GetIRContext()->get_type_mgr()->GetType(column_type_id);
|
||||
assert(column_type && column_type->AsVector() &&
|
||||
"The column type should be a registered vector type.");
|
||||
auto column_count = type_or_value.GetSingleWordInOperand(1);
|
||||
opt::analysis::Matrix matrix_type(column_type, column_count);
|
||||
auto matrix_type_id =
|
||||
GetIRContext()->get_type_mgr()->GetId(&matrix_type);
|
||||
if (matrix_type_id) {
|
||||
new_result_id = matrix_type_id;
|
||||
} else {
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
ApplyTransformation(TransformationAddTypeMatrix(
|
||||
new_result_id, column_type_id, column_count));
|
||||
}
|
||||
|
||||
} break;
|
||||
case SpvOpTypeArray: {
|
||||
// It is OK to have multiple structurally identical array types, so
|
||||
// we go ahead and add a remapped version of the type declared by the
|
||||
// donor.
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
ApplyTransformation(TransformationAddTypeArray(
|
||||
new_result_id,
|
||||
original_id_to_donated_id->at(
|
||||
type_or_value.GetSingleWordInOperand(0)),
|
||||
original_id_to_donated_id->at(
|
||||
type_or_value.GetSingleWordInOperand(1))));
|
||||
} break;
|
||||
case SpvOpTypeStruct: {
|
||||
// Similar to SpvOpTypeArray.
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
std::vector<uint32_t> member_type_ids;
|
||||
type_or_value.ForEachInId(
|
||||
[&member_type_ids,
|
||||
&original_id_to_donated_id](const uint32_t* component_type_id) {
|
||||
member_type_ids.push_back(
|
||||
original_id_to_donated_id->at(*component_type_id));
|
||||
});
|
||||
ApplyTransformation(
|
||||
TransformationAddTypeStruct(new_result_id, member_type_ids));
|
||||
} break;
|
||||
case SpvOpTypePointer: {
|
||||
// Similar to SpvOpTypeArray.
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
ApplyTransformation(TransformationAddTypePointer(
|
||||
new_result_id,
|
||||
AdaptStorageClass(static_cast<SpvStorageClass>(
|
||||
type_or_value.GetSingleWordInOperand(0))),
|
||||
original_id_to_donated_id->at(
|
||||
type_or_value.GetSingleWordInOperand(1))));
|
||||
} break;
|
||||
case SpvOpTypeFunction: {
|
||||
// It is not OK to have multiple function types that use identical ids
|
||||
// for their return and parameter types. We thus first look for a
|
||||
// matching function type in the recipient module and use the id of this
|
||||
// type if a match is found. Otherwise we add a remapped version of the
|
||||
// function type.
|
||||
|
||||
// Build a sequence of types used as parameters for the function type.
|
||||
std::vector<const opt::analysis::Type*> parameter_types;
|
||||
// We start iterating at 1 because 0 is the function's return type.
|
||||
for (uint32_t index = 1; index < type_or_value.NumInOperands();
|
||||
index++) {
|
||||
parameter_types.push_back(GetIRContext()->get_type_mgr()->GetType(
|
||||
original_id_to_donated_id->at(
|
||||
type_or_value.GetSingleWordInOperand(index))));
|
||||
}
|
||||
// Make a type object corresponding to the function type.
|
||||
opt::analysis::Function function_type(
|
||||
GetIRContext()->get_type_mgr()->GetType(
|
||||
original_id_to_donated_id->at(
|
||||
type_or_value.GetSingleWordInOperand(0))),
|
||||
parameter_types);
|
||||
|
||||
// Check whether a function type corresponding to this this type object
|
||||
// is already declared by the module.
|
||||
auto function_type_id =
|
||||
GetIRContext()->get_type_mgr()->GetId(&function_type);
|
||||
if (function_type_id) {
|
||||
// A suitable existing function was found - use its id.
|
||||
new_result_id = function_type_id;
|
||||
} else {
|
||||
// No match was found, so add a remapped version of the function type
|
||||
// to the module, with a fresh id.
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
std::vector<uint32_t> argument_type_ids;
|
||||
for (uint32_t index = 1; index < type_or_value.NumInOperands();
|
||||
index++) {
|
||||
argument_type_ids.push_back(original_id_to_donated_id->at(
|
||||
type_or_value.GetSingleWordInOperand(index)));
|
||||
}
|
||||
ApplyTransformation(TransformationAddTypeFunction(
|
||||
new_result_id,
|
||||
original_id_to_donated_id->at(
|
||||
type_or_value.GetSingleWordInOperand(0)),
|
||||
argument_type_ids));
|
||||
}
|
||||
} break;
|
||||
case SpvOpConstantTrue:
|
||||
case SpvOpConstantFalse: {
|
||||
// It is OK to have duplicate definitions of True and False, so add
|
||||
// these to the module, using a remapped Bool type.
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
ApplyTransformation(TransformationAddConstantBoolean(
|
||||
new_result_id, type_or_value.opcode() == SpvOpConstantTrue));
|
||||
} break;
|
||||
case SpvOpConstant: {
|
||||
// It is OK to have duplicate constant definitions, so add this to the
|
||||
// module using a remapped result type.
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
std::vector<uint32_t> data_words;
|
||||
type_or_value.ForEachInOperand(
|
||||
[&data_words](const uint32_t* in_operand) {
|
||||
data_words.push_back(*in_operand);
|
||||
});
|
||||
ApplyTransformation(TransformationAddConstantScalar(
|
||||
new_result_id,
|
||||
original_id_to_donated_id->at(type_or_value.type_id()),
|
||||
data_words));
|
||||
} break;
|
||||
case SpvOpConstantComposite: {
|
||||
// It is OK to have duplicate constant composite definitions, so add
|
||||
// this to the module using remapped versions of all consituent ids and
|
||||
// the result type.
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
std::vector<uint32_t> constituent_ids;
|
||||
type_or_value.ForEachInId(
|
||||
[&constituent_ids,
|
||||
&original_id_to_donated_id](const uint32_t* constituent_id) {
|
||||
constituent_ids.push_back(
|
||||
original_id_to_donated_id->at(*constituent_id));
|
||||
});
|
||||
ApplyTransformation(TransformationAddConstantComposite(
|
||||
new_result_id,
|
||||
original_id_to_donated_id->at(type_or_value.type_id()),
|
||||
constituent_ids));
|
||||
} break;
|
||||
case SpvOpVariable: {
|
||||
// This is a global variable that could have one of various storage
|
||||
// classes. However, we change all global variable pointer storage
|
||||
// classes (such as Uniform, Input and Output) to private when donating
|
||||
// pointer types. Thus this variable's pointer type is guaranteed to
|
||||
// have storage class private. As a result, we simply add a Private
|
||||
// storage class global variable, using remapped versions of the result
|
||||
// type and initializer ids for the global variable in the donor.
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
ApplyTransformation(TransformationAddGlobalVariable(
|
||||
new_result_id,
|
||||
original_id_to_donated_id->at(type_or_value.type_id()),
|
||||
type_or_value.NumInOperands() == 1
|
||||
? 0
|
||||
: original_id_to_donated_id->at(
|
||||
type_or_value.GetSingleWordInOperand(1))));
|
||||
} break;
|
||||
case SpvOpUndef: {
|
||||
// It is fine to have multiple Undef instructions of the same type, so
|
||||
// we just add this to the recipient module.
|
||||
new_result_id = GetFuzzerContext()->GetFreshId();
|
||||
ApplyTransformation(TransformationAddGlobalUndef(
|
||||
new_result_id,
|
||||
original_id_to_donated_id->at(type_or_value.type_id())));
|
||||
} break;
|
||||
default: {
|
||||
assert(0 && "Unknown type/value.");
|
||||
new_result_id = 0;
|
||||
} break;
|
||||
}
|
||||
// Update the id mapping to associate the instruction's result id with its
|
||||
// corresponding id in the recipient.
|
||||
original_id_to_donated_id->insert(
|
||||
{type_or_value.result_id(), new_result_id});
|
||||
}
|
||||
}
|
||||
|
||||
void FuzzerPassDonateModules::HandleFunctions(
|
||||
opt::IRContext* donor_ir_context,
|
||||
std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
|
||||
// Get the ids of functions in the donor module, topologically sorted
|
||||
// according to the donor's call graph.
|
||||
auto topological_order =
|
||||
GetFunctionsInCallGraphTopologicalOrder(donor_ir_context);
|
||||
|
||||
// Donate the functions in reverse topological order. This ensures that a
|
||||
// function gets donated before any function that depends on it. This allows
|
||||
// donation of the functions to be separated into a number of transformations,
|
||||
// each adding one function, such that every prefix of transformations leaves
|
||||
// the module valid.
|
||||
for (auto function_id = topological_order.rbegin();
|
||||
function_id != topological_order.rend(); ++function_id) {
|
||||
// Find the function to be donated.
|
||||
opt::Function* function_to_donate = nullptr;
|
||||
for (auto& function : *donor_ir_context->module()) {
|
||||
if (function.result_id() == *function_id) {
|
||||
function_to_donate = &function;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(function_to_donate && "Function to be donated was not found.");
|
||||
|
||||
// We will collect up protobuf messages representing the donor function's
|
||||
// instructions here, and use them to create an AddFunction transformation.
|
||||
std::vector<protobufs::Instruction> donated_instructions;
|
||||
|
||||
// Scan through the function, remapping each result id that it generates to
|
||||
// a fresh id. This is necessary because functions include forward
|
||||
// references, e.g. to labels.
|
||||
function_to_donate->ForEachInst([this, &original_id_to_donated_id](
|
||||
const opt::Instruction* instruction) {
|
||||
if (instruction->result_id()) {
|
||||
original_id_to_donated_id->insert(
|
||||
{instruction->result_id(), GetFuzzerContext()->GetFreshId()});
|
||||
}
|
||||
});
|
||||
|
||||
// Consider every instruction of the donor function.
|
||||
function_to_donate->ForEachInst(
|
||||
[&donated_instructions,
|
||||
&original_id_to_donated_id](const opt::Instruction* instruction) {
|
||||
// Get the instruction's input operands into donation-ready form,
|
||||
// remapping any id uses in the process.
|
||||
opt::Instruction::OperandList input_operands;
|
||||
|
||||
// Consider each input operand in turn.
|
||||
for (uint32_t in_operand_index = 0;
|
||||
in_operand_index < instruction->NumInOperands();
|
||||
in_operand_index++) {
|
||||
std::vector<uint32_t> operand_data;
|
||||
const opt::Operand& in_operand =
|
||||
instruction->GetInOperand(in_operand_index);
|
||||
switch (in_operand.type) {
|
||||
case SPV_OPERAND_TYPE_ID:
|
||||
case SPV_OPERAND_TYPE_TYPE_ID:
|
||||
case SPV_OPERAND_TYPE_RESULT_ID:
|
||||
case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
|
||||
case SPV_OPERAND_TYPE_SCOPE_ID:
|
||||
// This is an id operand - it consists of a single word of data,
|
||||
// which needs to be remapped so that it is replaced with the
|
||||
// donated form of the id.
|
||||
operand_data.push_back(
|
||||
original_id_to_donated_id->at(in_operand.words[0]));
|
||||
break;
|
||||
default:
|
||||
// For non-id operands, we just add each of the data words.
|
||||
for (auto word : in_operand.words) {
|
||||
operand_data.push_back(word);
|
||||
}
|
||||
break;
|
||||
}
|
||||
input_operands.push_back({in_operand.type, operand_data});
|
||||
}
|
||||
// Remap the result type and result id (if present) of the
|
||||
// instruction, and turn it into a protobuf message.
|
||||
donated_instructions.push_back(MakeInstructionMessage(
|
||||
instruction->opcode(),
|
||||
instruction->type_id()
|
||||
? original_id_to_donated_id->at(instruction->type_id())
|
||||
: 0,
|
||||
instruction->result_id()
|
||||
? original_id_to_donated_id->at(instruction->result_id())
|
||||
: 0,
|
||||
input_operands));
|
||||
});
|
||||
ApplyTransformation(TransformationAddFunction(donated_instructions));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint32_t>
|
||||
FuzzerPassDonateModules::GetFunctionsInCallGraphTopologicalOrder(
|
||||
opt::IRContext* context) {
|
||||
// This is an implementation of Kahn’s algorithm for topological sorting.
|
||||
|
||||
// For each function id, stores the number of distinct functions that call
|
||||
// the function.
|
||||
std::map<uint32_t, uint32_t> function_in_degree;
|
||||
|
||||
// We first build a call graph for the module, and compute the in-degree for
|
||||
// each function in the process.
|
||||
// TODO(afd): If there is functionality elsewhere in the SPIR-V tools
|
||||
// framework to construct call graphs it could be nice to re-use it here.
|
||||
std::map<uint32_t, std::set<uint32_t>> call_graph_edges;
|
||||
|
||||
// Initialize function in-degree and call graph edges to 0 and empty.
|
||||
for (auto& function : *context->module()) {
|
||||
function_in_degree[function.result_id()] = 0;
|
||||
call_graph_edges[function.result_id()] = std::set<uint32_t>();
|
||||
}
|
||||
|
||||
// Consider every function.
|
||||
for (auto& function : *context->module()) {
|
||||
// Avoid considering the same callee of this function multiple times by
|
||||
// recording known callees.
|
||||
std::set<uint32_t> known_callees;
|
||||
// Consider every function call instruction in every block.
|
||||
for (auto& block : function) {
|
||||
for (auto& instruction : block) {
|
||||
if (instruction.opcode() != SpvOpFunctionCall) {
|
||||
continue;
|
||||
}
|
||||
// Get the id of the function being called.
|
||||
uint32_t callee = instruction.GetSingleWordInOperand(0);
|
||||
if (known_callees.count(callee)) {
|
||||
// We have already considered a call to this function - ignore it.
|
||||
continue;
|
||||
}
|
||||
// Increase the callee's in-degree and add an edge to the call graph.
|
||||
function_in_degree[callee]++;
|
||||
call_graph_edges[function.result_id()].insert(callee);
|
||||
// Mark the callee as 'known'.
|
||||
known_callees.insert(callee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is the sorted order of function ids that we will eventually return.
|
||||
std::vector<uint32_t> result;
|
||||
|
||||
// Populate a queue with all those function ids with in-degree zero.
|
||||
std::queue<uint32_t> queue;
|
||||
for (auto& entry : function_in_degree) {
|
||||
if (entry.second == 0) {
|
||||
queue.push(entry.first);
|
||||
}
|
||||
}
|
||||
|
||||
// Pop ids from the queue, adding them to the sorted order and decreasing the
|
||||
// in-degrees of their successors. A successor who's in-degree becomes zero
|
||||
// gets added to the queue.
|
||||
while (!queue.empty()) {
|
||||
auto next = queue.front();
|
||||
queue.pop();
|
||||
result.push_back(next);
|
||||
for (auto successor : call_graph_edges.at(next)) {
|
||||
assert(function_in_degree.at(successor) > 0 &&
|
||||
"The in-degree cannot be zero if the function is a successor.");
|
||||
function_in_degree[successor] = function_in_degree.at(successor) - 1;
|
||||
if (function_in_degree.at(successor) == 0) {
|
||||
queue.push(successor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(result.size() == function_in_degree.size() &&
|
||||
"Every function should appear in the sort.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_
|
||||
#define SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "source/fuzz/fuzzer_pass.h"
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
// A fuzzer pass that randomly adds code from other SPIR-V modules to the module
|
||||
// being transformed.
|
||||
class FuzzerPassDonateModules : public FuzzerPass {
|
||||
public:
|
||||
FuzzerPassDonateModules(
|
||||
opt::IRContext* ir_context, FactManager* fact_manager,
|
||||
FuzzerContext* fuzzer_context,
|
||||
protobufs::TransformationSequence* transformations,
|
||||
const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers);
|
||||
|
||||
~FuzzerPassDonateModules();
|
||||
|
||||
void Apply() override;
|
||||
|
||||
// Donates the global declarations and functions of |donor_ir_context| into
|
||||
// the fuzzer pass's IR context.
|
||||
void DonateSingleModule(opt::IRContext* donor_ir_context);
|
||||
|
||||
private:
|
||||
// Adapts a storage class coming from a donor module so that it will work
|
||||
// in a recipient module, e.g. by changing Uniform to Private.
|
||||
static SpvStorageClass AdaptStorageClass(SpvStorageClass donor_storage_class);
|
||||
|
||||
// Identifies all external instruction set imports in |donor_ir_context| and
|
||||
// populates |original_id_to_donated_id| with a mapping from the donor's id
|
||||
// for such an import to a corresponding import in the recipient. Aborts if
|
||||
// no such corresponding import is available.
|
||||
void HandleExternalInstructionImports(
|
||||
opt::IRContext* donor_ir_context,
|
||||
std::map<uint32_t, uint32_t>* original_id_to_donated_id);
|
||||
|
||||
// Considers all types, globals, constants and undefs in |donor_ir_context|.
|
||||
// For each instruction, uses |original_to_donated_id| to map its result id to
|
||||
// either (1) the id of an existing identical instruction in the recipient, or
|
||||
// (2) to a fresh id, in which case the instruction is also added to the
|
||||
// recipient (with any operand ids that it uses being remapped via
|
||||
// |original_id_to_donated_id|).
|
||||
void HandleTypesAndValues(
|
||||
opt::IRContext* donor_ir_context,
|
||||
std::map<uint32_t, uint32_t>* original_id_to_donated_id);
|
||||
|
||||
// Assumes that |donor_ir_context| does not exhibit recursion. Considers the
|
||||
// functions in |donor_ir_context|'s call graph in a reverse-topologically-
|
||||
// sorted order (leaves-to-root), adding each function to the recipient
|
||||
// module, rewritten to use fresh ids and using |original_id_to_donated_id| to
|
||||
// remap ids.
|
||||
void HandleFunctions(opt::IRContext* donor_ir_context,
|
||||
std::map<uint32_t, uint32_t>* original_id_to_donated_id);
|
||||
|
||||
// Returns the ids of all functions in |context| in a topological order in
|
||||
// relation to the call graph of |context|, which is assumed to be recursion-
|
||||
// free.
|
||||
static std::vector<uint32_t> GetFunctionsInCallGraphTopologicalOrder(
|
||||
opt::IRContext* context);
|
||||
|
||||
// Functions that supply SPIR-V modules
|
||||
std::vector<fuzzerutil::ModuleSupplier> donor_suppliers_;
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_
|
||||
@@ -25,9 +25,12 @@
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
// Provides global utility methods for use by the fuzzer
|
||||
// Provides types and global utility methods for use by the fuzzer
|
||||
namespace fuzzerutil {
|
||||
|
||||
// Function type that produces a SPIR-V module.
|
||||
using ModuleSupplier = std::function<std::unique_ptr<opt::IRContext>()>;
|
||||
|
||||
// Returns true if and only if the module does not define the given id.
|
||||
bool IsFreshId(opt::IRContext* context, uint32_t id);
|
||||
|
||||
|
||||
@@ -21,16 +21,15 @@ namespace fuzz {
|
||||
|
||||
protobufs::Instruction MakeInstructionMessage(
|
||||
SpvOp opcode, uint32_t result_type_id, uint32_t result_id,
|
||||
const std::vector<std::pair<uint32_t, std::vector<uint32_t>>>&
|
||||
input_operands) {
|
||||
const opt::Instruction::OperandList& input_operands) {
|
||||
protobufs::Instruction result;
|
||||
result.set_opcode(opcode);
|
||||
result.set_result_type_id(result_type_id);
|
||||
result.set_result_id(result_id);
|
||||
for (auto& operand : input_operands) {
|
||||
auto operand_message = result.add_input_operand();
|
||||
operand_message->set_operand_type(operand.first);
|
||||
for (auto operand_word : operand.second) {
|
||||
operand_message->set_operand_type(static_cast<uint32_t>(operand.type));
|
||||
for (auto operand_word : operand.words) {
|
||||
operand_message->add_operand_data(operand_word);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,7 @@ namespace fuzz {
|
||||
// Creates an Instruction protobuf message from its component parts.
|
||||
protobufs::Instruction MakeInstructionMessage(
|
||||
SpvOp opcode, uint32_t result_type_id, uint32_t result_id,
|
||||
const std::vector<std::pair<uint32_t, std::vector<uint32_t>>>&
|
||||
input_operands);
|
||||
const opt::Instruction::OperandList& input_operands);
|
||||
|
||||
// Creates and returns an opt::Instruction from protobuf message
|
||||
// |instruction_message|, relative to |ir_context|. In the process, the module
|
||||
|
||||
@@ -22,12 +22,14 @@ if (${SPIRV_BUILD_FUZZER})
|
||||
fact_manager_test.cpp
|
||||
fuzz_test_util.cpp
|
||||
fuzzer_pass_add_useful_constructs_test.cpp
|
||||
fuzzer_pass_donate_modules_test.cpp
|
||||
instruction_descriptor_test.cpp
|
||||
transformation_add_constant_boolean_test.cpp
|
||||
transformation_add_constant_composite_test.cpp
|
||||
transformation_add_constant_scalar_test.cpp
|
||||
transformation_add_dead_break_test.cpp
|
||||
transformation_add_dead_continue_test.cpp
|
||||
transformation_add_function_test.cpp
|
||||
transformation_add_global_undef_test.cpp
|
||||
transformation_add_global_variable_test.cpp
|
||||
transformation_add_no_contraction_decoration_test.cpp
|
||||
|
||||
@@ -0,0 +1,620 @@
|
||||
// Copyright (c) 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "source/fuzz/fuzzer_pass_donate_modules.h"
|
||||
#include "source/fuzz/pseudo_random_generator.h"
|
||||
#include "test/fuzz/fuzz_test_util.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
namespace {
|
||||
|
||||
TEST(FuzzerPassDonateModulesTest, BasicDonation) {
|
||||
std::string recipient_shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %4 "main"
|
||||
OpName %10 "m"
|
||||
OpName %16 "v"
|
||||
OpDecorate %16 RelaxedPrecision
|
||||
OpDecorate %20 RelaxedPrecision
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%7 = OpTypeVector %6 3
|
||||
%8 = OpTypeMatrix %7 2
|
||||
%9 = OpTypePointer Private %8
|
||||
%10 = OpVariable %9 Private
|
||||
%11 = OpTypeInt 32 1
|
||||
%12 = OpConstant %11 0
|
||||
%13 = OpTypeInt 32 0
|
||||
%14 = OpTypeVector %13 4
|
||||
%15 = OpTypePointer Private %14
|
||||
%16 = OpVariable %15 Private
|
||||
%17 = OpConstant %13 2
|
||||
%18 = OpTypePointer Private %13
|
||||
%22 = OpConstant %13 0
|
||||
%23 = OpTypePointer Private %6
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%19 = OpAccessChain %18 %16 %17
|
||||
%20 = OpLoad %13 %19
|
||||
%21 = OpConvertUToF %6 %20
|
||||
%24 = OpAccessChain %23 %10 %12 %22
|
||||
OpStore %24 %21
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
std::string donor_shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %4 "main"
|
||||
OpName %12 "bar(mf24;"
|
||||
OpName %11 "m"
|
||||
OpName %20 "foo(vu4;"
|
||||
OpName %19 "v"
|
||||
OpName %23 "x"
|
||||
OpName %26 "param"
|
||||
OpName %29 "result"
|
||||
OpName %31 "i"
|
||||
OpName %81 "param"
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%7 = OpTypeVector %6 4
|
||||
%8 = OpTypeMatrix %7 2
|
||||
%9 = OpTypePointer Function %8
|
||||
%10 = OpTypeFunction %6 %9
|
||||
%14 = OpTypeInt 32 0
|
||||
%15 = OpTypeVector %14 4
|
||||
%16 = OpTypePointer Function %15
|
||||
%17 = OpTypeInt 32 1
|
||||
%18 = OpTypeFunction %17 %16
|
||||
%22 = OpTypePointer Function %17
|
||||
%24 = OpConstant %14 2
|
||||
%25 = OpConstantComposite %15 %24 %24 %24 %24
|
||||
%28 = OpTypePointer Function %6
|
||||
%30 = OpConstant %6 0
|
||||
%32 = OpConstant %17 0
|
||||
%39 = OpConstant %17 10
|
||||
%40 = OpTypeBool
|
||||
%43 = OpConstant %17 3
|
||||
%50 = OpConstant %17 1
|
||||
%55 = OpConstant %14 0
|
||||
%56 = OpTypePointer Function %14
|
||||
%59 = OpConstant %14 1
|
||||
%65 = OpConstant %17 2
|
||||
%68 = OpConstant %6 1
|
||||
%69 = OpConstant %6 2
|
||||
%70 = OpConstant %6 3
|
||||
%71 = OpConstant %6 4
|
||||
%72 = OpConstant %14 3
|
||||
%76 = OpConstant %6 6
|
||||
%77 = OpConstant %6 7
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%23 = OpVariable %22 Function
|
||||
%26 = OpVariable %16 Function
|
||||
OpStore %26 %25
|
||||
%27 = OpFunctionCall %17 %20 %26
|
||||
OpStore %23 %27
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%12 = OpFunction %6 None %10
|
||||
%11 = OpFunctionParameter %9
|
||||
%13 = OpLabel
|
||||
%29 = OpVariable %28 Function
|
||||
%31 = OpVariable %22 Function
|
||||
OpStore %29 %30
|
||||
OpStore %31 %32
|
||||
OpBranch %33
|
||||
%33 = OpLabel
|
||||
OpLoopMerge %35 %36 None
|
||||
OpBranch %37
|
||||
%37 = OpLabel
|
||||
%38 = OpLoad %17 %31
|
||||
%41 = OpSLessThan %40 %38 %39
|
||||
OpBranchConditional %41 %34 %35
|
||||
%34 = OpLabel
|
||||
%42 = OpLoad %17 %31
|
||||
%44 = OpExtInst %17 %1 SClamp %42 %32 %43
|
||||
%45 = OpAccessChain %28 %11 %32 %44
|
||||
%46 = OpLoad %6 %45
|
||||
%47 = OpLoad %6 %29
|
||||
%48 = OpFAdd %6 %47 %46
|
||||
OpStore %29 %48
|
||||
OpBranch %36
|
||||
%36 = OpLabel
|
||||
%49 = OpLoad %17 %31
|
||||
%51 = OpIAdd %17 %49 %50
|
||||
OpStore %31 %51
|
||||
OpBranch %33
|
||||
%35 = OpLabel
|
||||
%52 = OpLoad %6 %29
|
||||
OpReturnValue %52
|
||||
OpFunctionEnd
|
||||
%20 = OpFunction %17 None %18
|
||||
%19 = OpFunctionParameter %16
|
||||
%21 = OpLabel
|
||||
%81 = OpVariable %9 Function
|
||||
%57 = OpAccessChain %56 %19 %55
|
||||
%58 = OpLoad %14 %57
|
||||
%60 = OpAccessChain %56 %19 %59
|
||||
%61 = OpLoad %14 %60
|
||||
%62 = OpUGreaterThan %40 %58 %61
|
||||
OpSelectionMerge %64 None
|
||||
OpBranchConditional %62 %63 %67
|
||||
%63 = OpLabel
|
||||
OpReturnValue %65
|
||||
%67 = OpLabel
|
||||
%73 = OpAccessChain %56 %19 %72
|
||||
%74 = OpLoad %14 %73
|
||||
%75 = OpConvertUToF %6 %74
|
||||
%78 = OpCompositeConstruct %7 %30 %68 %69 %70
|
||||
%79 = OpCompositeConstruct %7 %71 %75 %76 %77
|
||||
%80 = OpCompositeConstruct %8 %78 %79
|
||||
OpStore %81 %80
|
||||
%82 = OpFunctionCall %6 %12 %81
|
||||
%83 = OpConvertFToS %17 %82
|
||||
OpReturnValue %83
|
||||
%64 = OpLabel
|
||||
%85 = OpUndef %17
|
||||
OpReturnValue %85
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||
const auto consumer = nullptr;
|
||||
const auto recipient_context =
|
||||
BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, recipient_context.get()));
|
||||
|
||||
const auto donor_context =
|
||||
BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, donor_context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
|
||||
FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
|
||||
protobufs::TransformationSequence transformation_sequence;
|
||||
|
||||
FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), &fact_manager,
|
||||
&fuzzer_context, &transformation_sequence,
|
||||
{});
|
||||
|
||||
fuzzer_pass.DonateSingleModule(donor_context.get());
|
||||
|
||||
// We just check that the result is valid. Checking to what it should be
|
||||
// exactly equal to would be very fragile.
|
||||
ASSERT_TRUE(IsValid(env, recipient_context.get()));
|
||||
}
|
||||
|
||||
TEST(FuzzerPassDonateModulesTest, DonationWithUniforms) {
|
||||
// This test checks that when donating a shader that contains uniforms,
|
||||
// uniform variables and associated pointer types are demoted from having
|
||||
// Uniform storage class to Private storage class.
|
||||
std::string recipient_and_donor_shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpMemberDecorate %9 0 Offset 0
|
||||
OpDecorate %9 Block
|
||||
OpDecorate %11 DescriptorSet 0
|
||||
OpDecorate %11 Binding 0
|
||||
OpMemberDecorate %19 0 Offset 0
|
||||
OpDecorate %19 Block
|
||||
OpDecorate %21 DescriptorSet 0
|
||||
OpDecorate %21 Binding 1
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%7 = OpTypePointer Function %6
|
||||
%9 = OpTypeStruct %6
|
||||
%10 = OpTypePointer Uniform %9
|
||||
%11 = OpVariable %10 Uniform
|
||||
%12 = OpTypeInt 32 1
|
||||
%13 = OpConstant %12 0
|
||||
%14 = OpTypePointer Uniform %6
|
||||
%17 = OpTypePointer Function %12
|
||||
%19 = OpTypeStruct %12
|
||||
%20 = OpTypePointer Uniform %19
|
||||
%21 = OpVariable %20 Uniform
|
||||
%22 = OpTypePointer Uniform %12
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%8 = OpVariable %7 Function
|
||||
%18 = OpVariable %17 Function
|
||||
%15 = OpAccessChain %14 %11 %13
|
||||
%16 = OpLoad %6 %15
|
||||
OpStore %8 %16
|
||||
%23 = OpAccessChain %22 %21 %13
|
||||
%24 = OpLoad %12 %23
|
||||
OpStore %18 %24
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||
const auto consumer = nullptr;
|
||||
const auto recipient_context = BuildModule(
|
||||
env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, recipient_context.get()));
|
||||
|
||||
const auto donor_context = BuildModule(
|
||||
env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, donor_context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
|
||||
FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
|
||||
protobufs::TransformationSequence transformation_sequence;
|
||||
|
||||
FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), &fact_manager,
|
||||
&fuzzer_context, &transformation_sequence,
|
||||
{});
|
||||
|
||||
fuzzer_pass.DonateSingleModule(donor_context.get());
|
||||
|
||||
ASSERT_TRUE(IsValid(env, recipient_context.get()));
|
||||
|
||||
std::string after_transformation = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpMemberDecorate %9 0 Offset 0
|
||||
OpDecorate %9 Block
|
||||
OpDecorate %11 DescriptorSet 0
|
||||
OpDecorate %11 Binding 0
|
||||
OpMemberDecorate %19 0 Offset 0
|
||||
OpDecorate %19 Block
|
||||
OpDecorate %21 DescriptorSet 0
|
||||
OpDecorate %21 Binding 1
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%7 = OpTypePointer Function %6
|
||||
%9 = OpTypeStruct %6
|
||||
%10 = OpTypePointer Uniform %9
|
||||
%11 = OpVariable %10 Uniform
|
||||
%12 = OpTypeInt 32 1
|
||||
%13 = OpConstant %12 0
|
||||
%14 = OpTypePointer Uniform %6
|
||||
%17 = OpTypePointer Function %12
|
||||
%19 = OpTypeStruct %12
|
||||
%20 = OpTypePointer Uniform %19
|
||||
%21 = OpVariable %20 Uniform
|
||||
%22 = OpTypePointer Uniform %12
|
||||
%100 = OpTypePointer Function %6
|
||||
%101 = OpTypeStruct %6
|
||||
%102 = OpTypePointer Private %101
|
||||
%103 = OpVariable %102 Private
|
||||
%104 = OpConstant %12 0
|
||||
%105 = OpTypePointer Private %6
|
||||
%106 = OpTypePointer Function %12
|
||||
%107 = OpTypeStruct %12
|
||||
%108 = OpTypePointer Private %107
|
||||
%109 = OpVariable %108 Private
|
||||
%110 = OpTypePointer Private %12
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%8 = OpVariable %7 Function
|
||||
%18 = OpVariable %17 Function
|
||||
%15 = OpAccessChain %14 %11 %13
|
||||
%16 = OpLoad %6 %15
|
||||
OpStore %8 %16
|
||||
%23 = OpAccessChain %22 %21 %13
|
||||
%24 = OpLoad %12 %23
|
||||
OpStore %18 %24
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%111 = OpFunction %2 None %3
|
||||
%112 = OpLabel
|
||||
%113 = OpVariable %100 Function
|
||||
%114 = OpVariable %106 Function
|
||||
%115 = OpAccessChain %105 %103 %104
|
||||
%116 = OpLoad %6 %115
|
||||
OpStore %113 %116
|
||||
%117 = OpAccessChain %110 %109 %104
|
||||
%118 = OpLoad %12 %117
|
||||
OpStore %114 %118
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
ASSERT_TRUE(IsEqual(env, after_transformation, recipient_context.get()));
|
||||
}
|
||||
|
||||
TEST(FuzzerPassDonateModulesTest, DonationWithInputAndOutputVariables) {
|
||||
// This test checks that when donating a shader that contains input and output
|
||||
// variables, such variables and associated pointer types are demoted to have
|
||||
// the Private storage class.
|
||||
std::string recipient_and_donor_shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main" %9 %11
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpDecorate %9 Location 0
|
||||
OpDecorate %11 Location 1
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%7 = OpTypeVector %6 4
|
||||
%8 = OpTypePointer Output %7
|
||||
%9 = OpVariable %8 Output
|
||||
%10 = OpTypePointer Input %7
|
||||
%11 = OpVariable %10 Input
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%12 = OpLoad %7 %11
|
||||
OpStore %9 %12
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||
const auto consumer = nullptr;
|
||||
const auto recipient_context = BuildModule(
|
||||
env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, recipient_context.get()));
|
||||
|
||||
const auto donor_context = BuildModule(
|
||||
env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, donor_context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
|
||||
FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
|
||||
protobufs::TransformationSequence transformation_sequence;
|
||||
|
||||
FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), &fact_manager,
|
||||
&fuzzer_context, &transformation_sequence,
|
||||
{});
|
||||
|
||||
fuzzer_pass.DonateSingleModule(donor_context.get());
|
||||
|
||||
ASSERT_TRUE(IsValid(env, recipient_context.get()));
|
||||
|
||||
std::string after_transformation = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main" %9 %11
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpDecorate %9 Location 0
|
||||
OpDecorate %11 Location 1
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%7 = OpTypeVector %6 4
|
||||
%8 = OpTypePointer Output %7
|
||||
%9 = OpVariable %8 Output
|
||||
%10 = OpTypePointer Input %7
|
||||
%11 = OpVariable %10 Input
|
||||
%100 = OpTypePointer Private %7
|
||||
%101 = OpVariable %100 Private
|
||||
%102 = OpTypePointer Private %7
|
||||
%103 = OpVariable %102 Private
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%12 = OpLoad %7 %11
|
||||
OpStore %9 %12
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%104 = OpFunction %2 None %3
|
||||
%105 = OpLabel
|
||||
%106 = OpLoad %7 %103
|
||||
OpStore %101 %106
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
ASSERT_TRUE(IsEqual(env, after_transformation, recipient_context.get()));
|
||||
}
|
||||
|
||||
TEST(FuzzerPassDonateModulesTest, Miscellaneous1) {
|
||||
std::string recipient_shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
std::string donor_shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %4 "main"
|
||||
OpName %6 "foo("
|
||||
OpName %10 "x"
|
||||
OpName %12 "i"
|
||||
OpName %33 "i"
|
||||
OpName %42 "j"
|
||||
OpDecorate %10 RelaxedPrecision
|
||||
OpDecorate %12 RelaxedPrecision
|
||||
OpDecorate %19 RelaxedPrecision
|
||||
OpDecorate %23 RelaxedPrecision
|
||||
OpDecorate %24 RelaxedPrecision
|
||||
OpDecorate %25 RelaxedPrecision
|
||||
OpDecorate %26 RelaxedPrecision
|
||||
OpDecorate %27 RelaxedPrecision
|
||||
OpDecorate %28 RelaxedPrecision
|
||||
OpDecorate %30 RelaxedPrecision
|
||||
OpDecorate %33 RelaxedPrecision
|
||||
OpDecorate %39 RelaxedPrecision
|
||||
OpDecorate %42 RelaxedPrecision
|
||||
OpDecorate %49 RelaxedPrecision
|
||||
OpDecorate %52 RelaxedPrecision
|
||||
OpDecorate %53 RelaxedPrecision
|
||||
OpDecorate %58 RelaxedPrecision
|
||||
OpDecorate %59 RelaxedPrecision
|
||||
OpDecorate %60 RelaxedPrecision
|
||||
OpDecorate %63 RelaxedPrecision
|
||||
OpDecorate %64 RelaxedPrecision
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%8 = OpTypeInt 32 1
|
||||
%9 = OpTypePointer Function %8
|
||||
%11 = OpConstant %8 2
|
||||
%13 = OpConstant %8 0
|
||||
%20 = OpConstant %8 100
|
||||
%21 = OpTypeBool
|
||||
%29 = OpConstant %8 1
|
||||
%40 = OpConstant %8 10
|
||||
%43 = OpConstant %8 20
|
||||
%61 = OpConstant %8 4
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%33 = OpVariable %9 Function
|
||||
%42 = OpVariable %9 Function
|
||||
%32 = OpFunctionCall %2 %6
|
||||
OpStore %33 %13
|
||||
OpBranch %34
|
||||
%34 = OpLabel
|
||||
OpLoopMerge %36 %37 None
|
||||
OpBranch %38
|
||||
%38 = OpLabel
|
||||
%39 = OpLoad %8 %33
|
||||
%41 = OpSLessThan %21 %39 %40
|
||||
OpBranchConditional %41 %35 %36
|
||||
%35 = OpLabel
|
||||
OpStore %42 %43
|
||||
OpBranch %44
|
||||
%44 = OpLabel
|
||||
OpLoopMerge %46 %47 None
|
||||
OpBranch %48
|
||||
%48 = OpLabel
|
||||
%49 = OpLoad %8 %42
|
||||
%50 = OpSGreaterThan %21 %49 %13
|
||||
OpBranchConditional %50 %45 %46
|
||||
%45 = OpLabel
|
||||
%51 = OpFunctionCall %2 %6
|
||||
%52 = OpLoad %8 %42
|
||||
%53 = OpISub %8 %52 %29
|
||||
OpStore %42 %53
|
||||
OpBranch %47
|
||||
%47 = OpLabel
|
||||
OpBranch %44
|
||||
%46 = OpLabel
|
||||
OpBranch %54
|
||||
%54 = OpLabel
|
||||
OpLoopMerge %56 %57 None
|
||||
OpBranch %55
|
||||
%55 = OpLabel
|
||||
%58 = OpLoad %8 %33
|
||||
%59 = OpIAdd %8 %58 %29
|
||||
OpStore %33 %59
|
||||
OpBranch %57
|
||||
%57 = OpLabel
|
||||
%60 = OpLoad %8 %33
|
||||
%62 = OpSLessThan %21 %60 %61
|
||||
OpBranchConditional %62 %54 %56
|
||||
%56 = OpLabel
|
||||
OpBranch %37
|
||||
%37 = OpLabel
|
||||
%63 = OpLoad %8 %33
|
||||
%64 = OpIAdd %8 %63 %29
|
||||
OpStore %33 %64
|
||||
OpBranch %34
|
||||
%36 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%6 = OpFunction %2 None %3
|
||||
%7 = OpLabel
|
||||
%10 = OpVariable %9 Function
|
||||
%12 = OpVariable %9 Function
|
||||
OpStore %10 %11
|
||||
OpStore %12 %13
|
||||
OpBranch %14
|
||||
%14 = OpLabel
|
||||
OpLoopMerge %16 %17 None
|
||||
OpBranch %18
|
||||
%18 = OpLabel
|
||||
%19 = OpLoad %8 %12
|
||||
%22 = OpSLessThan %21 %19 %20
|
||||
OpBranchConditional %22 %15 %16
|
||||
%15 = OpLabel
|
||||
%23 = OpLoad %8 %12
|
||||
%24 = OpLoad %8 %10
|
||||
%25 = OpIAdd %8 %24 %23
|
||||
OpStore %10 %25
|
||||
%26 = OpLoad %8 %10
|
||||
%27 = OpIMul %8 %26 %11
|
||||
OpStore %10 %27
|
||||
OpBranch %17
|
||||
%17 = OpLabel
|
||||
%28 = OpLoad %8 %12
|
||||
%30 = OpIAdd %8 %28 %29
|
||||
OpStore %12 %30
|
||||
OpBranch %14
|
||||
%16 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
const auto consumer = nullptr;
|
||||
const auto recipient_context =
|
||||
BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, recipient_context.get()));
|
||||
|
||||
const auto donor_context =
|
||||
BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, donor_context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
|
||||
FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
|
||||
protobufs::TransformationSequence transformation_sequence;
|
||||
|
||||
FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), &fact_manager,
|
||||
&fuzzer_context, &transformation_sequence,
|
||||
{});
|
||||
|
||||
fuzzer_pass.DonateSingleModule(donor_context.get());
|
||||
|
||||
// We just check that the result is valid. Checking to what it should be
|
||||
// exactly equal to would be very fragile.
|
||||
ASSERT_TRUE(IsValid(env, recipient_context.get()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
+302
-290
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include "source/fuzz/fuzzer.h"
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
#include "source/fuzz/replayer.h"
|
||||
#include "source/fuzz/uniform_buffer_element_descriptor.h"
|
||||
#include "test/fuzz/fuzz_test_util.h"
|
||||
@@ -23,109 +24,35 @@ namespace {
|
||||
|
||||
const uint32_t kNumFuzzerRuns = 20;
|
||||
|
||||
void AddConstantUniformFact(protobufs::FactSequence* facts,
|
||||
uint32_t descriptor_set, uint32_t binding,
|
||||
std::vector<uint32_t>&& indices, uint32_t value) {
|
||||
protobufs::FactConstantUniform fact;
|
||||
*fact.mutable_uniform_buffer_element_descriptor() =
|
||||
MakeUniformBufferElementDescriptor(descriptor_set, binding,
|
||||
std::move(indices));
|
||||
*fact.mutable_constant_word()->Add() = value;
|
||||
protobufs::Fact temp;
|
||||
*temp.mutable_constant_uniform_fact() = fact;
|
||||
*facts->mutable_fact()->Add() = temp;
|
||||
}
|
||||
// The SPIR-V came from this GLSL:
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// void foo() {
|
||||
// int x;
|
||||
// x = 2;
|
||||
// for (int i = 0; i < 100; i++) {
|
||||
// x += i;
|
||||
// x = x * 2;
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// void main() {
|
||||
// foo();
|
||||
// for (int i = 0; i < 10; i++) {
|
||||
// int j = 20;
|
||||
// while(j > 0) {
|
||||
// foo();
|
||||
// j--;
|
||||
// }
|
||||
// do {
|
||||
// i++;
|
||||
// } while(i < 4);
|
||||
// }
|
||||
// }
|
||||
|
||||
// Reinterpret the bits of |value| as a 32-bit unsigned int
|
||||
uint32_t FloatBitsAsUint(float value) {
|
||||
uint32_t result;
|
||||
memcpy(&result, &value, sizeof(float));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Assembles the given |shader| text, and then runs the fuzzer |num_runs|
|
||||
// times, using successive seeds starting from |initial_seed|. Checks that
|
||||
// the binary produced after each fuzzer run is valid, and that replaying
|
||||
// the transformations that were applied during fuzzing leads to an
|
||||
// identical binary.
|
||||
void RunFuzzerAndReplayer(const std::string& shader,
|
||||
const protobufs::FactSequence& initial_facts,
|
||||
uint32_t initial_seed, uint32_t num_runs) {
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
|
||||
std::vector<uint32_t> binary_in;
|
||||
SpirvTools t(env);
|
||||
t.SetMessageConsumer(kConsoleMessageConsumer);
|
||||
ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
|
||||
ASSERT_TRUE(t.Validate(binary_in));
|
||||
|
||||
for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) {
|
||||
std::vector<uint32_t> fuzzer_binary_out;
|
||||
protobufs::TransformationSequence fuzzer_transformation_sequence_out;
|
||||
|
||||
Fuzzer fuzzer(env, seed, true);
|
||||
fuzzer.SetMessageConsumer(kSilentConsumer);
|
||||
auto fuzzer_result_status =
|
||||
fuzzer.Run(binary_in, initial_facts, &fuzzer_binary_out,
|
||||
&fuzzer_transformation_sequence_out);
|
||||
ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
|
||||
ASSERT_TRUE(t.Validate(fuzzer_binary_out));
|
||||
|
||||
std::vector<uint32_t> replayer_binary_out;
|
||||
protobufs::TransformationSequence replayer_transformation_sequence_out;
|
||||
|
||||
Replayer replayer(env, false);
|
||||
replayer.SetMessageConsumer(kSilentConsumer);
|
||||
auto replayer_result_status = replayer.Run(
|
||||
binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
&replayer_binary_out, &replayer_transformation_sequence_out);
|
||||
ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
|
||||
replayer_result_status);
|
||||
|
||||
// After replaying the transformations applied by the fuzzer, exactly those
|
||||
// transformations should have been applied, and the binary resulting from
|
||||
// replay should be identical to that which resulted from fuzzing.
|
||||
std::string fuzzer_transformations_string;
|
||||
std::string replayer_transformations_string;
|
||||
fuzzer_transformation_sequence_out.SerializeToString(
|
||||
&fuzzer_transformations_string);
|
||||
replayer_transformation_sequence_out.SerializeToString(
|
||||
&replayer_transformations_string);
|
||||
ASSERT_EQ(fuzzer_transformations_string, replayer_transformations_string);
|
||||
ASSERT_EQ(fuzzer_binary_out, replayer_binary_out);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FuzzerReplayerTest, Miscellaneous1) {
|
||||
// The SPIR-V came from this GLSL:
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// void foo() {
|
||||
// int x;
|
||||
// x = 2;
|
||||
// for (int i = 0; i < 100; i++) {
|
||||
// x += i;
|
||||
// x = x * 2;
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// void main() {
|
||||
// foo();
|
||||
// for (int i = 0; i < 10; i++) {
|
||||
// int j = 20;
|
||||
// while(j > 0) {
|
||||
// foo();
|
||||
// j--;
|
||||
// }
|
||||
// do {
|
||||
// i++;
|
||||
// } while(i < 4);
|
||||
// }
|
||||
// }
|
||||
|
||||
std::string shader = R"(
|
||||
const std::string kTestShader1 = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
@@ -260,66 +187,60 @@ TEST(FuzzerReplayerTest, Miscellaneous1) {
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
// Do some fuzzer runs, starting from an initial seed of 0 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 0, kNumFuzzerRuns);
|
||||
}
|
||||
// The SPIR-V came from this GLSL, which was then optimized using spirv-opt
|
||||
// with the -O argument:
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// precision highp float;
|
||||
//
|
||||
// layout(location = 0) out vec4 _GLF_color;
|
||||
//
|
||||
// layout(set = 0, binding = 0) uniform buf0 {
|
||||
// vec2 injectionSwitch;
|
||||
// };
|
||||
// layout(set = 0, binding = 1) uniform buf1 {
|
||||
// vec2 resolution;
|
||||
// };
|
||||
// bool checkSwap(float a, float b)
|
||||
// {
|
||||
// return gl_FragCoord.y < resolution.y / 2.0 ? a > b : a < b;
|
||||
// }
|
||||
// void main()
|
||||
// {
|
||||
// float data[10];
|
||||
// for(int i = 0; i < 10; i++)
|
||||
// {
|
||||
// data[i] = float(10 - i) * injectionSwitch.y;
|
||||
// }
|
||||
// for(int i = 0; i < 9; i++)
|
||||
// {
|
||||
// for(int j = 0; j < 10; j++)
|
||||
// {
|
||||
// if(j < i + 1)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
// bool doSwap = checkSwap(data[i], data[j]);
|
||||
// if(doSwap)
|
||||
// {
|
||||
// float temp = data[i];
|
||||
// data[i] = data[j];
|
||||
// data[j] = temp;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if(gl_FragCoord.x < resolution.x / 2.0)
|
||||
// {
|
||||
// _GLF_color = vec4(data[0] / 10.0, data[5] / 10.0, data[9] / 10.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _GLF_color = vec4(data[5] / 10.0, data[9] / 10.0, data[0] / 10.0, 1.0);
|
||||
// }
|
||||
// }
|
||||
|
||||
TEST(FuzzerReplayerTest, Miscellaneous2) {
|
||||
// The SPIR-V came from this GLSL, which was then optimized using spirv-opt
|
||||
// with the -O argument:
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// precision highp float;
|
||||
//
|
||||
// layout(location = 0) out vec4 _GLF_color;
|
||||
//
|
||||
// layout(set = 0, binding = 0) uniform buf0 {
|
||||
// vec2 injectionSwitch;
|
||||
// };
|
||||
// layout(set = 0, binding = 1) uniform buf1 {
|
||||
// vec2 resolution;
|
||||
// };
|
||||
// bool checkSwap(float a, float b)
|
||||
// {
|
||||
// return gl_FragCoord.y < resolution.y / 2.0 ? a > b : a < b;
|
||||
// }
|
||||
// void main()
|
||||
// {
|
||||
// float data[10];
|
||||
// for(int i = 0; i < 10; i++)
|
||||
// {
|
||||
// data[i] = float(10 - i) * injectionSwitch.y;
|
||||
// }
|
||||
// for(int i = 0; i < 9; i++)
|
||||
// {
|
||||
// for(int j = 0; j < 10; j++)
|
||||
// {
|
||||
// if(j < i + 1)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
// bool doSwap = checkSwap(data[i], data[j]);
|
||||
// if(doSwap)
|
||||
// {
|
||||
// float temp = data[i];
|
||||
// data[i] = data[j];
|
||||
// data[j] = temp;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if(gl_FragCoord.x < resolution.x / 2.0)
|
||||
// {
|
||||
// _GLF_color = vec4(data[0] / 10.0, data[5] / 10.0, data[9] / 10.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _GLF_color = vec4(data[5] / 10.0, data[9] / 10.0, data[0] / 10.0, 1.0);
|
||||
// }
|
||||
// }
|
||||
|
||||
std::string shader = R"(
|
||||
const std::string kTestShader2 = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
@@ -507,121 +428,115 @@ TEST(FuzzerReplayerTest, Miscellaneous2) {
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
// Do some fuzzer runs, starting from an initial seed of 10 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 10, kNumFuzzerRuns);
|
||||
}
|
||||
// The SPIR-V came from this GLSL, which was then optimized using spirv-opt
|
||||
// with the -O argument:
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// precision highp float;
|
||||
//
|
||||
// layout(location = 0) out vec4 _GLF_color;
|
||||
//
|
||||
// layout(set = 0, binding = 0) uniform buf0 {
|
||||
// vec2 resolution;
|
||||
// };
|
||||
// void main(void)
|
||||
// {
|
||||
// float A[50];
|
||||
// for(
|
||||
// int i = 0;
|
||||
// i < 200;
|
||||
// i ++
|
||||
// )
|
||||
// {
|
||||
// if(i >= int(resolution.x))
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// if((4 * (i / 4)) == i)
|
||||
// {
|
||||
// A[i / 4] = float(i);
|
||||
// }
|
||||
// }
|
||||
// for(
|
||||
// int i = 0;
|
||||
// i < 50;
|
||||
// i ++
|
||||
// )
|
||||
// {
|
||||
// if(i < int(gl_FragCoord.x))
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// if(i > 0)
|
||||
// {
|
||||
// A[i] += A[i - 1];
|
||||
// }
|
||||
// }
|
||||
// if(int(gl_FragCoord.x) < 20)
|
||||
// {
|
||||
// _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 40)
|
||||
// {
|
||||
// _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 60)
|
||||
// {
|
||||
// _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 80)
|
||||
// {
|
||||
// _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 100)
|
||||
// {
|
||||
// _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 120)
|
||||
// {
|
||||
// _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 140)
|
||||
// {
|
||||
// _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 160)
|
||||
// {
|
||||
// _GLF_color = vec4(A[35] / resolution.x, A[39] /
|
||||
// resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 180)
|
||||
// {
|
||||
// _GLF_color = vec4(A[40] / resolution.x, A[44] /
|
||||
// resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 180)
|
||||
// {
|
||||
// _GLF_color = vec4(A[45] / resolution.x, A[49] /
|
||||
// resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// discard;
|
||||
// }
|
||||
// }
|
||||
|
||||
TEST(FuzzerReplayerTest, Miscellaneous3) {
|
||||
// The SPIR-V came from this GLSL, which was then optimized using spirv-opt
|
||||
// with the -O argument:
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// precision highp float;
|
||||
//
|
||||
// layout(location = 0) out vec4 _GLF_color;
|
||||
//
|
||||
// layout(set = 0, binding = 0) uniform buf0 {
|
||||
// vec2 resolution;
|
||||
// };
|
||||
// void main(void)
|
||||
// {
|
||||
// float A[50];
|
||||
// for(
|
||||
// int i = 0;
|
||||
// i < 200;
|
||||
// i ++
|
||||
// )
|
||||
// {
|
||||
// if(i >= int(resolution.x))
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// if((4 * (i / 4)) == i)
|
||||
// {
|
||||
// A[i / 4] = float(i);
|
||||
// }
|
||||
// }
|
||||
// for(
|
||||
// int i = 0;
|
||||
// i < 50;
|
||||
// i ++
|
||||
// )
|
||||
// {
|
||||
// if(i < int(gl_FragCoord.x))
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// if(i > 0)
|
||||
// {
|
||||
// A[i] += A[i - 1];
|
||||
// }
|
||||
// }
|
||||
// if(int(gl_FragCoord.x) < 20)
|
||||
// {
|
||||
// _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 40)
|
||||
// {
|
||||
// _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 60)
|
||||
// {
|
||||
// _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 80)
|
||||
// {
|
||||
// _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 100)
|
||||
// {
|
||||
// _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 120)
|
||||
// {
|
||||
// _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 140)
|
||||
// {
|
||||
// _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 160)
|
||||
// {
|
||||
// _GLF_color = vec4(A[35] / resolution.x, A[39] /
|
||||
// resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 180)
|
||||
// {
|
||||
// _GLF_color = vec4(A[40] / resolution.x, A[44] /
|
||||
// resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 180)
|
||||
// {
|
||||
// _GLF_color = vec4(A[45] / resolution.x, A[49] /
|
||||
// resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// discard;
|
||||
// }
|
||||
// }
|
||||
|
||||
std::string shader = R"(
|
||||
const std::string kTestShader3 = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
@@ -969,21 +884,10 @@ TEST(FuzzerReplayerTest, Miscellaneous3) {
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
// Add the facts "resolution.x == 250" and "resolution.y == 100".
|
||||
protobufs::FactSequence facts;
|
||||
AddConstantUniformFact(&facts, 0, 0, {0, 0}, 250);
|
||||
AddConstantUniformFact(&facts, 0, 0, {0, 1}, 100);
|
||||
// The SPIR-V comes from the 'matrices_smart_loops' GLSL shader that ships
|
||||
// with GraphicsFuzz.
|
||||
|
||||
// Do some fuzzer runs, starting from an initial seed of 94 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndReplayer(shader, facts, 94, kNumFuzzerRuns);
|
||||
}
|
||||
|
||||
TEST(FuzzerReplayerTest, Miscellaneous4) {
|
||||
// The SPIR-V comes from the 'matrices_smart_loops' GLSL shader that ships
|
||||
// with GraphicsFuzz.
|
||||
|
||||
std::string shader = R"(
|
||||
const std::string kTestShader4 = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
@@ -1611,6 +1515,114 @@ TEST(FuzzerReplayerTest, Miscellaneous4) {
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
void AddConstantUniformFact(protobufs::FactSequence* facts,
|
||||
uint32_t descriptor_set, uint32_t binding,
|
||||
std::vector<uint32_t>&& indices, uint32_t value) {
|
||||
protobufs::FactConstantUniform fact;
|
||||
*fact.mutable_uniform_buffer_element_descriptor() =
|
||||
MakeUniformBufferElementDescriptor(descriptor_set, binding,
|
||||
std::move(indices));
|
||||
*fact.mutable_constant_word()->Add() = value;
|
||||
protobufs::Fact temp;
|
||||
*temp.mutable_constant_uniform_fact() = fact;
|
||||
*facts->mutable_fact()->Add() = temp;
|
||||
}
|
||||
|
||||
// Reinterpret the bits of |value| as a 32-bit unsigned int
|
||||
uint32_t FloatBitsAsUint(float value) {
|
||||
uint32_t result;
|
||||
memcpy(&result, &value, sizeof(float));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Assembles the given |shader| text, and then runs the fuzzer |num_runs|
|
||||
// times, using successive seeds starting from |initial_seed|. Checks that
|
||||
// the binary produced after each fuzzer run is valid, and that replaying
|
||||
// the transformations that were applied during fuzzing leads to an
|
||||
// identical binary.
|
||||
void RunFuzzerAndReplayer(const std::string& shader,
|
||||
const protobufs::FactSequence& initial_facts,
|
||||
uint32_t initial_seed, uint32_t num_runs) {
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
|
||||
std::vector<uint32_t> binary_in;
|
||||
SpirvTools t(env);
|
||||
t.SetMessageConsumer(kConsoleMessageConsumer);
|
||||
ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
|
||||
ASSERT_TRUE(t.Validate(binary_in));
|
||||
|
||||
std::vector<fuzzerutil::ModuleSupplier> donor_suppliers;
|
||||
for (auto donor :
|
||||
{&kTestShader1, &kTestShader2, &kTestShader3, &kTestShader4}) {
|
||||
donor_suppliers.emplace_back([donor]() {
|
||||
return BuildModule(env, kConsoleMessageConsumer, *donor,
|
||||
kFuzzAssembleOption);
|
||||
});
|
||||
}
|
||||
|
||||
for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) {
|
||||
std::vector<uint32_t> fuzzer_binary_out;
|
||||
protobufs::TransformationSequence fuzzer_transformation_sequence_out;
|
||||
|
||||
Fuzzer fuzzer(env, seed, true);
|
||||
fuzzer.SetMessageConsumer(kSilentConsumer);
|
||||
auto fuzzer_result_status =
|
||||
fuzzer.Run(binary_in, initial_facts, donor_suppliers,
|
||||
&fuzzer_binary_out, &fuzzer_transformation_sequence_out);
|
||||
ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
|
||||
ASSERT_TRUE(t.Validate(fuzzer_binary_out));
|
||||
|
||||
std::vector<uint32_t> replayer_binary_out;
|
||||
protobufs::TransformationSequence replayer_transformation_sequence_out;
|
||||
|
||||
Replayer replayer(env, false);
|
||||
replayer.SetMessageConsumer(kSilentConsumer);
|
||||
auto replayer_result_status = replayer.Run(
|
||||
binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
&replayer_binary_out, &replayer_transformation_sequence_out);
|
||||
ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
|
||||
replayer_result_status);
|
||||
|
||||
// After replaying the transformations applied by the fuzzer, exactly those
|
||||
// transformations should have been applied, and the binary resulting from
|
||||
// replay should be identical to that which resulted from fuzzing.
|
||||
std::string fuzzer_transformations_string;
|
||||
std::string replayer_transformations_string;
|
||||
fuzzer_transformation_sequence_out.SerializeToString(
|
||||
&fuzzer_transformations_string);
|
||||
replayer_transformation_sequence_out.SerializeToString(
|
||||
&replayer_transformations_string);
|
||||
ASSERT_EQ(fuzzer_transformations_string, replayer_transformations_string);
|
||||
ASSERT_EQ(fuzzer_binary_out, replayer_binary_out);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FuzzerReplayerTest, Miscellaneous1) {
|
||||
// Do some fuzzer runs, starting from an initial seed of 0 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndReplayer(kTestShader1, protobufs::FactSequence(), 0,
|
||||
kNumFuzzerRuns);
|
||||
}
|
||||
|
||||
TEST(FuzzerReplayerTest, Miscellaneous2) {
|
||||
// Do some fuzzer runs, starting from an initial seed of 10 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndReplayer(kTestShader2, protobufs::FactSequence(), 10,
|
||||
kNumFuzzerRuns);
|
||||
}
|
||||
|
||||
TEST(FuzzerReplayerTest, Miscellaneous3) {
|
||||
// Add the facts "resolution.x == 250" and "resolution.y == 100".
|
||||
protobufs::FactSequence facts;
|
||||
AddConstantUniformFact(&facts, 0, 0, {0, 0}, 250);
|
||||
AddConstantUniformFact(&facts, 0, 0, {0, 1}, 100);
|
||||
|
||||
// Do some fuzzer runs, starting from an initial seed of 94 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndReplayer(kTestShader3, facts, 94, kNumFuzzerRuns);
|
||||
}
|
||||
|
||||
TEST(FuzzerReplayerTest, Miscellaneous4) {
|
||||
// Add the facts:
|
||||
// - "one == 1.0"
|
||||
// - "resolution.y == 256.0",
|
||||
@@ -1621,7 +1633,7 @@ TEST(FuzzerReplayerTest, Miscellaneous4) {
|
||||
|
||||
// Do some fuzzer runs, starting from an initial seed of 14 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndReplayer(shader, facts, 14, kNumFuzzerRuns);
|
||||
RunFuzzerAndReplayer(kTestShader4, facts, 14, kNumFuzzerRuns);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
+388
-379
@@ -16,6 +16,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "source/fuzz/fuzzer.h"
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
#include "source/fuzz/pseudo_random_generator.h"
|
||||
#include "source/fuzz/shrinker.h"
|
||||
#include "source/fuzz/uniform_buffer_element_descriptor.h"
|
||||
@@ -25,217 +26,35 @@ namespace spvtools {
|
||||
namespace fuzz {
|
||||
namespace {
|
||||
|
||||
// Abstract class exposing an interestingness function as a virtual method.
|
||||
class InterestingnessTest {
|
||||
public:
|
||||
virtual ~InterestingnessTest() = default;
|
||||
|
||||
// Abstract method that subclasses should implement for specific notions of
|
||||
// interestingness. Its signature matches Shrinker::InterestingnessFunction.
|
||||
// Argument |binary| is the SPIR-V binary to be checked; |counter| is used for
|
||||
// debugging purposes.
|
||||
virtual bool Interesting(const std::vector<uint32_t>& binary,
|
||||
uint32_t counter) = 0;
|
||||
|
||||
// Yields the Interesting instance method wrapped in a function object.
|
||||
Shrinker::InterestingnessFunction AsFunction() {
|
||||
return std::bind(&InterestingnessTest::Interesting, this,
|
||||
std::placeholders::_1, std::placeholders::_2);
|
||||
}
|
||||
};
|
||||
|
||||
// A test that says all binaries are interesting.
|
||||
class AlwaysInteresting : public InterestingnessTest {
|
||||
public:
|
||||
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// A test that says a binary is interesting first time round, and uninteresting
|
||||
// thereafter.
|
||||
class OnlyInterestingFirstTime : public InterestingnessTest {
|
||||
public:
|
||||
explicit OnlyInterestingFirstTime() : first_time_(true) {}
|
||||
|
||||
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
|
||||
if (first_time_) {
|
||||
first_time_ = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool first_time_;
|
||||
};
|
||||
|
||||
// A test that says a binary is interesting first time round, after which
|
||||
// interestingness ping pongs between false and true.
|
||||
class PingPong : public InterestingnessTest {
|
||||
public:
|
||||
explicit PingPong() : interesting_(false) {}
|
||||
|
||||
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
|
||||
interesting_ = !interesting_;
|
||||
return interesting_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool interesting_;
|
||||
};
|
||||
|
||||
// A test that says a binary is interesting first time round, thereafter
|
||||
// decides at random whether it is interesting. This allows the logic of the
|
||||
// shrinker to be exercised quite a bit.
|
||||
class InterestingThenRandom : public InterestingnessTest {
|
||||
public:
|
||||
InterestingThenRandom(const PseudoRandomGenerator& random_generator)
|
||||
: first_time_(true), random_generator_(random_generator) {}
|
||||
|
||||
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
|
||||
if (first_time_) {
|
||||
first_time_ = false;
|
||||
return true;
|
||||
}
|
||||
return random_generator_.RandomBool();
|
||||
}
|
||||
|
||||
private:
|
||||
bool first_time_;
|
||||
PseudoRandomGenerator random_generator_;
|
||||
};
|
||||
|
||||
// |binary_in| and |initial_facts| are a SPIR-V binary and sequence of facts to
|
||||
// which |transformation_sequence_in| can be applied. Shrinking of
|
||||
// |transformation_sequence_in| gets performed with respect to
|
||||
// |interestingness_function|. If |expected_binary_out| is non-empty, it must
|
||||
// match the binary obtained by applying the final shrunk set of
|
||||
// transformations, in which case the number of such transformations should
|
||||
// equal |expected_transformations_out_size|.
|
||||
// The following SPIR-V came from this GLSL:
|
||||
//
|
||||
// The |step_limit| parameter restricts the number of steps that the shrinker
|
||||
// will try; it can be set to something small for a faster (but less thorough)
|
||||
// test.
|
||||
void RunAndCheckShrinker(
|
||||
const spv_target_env& target_env, const std::vector<uint32_t>& binary_in,
|
||||
const protobufs::FactSequence& initial_facts,
|
||||
const protobufs::TransformationSequence& transformation_sequence_in,
|
||||
const Shrinker::InterestingnessFunction& interestingness_function,
|
||||
const std::vector<uint32_t>& expected_binary_out,
|
||||
uint32_t expected_transformations_out_size, uint32_t step_limit) {
|
||||
// Run the shrinker.
|
||||
Shrinker shrinker(target_env, step_limit, false);
|
||||
shrinker.SetMessageConsumer(kSilentConsumer);
|
||||
// #version 310 es
|
||||
//
|
||||
// void foo() {
|
||||
// int x;
|
||||
// x = 2;
|
||||
// for (int i = 0; i < 100; i++) {
|
||||
// x += i;
|
||||
// x = x * 2;
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// void main() {
|
||||
// foo();
|
||||
// for (int i = 0; i < 10; i++) {
|
||||
// int j = 20;
|
||||
// while(j > 0) {
|
||||
// foo();
|
||||
// j--;
|
||||
// }
|
||||
// do {
|
||||
// i++;
|
||||
// } while(i < 4);
|
||||
// }
|
||||
// }
|
||||
|
||||
std::vector<uint32_t> binary_out;
|
||||
protobufs::TransformationSequence transformations_out;
|
||||
Shrinker::ShrinkerResultStatus shrinker_result_status =
|
||||
shrinker.Run(binary_in, initial_facts, transformation_sequence_in,
|
||||
interestingness_function, &binary_out, &transformations_out);
|
||||
ASSERT_TRUE(Shrinker::ShrinkerResultStatus::kComplete ==
|
||||
shrinker_result_status ||
|
||||
Shrinker::ShrinkerResultStatus::kStepLimitReached ==
|
||||
shrinker_result_status);
|
||||
|
||||
// If a non-empty expected binary was provided, check that it matches the
|
||||
// result of shrinking and that the expected number of transformations remain.
|
||||
if (!expected_binary_out.empty()) {
|
||||
ASSERT_EQ(expected_binary_out, binary_out);
|
||||
ASSERT_EQ(expected_transformations_out_size,
|
||||
static_cast<uint32_t>(transformations_out.transformation_size()));
|
||||
}
|
||||
}
|
||||
|
||||
// Assembles the given |shader| text, and then:
|
||||
// - Runs the fuzzer with |seed| to yield a set of transformations
|
||||
// - Shrinks the transformation with various interestingness functions,
|
||||
// asserting some properties about the result each time
|
||||
void RunFuzzerAndShrinker(const std::string& shader,
|
||||
const protobufs::FactSequence& initial_facts,
|
||||
uint32_t seed) {
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
|
||||
std::vector<uint32_t> binary_in;
|
||||
SpirvTools t(env);
|
||||
t.SetMessageConsumer(kConsoleMessageConsumer);
|
||||
ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
|
||||
ASSERT_TRUE(t.Validate(binary_in));
|
||||
|
||||
// Run the fuzzer and check that it successfully yields a valid binary.
|
||||
std::vector<uint32_t> fuzzer_binary_out;
|
||||
protobufs::TransformationSequence fuzzer_transformation_sequence_out;
|
||||
Fuzzer fuzzer(env, seed, true);
|
||||
fuzzer.SetMessageConsumer(kSilentConsumer);
|
||||
auto fuzzer_result_status =
|
||||
fuzzer.Run(binary_in, initial_facts, &fuzzer_binary_out,
|
||||
&fuzzer_transformation_sequence_out);
|
||||
ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
|
||||
ASSERT_TRUE(t.Validate(fuzzer_binary_out));
|
||||
|
||||
const uint32_t kReasonableStepLimit = 50;
|
||||
const uint32_t kSmallStepLimit = 20;
|
||||
|
||||
// With the AlwaysInteresting test, we should quickly shrink to the original
|
||||
// binary with no transformations remaining.
|
||||
RunAndCheckShrinker(
|
||||
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
AlwaysInteresting().AsFunction(), binary_in, 0, kReasonableStepLimit);
|
||||
|
||||
// With the OnlyInterestingFirstTime test, no shrinking should be achieved.
|
||||
RunAndCheckShrinker(
|
||||
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out,
|
||||
static_cast<uint32_t>(
|
||||
fuzzer_transformation_sequence_out.transformation_size()),
|
||||
kReasonableStepLimit);
|
||||
|
||||
// The PingPong test is unpredictable; passing an empty expected binary
|
||||
// means that we don't check anything beyond that shrinking completes
|
||||
// successfully.
|
||||
RunAndCheckShrinker(env, binary_in, initial_facts,
|
||||
fuzzer_transformation_sequence_out,
|
||||
PingPong().AsFunction(), {}, 0, kSmallStepLimit);
|
||||
|
||||
// The InterestingThenRandom test is unpredictable; passing an empty
|
||||
// expected binary means that we do not check anything about shrinking
|
||||
// results.
|
||||
RunAndCheckShrinker(
|
||||
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0,
|
||||
kSmallStepLimit);
|
||||
}
|
||||
|
||||
TEST(FuzzerShrinkerTest, Miscellaneous1) {
|
||||
// The following SPIR-V came from this GLSL:
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// void foo() {
|
||||
// int x;
|
||||
// x = 2;
|
||||
// for (int i = 0; i < 100; i++) {
|
||||
// x += i;
|
||||
// x = x * 2;
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// void main() {
|
||||
// foo();
|
||||
// for (int i = 0; i < 10; i++) {
|
||||
// int j = 20;
|
||||
// while(j > 0) {
|
||||
// foo();
|
||||
// j--;
|
||||
// }
|
||||
// do {
|
||||
// i++;
|
||||
// } while(i < 4);
|
||||
// }
|
||||
// }
|
||||
|
||||
const std::string shader = R"(
|
||||
const std::string kTestShader1 = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
@@ -371,64 +190,60 @@ TEST(FuzzerShrinkerTest, Miscellaneous1) {
|
||||
|
||||
)";
|
||||
|
||||
RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 2);
|
||||
}
|
||||
// The following SPIR-V came from this GLSL, which was then optimized using
|
||||
// spirv-opt with the -O argument:
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// precision highp float;
|
||||
//
|
||||
// layout(location = 0) out vec4 _GLF_color;
|
||||
//
|
||||
// layout(set = 0, binding = 0) uniform buf0 {
|
||||
// vec2 injectionSwitch;
|
||||
// };
|
||||
// layout(set = 0, binding = 1) uniform buf1 {
|
||||
// vec2 resolution;
|
||||
// };
|
||||
// bool checkSwap(float a, float b)
|
||||
// {
|
||||
// return gl_FragCoord.y < resolution.y / 2.0 ? a > b : a < b;
|
||||
// }
|
||||
// void main()
|
||||
// {
|
||||
// float data[10];
|
||||
// for(int i = 0; i < 10; i++)
|
||||
// {
|
||||
// data[i] = float(10 - i) * injectionSwitch.y;
|
||||
// }
|
||||
// for(int i = 0; i < 9; i++)
|
||||
// {
|
||||
// for(int j = 0; j < 10; j++)
|
||||
// {
|
||||
// if(j < i + 1)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
// bool doSwap = checkSwap(data[i], data[j]);
|
||||
// if(doSwap)
|
||||
// {
|
||||
// float temp = data[i];
|
||||
// data[i] = data[j];
|
||||
// data[j] = temp;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if(gl_FragCoord.x < resolution.x / 2.0)
|
||||
// {
|
||||
// _GLF_color = vec4(data[0] / 10.0, data[5] / 10.0, data[9] / 10.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _GLF_color = vec4(data[5] / 10.0, data[9] / 10.0, data[0] / 10.0, 1.0);
|
||||
// }
|
||||
// }
|
||||
|
||||
TEST(FuzzerShrinkerTest, Miscellaneous2) {
|
||||
// The following SPIR-V came from this GLSL, which was then optimized using
|
||||
// spirv-opt with the -O argument:
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// precision highp float;
|
||||
//
|
||||
// layout(location = 0) out vec4 _GLF_color;
|
||||
//
|
||||
// layout(set = 0, binding = 0) uniform buf0 {
|
||||
// vec2 injectionSwitch;
|
||||
// };
|
||||
// layout(set = 0, binding = 1) uniform buf1 {
|
||||
// vec2 resolution;
|
||||
// };
|
||||
// bool checkSwap(float a, float b)
|
||||
// {
|
||||
// return gl_FragCoord.y < resolution.y / 2.0 ? a > b : a < b;
|
||||
// }
|
||||
// void main()
|
||||
// {
|
||||
// float data[10];
|
||||
// for(int i = 0; i < 10; i++)
|
||||
// {
|
||||
// data[i] = float(10 - i) * injectionSwitch.y;
|
||||
// }
|
||||
// for(int i = 0; i < 9; i++)
|
||||
// {
|
||||
// for(int j = 0; j < 10; j++)
|
||||
// {
|
||||
// if(j < i + 1)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
// bool doSwap = checkSwap(data[i], data[j]);
|
||||
// if(doSwap)
|
||||
// {
|
||||
// float temp = data[i];
|
||||
// data[i] = data[j];
|
||||
// data[j] = temp;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if(gl_FragCoord.x < resolution.x / 2.0)
|
||||
// {
|
||||
// _GLF_color = vec4(data[0] / 10.0, data[5] / 10.0, data[9] / 10.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _GLF_color = vec4(data[5] / 10.0, data[9] / 10.0, data[0] / 10.0, 1.0);
|
||||
// }
|
||||
// }
|
||||
|
||||
const std::string shader = R"(
|
||||
const std::string kTestShader2 = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
@@ -616,119 +431,115 @@ TEST(FuzzerShrinkerTest, Miscellaneous2) {
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 19);
|
||||
}
|
||||
// The following SPIR-V came from this GLSL, which was then optimized using
|
||||
// spirv-opt with the -O argument:
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// precision highp float;
|
||||
//
|
||||
// layout(location = 0) out vec4 _GLF_color;
|
||||
//
|
||||
// layout(set = 0, binding = 0) uniform buf0 {
|
||||
// vec2 resolution;
|
||||
// };
|
||||
// void main(void)
|
||||
// {
|
||||
// float A[50];
|
||||
// for(
|
||||
// int i = 0;
|
||||
// i < 200;
|
||||
// i ++
|
||||
// )
|
||||
// {
|
||||
// if(i >= int(resolution.x))
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// if((4 * (i / 4)) == i)
|
||||
// {
|
||||
// A[i / 4] = float(i);
|
||||
// }
|
||||
// }
|
||||
// for(
|
||||
// int i = 0;
|
||||
// i < 50;
|
||||
// i ++
|
||||
// )
|
||||
// {
|
||||
// if(i < int(gl_FragCoord.x))
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// if(i > 0)
|
||||
// {
|
||||
// A[i] += A[i - 1];
|
||||
// }
|
||||
// }
|
||||
// if(int(gl_FragCoord.x) < 20)
|
||||
// {
|
||||
// _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 40)
|
||||
// {
|
||||
// _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 60)
|
||||
// {
|
||||
// _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 80)
|
||||
// {
|
||||
// _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 100)
|
||||
// {
|
||||
// _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 120)
|
||||
// {
|
||||
// _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 140)
|
||||
// {
|
||||
// _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 160)
|
||||
// {
|
||||
// _GLF_color = vec4(A[35] / resolution.x, A[39] /
|
||||
// resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 180)
|
||||
// {
|
||||
// _GLF_color = vec4(A[40] / resolution.x, A[44] /
|
||||
// resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 180)
|
||||
// {
|
||||
// _GLF_color = vec4(A[45] / resolution.x, A[49] /
|
||||
// resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// discard;
|
||||
// }
|
||||
// }
|
||||
|
||||
TEST(FuzzerShrinkerTest, Miscellaneous3) {
|
||||
// The following SPIR-V came from this GLSL, which was then optimized using
|
||||
// spirv-opt with the -O argument:
|
||||
//
|
||||
// #version 310 es
|
||||
//
|
||||
// precision highp float;
|
||||
//
|
||||
// layout(location = 0) out vec4 _GLF_color;
|
||||
//
|
||||
// layout(set = 0, binding = 0) uniform buf0 {
|
||||
// vec2 resolution;
|
||||
// };
|
||||
// void main(void)
|
||||
// {
|
||||
// float A[50];
|
||||
// for(
|
||||
// int i = 0;
|
||||
// i < 200;
|
||||
// i ++
|
||||
// )
|
||||
// {
|
||||
// if(i >= int(resolution.x))
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// if((4 * (i / 4)) == i)
|
||||
// {
|
||||
// A[i / 4] = float(i);
|
||||
// }
|
||||
// }
|
||||
// for(
|
||||
// int i = 0;
|
||||
// i < 50;
|
||||
// i ++
|
||||
// )
|
||||
// {
|
||||
// if(i < int(gl_FragCoord.x))
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// if(i > 0)
|
||||
// {
|
||||
// A[i] += A[i - 1];
|
||||
// }
|
||||
// }
|
||||
// if(int(gl_FragCoord.x) < 20)
|
||||
// {
|
||||
// _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 40)
|
||||
// {
|
||||
// _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 60)
|
||||
// {
|
||||
// _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 80)
|
||||
// {
|
||||
// _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 100)
|
||||
// {
|
||||
// _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 120)
|
||||
// {
|
||||
// _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 140)
|
||||
// {
|
||||
// _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y,
|
||||
// 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 160)
|
||||
// {
|
||||
// _GLF_color = vec4(A[35] / resolution.x, A[39] /
|
||||
// resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 180)
|
||||
// {
|
||||
// _GLF_color = vec4(A[40] / resolution.x, A[44] /
|
||||
// resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// if(int(gl_FragCoord.x) < 180)
|
||||
// {
|
||||
// _GLF_color = vec4(A[45] / resolution.x, A[49] /
|
||||
// resolution.y, 1.0, 1.0);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// discard;
|
||||
// }
|
||||
// }
|
||||
|
||||
const std::string shader = R"(
|
||||
const std::string kTestShader3 = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
@@ -1076,6 +887,204 @@ TEST(FuzzerShrinkerTest, Miscellaneous3) {
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
// Abstract class exposing an interestingness function as a virtual method.
|
||||
class InterestingnessTest {
|
||||
public:
|
||||
virtual ~InterestingnessTest() = default;
|
||||
|
||||
// Abstract method that subclasses should implement for specific notions of
|
||||
// interestingness. Its signature matches Shrinker::InterestingnessFunction.
|
||||
// Argument |binary| is the SPIR-V binary to be checked; |counter| is used for
|
||||
// debugging purposes.
|
||||
virtual bool Interesting(const std::vector<uint32_t>& binary,
|
||||
uint32_t counter) = 0;
|
||||
|
||||
// Yields the Interesting instance method wrapped in a function object.
|
||||
Shrinker::InterestingnessFunction AsFunction() {
|
||||
return std::bind(&InterestingnessTest::Interesting, this,
|
||||
std::placeholders::_1, std::placeholders::_2);
|
||||
}
|
||||
};
|
||||
|
||||
// A test that says all binaries are interesting.
|
||||
class AlwaysInteresting : public InterestingnessTest {
|
||||
public:
|
||||
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// A test that says a binary is interesting first time round, and uninteresting
|
||||
// thereafter.
|
||||
class OnlyInterestingFirstTime : public InterestingnessTest {
|
||||
public:
|
||||
explicit OnlyInterestingFirstTime() : first_time_(true) {}
|
||||
|
||||
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
|
||||
if (first_time_) {
|
||||
first_time_ = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool first_time_;
|
||||
};
|
||||
|
||||
// A test that says a binary is interesting first time round, after which
|
||||
// interestingness ping pongs between false and true.
|
||||
class PingPong : public InterestingnessTest {
|
||||
public:
|
||||
explicit PingPong() : interesting_(false) {}
|
||||
|
||||
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
|
||||
interesting_ = !interesting_;
|
||||
return interesting_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool interesting_;
|
||||
};
|
||||
|
||||
// A test that says a binary is interesting first time round, thereafter
|
||||
// decides at random whether it is interesting. This allows the logic of the
|
||||
// shrinker to be exercised quite a bit.
|
||||
class InterestingThenRandom : public InterestingnessTest {
|
||||
public:
|
||||
InterestingThenRandom(const PseudoRandomGenerator& random_generator)
|
||||
: first_time_(true), random_generator_(random_generator) {}
|
||||
|
||||
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
|
||||
if (first_time_) {
|
||||
first_time_ = false;
|
||||
return true;
|
||||
}
|
||||
return random_generator_.RandomBool();
|
||||
}
|
||||
|
||||
private:
|
||||
bool first_time_;
|
||||
PseudoRandomGenerator random_generator_;
|
||||
};
|
||||
|
||||
// |binary_in| and |initial_facts| are a SPIR-V binary and sequence of facts to
|
||||
// which |transformation_sequence_in| can be applied. Shrinking of
|
||||
// |transformation_sequence_in| gets performed with respect to
|
||||
// |interestingness_function|. If |expected_binary_out| is non-empty, it must
|
||||
// match the binary obtained by applying the final shrunk set of
|
||||
// transformations, in which case the number of such transformations should
|
||||
// equal |expected_transformations_out_size|.
|
||||
//
|
||||
// The |step_limit| parameter restricts the number of steps that the shrinker
|
||||
// will try; it can be set to something small for a faster (but less thorough)
|
||||
// test.
|
||||
void RunAndCheckShrinker(
|
||||
const spv_target_env& target_env, const std::vector<uint32_t>& binary_in,
|
||||
const protobufs::FactSequence& initial_facts,
|
||||
const protobufs::TransformationSequence& transformation_sequence_in,
|
||||
const Shrinker::InterestingnessFunction& interestingness_function,
|
||||
const std::vector<uint32_t>& expected_binary_out,
|
||||
uint32_t expected_transformations_out_size, uint32_t step_limit) {
|
||||
// Run the shrinker.
|
||||
Shrinker shrinker(target_env, step_limit, false);
|
||||
shrinker.SetMessageConsumer(kSilentConsumer);
|
||||
|
||||
std::vector<uint32_t> binary_out;
|
||||
protobufs::TransformationSequence transformations_out;
|
||||
Shrinker::ShrinkerResultStatus shrinker_result_status =
|
||||
shrinker.Run(binary_in, initial_facts, transformation_sequence_in,
|
||||
interestingness_function, &binary_out, &transformations_out);
|
||||
ASSERT_TRUE(Shrinker::ShrinkerResultStatus::kComplete ==
|
||||
shrinker_result_status ||
|
||||
Shrinker::ShrinkerResultStatus::kStepLimitReached ==
|
||||
shrinker_result_status);
|
||||
|
||||
// If a non-empty expected binary was provided, check that it matches the
|
||||
// result of shrinking and that the expected number of transformations remain.
|
||||
if (!expected_binary_out.empty()) {
|
||||
ASSERT_EQ(expected_binary_out, binary_out);
|
||||
ASSERT_EQ(expected_transformations_out_size,
|
||||
static_cast<uint32_t>(transformations_out.transformation_size()));
|
||||
}
|
||||
}
|
||||
|
||||
// Assembles the given |shader| text, and then:
|
||||
// - Runs the fuzzer with |seed| to yield a set of transformations
|
||||
// - Shrinks the transformation with various interestingness functions,
|
||||
// asserting some properties about the result each time
|
||||
void RunFuzzerAndShrinker(const std::string& shader,
|
||||
const protobufs::FactSequence& initial_facts,
|
||||
uint32_t seed) {
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
||||
|
||||
std::vector<uint32_t> binary_in;
|
||||
SpirvTools t(env);
|
||||
t.SetMessageConsumer(kConsoleMessageConsumer);
|
||||
ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
|
||||
ASSERT_TRUE(t.Validate(binary_in));
|
||||
|
||||
std::vector<fuzzerutil::ModuleSupplier> donor_suppliers;
|
||||
for (auto donor : {&kTestShader1, &kTestShader2, &kTestShader3}) {
|
||||
donor_suppliers.emplace_back([donor]() {
|
||||
return BuildModule(env, kConsoleMessageConsumer, *donor,
|
||||
kFuzzAssembleOption);
|
||||
});
|
||||
}
|
||||
|
||||
// Run the fuzzer and check that it successfully yields a valid binary.
|
||||
std::vector<uint32_t> fuzzer_binary_out;
|
||||
protobufs::TransformationSequence fuzzer_transformation_sequence_out;
|
||||
Fuzzer fuzzer(env, seed, true);
|
||||
fuzzer.SetMessageConsumer(kSilentConsumer);
|
||||
auto fuzzer_result_status =
|
||||
fuzzer.Run(binary_in, initial_facts, donor_suppliers, &fuzzer_binary_out,
|
||||
&fuzzer_transformation_sequence_out);
|
||||
ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
|
||||
ASSERT_TRUE(t.Validate(fuzzer_binary_out));
|
||||
|
||||
const uint32_t kReasonableStepLimit = 50;
|
||||
const uint32_t kSmallStepLimit = 20;
|
||||
|
||||
// With the AlwaysInteresting test, we should quickly shrink to the original
|
||||
// binary with no transformations remaining.
|
||||
RunAndCheckShrinker(
|
||||
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
AlwaysInteresting().AsFunction(), binary_in, 0, kReasonableStepLimit);
|
||||
|
||||
// With the OnlyInterestingFirstTime test, no shrinking should be achieved.
|
||||
RunAndCheckShrinker(
|
||||
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out,
|
||||
static_cast<uint32_t>(
|
||||
fuzzer_transformation_sequence_out.transformation_size()),
|
||||
kReasonableStepLimit);
|
||||
|
||||
// The PingPong test is unpredictable; passing an empty expected binary
|
||||
// means that we don't check anything beyond that shrinking completes
|
||||
// successfully.
|
||||
RunAndCheckShrinker(env, binary_in, initial_facts,
|
||||
fuzzer_transformation_sequence_out,
|
||||
PingPong().AsFunction(), {}, 0, kSmallStepLimit);
|
||||
|
||||
// The InterestingThenRandom test is unpredictable; passing an empty
|
||||
// expected binary means that we do not check anything about shrinking
|
||||
// results.
|
||||
RunAndCheckShrinker(
|
||||
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
|
||||
InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0,
|
||||
kSmallStepLimit);
|
||||
}
|
||||
|
||||
TEST(FuzzerShrinkerTest, Miscellaneous1) {
|
||||
RunFuzzerAndShrinker(kTestShader1, protobufs::FactSequence(), 2);
|
||||
}
|
||||
|
||||
TEST(FuzzerShrinkerTest, Miscellaneous2) {
|
||||
RunFuzzerAndShrinker(kTestShader2, protobufs::FactSequence(), 19);
|
||||
}
|
||||
|
||||
TEST(FuzzerShrinkerTest, Miscellaneous3) {
|
||||
// Add the facts "resolution.x == 250" and "resolution.y == 100".
|
||||
protobufs::FactSequence facts;
|
||||
{
|
||||
@@ -1099,7 +1108,7 @@ TEST(FuzzerShrinkerTest, Miscellaneous3) {
|
||||
|
||||
// Do 2 fuzzer runs, starting from an initial seed of 194 (seed value chosen
|
||||
// arbitrarily).
|
||||
RunFuzzerAndShrinker(shader, facts, 194);
|
||||
RunFuzzerAndShrinker(kTestShader3, facts, 194);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
+62
-7
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "source/fuzz/force_render_red.h"
|
||||
#include "source/fuzz/fuzzer.h"
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
|
||||
#include "source/fuzz/replayer.h"
|
||||
#include "source/fuzz/shrinker.h"
|
||||
@@ -75,7 +76,8 @@ void PrintUsage(const char* program) {
|
||||
printf(
|
||||
R"(%s - Fuzzes an equivalent SPIR-V binary based on a given binary.
|
||||
|
||||
USAGE: %s [options] <input.spv> -o <output.spv>
|
||||
USAGE: %s [options] <input.spv> -o <output.spv> \
|
||||
--donors=<donors.txt>
|
||||
USAGE: %s [options] <input.spv> -o <output.spv> \
|
||||
--shrink=<input.transformations> -- <interestingness_test> [args...]
|
||||
|
||||
@@ -100,6 +102,11 @@ Options (in lexicographical order):
|
||||
|
||||
-h, --help
|
||||
Print this help.
|
||||
--donors=
|
||||
File specifying a series of donor files, one per line. Must be
|
||||
provided if the tool is invoked in fuzzing mode; incompatible
|
||||
with replay and shrink modes. The file should be empty if no
|
||||
donors are to be used.
|
||||
--force-render-red
|
||||
Transforms the input shader into a shader that writes red to the
|
||||
output buffer, and then captures the original shader as the body
|
||||
@@ -154,7 +161,7 @@ void FuzzDiagnostic(spv_message_level_t level, const char* /*source*/,
|
||||
}
|
||||
|
||||
FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
|
||||
std::string* out_binary_file,
|
||||
std::string* out_binary_file, std::string* donors_file,
|
||||
std::string* replay_transformations_file,
|
||||
std::vector<std::string>* interestingness_test,
|
||||
std::string* shrink_transformations_file,
|
||||
@@ -181,6 +188,9 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
|
||||
PrintUsage(argv[0]);
|
||||
return {FuzzActions::STOP, 1};
|
||||
}
|
||||
} else if (0 == strncmp(cur_arg, "--donors=", sizeof("--donors=") - 1)) {
|
||||
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
|
||||
*donors_file = std::string(split_flag.second);
|
||||
} else if (0 == strncmp(cur_arg, "--force-render-red",
|
||||
sizeof("--force-render-red") - 1)) {
|
||||
force_render_red = true;
|
||||
@@ -285,6 +295,18 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
|
||||
return {FuzzActions::STOP, 1};
|
||||
}
|
||||
|
||||
if (!replay_transformations_file->empty() ||
|
||||
!shrink_transformations_file->empty()) {
|
||||
// Donors should not be provided when replaying or shrinking: they only make
|
||||
// sense during fuzzing.
|
||||
if (!donors_file->empty()) {
|
||||
spvtools::Error(FuzzDiagnostic, nullptr, {},
|
||||
"The --donors argument is not compatible with --replay "
|
||||
"nor --shrink.");
|
||||
return {FuzzActions::STOP, 1};
|
||||
}
|
||||
}
|
||||
|
||||
if (!replay_transformations_file->empty()) {
|
||||
// A replay transformations file was given, thus the tool is being invoked
|
||||
// in replay mode.
|
||||
@@ -305,6 +327,12 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
|
||||
return {FuzzActions::SHRINK, 0};
|
||||
}
|
||||
|
||||
// The tool is being invoked in fuzz mode.
|
||||
if (donors_file->empty()) {
|
||||
spvtools::Error(FuzzDiagnostic, nullptr, {},
|
||||
"Fuzzing requires that the --donors option is used.");
|
||||
return {FuzzActions::STOP, 1};
|
||||
}
|
||||
return {FuzzActions::FUZZ, 0};
|
||||
}
|
||||
|
||||
@@ -408,18 +436,44 @@ bool Fuzz(const spv_target_env& target_env,
|
||||
spv_const_fuzzer_options fuzzer_options,
|
||||
const std::vector<uint32_t>& binary_in,
|
||||
const spvtools::fuzz::protobufs::FactSequence& initial_facts,
|
||||
std::vector<uint32_t>* binary_out,
|
||||
const std::string& donors, std::vector<uint32_t>* binary_out,
|
||||
spvtools::fuzz::protobufs::TransformationSequence*
|
||||
transformations_applied) {
|
||||
auto message_consumer = spvtools::utils::CLIMessageConsumer;
|
||||
|
||||
std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> donor_suppliers;
|
||||
|
||||
std::ifstream donors_file(donors);
|
||||
if (!donors_file) {
|
||||
spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error opening donors file");
|
||||
return false;
|
||||
}
|
||||
std::string donor_filename;
|
||||
while (std::getline(donors_file, donor_filename)) {
|
||||
donor_suppliers.emplace_back(
|
||||
[donor_filename, message_consumer,
|
||||
target_env]() -> std::unique_ptr<spvtools::opt::IRContext> {
|
||||
std::vector<uint32_t> donor_binary;
|
||||
if (!ReadFile<uint32_t>(donor_filename.c_str(), "rb",
|
||||
&donor_binary)) {
|
||||
return nullptr;
|
||||
}
|
||||
return spvtools::BuildModule(target_env, message_consumer,
|
||||
donor_binary.data(),
|
||||
donor_binary.size());
|
||||
});
|
||||
}
|
||||
|
||||
spvtools::fuzz::Fuzzer fuzzer(
|
||||
target_env,
|
||||
fuzzer_options->has_random_seed
|
||||
? fuzzer_options->random_seed
|
||||
: static_cast<uint32_t>(std::random_device()()),
|
||||
fuzzer_options->fuzzer_pass_validation_enabled);
|
||||
fuzzer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
|
||||
fuzzer.SetMessageConsumer(message_consumer);
|
||||
auto fuzz_result_status =
|
||||
fuzzer.Run(binary_in, initial_facts, binary_out, transformations_applied);
|
||||
fuzzer.Run(binary_in, initial_facts, donor_suppliers, binary_out,
|
||||
transformations_applied);
|
||||
if (fuzz_result_status !=
|
||||
spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) {
|
||||
spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer");
|
||||
@@ -452,6 +506,7 @@ const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
|
||||
int main(int argc, const char** argv) {
|
||||
std::string in_binary_file;
|
||||
std::string out_binary_file;
|
||||
std::string donors_file;
|
||||
std::string replay_transformations_file;
|
||||
std::vector<std::string> interestingness_test;
|
||||
std::string shrink_transformations_file;
|
||||
@@ -460,7 +515,7 @@ int main(int argc, const char** argv) {
|
||||
spvtools::FuzzerOptions fuzzer_options;
|
||||
|
||||
FuzzStatus status = ParseFlags(
|
||||
argc, argv, &in_binary_file, &out_binary_file,
|
||||
argc, argv, &in_binary_file, &out_binary_file, &donors_file,
|
||||
&replay_transformations_file, &interestingness_test,
|
||||
&shrink_transformations_file, &shrink_temp_file_prefix, &fuzzer_options);
|
||||
|
||||
@@ -507,7 +562,7 @@ int main(int argc, const char** argv) {
|
||||
break;
|
||||
case FuzzActions::FUZZ:
|
||||
if (!Fuzz(target_env, fuzzer_options, binary_in, initial_facts,
|
||||
&binary_out, &transformations_applied)) {
|
||||
donors_file, &binary_out, &transformations_applied)) {
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user