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:
Alastair Donaldson 2020-02-11 23:10:57 +00:00 коммит произвёл GitHub
Родитель 77fb303e58
Коммит 6c218ec60b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 1102 добавлений и 46 удалений

Просмотреть файл

@ -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,44 +262,48 @@ std::vector<uint32_t> RepeatedFieldToVector(
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(
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) {
auto should_be_composite_type =
context->get_def_use_mgr()->GetDef(sub_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;
}
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;
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