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.h
|
||||||
fuzzer_context.h
|
fuzzer_context.h
|
||||||
fuzzer_pass.h
|
fuzzer_pass.h
|
||||||
|
fuzzer_pass_add_access_chains.h
|
||||||
fuzzer_pass_add_composite_types.h
|
fuzzer_pass_add_composite_types.h
|
||||||
fuzzer_pass_add_dead_blocks.h
|
fuzzer_pass_add_dead_blocks.h
|
||||||
fuzzer_pass_add_dead_breaks.h
|
fuzzer_pass_add_dead_breaks.h
|
||||||
|
@ -71,6 +72,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||||
replayer.h
|
replayer.h
|
||||||
shrinker.h
|
shrinker.h
|
||||||
transformation.h
|
transformation.h
|
||||||
|
transformation_access_chain.h
|
||||||
transformation_add_constant_boolean.h
|
transformation_add_constant_boolean.h
|
||||||
transformation_add_constant_composite.h
|
transformation_add_constant_composite.h
|
||||||
transformation_add_constant_scalar.h
|
transformation_add_constant_scalar.h
|
||||||
|
@ -119,6 +121,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||||
fuzzer.cpp
|
fuzzer.cpp
|
||||||
fuzzer_context.cpp
|
fuzzer_context.cpp
|
||||||
fuzzer_pass.cpp
|
fuzzer_pass.cpp
|
||||||
|
fuzzer_pass_add_access_chains.cpp
|
||||||
fuzzer_pass_add_composite_types.cpp
|
fuzzer_pass_add_composite_types.cpp
|
||||||
fuzzer_pass_add_dead_blocks.cpp
|
fuzzer_pass_add_dead_blocks.cpp
|
||||||
fuzzer_pass_add_dead_breaks.cpp
|
fuzzer_pass_add_dead_breaks.cpp
|
||||||
|
@ -152,6 +155,7 @@ if(SPIRV_BUILD_FUZZER)
|
||||||
replayer.cpp
|
replayer.cpp
|
||||||
shrinker.cpp
|
shrinker.cpp
|
||||||
transformation.cpp
|
transformation.cpp
|
||||||
|
transformation_access_chain.cpp
|
||||||
transformation_add_constant_boolean.cpp
|
transformation_add_constant_boolean.cpp
|
||||||
transformation_add_constant_composite.cpp
|
transformation_add_constant_composite.cpp
|
||||||
transformation_add_constant_scalar.cpp
|
transformation_add_constant_scalar.cpp
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "fuzzer_pass_adjust_memory_operands_masks.h"
|
#include "fuzzer_pass_adjust_memory_operands_masks.h"
|
||||||
#include "source/fuzz/fact_manager.h"
|
#include "source/fuzz/fact_manager.h"
|
||||||
#include "source/fuzz/fuzzer_context.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_composite_types.h"
|
||||||
#include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
|
#include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
|
||||||
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
|
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
|
||||||
|
@ -184,6 +185,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
|
||||||
// Apply some semantics-preserving passes.
|
// Apply some semantics-preserving passes.
|
||||||
std::vector<std::unique_ptr<FuzzerPass>> passes;
|
std::vector<std::unique_ptr<FuzzerPass>> passes;
|
||||||
while (passes.empty()) {
|
while (passes.empty()) {
|
||||||
|
MaybeAddPass<FuzzerPassAddAccessChains>(&passes, ir_context.get(),
|
||||||
|
&fact_manager, &fuzzer_context,
|
||||||
|
transformation_sequence_out);
|
||||||
MaybeAddPass<FuzzerPassAddCompositeTypes>(&passes, ir_context.get(),
|
MaybeAddPass<FuzzerPassAddCompositeTypes>(&passes, ir_context.get(),
|
||||||
&fact_manager, &fuzzer_context,
|
&fact_manager, &fuzzer_context,
|
||||||
transformation_sequence_out);
|
transformation_sequence_out);
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace {
|
||||||
// Default <minimum, maximum> pairs of probabilities for applying various
|
// Default <minimum, maximum> pairs of probabilities for applying various
|
||||||
// transformations. All values are percentages. Keep them in alphabetical order.
|
// 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,
|
const std::pair<uint32_t, uint32_t> kChanceOfAddingAnotherStructField = {20,
|
||||||
90};
|
90};
|
||||||
const std::pair<uint32_t, uint32_t> kChanceOfAddingArrayOrStructType = {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> kChanceOfConstructingComposite = {20, 50};
|
||||||
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {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> 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> kChanceOfMakingDonorLivesafe = {40, 60};
|
||||||
const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95};
|
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> kChanceOfMovingBlockDown = {20, 50};
|
||||||
|
@ -81,8 +84,14 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
|
||||||
uint32_t min_fresh_id)
|
uint32_t min_fresh_id)
|
||||||
: random_generator_(random_generator),
|
: random_generator_(random_generator),
|
||||||
next_fresh_id_(min_fresh_id),
|
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_(
|
go_deeper_in_constant_obfuscation_(
|
||||||
kDefaultGoDeeperInConstantObfuscation) {
|
kDefaultGoDeeperInConstantObfuscation) {
|
||||||
|
chance_of_adding_access_chain_ =
|
||||||
|
ChooseBetweenMinAndMax(kChanceOfAddingAccessChain);
|
||||||
chance_of_adding_another_struct_field_ =
|
chance_of_adding_another_struct_field_ =
|
||||||
ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField);
|
ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField);
|
||||||
chance_of_adding_array_or_struct_type_ =
|
chance_of_adding_array_or_struct_type_ =
|
||||||
|
@ -122,6 +131,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
|
||||||
chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
|
chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
|
||||||
chance_of_donating_additional_module_ =
|
chance_of_donating_additional_module_ =
|
||||||
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
|
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
|
||||||
|
chance_of_going_deeper_when_making_access_chain_ =
|
||||||
|
ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
|
||||||
chance_of_making_donor_livesafe_ =
|
chance_of_making_donor_livesafe_ =
|
||||||
ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe);
|
ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe);
|
||||||
chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks);
|
chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks);
|
||||||
|
@ -134,10 +145,6 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
|
||||||
chance_of_replacing_id_with_synonym_ =
|
chance_of_replacing_id_with_synonym_ =
|
||||||
ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
|
ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
|
||||||
chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
|
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;
|
FuzzerContext::~FuzzerContext() = default;
|
||||||
|
|
|
@ -68,6 +68,9 @@ class FuzzerContext {
|
||||||
|
|
||||||
// Probabilities associated with applying various transformations.
|
// Probabilities associated with applying various transformations.
|
||||||
// Keep them in alphabetical order.
|
// Keep them in alphabetical order.
|
||||||
|
uint32_t GetChanceOfAddingAccessChain() {
|
||||||
|
return chance_of_adding_access_chain_;
|
||||||
|
}
|
||||||
uint32_t GetChanceOfAddingAnotherStructField() {
|
uint32_t GetChanceOfAddingAnotherStructField() {
|
||||||
return chance_of_adding_another_struct_field_;
|
return chance_of_adding_another_struct_field_;
|
||||||
}
|
}
|
||||||
|
@ -119,6 +122,9 @@ class FuzzerContext {
|
||||||
uint32_t GetChanceOfDonatingAdditionalModule() {
|
uint32_t GetChanceOfDonatingAdditionalModule() {
|
||||||
return chance_of_donating_additional_module_;
|
return chance_of_donating_additional_module_;
|
||||||
}
|
}
|
||||||
|
uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
|
||||||
|
return chance_of_going_deeper_when_making_access_chain_;
|
||||||
|
}
|
||||||
uint32_t ChanceOfMakingDonorLivesafe() {
|
uint32_t ChanceOfMakingDonorLivesafe() {
|
||||||
return chance_of_making_donor_livesafe_;
|
return chance_of_making_donor_livesafe_;
|
||||||
}
|
}
|
||||||
|
@ -148,8 +154,11 @@ class FuzzerContext {
|
||||||
return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1;
|
return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions to control how deeply to recurse.
|
// Other functions to control transformations. Keep them in alphabetical
|
||||||
// Keep them in alphabetical order.
|
// order.
|
||||||
|
uint32_t GetRandomIndexForAccessChain(uint32_t composite_size_bound) {
|
||||||
|
return random_generator_->RandomUint32(composite_size_bound);
|
||||||
|
}
|
||||||
bool GoDeeperInConstantObfuscation(uint32_t depth) {
|
bool GoDeeperInConstantObfuscation(uint32_t depth) {
|
||||||
return go_deeper_in_constant_obfuscation_(depth, random_generator_);
|
return go_deeper_in_constant_obfuscation_(depth, random_generator_);
|
||||||
}
|
}
|
||||||
|
@ -162,6 +171,7 @@ class FuzzerContext {
|
||||||
|
|
||||||
// Probabilities associated with applying various transformations.
|
// Probabilities associated with applying various transformations.
|
||||||
// Keep them in alphabetical order.
|
// 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_another_struct_field_;
|
||||||
uint32_t chance_of_adding_array_or_struct_type_;
|
uint32_t chance_of_adding_array_or_struct_type_;
|
||||||
uint32_t chance_of_adding_dead_block_;
|
uint32_t chance_of_adding_dead_block_;
|
||||||
|
@ -183,6 +193,7 @@ class FuzzerContext {
|
||||||
uint32_t chance_of_constructing_composite_;
|
uint32_t chance_of_constructing_composite_;
|
||||||
uint32_t chance_of_copying_object_;
|
uint32_t chance_of_copying_object_;
|
||||||
uint32_t chance_of_donating_additional_module_;
|
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_making_donor_livesafe_;
|
||||||
uint32_t chance_of_merging_blocks_;
|
uint32_t chance_of_merging_blocks_;
|
||||||
uint32_t chance_of_moving_block_down_;
|
uint32_t chance_of_moving_block_down_;
|
||||||
|
|
|
@ -219,21 +219,27 @@ uint32_t FuzzerPass::FindOrCreateMatrixType(uint32_t column_count,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType(
|
uint32_t FuzzerPass::FindOrCreatePointerType(uint32_t base_type_id,
|
||||||
bool is_signed, SpvStorageClass storage_class) {
|
SpvStorageClass storage_class) {
|
||||||
auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed);
|
// We do not use the type manager here, due to problems related to isomorphic
|
||||||
opt::analysis::Pointer pointer_type(
|
// but distinct structs not being regarded as different.
|
||||||
GetIRContext()->get_type_mgr()->GetType(uint32_type_id), storage_class);
|
auto existing_id = fuzzerutil::MaybeGetPointerType(
|
||||||
auto existing_id = GetIRContext()->get_type_mgr()->GetId(&pointer_type);
|
GetIRContext(), base_type_id, storage_class);
|
||||||
if (existing_id) {
|
if (existing_id) {
|
||||||
return existing_id;
|
return existing_id;
|
||||||
}
|
}
|
||||||
auto result = GetFuzzerContext()->GetFreshId();
|
auto result = GetFuzzerContext()->GetFreshId();
|
||||||
ApplyTransformation(
|
ApplyTransformation(
|
||||||
TransformationAddTypePointer(result, storage_class, uint32_type_id));
|
TransformationAddTypePointer(result, storage_class, base_type_id));
|
||||||
return result;
|
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,
|
uint32_t FuzzerPass::FindOrCreate32BitIntegerConstant(uint32_t word,
|
||||||
bool is_signed) {
|
bool is_signed) {
|
||||||
auto uint32_type_id = FindOrCreate32BitIntegerType(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.
|
// type itself do not exist, transformations are applied to add them.
|
||||||
uint32_t FindOrCreateMatrixType(uint32_t column_count, uint32_t row_count);
|
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
|
// 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
|
// type of signedness specified by |is_signed|. If the pointer type or
|
||||||
// required integer base type do not exist, transformations are applied to add
|
// 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,44 +262,48 @@ std::vector<uint32_t> RepeatedFieldToVector(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(base_object_type_id);
|
||||||
|
assert(should_be_composite_type && "The type should exist.");
|
||||||
|
switch (should_be_composite_type->opcode()) {
|
||||||
|
case SpvOpTypeArray: {
|
||||||
|
auto array_length = GetArraySize(*should_be_composite_type, context);
|
||||||
|
if (array_length == 0 || index >= array_length) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return should_be_composite_type->GetSingleWordInOperand(0);
|
||||||
|
}
|
||||||
|
case SpvOpTypeMatrix:
|
||||||
|
case SpvOpTypeVector: {
|
||||||
|
auto count = should_be_composite_type->GetSingleWordInOperand(1);
|
||||||
|
if (index >= count) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return should_be_composite_type->GetSingleWordInOperand(0);
|
||||||
|
}
|
||||||
|
case SpvOpTypeStruct: {
|
||||||
|
if (index >= GetNumberOfStructMembers(*should_be_composite_type)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return should_be_composite_type->GetSingleWordInOperand(index);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t WalkCompositeTypeIndices(
|
uint32_t WalkCompositeTypeIndices(
|
||||||
opt::IRContext* context, uint32_t base_object_type_id,
|
opt::IRContext* context, uint32_t base_object_type_id,
|
||||||
const google::protobuf::RepeatedField<google::protobuf::uint32>& indices) {
|
const google::protobuf::RepeatedField<google::protobuf::uint32>& indices) {
|
||||||
uint32_t sub_object_type_id = base_object_type_id;
|
uint32_t sub_object_type_id = base_object_type_id;
|
||||||
for (auto index : indices) {
|
for (auto index : indices) {
|
||||||
auto should_be_composite_type =
|
sub_object_type_id =
|
||||||
context->get_def_use_mgr()->GetDef(sub_object_type_id);
|
WalkOneCompositeTypeIndex(context, sub_object_type_id, index);
|
||||||
assert(should_be_composite_type && "The type should exist.");
|
if (!sub_object_type_id) {
|
||||||
switch (should_be_composite_type->opcode()) {
|
return 0;
|
||||||
case SpvOpTypeArray: {
|
|
||||||
auto array_length = GetArraySize(*should_be_composite_type, context);
|
|
||||||
if (array_length == 0 || index >= array_length) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
sub_object_type_id =
|
|
||||||
should_be_composite_type->GetSingleWordInOperand(0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SpvOpTypeMatrix:
|
|
||||||
case SpvOpTypeVector: {
|
|
||||||
auto count = should_be_composite_type->GetSingleWordInOperand(1);
|
|
||||||
if (index >= count) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
sub_object_type_id =
|
|
||||||
should_be_composite_type->GetSingleWordInOperand(0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SpvOpTypeStruct: {
|
|
||||||
if (index >= GetNumberOfStructMembers(*should_be_composite_type)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
sub_object_type_id =
|
|
||||||
should_be_composite_type->GetSingleWordInOperand(index);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sub_object_type_id;
|
return sub_object_type_id;
|
||||||
|
@ -501,6 +505,23 @@ SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context,
|
||||||
context->get_def_use_mgr()->GetDef(pointer_type_id));
|
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 fuzzerutil
|
||||||
|
|
||||||
} // namespace fuzz
|
} // namespace fuzz
|
||||||
|
|
|
@ -98,6 +98,21 @@ bool IsCompositeType(const opt::analysis::Type* type);
|
||||||
std::vector<uint32_t> RepeatedFieldToVector(
|
std::vector<uint32_t> RepeatedFieldToVector(
|
||||||
const google::protobuf::RepeatedField<uint32_t>& repeated_field);
|
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
|
// 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
|
// |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
|
// 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,
|
SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context,
|
||||||
uint32_t pointer_type_id);
|
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 fuzzerutil
|
||||||
|
|
||||||
} // namespace fuzz
|
} // namespace fuzz
|
||||||
|
|
|
@ -341,12 +341,32 @@ message Transformation {
|
||||||
TransformationLoad load = 36;
|
TransformationLoad load = 36;
|
||||||
TransformationStore store = 37;
|
TransformationStore store = 37;
|
||||||
TransformationFunctionCall function_call = 38;
|
TransformationFunctionCall function_call = 38;
|
||||||
|
TransformationAccessChain access_chain = 39;
|
||||||
// Add additional option using the next available number.
|
// Add additional option using the next available number.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep transformation message types in alphabetical order:
|
// 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 {
|
message TransformationAddConstantBoolean {
|
||||||
|
|
||||||
// Supports adding the constants true and false to a module, which may be
|
// Supports adding the constants true and false to a module, which may be
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "source/fuzz/fuzzer_util.h"
|
#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_boolean.h"
|
||||||
#include "source/fuzz/transformation_add_constant_composite.h"
|
#include "source/fuzz/transformation_add_constant_composite.h"
|
||||||
#include "source/fuzz/transformation_add_constant_scalar.h"
|
#include "source/fuzz/transformation_add_constant_scalar.h"
|
||||||
|
@ -65,6 +66,8 @@ Transformation::~Transformation() = default;
|
||||||
std::unique_ptr<Transformation> Transformation::FromMessage(
|
std::unique_ptr<Transformation> Transformation::FromMessage(
|
||||||
const protobufs::Transformation& message) {
|
const protobufs::Transformation& message) {
|
||||||
switch (message.transformation_case()) {
|
switch (message.transformation_case()) {
|
||||||
|
case protobufs::Transformation::TransformationCase::kAccessChain:
|
||||||
|
return MakeUnique<TransformationAccessChain>(message.access_chain());
|
||||||
case protobufs::Transformation::TransformationCase::kAddConstantBoolean:
|
case protobufs::Transformation::TransformationCase::kAddConstantBoolean:
|
||||||
return MakeUnique<TransformationAddConstantBoolean>(
|
return MakeUnique<TransformationAddConstantBoolean>(
|
||||||
message.add_constant_boolean());
|
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_add_useful_constructs_test.cpp
|
||||||
fuzzer_pass_donate_modules_test.cpp
|
fuzzer_pass_donate_modules_test.cpp
|
||||||
instruction_descriptor_test.cpp
|
instruction_descriptor_test.cpp
|
||||||
|
transformation_access_chain_test.cpp
|
||||||
transformation_add_constant_boolean_test.cpp
|
transformation_add_constant_boolean_test.cpp
|
||||||
transformation_add_constant_composite_test.cpp
|
transformation_add_constant_composite_test.cpp
|
||||||
transformation_add_constant_scalar_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
|
Загрузка…
Ссылка в новой задаче