spirv-fuzz: Fuzzer pass to interchange zero-like constants (#3524)

This fuzzer pass:

For each zero-like constant, either finds the existing definition of
the corresponding toggled one (OpConstantNull becomes zero-valued
scalar OpConstant or vice versa) or creates a new one if it doesn't
exist and records that the two are synonyms

For each use of these constants, probabilistically decides whether to
change it with the corresponding toggled constant id (as described in
#3486 )

Only uses inside blocks of instructions are considered and not, for
example, in instructions declaring other constants.
This commit is contained in:
Stefano Milizia 2020-07-15 11:58:29 +00:00 committed by GitHub
parent 3e7238c68d
commit f12c40f5a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 243 additions and 4 deletions

View File

@ -64,6 +64,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_copy_objects.h
fuzzer_pass_donate_modules.h
fuzzer_pass_invert_comparison_operators.h
fuzzer_pass_interchange_zero_like_constants.h
fuzzer_pass_merge_blocks.h
fuzzer_pass_obfuscate_constants.h
fuzzer_pass_outline_functions.h
@ -183,6 +184,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_copy_objects.cpp
fuzzer_pass_donate_modules.cpp
fuzzer_pass_invert_comparison_operators.cpp
fuzzer_pass_interchange_zero_like_constants.cpp
fuzzer_pass_merge_blocks.cpp
fuzzer_pass_obfuscate_constants.cpp
fuzzer_pass_outline_functions.cpp

View File

@ -18,7 +18,6 @@
#include <memory>
#include <sstream>
#include "fuzzer_pass_adjust_memory_operands_masks.h"
#include "source/fuzz/fact_manager.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_pass_add_access_chains.h"
@ -41,11 +40,13 @@
#include "source/fuzz/fuzzer_pass_adjust_branch_weights.h"
#include "source/fuzz/fuzzer_pass_adjust_function_controls.h"
#include "source/fuzz/fuzzer_pass_adjust_loop_controls.h"
#include "source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h"
#include "source/fuzz/fuzzer_pass_adjust_selection_controls.h"
#include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
#include "source/fuzz/fuzzer_pass_construct_composites.h"
#include "source/fuzz/fuzzer_pass_copy_objects.h"
#include "source/fuzz/fuzzer_pass_donate_modules.h"
#include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h"
#include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
#include "source/fuzz/fuzzer_pass_merge_blocks.h"
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
@ -330,6 +331,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassAddNoContractionDecorations>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassInterchangeZeroLikeConstants>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassPermutePhiOperands>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);

View File

@ -64,6 +64,8 @@ const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfDonatingAdditionalModule = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
{50, 95};
const std::pair<uint32_t, uint32_t> kChanceOfInterchangingZeroLikeConstants = {
10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfInvertingComparisonOperators = {
20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfMakingDonorLivesafe = {40, 60};
@ -181,6 +183,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
chance_of_going_deeper_when_making_access_chain_ =
ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
chance_of_interchanging_zero_like_constants_ =
ChooseBetweenMinAndMax(kChanceOfInterchangingZeroLikeConstants);
chance_of_inverting_comparison_operators_ =
ChooseBetweenMinAndMax(kChanceOfInvertingComparisonOperators);
chance_of_making_donor_livesafe_ =

View File

@ -183,6 +183,9 @@ class FuzzerContext {
uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
return chance_of_going_deeper_when_making_access_chain_;
}
uint32_t GetChanceOfInterchangingZeroLikeConstants() {
return chance_of_interchanging_zero_like_constants_;
}
uint32_t GetChanceOfInvertingComparisonOperators() {
return chance_of_inverting_comparison_operators_;
}
@ -325,6 +328,7 @@ class FuzzerContext {
uint32_t chance_of_copying_object_;
uint32_t chance_of_donating_additional_module_;
uint32_t chance_of_going_deeper_when_making_access_chain_;
uint32_t chance_of_interchanging_zero_like_constants_;
uint32_t chance_of_inverting_comparison_operators_;
uint32_t chance_of_making_donor_livesafe_;
uint32_t chance_of_merging_blocks_;

View File

@ -20,6 +20,7 @@
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_add_constant_boolean.h"
#include "source/fuzz/transformation_add_constant_composite.h"
#include "source/fuzz/transformation_add_constant_null.h"
#include "source/fuzz/transformation_add_constant_scalar.h"
#include "source/fuzz/transformation_add_global_undef.h"
#include "source/fuzz/transformation_add_type_boolean.h"
@ -373,6 +374,27 @@ uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) {
return result;
}
uint32_t FuzzerPass::FindOrCreateNullConstant(uint32_t type_id) {
// Find existing declaration
opt::analysis::NullConstant null_constant(
GetIRContext()->get_type_mgr()->GetType(type_id));
auto existing_constant =
GetIRContext()->get_constant_mgr()->FindConstant(&null_constant);
// Return if found
if (existing_constant) {
return GetIRContext()
->get_constant_mgr()
->GetDefiningInstruction(existing_constant)
->result_id();
}
// Create new if not found
auto result = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddConstantNull(result, type_id));
return result;
}
std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
FuzzerPass::GetAvailableBasicTypesAndPointers(
SpvStorageClass storage_class) const {

View File

@ -192,6 +192,12 @@ class FuzzerPass {
// If no such instruction exists, a transformation is applied to add it.
uint32_t FindOrCreateGlobalUndef(uint32_t type_id);
// Returns the id of an OpNullConstant instruction of type |type_id|. If
// that instruction doesn't exist, it is added through a transformation.
// |type_id| must be a valid result id of an OpType* instruction that exists
// in the module.
uint32_t FindOrCreateNullConstant(uint32_t type_id);
// Define a *basic type* to be an integer, boolean or floating-point type,
// or a matrix, vector, struct or fixed-size array built from basic types. In
// particular, a basic type cannot contain an opaque type (such as an image),

View File

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

View File

@ -0,0 +1,124 @@
// Copyright (c) 2020 Stefano Milizia
// Copyright (c) 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/id_use_descriptor.h"
#include "source/fuzz/transformation_record_synonymous_constants.h"
#include "source/fuzz/transformation_replace_id_with_synonym.h"
namespace spvtools {
namespace fuzz {
FuzzerPassInterchangeZeroLikeConstants::FuzzerPassInterchangeZeroLikeConstants(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassInterchangeZeroLikeConstants::
~FuzzerPassInterchangeZeroLikeConstants() = default;
uint32_t FuzzerPassInterchangeZeroLikeConstants::FindOrCreateToggledConstant(
opt::Instruction* declaration) {
auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
declaration->result_id());
// This pass only toggles zero-like constants
if (!constant->IsZero()) {
return 0;
}
if (constant->AsScalarConstant()) {
return FindOrCreateNullConstant(declaration->type_id());
} else if (constant->AsNullConstant()) {
// Add declaration of equivalent scalar constant
auto kind = constant->type()->kind();
if (kind == opt::analysis::Type::kBool ||
kind == opt::analysis::Type::kInteger ||
kind == opt::analysis::Type::kFloat) {
return FindOrCreateZeroConstant(declaration->type_id());
}
}
return 0;
}
void FuzzerPassInterchangeZeroLikeConstants::MaybeAddUseToReplace(
opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
uses_to_replace) {
// Only consider this use if it is in a block
if (!GetIRContext()->get_instr_block(use_inst)) {
return;
}
// Get the index of the operand restricted to input operands.
uint32_t in_operand_index =
fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
auto id_use_descriptor =
MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, in_operand_index);
uses_to_replace->emplace_back(
std::make_pair(id_use_descriptor, replacement_id));
}
void FuzzerPassInterchangeZeroLikeConstants::Apply() {
// Make vector keeping track of all the uses we want to replace.
// This is a vector of pairs, where the first element is an id use descriptor
// identifying the use of a constant id and the second is the id that should
// be used to replace it.
std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>> uses_to_replace;
for (auto constant : GetIRContext()->GetConstants()) {
uint32_t constant_id = constant->result_id();
uint32_t toggled_id = FindOrCreateToggledConstant(constant);
if (!toggled_id) {
// Not a zero-like constant
continue;
}
// Record synonymous constants
ApplyTransformation(
TransformationRecordSynonymousConstants(constant_id, toggled_id));
// Find all the uses of the constant and, for each, probabilistically
// decide whether to replace it.
GetIRContext()->get_def_use_mgr()->ForEachUse(
constant_id,
[this, toggled_id, &uses_to_replace](opt::Instruction* use_inst,
uint32_t use_index) -> void {
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfInterchangingZeroLikeConstants())) {
MaybeAddUseToReplace(use_inst, use_index, toggled_id,
&uses_to_replace);
}
});
}
// Replace the ids
for (auto use_to_replace : uses_to_replace) {
auto transformation = TransformationReplaceIdWithSynonym(
use_to_replace.first, use_to_replace.second);
if (transformation.IsApplicable(GetIRContext(),
*GetTransformationContext())) {
transformation.Apply(GetIRContext(), GetTransformationContext());
*GetTransformations()->add_transformation() = transformation.ToMessage();
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,63 @@
// Copyright (c) 2020 Stefano Milizia
// Copyright (c) 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_
#define SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A pass that:
// - Finds all the zero-like constant definitions in the module and adds the
// definitions of the corresponding synonym, recording the fact that they
// are synonymous. If the synonym is already in the module, it does not
// add a new one.
// - For each use of a zero-like constant, decides whether to change it to the
// id of the toggled constant.
class FuzzerPassInterchangeZeroLikeConstants : public FuzzerPass {
public:
FuzzerPassInterchangeZeroLikeConstants(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassInterchangeZeroLikeConstants() override;
void Apply() override;
private:
// Given the declaration of a zero-like constant, it finds or creates the
// corresponding toggled constant (a scalar constant of value 0 becomes a
// null constant of the same type and vice versa).
// Returns the id of the toggled instruction if the constant is zero-like,
// 0 otherwise.
uint32_t FindOrCreateToggledConstant(opt::Instruction* declaration);
// Given an id use (described by an instruction and an index) and an id with
// which the original one should be replaced, adds a pair (with the elements
// being the corresponding id use descriptor and the replacement id) to
// |uses_to_replace| if the use is in an instruction block, otherwise does
// nothing.
void MaybeAddUseToReplace(
opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
uses_to_replace);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_

View File

@ -552,6 +552,12 @@ uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id,
return 0;
}
uint32_t InOperandIndexFromOperandIndex(const opt::Instruction& inst,
uint32_t absolute_index) {
// Subtract the number of non-input operands from the index
return absolute_index - inst.NumOperands() + inst.NumInOperands();
}
bool IsNullConstantSupported(const opt::analysis::Type& type) {
return type.AsBool() || type.AsInteger() || type.AsFloat() ||
type.AsMatrix() || type.AsVector() || type.AsArray() ||

View File

@ -213,6 +213,11 @@ SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context,
uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id,
SpvStorageClass storage_class);
// Given an instruction |inst| and an operand absolute index |absolute_index|,
// returns the index of the operand restricted to the input operands.
uint32_t InOperandIndexFromOperandIndex(const opt::Instruction& inst,
uint32_t absolute_index);
// Returns true if and only if |type| is one of the types for which it is legal
// to have an OpConstantNull value.
bool IsNullConstantSupported(const opt::analysis::Type& type);