spirv-fuzz: Fuzzer pass that adds access chains (#3182)
This change adds a fuzzer pass that sprinkles access chain instructions into a module at random. This allows other passes to have a richer set of pointers available to them, in particular the passes that add loads and stores.
This commit is contained in:
Родитель
77fb303e58
Коммит
6c218ec60b
|
@ -37,6 +37,7 @@ if(SPIRV_BUILD_FUZZER)
|
|||
fuzzer.h
|
||||
fuzzer_context.h
|
||||
fuzzer_pass.h
|
||||
fuzzer_pass_add_access_chains.h
|
||||
fuzzer_pass_add_composite_types.h
|
||||
fuzzer_pass_add_dead_blocks.h
|
||||
fuzzer_pass_add_dead_breaks.h
|
||||
|
@ -71,6 +72,7 @@ if(SPIRV_BUILD_FUZZER)
|
|||
replayer.h
|
||||
shrinker.h
|
||||
transformation.h
|
||||
transformation_access_chain.h
|
||||
transformation_add_constant_boolean.h
|
||||
transformation_add_constant_composite.h
|
||||
transformation_add_constant_scalar.h
|
||||
|
@ -119,6 +121,7 @@ if(SPIRV_BUILD_FUZZER)
|
|||
fuzzer.cpp
|
||||
fuzzer_context.cpp
|
||||
fuzzer_pass.cpp
|
||||
fuzzer_pass_add_access_chains.cpp
|
||||
fuzzer_pass_add_composite_types.cpp
|
||||
fuzzer_pass_add_dead_blocks.cpp
|
||||
fuzzer_pass_add_dead_breaks.cpp
|
||||
|
@ -152,6 +155,7 @@ if(SPIRV_BUILD_FUZZER)
|
|||
replayer.cpp
|
||||
shrinker.cpp
|
||||
transformation.cpp
|
||||
transformation_access_chain.cpp
|
||||
transformation_add_constant_boolean.cpp
|
||||
transformation_add_constant_composite.cpp
|
||||
transformation_add_constant_scalar.cpp
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#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"
|
||||
#include "source/fuzz/fuzzer_pass_add_composite_types.h"
|
||||
#include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
|
||||
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
|
||||
|
@ -184,6 +185,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
|
|||
// Apply some semantics-preserving passes.
|
||||
std::vector<std::unique_ptr<FuzzerPass>> passes;
|
||||
while (passes.empty()) {
|
||||
MaybeAddPass<FuzzerPassAddAccessChains>(&passes, ir_context.get(),
|
||||
&fact_manager, &fuzzer_context,
|
||||
transformation_sequence_out);
|
||||
MaybeAddPass<FuzzerPassAddCompositeTypes>(&passes, ir_context.get(),
|
||||
&fact_manager, &fuzzer_context,
|
||||
transformation_sequence_out);
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace {
|
|||
// Default <minimum, maximum> pairs of probabilities for applying various
|
||||
// transformations. All values are percentages. Keep them in alphabetical order.
|
||||
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfAddingAccessChain = {5, 50};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfAddingAnotherStructField = {20,
|
||||
90};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfAddingArrayOrStructType = {20, 90};
|
||||
|
@ -50,6 +51,8 @@ const std::pair<uint32_t, uint32_t> kChanceOfChoosingStructTypeVsArrayType = {
|
|||
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> kChanceOfGoingDeeperWhenMakingAccessChain =
|
||||
{50, 95};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfMakingDonorLivesafe = {40, 60};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
|
||||
|
@ -81,8 +84,14 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
|
|||
uint32_t min_fresh_id)
|
||||
: random_generator_(random_generator),
|
||||
next_fresh_id_(min_fresh_id),
|
||||
max_loop_control_partial_count_(kDefaultMaxLoopControlPartialCount),
|
||||
max_loop_control_peel_count_(kDefaultMaxLoopControlPeelCount),
|
||||
max_loop_limit_(kDefaultMaxLoopLimit),
|
||||
max_new_array_size_limit_(kDefaultMaxNewArraySizeLimit),
|
||||
go_deeper_in_constant_obfuscation_(
|
||||
kDefaultGoDeeperInConstantObfuscation) {
|
||||
chance_of_adding_access_chain_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfAddingAccessChain);
|
||||
chance_of_adding_another_struct_field_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField);
|
||||
chance_of_adding_array_or_struct_type_ =
|
||||
|
@ -122,6 +131,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
|
|||
chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
|
||||
chance_of_donating_additional_module_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
|
||||
chance_of_going_deeper_when_making_access_chain_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
|
||||
chance_of_making_donor_livesafe_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe);
|
||||
chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks);
|
||||
|
@ -134,10 +145,6 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
|
|||
chance_of_replacing_id_with_synonym_ =
|
||||
ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
|
||||
chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
|
||||
max_loop_control_partial_count_ = kDefaultMaxLoopControlPartialCount;
|
||||
max_loop_control_peel_count_ = kDefaultMaxLoopControlPeelCount;
|
||||
max_loop_limit_ = kDefaultMaxLoopLimit;
|
||||
max_new_array_size_limit_ = kDefaultMaxNewArraySizeLimit;
|
||||
}
|
||||
|
||||
FuzzerContext::~FuzzerContext() = default;
|
||||
|
|
|
@ -68,6 +68,9 @@ class FuzzerContext {
|
|||
|
||||
// Probabilities associated with applying various transformations.
|
||||
// Keep them in alphabetical order.
|
||||
uint32_t GetChanceOfAddingAccessChain() {
|
||||
return chance_of_adding_access_chain_;
|
||||
}
|
||||
uint32_t GetChanceOfAddingAnotherStructField() {
|
||||
return chance_of_adding_another_struct_field_;
|
||||
}
|
||||
|
@ -119,6 +122,9 @@ class FuzzerContext {
|
|||
uint32_t GetChanceOfDonatingAdditionalModule() {
|
||||
return chance_of_donating_additional_module_;
|
||||
}
|
||||
uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
|
||||
return chance_of_going_deeper_when_making_access_chain_;
|
||||
}
|
||||
uint32_t ChanceOfMakingDonorLivesafe() {
|
||||
return chance_of_making_donor_livesafe_;
|
||||
}
|
||||
|
@ -148,8 +154,11 @@ class FuzzerContext {
|
|||
return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1;
|
||||
}
|
||||
|
||||
// Functions to control how deeply to recurse.
|
||||
// Keep them in alphabetical order.
|
||||
// Other functions to control transformations. Keep them in alphabetical
|
||||
// order.
|
||||
uint32_t GetRandomIndexForAccessChain(uint32_t composite_size_bound) {
|
||||
return random_generator_->RandomUint32(composite_size_bound);
|
||||
}
|
||||
bool GoDeeperInConstantObfuscation(uint32_t depth) {
|
||||
return go_deeper_in_constant_obfuscation_(depth, random_generator_);
|
||||
}
|
||||
|
@ -162,6 +171,7 @@ class FuzzerContext {
|
|||
|
||||
// Probabilities associated with applying various transformations.
|
||||
// Keep them in alphabetical order.
|
||||
uint32_t chance_of_adding_access_chain_;
|
||||
uint32_t chance_of_adding_another_struct_field_;
|
||||
uint32_t chance_of_adding_array_or_struct_type_;
|
||||
uint32_t chance_of_adding_dead_block_;
|
||||
|
@ -183,6 +193,7 @@ class FuzzerContext {
|
|||
uint32_t chance_of_constructing_composite_;
|
||||
uint32_t chance_of_copying_object_;
|
||||
uint32_t chance_of_donating_additional_module_;
|
||||
uint32_t chance_of_going_deeper_when_making_access_chain_;
|
||||
uint32_t chance_of_making_donor_livesafe_;
|
||||
uint32_t chance_of_merging_blocks_;
|
||||
uint32_t chance_of_moving_block_down_;
|
||||
|
|
|
@ -219,21 +219,27 @@ uint32_t FuzzerPass::FindOrCreateMatrixType(uint32_t column_count,
|
|||
return result;
|
||||
}
|
||||
|
||||
uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType(
|
||||
bool is_signed, SpvStorageClass storage_class) {
|
||||
auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed);
|
||||
opt::analysis::Pointer pointer_type(
|
||||
GetIRContext()->get_type_mgr()->GetType(uint32_type_id), storage_class);
|
||||
auto existing_id = GetIRContext()->get_type_mgr()->GetId(&pointer_type);
|
||||
uint32_t FuzzerPass::FindOrCreatePointerType(uint32_t base_type_id,
|
||||
SpvStorageClass storage_class) {
|
||||
// We do not use the type manager here, due to problems related to isomorphic
|
||||
// but distinct structs not being regarded as different.
|
||||
auto existing_id = fuzzerutil::MaybeGetPointerType(
|
||||
GetIRContext(), base_type_id, storage_class);
|
||||
if (existing_id) {
|
||||
return existing_id;
|
||||
}
|
||||
auto result = GetFuzzerContext()->GetFreshId();
|
||||
ApplyTransformation(
|
||||
TransformationAddTypePointer(result, storage_class, uint32_type_id));
|
||||
TransformationAddTypePointer(result, storage_class, base_type_id));
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType(
|
||||
bool is_signed, SpvStorageClass storage_class) {
|
||||
return FindOrCreatePointerType(FindOrCreate32BitIntegerType(is_signed),
|
||||
storage_class);
|
||||
}
|
||||
|
||||
uint32_t FuzzerPass::FindOrCreate32BitIntegerConstant(uint32_t word,
|
||||
bool is_signed) {
|
||||
auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed);
|
||||
|
|
|
@ -125,6 +125,12 @@ class FuzzerPass {
|
|||
// type itself do not exist, transformations are applied to add them.
|
||||
uint32_t FindOrCreateMatrixType(uint32_t column_count, uint32_t row_count);
|
||||
|
||||
// Returns the id of a pointer type with base type |base_type_id| (which must
|
||||
// already exist) and storage class |storage_class|. A transformation is
|
||||
// applied to add the pointer if it does not already exist.
|
||||
uint32_t FindOrCreatePointerType(uint32_t base_type_id,
|
||||
SpvStorageClass storage_class);
|
||||
|
||||
// Returns the id of an OpTypePointer instruction, with a 32-bit integer base
|
||||
// type of signedness specified by |is_signed|. If the pointer type or
|
||||
// required integer base type do not exist, transformations are applied to add
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
// Copyright (c) 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "source/fuzz/fuzzer_pass_add_access_chains.h"
|
||||
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
#include "source/fuzz/transformation_access_chain.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
FuzzerPassAddAccessChains::FuzzerPassAddAccessChains(
|
||||
opt::IRContext* ir_context, FactManager* fact_manager,
|
||||
FuzzerContext* fuzzer_context,
|
||||
protobufs::TransformationSequence* transformations)
|
||||
: FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
|
||||
|
||||
FuzzerPassAddAccessChains::~FuzzerPassAddAccessChains() = default;
|
||||
|
||||
void FuzzerPassAddAccessChains::Apply() {
|
||||
MaybeAddTransformationBeforeEachInstruction(
|
||||
[this](opt::Function* function, opt::BasicBlock* block,
|
||||
opt::BasicBlock::iterator inst_it,
|
||||
const protobufs::InstructionDescriptor& instruction_descriptor)
|
||||
-> void {
|
||||
assert(inst_it->opcode() ==
|
||||
instruction_descriptor.target_instruction_opcode() &&
|
||||
"The opcode of the instruction we might insert before must be "
|
||||
"the same as the opcode in the descriptor for the instruction");
|
||||
|
||||
// Check whether it is legitimate to insert an access chain
|
||||
// instruction before this instruction.
|
||||
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpAccessChain,
|
||||
inst_it)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Randomly decide whether to try inserting a load here.
|
||||
if (!GetFuzzerContext()->ChoosePercentage(
|
||||
GetFuzzerContext()->GetChanceOfAddingAccessChain())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all of the pointers that are currently in scope, excluding
|
||||
// explicitly null and undefined pointers.
|
||||
std::vector<opt::Instruction*> relevant_pointer_instructions =
|
||||
FindAvailableInstructions(
|
||||
function, block, inst_it,
|
||||
[](opt::IRContext* context,
|
||||
opt::Instruction* instruction) -> bool {
|
||||
if (!instruction->result_id() || !instruction->type_id()) {
|
||||
// A pointer needs both a result and type id.
|
||||
return false;
|
||||
}
|
||||
switch (instruction->opcode()) {
|
||||
case SpvOpConstantNull:
|
||||
case SpvOpUndef:
|
||||
// Do not allow making an access chain from a null or
|
||||
// undefined pointer. (We can eliminate these cases
|
||||
// before actually checking that the instruction is a
|
||||
// pointer.)
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// If the instruction has pointer type, we can legitimately
|
||||
// make an access chain from it.
|
||||
return context->get_def_use_mgr()
|
||||
->GetDef(instruction->type_id())
|
||||
->opcode() == SpvOpTypePointer;
|
||||
});
|
||||
|
||||
// At this point, |relevant_instructions| contains all the pointers
|
||||
// we might think of making an access chain from.
|
||||
if (relevant_pointer_instructions.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto chosen_pointer =
|
||||
relevant_pointer_instructions[GetFuzzerContext()->RandomIndex(
|
||||
relevant_pointer_instructions)];
|
||||
std::vector<uint32_t> index_ids;
|
||||
auto pointer_type = GetIRContext()->get_def_use_mgr()->GetDef(
|
||||
chosen_pointer->type_id());
|
||||
uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1);
|
||||
while (true) {
|
||||
auto subobject_type =
|
||||
GetIRContext()->get_def_use_mgr()->GetDef(subobject_type_id);
|
||||
if (!spvOpcodeIsComposite(subobject_type->opcode())) {
|
||||
break;
|
||||
}
|
||||
if (!GetFuzzerContext()->ChoosePercentage(
|
||||
GetFuzzerContext()
|
||||
->GetChanceOfGoingDeeperWhenMakingAccessChain())) {
|
||||
break;
|
||||
}
|
||||
uint32_t bound;
|
||||
switch (subobject_type->opcode()) {
|
||||
case SpvOpTypeArray:
|
||||
bound = fuzzerutil::GetArraySize(*subobject_type, GetIRContext());
|
||||
break;
|
||||
case SpvOpTypeMatrix:
|
||||
case SpvOpTypeVector:
|
||||
bound = subobject_type->GetSingleWordInOperand(1);
|
||||
break;
|
||||
case SpvOpTypeStruct:
|
||||
bound = fuzzerutil::GetNumberOfStructMembers(*subobject_type);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Not a composite type opcode.");
|
||||
// Set the bound to a value in order to keep release compilers
|
||||
// happy.
|
||||
bound = 0;
|
||||
break;
|
||||
}
|
||||
if (bound == 0) {
|
||||
// It is possible for a composite type to legitimately have zero
|
||||
// sub-components, at least in the case of a struct, which
|
||||
// can have no fields.
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3179) We
|
||||
// could allow non-constant indices when looking up non-structs,
|
||||
// using clamping to ensure they are in-bounds.
|
||||
uint32_t index_value =
|
||||
GetFuzzerContext()->GetRandomIndexForAccessChain(bound);
|
||||
index_ids.push_back(FindOrCreate32BitIntegerConstant(
|
||||
index_value, GetFuzzerContext()->ChooseEven()));
|
||||
switch (subobject_type->opcode()) {
|
||||
case SpvOpTypeArray:
|
||||
case SpvOpTypeMatrix:
|
||||
case SpvOpTypeVector:
|
||||
subobject_type_id = subobject_type->GetSingleWordInOperand(0);
|
||||
break;
|
||||
case SpvOpTypeStruct:
|
||||
subobject_type_id =
|
||||
subobject_type->GetSingleWordInOperand(index_value);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Not a composite type opcode.");
|
||||
}
|
||||
}
|
||||
// The transformation we are about to create will only apply if a
|
||||
// pointer suitable for the access chain's result type exists, so we
|
||||
// create one if it does not.
|
||||
FindOrCreatePointerType(subobject_type_id,
|
||||
static_cast<SpvStorageClass>(
|
||||
pointer_type->GetSingleWordInOperand(0)));
|
||||
// Apply the transformation to add an access chain.
|
||||
ApplyTransformation(TransformationAccessChain(
|
||||
GetFuzzerContext()->GetFreshId(), chosen_pointer->result_id(),
|
||||
index_ids, instruction_descriptor));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_ACCESS_CHAINS_H_
|
||||
#define SOURCE_FUZZ_FUZZER_PASS_ADD_ACCESS_CHAINS_H_
|
||||
|
||||
#include "source/fuzz/fuzzer_pass.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
// Fuzzer pass that randomly adds access chains based on pointers available in
|
||||
// the module. Other passes can use these access chains, e.g. by loading from
|
||||
// them.
|
||||
class FuzzerPassAddAccessChains : public FuzzerPass {
|
||||
public:
|
||||
FuzzerPassAddAccessChains(opt::IRContext* ir_context,
|
||||
FactManager* fact_manager,
|
||||
FuzzerContext* fuzzer_context,
|
||||
protobufs::TransformationSequence* transformations);
|
||||
|
||||
~FuzzerPassAddAccessChains();
|
||||
|
||||
void Apply() override;
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_ACCESS_CHAINS_H_
|
|
@ -262,13 +262,11 @@ std::vector<uint32_t> RepeatedFieldToVector(
|
|||
return result;
|
||||
}
|
||||
|
||||
uint32_t WalkCompositeTypeIndices(
|
||||
opt::IRContext* context, uint32_t base_object_type_id,
|
||||
const google::protobuf::RepeatedField<google::protobuf::uint32>& indices) {
|
||||
uint32_t sub_object_type_id = base_object_type_id;
|
||||
for (auto index : indices) {
|
||||
uint32_t WalkOneCompositeTypeIndex(opt::IRContext* context,
|
||||
uint32_t base_object_type_id,
|
||||
uint32_t index) {
|
||||
auto should_be_composite_type =
|
||||
context->get_def_use_mgr()->GetDef(sub_object_type_id);
|
||||
context->get_def_use_mgr()->GetDef(base_object_type_id);
|
||||
assert(should_be_composite_type && "The type should exist.");
|
||||
switch (should_be_composite_type->opcode()) {
|
||||
case SpvOpTypeArray: {
|
||||
|
@ -276,9 +274,7 @@ uint32_t WalkCompositeTypeIndices(
|
|||
if (array_length == 0 || index >= array_length) {
|
||||
return 0;
|
||||
}
|
||||
sub_object_type_id =
|
||||
should_be_composite_type->GetSingleWordInOperand(0);
|
||||
break;
|
||||
return should_be_composite_type->GetSingleWordInOperand(0);
|
||||
}
|
||||
case SpvOpTypeMatrix:
|
||||
case SpvOpTypeVector: {
|
||||
|
@ -286,22 +282,30 @@ uint32_t WalkCompositeTypeIndices(
|
|||
if (index >= count) {
|
||||
return 0;
|
||||
}
|
||||
sub_object_type_id =
|
||||
should_be_composite_type->GetSingleWordInOperand(0);
|
||||
break;
|
||||
return should_be_composite_type->GetSingleWordInOperand(0);
|
||||
}
|
||||
case SpvOpTypeStruct: {
|
||||
if (index >= GetNumberOfStructMembers(*should_be_composite_type)) {
|
||||
return 0;
|
||||
}
|
||||
sub_object_type_id =
|
||||
should_be_composite_type->GetSingleWordInOperand(index);
|
||||
break;
|
||||
return should_be_composite_type->GetSingleWordInOperand(index);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t WalkCompositeTypeIndices(
|
||||
opt::IRContext* context, uint32_t base_object_type_id,
|
||||
const google::protobuf::RepeatedField<google::protobuf::uint32>& indices) {
|
||||
uint32_t sub_object_type_id = base_object_type_id;
|
||||
for (auto index : indices) {
|
||||
sub_object_type_id =
|
||||
WalkOneCompositeTypeIndex(context, sub_object_type_id, index);
|
||||
if (!sub_object_type_id) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return sub_object_type_id;
|
||||
}
|
||||
|
||||
|
@ -501,6 +505,23 @@ SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context,
|
|||
context->get_def_use_mgr()->GetDef(pointer_type_id));
|
||||
}
|
||||
|
||||
uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id,
|
||||
SpvStorageClass storage_class) {
|
||||
for (auto& inst : context->types_values()) {
|
||||
switch (inst.opcode()) {
|
||||
case SpvOpTypePointer:
|
||||
if (inst.GetSingleWordInOperand(0) == storage_class &&
|
||||
inst.GetSingleWordInOperand(1) == pointee_type_id) {
|
||||
return inst.result_id();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace fuzzerutil
|
||||
|
||||
} // namespace fuzz
|
||||
|
|
|
@ -98,6 +98,21 @@ bool IsCompositeType(const opt::analysis::Type* type);
|
|||
std::vector<uint32_t> RepeatedFieldToVector(
|
||||
const google::protobuf::RepeatedField<uint32_t>& repeated_field);
|
||||
|
||||
// Given a type id, |base_object_type_id|, returns 0 if the type is not a
|
||||
// composite type or if |index| is too large to be used as an index into the
|
||||
// composite. Otherwise returns the type id of the type associated with the
|
||||
// composite's index.
|
||||
//
|
||||
// Example: if |base_object_type_id| is 10, and we have:
|
||||
//
|
||||
// %10 = OpTypeStruct %3 %4 %5
|
||||
//
|
||||
// then 3 will be returned if |index| is 0, 5 if |index| is 2, and 0 if index
|
||||
// is 3 or larger.
|
||||
uint32_t WalkOneCompositeTypeIndex(opt::IRContext* context,
|
||||
uint32_t base_object_type_id,
|
||||
uint32_t index);
|
||||
|
||||
// Given a type id, |base_object_type_id|, checks that the given sequence of
|
||||
// |indices| is suitable for indexing into this type. Returns the id of the
|
||||
// type of the final sub-object reached via the indices if they are valid, and
|
||||
|
@ -181,6 +196,11 @@ SpvStorageClass GetStorageClassFromPointerType(
|
|||
SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context,
|
||||
uint32_t pointer_type_id);
|
||||
|
||||
// Returns the id of a pointer with pointee type |pointee_type_id| and storage
|
||||
// class |storage_class|, if it exists, and 0 otherwise.
|
||||
uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id,
|
||||
SpvStorageClass storage_class);
|
||||
|
||||
} // namespace fuzzerutil
|
||||
|
||||
} // namespace fuzz
|
||||
|
|
|
@ -341,12 +341,32 @@ message Transformation {
|
|||
TransformationLoad load = 36;
|
||||
TransformationStore store = 37;
|
||||
TransformationFunctionCall function_call = 38;
|
||||
TransformationAccessChain access_chain = 39;
|
||||
// Add additional option using the next available number.
|
||||
}
|
||||
}
|
||||
|
||||
// Keep transformation message types in alphabetical order:
|
||||
|
||||
message TransformationAccessChain {
|
||||
|
||||
// Adds an access chain instruction based on a given pointer and indices.
|
||||
|
||||
// Result id for the access chain
|
||||
uint32 fresh_id = 1;
|
||||
|
||||
// The pointer from which the access chain starts
|
||||
uint32 pointer_id = 2;
|
||||
|
||||
// Zero or more access chain indices
|
||||
repeated uint32 index_id = 3;
|
||||
|
||||
// A descriptor for an instruction in a block before which the new
|
||||
// OpAccessChain instruction should be inserted
|
||||
InstructionDescriptor instruction_to_insert_before = 4;
|
||||
|
||||
}
|
||||
|
||||
message TransformationAddConstantBoolean {
|
||||
|
||||
// Supports adding the constants true and false to a module, which may be
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <cassert>
|
||||
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
#include "source/fuzz/transformation_access_chain.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"
|
||||
|
@ -65,6 +66,8 @@ Transformation::~Transformation() = default;
|
|||
std::unique_ptr<Transformation> Transformation::FromMessage(
|
||||
const protobufs::Transformation& message) {
|
||||
switch (message.transformation_case()) {
|
||||
case protobufs::Transformation::TransformationCase::kAccessChain:
|
||||
return MakeUnique<TransformationAccessChain>(message.access_chain());
|
||||
case protobufs::Transformation::TransformationCase::kAddConstantBoolean:
|
||||
return MakeUnique<TransformationAddConstantBoolean>(
|
||||
message.add_constant_boolean());
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
// 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/transformation_access_chain.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
#include "source/fuzz/instruction_descriptor.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
TransformationAccessChain::TransformationAccessChain(
|
||||
const spvtools::fuzz::protobufs::TransformationAccessChain& message)
|
||||
: message_(message) {}
|
||||
|
||||
TransformationAccessChain::TransformationAccessChain(
|
||||
uint32_t fresh_id, uint32_t pointer_id,
|
||||
const std::vector<uint32_t>& index_id,
|
||||
const protobufs::InstructionDescriptor& instruction_to_insert_before) {
|
||||
message_.set_fresh_id(fresh_id);
|
||||
message_.set_pointer_id(pointer_id);
|
||||
for (auto id : index_id) {
|
||||
message_.add_index_id(id);
|
||||
}
|
||||
*message_.mutable_instruction_to_insert_before() =
|
||||
instruction_to_insert_before;
|
||||
}
|
||||
|
||||
bool TransformationAccessChain::IsApplicable(
|
||||
opt::IRContext* context,
|
||||
const spvtools::fuzz::FactManager& /*unused*/) const {
|
||||
// The result id must be fresh
|
||||
if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
|
||||
return false;
|
||||
}
|
||||
// The pointer id must exist and have a type.
|
||||
auto pointer = context->get_def_use_mgr()->GetDef(message_.pointer_id());
|
||||
if (!pointer || !pointer->type_id()) {
|
||||
return false;
|
||||
}
|
||||
// The type must indeed be a pointer
|
||||
auto pointer_type = context->get_def_use_mgr()->GetDef(pointer->type_id());
|
||||
if (pointer_type->opcode() != SpvOpTypePointer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The described instruction to insert before must exist and be a suitable
|
||||
// point where an OpAccessChain instruction could be inserted.
|
||||
auto instruction_to_insert_before =
|
||||
FindInstruction(message_.instruction_to_insert_before(), context);
|
||||
if (!instruction_to_insert_before) {
|
||||
return false;
|
||||
}
|
||||
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
|
||||
SpvOpAccessChain, instruction_to_insert_before)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not allow making an access chain from a null or undefined pointer, as
|
||||
// we do not want to allow accessing such pointers. This might be acceptable
|
||||
// in dead blocks, but we conservatively avoid it.
|
||||
switch (pointer->opcode()) {
|
||||
case SpvOpConstantNull:
|
||||
case SpvOpUndef:
|
||||
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3185): When
|
||||
// fuzzing for real we would like an 'assert(false)' here. But we also
|
||||
// want to be able to write negative unit tests.
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// The pointer on which the access chain is to be based needs to be available
|
||||
// (according to dominance rules) at the insertion point.
|
||||
if (!fuzzerutil::IdIsAvailableBeforeInstruction(
|
||||
context, instruction_to_insert_before, message_.pointer_id())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We now need to use the given indices to walk the type structure of the
|
||||
// base type of the pointer, making sure that (a) the indices correspond to
|
||||
// integers, and (b) these integer values are in-bounds.
|
||||
|
||||
// Start from the base type of the pointer.
|
||||
uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1);
|
||||
|
||||
// Consider the given index ids in turn.
|
||||
for (auto index_id : message_.index_id()) {
|
||||
// Try to get the integer value associated with this index is. The first
|
||||
// component of the result will be false if the id did not correspond to an
|
||||
// integer. Otherwise, the integer with which the id is associated is the
|
||||
// second component.
|
||||
std::pair<bool, uint32_t> maybe_index_value =
|
||||
GetIndexValue(context, index_id);
|
||||
if (!maybe_index_value.first) {
|
||||
// There was no integer: this index is no good.
|
||||
return false;
|
||||
}
|
||||
// Try to walk down the type using this index. This will yield 0 if the
|
||||
// type is not a composite or the index is out of bounds, and the id of
|
||||
// the next type otherwise.
|
||||
subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
|
||||
context, subobject_type_id, maybe_index_value.second);
|
||||
if (!subobject_type_id) {
|
||||
// Either the type was not a composite (so that too many indices were
|
||||
// provided), or the index was out of bounds.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// At this point, |subobject_type_id| is the type of the value targeted by
|
||||
// the new access chain. The result type of the access chain should be a
|
||||
// pointer to this type, with the same storage class as for the original
|
||||
// pointer. Such a pointer type needs to exist in the module.
|
||||
//
|
||||
// We do not use the type manager to look up this type, due to problems
|
||||
// associated with pointers to isomorphic structs being regarded as the same.
|
||||
return fuzzerutil::MaybeGetPointerType(
|
||||
context, subobject_type_id,
|
||||
static_cast<SpvStorageClass>(
|
||||
pointer_type->GetSingleWordInOperand(0))) != 0;
|
||||
}
|
||||
|
||||
void TransformationAccessChain::Apply(
|
||||
opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const {
|
||||
// The operands to the access chain are the pointer followed by the indices.
|
||||
// The result type of the access chain is determined by where the indices
|
||||
// lead. We thus push the pointer to a sequence of operands, and then follow
|
||||
// the indices, pushing each to the operand list and tracking the type
|
||||
// obtained by following it. Ultimately this yields the type of the
|
||||
// component reached by following all the indices, and the result type is
|
||||
// a pointer to this component type.
|
||||
opt::Instruction::OperandList operands;
|
||||
|
||||
// Add the pointer id itself.
|
||||
operands.push_back({SPV_OPERAND_TYPE_ID, {message_.pointer_id()}});
|
||||
|
||||
// Start walking the indices, starting with the pointer's base type.
|
||||
auto pointer_type = context->get_def_use_mgr()->GetDef(
|
||||
context->get_def_use_mgr()->GetDef(message_.pointer_id())->type_id());
|
||||
uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1);
|
||||
|
||||
// Go through the index ids in turn.
|
||||
for (auto index_id : message_.index_id()) {
|
||||
// Add the index id to the operands.
|
||||
operands.push_back({SPV_OPERAND_TYPE_ID, {index_id}});
|
||||
// Get the integer value associated with the index id.
|
||||
uint32_t index_value = GetIndexValue(context, index_id).second;
|
||||
// Walk to the next type in the composite object using this index.
|
||||
subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
|
||||
context, subobject_type_id, index_value);
|
||||
}
|
||||
// The access chain's result type is a pointer to the composite component that
|
||||
// was reached after following all indices. The storage class is that of the
|
||||
// original pointer.
|
||||
uint32_t result_type = fuzzerutil::MaybeGetPointerType(
|
||||
context, subobject_type_id,
|
||||
static_cast<SpvStorageClass>(pointer_type->GetSingleWordInOperand(0)));
|
||||
|
||||
// Add the access chain instruction to the module, and update the module's id
|
||||
// bound.
|
||||
fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
|
||||
FindInstruction(message_.instruction_to_insert_before(), context)
|
||||
->InsertBefore(
|
||||
MakeUnique<opt::Instruction>(context, SpvOpAccessChain, result_type,
|
||||
message_.fresh_id(), operands));
|
||||
|
||||
// Conservatively invalidate all analyses.
|
||||
context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
|
||||
|
||||
// If the base pointer's pointee value was irrelevant, the same is true of the
|
||||
// pointee value of the result of this access chain.
|
||||
if (fact_manager->PointeeValueIsIrrelevant(message_.pointer_id())) {
|
||||
fact_manager->AddFactValueOfPointeeIsIrrelevant(message_.fresh_id());
|
||||
}
|
||||
}
|
||||
|
||||
protobufs::Transformation TransformationAccessChain::ToMessage() const {
|
||||
protobufs::Transformation result;
|
||||
*result.mutable_access_chain() = message_;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<bool, uint32_t> TransformationAccessChain::GetIndexValue(
|
||||
opt::IRContext* context, uint32_t index_id) const {
|
||||
auto index_instruction = context->get_def_use_mgr()->GetDef(index_id);
|
||||
if (!index_instruction || !spvOpcodeIsConstant(index_instruction->opcode())) {
|
||||
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3179) We could
|
||||
// allow non-constant indices when looking up non-structs, using clamping
|
||||
// to ensure they are in-bounds.
|
||||
return {false, 0};
|
||||
}
|
||||
auto index_type =
|
||||
context->get_def_use_mgr()->GetDef(index_instruction->type_id());
|
||||
if (index_type->opcode() != SpvOpTypeInt ||
|
||||
index_type->GetSingleWordInOperand(0) != 32) {
|
||||
return {false, 0};
|
||||
}
|
||||
return {true, index_instruction->GetSingleWordInOperand(0)};
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
|
@ -0,0 +1,80 @@
|
|||
// 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_TRANSFORMATION_ACCESS_CHAIN_H_
|
||||
#define SOURCE_FUZZ_TRANSFORMATION_ACCESS_CHAIN_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "source/fuzz/fact_manager.h"
|
||||
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
|
||||
#include "source/fuzz/transformation.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
class TransformationAccessChain : public Transformation {
|
||||
public:
|
||||
explicit TransformationAccessChain(
|
||||
const protobufs::TransformationAccessChain& message);
|
||||
|
||||
TransformationAccessChain(
|
||||
uint32_t fresh_id, uint32_t pointer_id,
|
||||
const std::vector<uint32_t>& index_id,
|
||||
const protobufs::InstructionDescriptor& instruction_to_insert_before);
|
||||
|
||||
// - |message_.fresh_id| must be fresh
|
||||
// - |message_.instruction_to_insert_before| must identify an instruction
|
||||
// before which it is legitimate to insert an OpAccessChain instruction
|
||||
// - |message_.pointer_id| must be a result id with pointer type that is
|
||||
// available (according to dominance rules) at the insertion point.
|
||||
// - The pointer must not be OpConstantNull or OpUndef
|
||||
// - |message_.index_id| must be a sequence of ids of 32-bit integer constants
|
||||
// such that it is possible to walk the pointee type of
|
||||
// |message_.pointer_id| using these indices, remaining in-bounds.
|
||||
// - If type t is the final type reached by walking these indices, the module
|
||||
// must include an instruction "OpTypePointer SC %t" where SC is the storage
|
||||
// class associated with |message_.pointer_id|
|
||||
bool IsApplicable(opt::IRContext* context,
|
||||
const FactManager& fact_manager) const override;
|
||||
|
||||
// Adds an instruction of the form:
|
||||
// |message_.fresh_id| = OpAccessChain %ptr |message_.index_id|
|
||||
// where %ptr is the result if of an instruction declaring a pointer to the
|
||||
// type reached by walking the pointee type of |message_.pointer_id| using
|
||||
// the indices in |message_.index_id|, and with the same storage class as
|
||||
// |message_.pointer_id|.
|
||||
//
|
||||
// If |fact_manager| reports that |message_.pointer_id| has an irrelevant
|
||||
// pointee value, then the fact that |message_.fresh_id| (the result of the
|
||||
// access chain) also has an irrelevant pointee value is also recorded.
|
||||
void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
|
||||
|
||||
protobufs::Transformation ToMessage() const override;
|
||||
|
||||
private:
|
||||
// Returns {false, 0} if |index_id| does not correspond to a 32-bit integer
|
||||
// constant. Otherwise, returns {true, value}, where value is the value of
|
||||
// the 32-bit integer constant to which |index_id| corresponds.
|
||||
std::pair<bool, uint32_t> GetIndexValue(opt::IRContext* context,
|
||||
uint32_t index_id) const;
|
||||
|
||||
protobufs::TransformationAccessChain message_;
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_FUZZ_TRANSFORMATION_ACCESS_CHAIN_H_
|
|
@ -24,6 +24,7 @@ if (${SPIRV_BUILD_FUZZER})
|
|||
fuzzer_pass_add_useful_constructs_test.cpp
|
||||
fuzzer_pass_donate_modules_test.cpp
|
||||
instruction_descriptor_test.cpp
|
||||
transformation_access_chain_test.cpp
|
||||
transformation_add_constant_boolean_test.cpp
|
||||
transformation_add_constant_composite_test.cpp
|
||||
transformation_add_constant_scalar_test.cpp
|
||||
|
|
|
@ -0,0 +1,448 @@
|
|||
// 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/transformation_access_chain.h"
|
||||
#include "source/fuzz/instruction_descriptor.h"
|
||||
#include "test/fuzz/fuzz_test_util.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
namespace {
|
||||
|
||||
TEST(TransformationAccessChainTest, BasicTest) {
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main" %48 %54
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%7 = OpTypeVector %6 2
|
||||
%50 = OpTypeMatrix %7 2
|
||||
%70 = OpTypePointer Function %7
|
||||
%71 = OpTypePointer Function %50
|
||||
%8 = OpTypeStruct %7 %6
|
||||
%9 = OpTypePointer Function %8
|
||||
%10 = OpTypeInt 32 1
|
||||
%11 = OpTypePointer Function %10
|
||||
%12 = OpTypeFunction %10 %9 %11
|
||||
%17 = OpConstant %10 0
|
||||
%18 = OpTypeInt 32 0
|
||||
%19 = OpConstant %18 0
|
||||
%20 = OpTypePointer Function %6
|
||||
%99 = OpTypePointer Private %6
|
||||
%29 = OpConstant %6 0
|
||||
%30 = OpConstant %6 1
|
||||
%31 = OpConstantComposite %7 %29 %30
|
||||
%32 = OpConstant %6 2
|
||||
%33 = OpConstantComposite %8 %31 %32
|
||||
%35 = OpConstant %10 10
|
||||
%51 = OpConstant %18 10
|
||||
%80 = OpConstant %18 0
|
||||
%81 = OpConstant %10 1
|
||||
%82 = OpConstant %18 2
|
||||
%83 = OpConstant %10 3
|
||||
%84 = OpConstant %18 4
|
||||
%85 = OpConstant %10 5
|
||||
%52 = OpTypeArray %50 %51
|
||||
%53 = OpTypePointer Private %52
|
||||
%45 = OpUndef %9
|
||||
%46 = OpConstantNull %9
|
||||
%47 = OpTypePointer Private %8
|
||||
%48 = OpVariable %47 Private
|
||||
%54 = OpVariable %53 Private
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%28 = OpVariable %9 Function
|
||||
%34 = OpVariable %11 Function
|
||||
%36 = OpVariable %9 Function
|
||||
%38 = OpVariable %11 Function
|
||||
%44 = OpCopyObject %9 %36
|
||||
OpStore %28 %33
|
||||
OpStore %34 %35
|
||||
%37 = OpLoad %8 %28
|
||||
OpStore %36 %37
|
||||
%39 = OpLoad %10 %34
|
||||
OpStore %38 %39
|
||||
%40 = OpFunctionCall %10 %15 %36 %38
|
||||
%41 = OpLoad %10 %34
|
||||
%42 = OpIAdd %10 %41 %40
|
||||
OpStore %34 %42
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%15 = OpFunction %10 None %12
|
||||
%13 = OpFunctionParameter %9
|
||||
%14 = OpFunctionParameter %11
|
||||
%16 = OpLabel
|
||||
%21 = OpAccessChain %20 %13 %17 %19
|
||||
%43 = OpCopyObject %9 %13
|
||||
%22 = OpLoad %6 %21
|
||||
%23 = OpConvertFToS %10 %22
|
||||
%24 = OpLoad %10 %14
|
||||
%25 = OpIAdd %10 %23 %24
|
||||
OpReturnValue %25
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_4;
|
||||
const auto consumer = nullptr;
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
// Types:
|
||||
// Ptr | Pointee | Storage class | GLSL for pointee | Ids of this type
|
||||
// ----+---------+---------------+---------------------+------------------
|
||||
// 9 | 8 | Function | struct(vec2, float) | 28, 36, 44, 13, 43
|
||||
// 11 | 10 | Function | int | 34, 38, 14
|
||||
// 20 | 6 | Function | float | -
|
||||
// 99 | 6 | Private | float | -
|
||||
// 53 | 52 | Private | mat2x2[10] | 54
|
||||
// 47 | 8 | Private | struct(vec2, float) | 48
|
||||
// 70 | 7 | Function | vec2 | -
|
||||
// 71 | 59 | Function | mat2x2 | -
|
||||
|
||||
// Indices 0-5 are in ids 80-85
|
||||
|
||||
FactManager fact_manager;
|
||||
fact_manager.AddFactValueOfPointeeIsIrrelevant(54);
|
||||
|
||||
// Bad: id is not fresh
|
||||
ASSERT_FALSE(TransformationAccessChain(
|
||||
43, 43, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: pointer id does not exist
|
||||
ASSERT_FALSE(TransformationAccessChain(
|
||||
100, 1000, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: pointer id is not a type
|
||||
ASSERT_FALSE(TransformationAccessChain(
|
||||
100, 5, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: pointer id is not a pointer
|
||||
ASSERT_FALSE(TransformationAccessChain(
|
||||
100, 23, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: index id does not exist
|
||||
ASSERT_FALSE(TransformationAccessChain(
|
||||
100, 43, {1000}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: index id is not a constant
|
||||
ASSERT_FALSE(TransformationAccessChain(
|
||||
100, 43, {24}, MakeInstructionDescriptor(25, SpvOpIAdd, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: too many indices
|
||||
ASSERT_FALSE(
|
||||
TransformationAccessChain(100, 43, {80, 80, 80},
|
||||
MakeInstructionDescriptor(24, SpvOpLoad, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: index id is out of bounds
|
||||
ASSERT_FALSE(
|
||||
TransformationAccessChain(100, 43, {80, 83},
|
||||
MakeInstructionDescriptor(24, SpvOpLoad, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: attempt to insert before variable
|
||||
ASSERT_FALSE(TransformationAccessChain(
|
||||
100, 34, {}, MakeInstructionDescriptor(36, SpvOpVariable, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: pointer not available
|
||||
ASSERT_FALSE(
|
||||
TransformationAccessChain(
|
||||
100, 43, {80}, MakeInstructionDescriptor(21, SpvOpAccessChain, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: instruction descriptor does not identify anything
|
||||
ASSERT_FALSE(TransformationAccessChain(
|
||||
100, 43, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 100))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: pointer is null
|
||||
ASSERT_FALSE(TransformationAccessChain(
|
||||
100, 45, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: pointer is undef
|
||||
ASSERT_FALSE(TransformationAccessChain(
|
||||
100, 46, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
// Bad: pointer to result type does not exist
|
||||
ASSERT_FALSE(TransformationAccessChain(
|
||||
100, 52, {0}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
|
||||
.IsApplicable(context.get(), fact_manager));
|
||||
|
||||
{
|
||||
TransformationAccessChain transformation(
|
||||
100, 43, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0));
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(100));
|
||||
}
|
||||
|
||||
{
|
||||
TransformationAccessChain transformation(
|
||||
101, 28, {81}, MakeInstructionDescriptor(42, SpvOpReturn, 0));
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(101));
|
||||
}
|
||||
|
||||
{
|
||||
TransformationAccessChain transformation(
|
||||
102, 36, {80, 81}, MakeInstructionDescriptor(37, SpvOpStore, 0));
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(102));
|
||||
}
|
||||
|
||||
{
|
||||
TransformationAccessChain transformation(
|
||||
103, 44, {}, MakeInstructionDescriptor(44, SpvOpStore, 0));
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(103));
|
||||
}
|
||||
|
||||
{
|
||||
TransformationAccessChain transformation(
|
||||
104, 13, {80}, MakeInstructionDescriptor(21, SpvOpAccessChain, 0));
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(104));
|
||||
}
|
||||
|
||||
{
|
||||
TransformationAccessChain transformation(
|
||||
105, 34, {}, MakeInstructionDescriptor(44, SpvOpStore, 1));
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(105));
|
||||
}
|
||||
|
||||
{
|
||||
TransformationAccessChain transformation(
|
||||
106, 38, {}, MakeInstructionDescriptor(40, SpvOpFunctionCall, 0));
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(106));
|
||||
}
|
||||
|
||||
{
|
||||
TransformationAccessChain transformation(
|
||||
107, 14, {}, MakeInstructionDescriptor(24, SpvOpLoad, 0));
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(107));
|
||||
}
|
||||
|
||||
{
|
||||
TransformationAccessChain transformation(
|
||||
108, 54, {85, 81, 81}, MakeInstructionDescriptor(24, SpvOpLoad, 0));
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
ASSERT_TRUE(fact_manager.PointeeValueIsIrrelevant(108));
|
||||
}
|
||||
|
||||
{
|
||||
TransformationAccessChain transformation(
|
||||
109, 48, {80, 80}, MakeInstructionDescriptor(24, SpvOpLoad, 0));
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(109));
|
||||
}
|
||||
|
||||
std::string after_transformation = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main" %48 %54
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%7 = OpTypeVector %6 2
|
||||
%50 = OpTypeMatrix %7 2
|
||||
%70 = OpTypePointer Function %7
|
||||
%71 = OpTypePointer Function %50
|
||||
%8 = OpTypeStruct %7 %6
|
||||
%9 = OpTypePointer Function %8
|
||||
%10 = OpTypeInt 32 1
|
||||
%11 = OpTypePointer Function %10
|
||||
%12 = OpTypeFunction %10 %9 %11
|
||||
%17 = OpConstant %10 0
|
||||
%18 = OpTypeInt 32 0
|
||||
%19 = OpConstant %18 0
|
||||
%20 = OpTypePointer Function %6
|
||||
%99 = OpTypePointer Private %6
|
||||
%29 = OpConstant %6 0
|
||||
%30 = OpConstant %6 1
|
||||
%31 = OpConstantComposite %7 %29 %30
|
||||
%32 = OpConstant %6 2
|
||||
%33 = OpConstantComposite %8 %31 %32
|
||||
%35 = OpConstant %10 10
|
||||
%51 = OpConstant %18 10
|
||||
%80 = OpConstant %18 0
|
||||
%81 = OpConstant %10 1
|
||||
%82 = OpConstant %18 2
|
||||
%83 = OpConstant %10 3
|
||||
%84 = OpConstant %18 4
|
||||
%85 = OpConstant %10 5
|
||||
%52 = OpTypeArray %50 %51
|
||||
%53 = OpTypePointer Private %52
|
||||
%45 = OpUndef %9
|
||||
%46 = OpConstantNull %9
|
||||
%47 = OpTypePointer Private %8
|
||||
%48 = OpVariable %47 Private
|
||||
%54 = OpVariable %53 Private
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%28 = OpVariable %9 Function
|
||||
%34 = OpVariable %11 Function
|
||||
%36 = OpVariable %9 Function
|
||||
%38 = OpVariable %11 Function
|
||||
%44 = OpCopyObject %9 %36
|
||||
%103 = OpAccessChain %9 %44
|
||||
OpStore %28 %33
|
||||
%105 = OpAccessChain %11 %34
|
||||
OpStore %34 %35
|
||||
%37 = OpLoad %8 %28
|
||||
%102 = OpAccessChain %20 %36 %80 %81
|
||||
OpStore %36 %37
|
||||
%39 = OpLoad %10 %34
|
||||
OpStore %38 %39
|
||||
%106 = OpAccessChain %11 %38
|
||||
%40 = OpFunctionCall %10 %15 %36 %38
|
||||
%41 = OpLoad %10 %34
|
||||
%42 = OpIAdd %10 %41 %40
|
||||
OpStore %34 %42
|
||||
%101 = OpAccessChain %20 %28 %81
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%15 = OpFunction %10 None %12
|
||||
%13 = OpFunctionParameter %9
|
||||
%14 = OpFunctionParameter %11
|
||||
%16 = OpLabel
|
||||
%104 = OpAccessChain %70 %13 %80
|
||||
%21 = OpAccessChain %20 %13 %17 %19
|
||||
%43 = OpCopyObject %9 %13
|
||||
%22 = OpLoad %6 %21
|
||||
%23 = OpConvertFToS %10 %22
|
||||
%100 = OpAccessChain %70 %43 %80
|
||||
%107 = OpAccessChain %11 %14
|
||||
%108 = OpAccessChain %99 %54 %85 %81 %81
|
||||
%109 = OpAccessChain %99 %48 %80 %80
|
||||
%24 = OpLoad %10 %14
|
||||
%25 = OpIAdd %10 %23 %24
|
||||
OpReturnValue %25
|
||||
OpFunctionEnd
|
||||
)";
|
||||
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
|
||||
}
|
||||
|
||||
TEST(TransformationAccessChainTest, IsomorphicStructs) {
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main" %11 %12
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%7 = OpTypeStruct %6
|
||||
%8 = OpTypePointer Private %7
|
||||
%9 = OpTypeStruct %6
|
||||
%10 = OpTypePointer Private %9
|
||||
%11 = OpVariable %8 Private
|
||||
%12 = OpVariable %10 Private
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_4;
|
||||
const auto consumer = nullptr;
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
|
||||
{
|
||||
TransformationAccessChain transformation(
|
||||
100, 11, {}, MakeInstructionDescriptor(5, SpvOpReturn, 0));
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
}
|
||||
{
|
||||
TransformationAccessChain transformation(
|
||||
101, 12, {}, MakeInstructionDescriptor(5, SpvOpReturn, 0));
|
||||
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
||||
transformation.Apply(context.get(), &fact_manager);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
}
|
||||
|
||||
std::string after_transformation = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main" %11 %12
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%7 = OpTypeStruct %6
|
||||
%8 = OpTypePointer Private %7
|
||||
%9 = OpTypeStruct %6
|
||||
%10 = OpTypePointer Private %9
|
||||
%11 = OpVariable %8 Private
|
||||
%12 = OpVariable %10 Private
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%100 = OpAccessChain %8 %11
|
||||
%101 = OpAccessChain %10 %12
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
Загрузка…
Ссылка в новой задаче