spirv-fuzz: TransformationReplaceParamsWithStruct (#3455)

Fixes #3453.
This commit is contained in:
Vasyl Teliman 2020-07-21 23:02:32 +03:00 committed by GitHub
parent e4aebf99fa
commit fe9e5db890
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1274 additions and 75 deletions

View File

@ -75,6 +75,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_push_ids_through_variables.h fuzzer_pass_push_ids_through_variables.h
fuzzer_pass_replace_linear_algebra_instructions.h fuzzer_pass_replace_linear_algebra_instructions.h
fuzzer_pass_replace_parameter_with_global.h fuzzer_pass_replace_parameter_with_global.h
fuzzer_pass_replace_params_with_struct.h
fuzzer_pass_split_blocks.h fuzzer_pass_split_blocks.h
fuzzer_pass_swap_commutable_operands.h fuzzer_pass_swap_commutable_operands.h
fuzzer_pass_swap_conditional_branch_operands.h fuzzer_pass_swap_conditional_branch_operands.h
@ -138,6 +139,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_replace_id_with_synonym.h transformation_replace_id_with_synonym.h
transformation_replace_linear_algebra_instruction.h transformation_replace_linear_algebra_instruction.h
transformation_replace_parameter_with_global.h transformation_replace_parameter_with_global.h
transformation_replace_params_with_struct.h
transformation_set_function_control.h transformation_set_function_control.h
transformation_set_loop_control.h transformation_set_loop_control.h
transformation_set_memory_operands_mask.h transformation_set_memory_operands_mask.h
@ -196,6 +198,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_push_ids_through_variables.cpp fuzzer_pass_push_ids_through_variables.cpp
fuzzer_pass_replace_linear_algebra_instructions.cpp fuzzer_pass_replace_linear_algebra_instructions.cpp
fuzzer_pass_replace_parameter_with_global.cpp fuzzer_pass_replace_parameter_with_global.cpp
fuzzer_pass_replace_params_with_struct.cpp
fuzzer_pass_split_blocks.cpp fuzzer_pass_split_blocks.cpp
fuzzer_pass_swap_commutable_operands.cpp fuzzer_pass_swap_commutable_operands.cpp
fuzzer_pass_swap_conditional_branch_operands.cpp fuzzer_pass_swap_conditional_branch_operands.cpp
@ -258,6 +261,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_replace_id_with_synonym.cpp transformation_replace_id_with_synonym.cpp
transformation_replace_linear_algebra_instruction.cpp transformation_replace_linear_algebra_instruction.cpp
transformation_replace_parameter_with_global.cpp transformation_replace_parameter_with_global.cpp
transformation_replace_params_with_struct.cpp
transformation_set_function_control.cpp transformation_set_function_control.cpp
transformation_set_loop_control.cpp transformation_set_loop_control.cpp
transformation_set_memory_operands_mask.cpp transformation_set_memory_operands_mask.cpp

View File

@ -57,6 +57,7 @@
#include "source/fuzz/fuzzer_pass_push_ids_through_variables.h" #include "source/fuzz/fuzzer_pass_push_ids_through_variables.h"
#include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h" #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h"
#include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h" #include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h"
#include "source/fuzz/fuzzer_pass_replace_params_with_struct.h"
#include "source/fuzz/fuzzer_pass_split_blocks.h" #include "source/fuzz/fuzzer_pass_split_blocks.h"
#include "source/fuzz/fuzzer_pass_swap_commutable_operands.h" #include "source/fuzz/fuzzer_pass_swap_commutable_operands.h"
#include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h" #include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h"
@ -288,6 +289,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassReplaceLinearAlgebraInstructions>( MaybeAddPass<FuzzerPassReplaceLinearAlgebraInstructions>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context, &passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out); transformation_sequence_out);
MaybeAddPass<FuzzerPassReplaceParamsWithStruct>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassSplitBlocks>( MaybeAddPass<FuzzerPassSplitBlocks>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context, &passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out); transformation_sequence_out);

View File

@ -82,6 +82,8 @@ const std::pair<uint32_t, uint32_t>
kChanceOfReplacingLinearAlgebraInstructions = {10, 90}; kChanceOfReplacingLinearAlgebraInstructions = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithGlobals = { const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithGlobals = {
30, 70}; 30, 70};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithStruct = {
20, 40};
const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95}; const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
const std::pair<uint32_t, uint32_t> kChanceOfSwappingConditionalBranchOperands = const std::pair<uint32_t, uint32_t> kChanceOfSwappingConditionalBranchOperands =
{10, 70}; {10, 70};
@ -99,6 +101,7 @@ const uint32_t kDefaultMaxNewArraySizeLimit = 100;
// think whether there is a better limit on the maximum number of parameters. // think whether there is a better limit on the maximum number of parameters.
const uint32_t kDefaultMaxNumberOfFunctionParameters = 128; const uint32_t kDefaultMaxNumberOfFunctionParameters = 128;
const uint32_t kDefaultMaxNumberOfNewParameters = 15; const uint32_t kDefaultMaxNumberOfNewParameters = 15;
const uint32_t kGetDefaultMaxNumberOfParametersReplacedWithStruct = 5;
// Default functions for controlling how deep to go during recursive // Default functions for controlling how deep to go during recursive
// generation/transformation. Keep them in alphabetical order. // generation/transformation. Keep them in alphabetical order.
@ -124,6 +127,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
max_new_array_size_limit_(kDefaultMaxNewArraySizeLimit), max_new_array_size_limit_(kDefaultMaxNewArraySizeLimit),
max_number_of_function_parameters_(kDefaultMaxNumberOfFunctionParameters), max_number_of_function_parameters_(kDefaultMaxNumberOfFunctionParameters),
max_number_of_new_parameters_(kDefaultMaxNumberOfNewParameters), max_number_of_new_parameters_(kDefaultMaxNumberOfNewParameters),
max_number_of_parameters_replaced_with_struct_(
kGetDefaultMaxNumberOfParametersReplacedWithStruct),
go_deeper_in_constant_obfuscation_( go_deeper_in_constant_obfuscation_(
kDefaultGoDeeperInConstantObfuscation) { kDefaultGoDeeperInConstantObfuscation) {
chance_of_adding_access_chain_ = chance_of_adding_access_chain_ =
@ -211,6 +216,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfReplacingLinearAlgebraInstructions); ChooseBetweenMinAndMax(kChanceOfReplacingLinearAlgebraInstructions);
chance_of_replacing_parameters_with_globals_ = chance_of_replacing_parameters_with_globals_ =
ChooseBetweenMinAndMax(kChanceOfReplacingParametersWithGlobals); ChooseBetweenMinAndMax(kChanceOfReplacingParametersWithGlobals);
chance_of_replacing_parameters_with_struct_ =
ChooseBetweenMinAndMax(kChanceOfReplacingParametersWithStruct);
chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock); chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
chance_of_swapping_conditional_branch_operands_ = chance_of_swapping_conditional_branch_operands_ =
ChooseBetweenMinAndMax(kChanceOfSwappingConditionalBranchOperands); ChooseBetweenMinAndMax(kChanceOfSwappingConditionalBranchOperands);

View File

@ -221,6 +221,9 @@ class FuzzerContext {
uint32_t GetChanceOfReplacingParametersWithGlobals() { uint32_t GetChanceOfReplacingParametersWithGlobals() {
return chance_of_replacing_parameters_with_globals_; return chance_of_replacing_parameters_with_globals_;
} }
uint32_t GetChanceOfReplacingParametersWithStruct() {
return chance_of_replacing_parameters_with_struct_;
}
uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; } uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; }
uint32_t GetChanceOfSwappingConditionalBranchOperands() { uint32_t GetChanceOfSwappingConditionalBranchOperands() {
return chance_of_swapping_conditional_branch_operands_; return chance_of_swapping_conditional_branch_operands_;
@ -237,6 +240,9 @@ class FuzzerContext {
uint32_t GetMaximumNumberOfFunctionParameters() { uint32_t GetMaximumNumberOfFunctionParameters() {
return max_number_of_function_parameters_; return max_number_of_function_parameters_;
} }
uint32_t GetMaximumNumberOfParametersReplacedWithStruct() {
return max_number_of_parameters_replaced_with_struct_;
}
std::pair<uint32_t, uint32_t> GetRandomBranchWeights() { std::pair<uint32_t, uint32_t> GetRandomBranchWeights() {
std::pair<uint32_t, uint32_t> branch_weights = {0, 0}; std::pair<uint32_t, uint32_t> branch_weights = {0, 0};
@ -278,6 +284,12 @@ class FuzzerContext {
{1, std::min(max_number_of_new_parameters_, {1, std::min(max_number_of_new_parameters_,
GetMaximumNumberOfFunctionParameters() - num_of_params)}); GetMaximumNumberOfFunctionParameters() - num_of_params)});
} }
uint32_t GetRandomNumberOfParametersReplacedWithStruct(uint32_t num_params) {
assert(num_params != 0 && "A function must have parameters to replace");
return ChooseBetweenMinAndMax(
{1, std::min(num_params,
GetMaximumNumberOfParametersReplacedWithStruct())});
}
uint32_t GetRandomSizeForNewArray() { uint32_t GetRandomSizeForNewArray() {
// Ensure that the array size is non-zero. // Ensure that the array size is non-zero.
return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1; return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1;
@ -345,6 +357,7 @@ class FuzzerContext {
uint32_t chance_of_replacing_id_with_synonym_; uint32_t chance_of_replacing_id_with_synonym_;
uint32_t chance_of_replacing_linear_algebra_instructions_; uint32_t chance_of_replacing_linear_algebra_instructions_;
uint32_t chance_of_replacing_parameters_with_globals_; uint32_t chance_of_replacing_parameters_with_globals_;
uint32_t chance_of_replacing_parameters_with_struct_;
uint32_t chance_of_splitting_block_; uint32_t chance_of_splitting_block_;
uint32_t chance_of_swapping_conditional_branch_operands_; uint32_t chance_of_swapping_conditional_branch_operands_;
uint32_t chance_of_toggling_access_chain_instruction_; uint32_t chance_of_toggling_access_chain_instruction_;
@ -359,6 +372,7 @@ class FuzzerContext {
uint32_t max_new_array_size_limit_; uint32_t max_new_array_size_limit_;
uint32_t max_number_of_function_parameters_; uint32_t max_number_of_function_parameters_;
uint32_t max_number_of_new_parameters_; uint32_t max_number_of_new_parameters_;
uint32_t max_number_of_parameters_replaced_with_struct_;
// Functions to determine with what probability to go deeper when generating // Functions to determine with what probability to go deeper when generating
// or mutating constructs recursively. // or mutating constructs recursively.

View File

@ -0,0 +1,115 @@
// Copyright (c) 2020 Vasyl Teliman
//
// 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_replace_params_with_struct.h"
#include <numeric>
#include <vector>
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_replace_params_with_struct.h"
namespace spvtools {
namespace fuzz {
FuzzerPassReplaceParamsWithStruct::FuzzerPassReplaceParamsWithStruct(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassReplaceParamsWithStruct::~FuzzerPassReplaceParamsWithStruct() =
default;
void FuzzerPassReplaceParamsWithStruct::Apply() {
for (const auto& function : *GetIRContext()->module()) {
auto params =
fuzzerutil::GetParameters(GetIRContext(), function.result_id());
if (params.empty() || fuzzerutil::FunctionIsEntryPoint(
GetIRContext(), function.result_id())) {
continue;
}
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfReplacingParametersWithStruct())) {
continue;
}
std::vector<uint32_t> parameter_index(params.size());
std::iota(parameter_index.begin(), parameter_index.end(), 0);
// Remove the indices of unsupported parameters.
auto new_end = std::remove_if(
parameter_index.begin(), parameter_index.end(),
[this, &params](uint32_t index) {
const auto* type =
GetIRContext()->get_type_mgr()->GetType(params[index]->type_id());
assert(type && "Parameter has invalid type");
return !TransformationReplaceParamsWithStruct::
IsParameterTypeSupported(*type);
});
// std::remove_if changes the vector so that removed elements are placed at
// the end (i.e. [new_end, parameter_index.end()) is a range of removed
// elements). However, the size of the vector is not changed so we need to
// change that explicitly by popping those elements from the vector.
parameter_index.erase(new_end, parameter_index.end());
if (parameter_index.empty()) {
continue;
}
// Select |num_replaced_params| parameters at random. We shuffle the vector
// of indices for randomization and shrink it to select first
// |num_replaced_params| parameters.
auto num_replaced_params = std::min<size_t>(
parameter_index.size(),
GetFuzzerContext()->GetRandomNumberOfParametersReplacedWithStruct(
static_cast<uint32_t>(params.size())));
GetFuzzerContext()->Shuffle(&parameter_index);
parameter_index.resize(num_replaced_params);
// Make sure OpTypeStruct exists in the module.
std::vector<uint32_t> component_type_ids;
for (auto index : parameter_index) {
component_type_ids.push_back(params[index]->type_id());
}
FindOrCreateStructType(component_type_ids);
// Map parameters' indices to parameters' ids.
std::vector<uint32_t> parameter_id;
for (auto index : parameter_index) {
parameter_id.push_back(params[index]->result_id());
}
std::unordered_map<uint32_t, uint32_t> caller_id_to_fresh_id;
for (const auto* inst :
fuzzerutil::GetCallers(GetIRContext(), function.result_id())) {
caller_id_to_fresh_id[inst->result_id()] =
GetFuzzerContext()->GetFreshId();
}
ApplyTransformation(TransformationReplaceParamsWithStruct(
parameter_id, GetFuzzerContext()->GetFreshId(),
GetFuzzerContext()->GetFreshId(), caller_id_to_fresh_id));
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,40 @@
// Copyright (c) 2020 Vasyl Teliman
//
// 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_REPLACE_PARAMS_WITH_STRUCT_H_
#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_PARAMS_WITH_STRUCT_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Iterates over all functions in the module and randomly decides for each one
// whether to replace a subset of its parameters with a struct value.
class FuzzerPassReplaceParamsWithStruct : public FuzzerPass {
public:
FuzzerPassReplaceParamsWithStruct(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassReplaceParamsWithStruct() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_PARAMS_WITH_STRUCT_H_

View File

@ -728,6 +728,37 @@ std::vector<opt::Instruction*> GetParameters(opt::IRContext* ir_context,
return result; return result;
} }
std::vector<opt::Instruction*> GetCallers(opt::IRContext* ir_context,
uint32_t function_id) {
assert(FindFunction(ir_context, function_id) &&
"|function_id| is not a result id of a function");
std::vector<opt::Instruction*> result;
ir_context->get_def_use_mgr()->ForEachUser(
function_id, [&result, function_id](opt::Instruction* inst) {
if (inst->opcode() == SpvOpFunctionCall &&
inst->GetSingleWordInOperand(0) == function_id) {
result.push_back(inst);
}
});
return result;
}
opt::Function* GetFunctionFromParameterId(opt::IRContext* ir_context,
uint32_t param_id) {
auto* param_inst = ir_context->get_def_use_mgr()->GetDef(param_id);
assert(param_inst && "Parameter id is invalid");
for (auto& function : *ir_context->module()) {
if (InstructionIsFunctionParameter(param_inst, &function)) {
return &function;
}
}
return nullptr;
}
void AddFunctionType(opt::IRContext* ir_context, uint32_t result_id, void AddFunctionType(opt::IRContext* ir_context, uint32_t result_id,
const std::vector<uint32_t>& type_ids) { const std::vector<uint32_t>& type_ids) {
assert(result_id != 0 && "Result id can't be 0"); assert(result_id != 0 && "Result id can't be 0");

View File

@ -277,6 +277,16 @@ bool IsPermutationOfRange(const std::vector<uint32_t>& arr, uint32_t lo,
std::vector<opt::Instruction*> GetParameters(opt::IRContext* ir_context, std::vector<opt::Instruction*> GetParameters(opt::IRContext* ir_context,
uint32_t function_id); uint32_t function_id);
// Returns all OpFunctionCall instructions that call a function with result id
// |function_id|.
std::vector<opt::Instruction*> GetCallers(opt::IRContext* ir_context,
uint32_t function_id);
// Returns a function that contains OpFunctionParameter instruction with result
// id |param_id|. Returns nullptr if the module has no such function.
opt::Function* GetFunctionFromParameterId(opt::IRContext* ir_context,
uint32_t param_id);
// Creates new OpTypeFunction instruction in the module. |type_ids| may not be // Creates new OpTypeFunction instruction in the module. |type_ids| may not be
// empty. It may not contain result ids of OpTypeFunction instructions. // empty. It may not contain result ids of OpTypeFunction instructions.
// |type_ids[i]| may not be a result id of OpTypeVoid instruction for |i >= 1|. // |type_ids[i]| may not be a result id of OpTypeVoid instruction for |i >= 1|.

View File

@ -403,6 +403,7 @@ message Transformation {
TransformationRecordSynonymousConstants record_synonymous_constants = 56; TransformationRecordSynonymousConstants record_synonymous_constants = 56;
TransformationAddSynonym add_synonym = 57; TransformationAddSynonym add_synonym = 57;
TransformationAddRelaxedDecoration add_relaxed_decoration = 58; TransformationAddRelaxedDecoration add_relaxed_decoration = 58;
TransformationReplaceParamsWithStruct replace_params_with_struct = 59;
// Add additional option using the next available number. // Add additional option using the next available number.
} }
} }
@ -1313,6 +1314,31 @@ message TransformationReplaceLinearAlgebraInstruction {
} }
message TransformationReplaceParamsWithStruct {
// Replaces parameters of the function with a struct containing
// values of those parameters.
// Result ids of parameters to replace.
repeated uint32 parameter_id = 1;
// Fresh id for a new function type. This might be unused if the required type
// already exists in the module or if we can change the old type.
uint32 fresh_function_type_id = 2;
// Fresh id for a new struct function parameter to be used as a replacement.
uint32 fresh_parameter_id = 3;
// Fresh ids for struct objects containing values of replaced parameters.
// This map contains a fresh id for at least every result id of a relevant
// OpFunctionCall instruction.
//
// While maps are not fully deterministic, the way this map is used does not
// exhibit nondeterminism. Change to repeated Uint32Pair if this changes.
map<uint32, uint32> caller_id_to_fresh_composite_id = 4;
}
message TransformationSetFunctionControl { message TransformationSetFunctionControl {
// A transformation that sets the function control operand of an OpFunction // A transformation that sets the function control operand of an OpFunction

View File

@ -65,6 +65,7 @@
#include "source/fuzz/transformation_replace_id_with_synonym.h" #include "source/fuzz/transformation_replace_id_with_synonym.h"
#include "source/fuzz/transformation_replace_linear_algebra_instruction.h" #include "source/fuzz/transformation_replace_linear_algebra_instruction.h"
#include "source/fuzz/transformation_replace_parameter_with_global.h" #include "source/fuzz/transformation_replace_parameter_with_global.h"
#include "source/fuzz/transformation_replace_params_with_struct.h"
#include "source/fuzz/transformation_set_function_control.h" #include "source/fuzz/transformation_set_function_control.h"
#include "source/fuzz/transformation_set_loop_control.h" #include "source/fuzz/transformation_set_loop_control.h"
#include "source/fuzz/transformation_set_memory_operands_mask.h" #include "source/fuzz/transformation_set_memory_operands_mask.h"
@ -222,6 +223,10 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
kReplaceLinearAlgebraInstruction: kReplaceLinearAlgebraInstruction:
return MakeUnique<TransformationReplaceLinearAlgebraInstruction>( return MakeUnique<TransformationReplaceLinearAlgebraInstruction>(
message.replace_linear_algebra_instruction()); message.replace_linear_algebra_instruction());
case protobufs::Transformation::TransformationCase::
kReplaceParamsWithStruct:
return MakeUnique<TransformationReplaceParamsWithStruct>(
message.replace_params_with_struct());
case protobufs::Transformation::TransformationCase::kSetFunctionControl: case protobufs::Transformation::TransformationCase::kSetFunctionControl:
return MakeUnique<TransformationSetFunctionControl>( return MakeUnique<TransformationSetFunctionControl>(
message.set_function_control()); message.set_function_control());

View File

@ -91,29 +91,29 @@ void TransformationPermuteFunctionParameters::Apply(
fuzzerutil::GetFunctionType(ir_context, function); fuzzerutil::GetFunctionType(ir_context, function);
assert(old_function_type_inst && "Function must have a valid type"); assert(old_function_type_inst && "Function must have a valid type");
// Change function's type std::vector<uint32_t> type_ids = {
if (ir_context->get_def_use_mgr()->NumUsers(old_function_type_inst) == 1) { old_function_type_inst->GetSingleWordInOperand(0)};
for (auto index : message_.permutation()) {
// +1 since the first operand to OpTypeFunction is a return type.
type_ids.push_back(
old_function_type_inst->GetSingleWordInOperand(index + 1));
}
// Change function's type.
if (ir_context->get_def_use_mgr()->NumUsers(old_function_type_inst) == 1 &&
fuzzerutil::FindFunctionType(ir_context, type_ids) == 0) {
// If only the current function uses |old_function_type_inst| - change it // If only the current function uses |old_function_type_inst| - change it
// in-place. // in-place. We can only do that if the module doesn't contain
opt::Instruction::OperandList permuted_operands = { // a function type with the permuted order of operands.
std::move(old_function_type_inst->GetInOperand(0))}; opt::Instruction::OperandList permuted_operands;
for (auto index : message_.permutation()) { for (auto id : type_ids) {
// +1 since the first operand to OpTypeFunction is a return type. // +1 since the first operand to OpTypeFunction is a return type.
permuted_operands.push_back( permuted_operands.push_back({SPV_OPERAND_TYPE_ID, {id}});
std::move(old_function_type_inst->GetInOperand(index + 1)));
} }
old_function_type_inst->SetInOperands(std::move(permuted_operands)); old_function_type_inst->SetInOperands(std::move(permuted_operands));
} else { } else {
// Either use an existing type or create a new one. // Either use an existing type or create a new one.
std::vector<uint32_t> type_ids = {
old_function_type_inst->GetSingleWordInOperand(0)};
for (auto index : message_.permutation()) {
// +1 since the first operand to OpTypeFunction is a return type.
type_ids.push_back(
old_function_type_inst->GetSingleWordInOperand(index + 1));
}
function->DefInst().SetInOperand( function->DefInst().SetInOperand(
1, {fuzzerutil::FindOrCreateFunctionType( 1, {fuzzerutil::FindOrCreateFunctionType(
ir_context, message_.function_type_fresh_id(), type_ids)}); ir_context, message_.function_type_fresh_id(), type_ids)});
@ -146,24 +146,18 @@ void TransformationPermuteFunctionParameters::Apply(
}); });
// Fix all OpFunctionCall instructions // Fix all OpFunctionCall instructions
ir_context->get_def_use_mgr()->ForEachUser( for (auto* call : fuzzerutil::GetCallers(ir_context, function->result_id())) {
&function->DefInst(), [this](opt::Instruction* call) { opt::Instruction::OperandList call_operands = {
if (call->opcode() != SpvOpFunctionCall || call->GetInOperand(0) // Function id
call->GetSingleWordInOperand(0) != message_.function_id()) { };
return;
}
opt::Instruction::OperandList call_operands = { for (auto index : message_.permutation()) {
call->GetInOperand(0) // Function id // Take function id into account
}; call_operands.push_back(call->GetInOperand(index + 1));
}
for (auto index : message_.permutation()) { call->SetInOperands(std::move(call_operands));
// Take function id into account }
call_operands.push_back(call->GetInOperand(index + 1));
}
call->SetInOperands(std::move(call_operands));
});
// Make sure our changes are analyzed // Make sure our changes are analyzed
ir_context->InvalidateAnalysesExceptFor( ir_context->InvalidateAnalysesExceptFor(

View File

@ -20,23 +20,6 @@
namespace spvtools { namespace spvtools {
namespace fuzz { namespace fuzz {
namespace {
opt::Function* GetFunctionFromParameterId(opt::IRContext* ir_context,
uint32_t param_id) {
auto* param_inst = ir_context->get_def_use_mgr()->GetDef(param_id);
assert(param_inst && "Parameter id is invalid");
for (auto& function : *ir_context->module()) {
if (fuzzerutil::InstructionIsFunctionParameter(param_inst, &function)) {
return &function;
}
}
return nullptr;
}
} // namespace
TransformationReplaceParameterWithGlobal:: TransformationReplaceParameterWithGlobal::
TransformationReplaceParameterWithGlobal( TransformationReplaceParameterWithGlobal(
@ -63,8 +46,8 @@ bool TransformationReplaceParameterWithGlobal::IsApplicable(
} }
// Check that function exists and is not an entry point. // Check that function exists and is not an entry point.
const auto* function = const auto* function = fuzzerutil::GetFunctionFromParameterId(
GetFunctionFromParameterId(ir_context, message_.parameter_id()); ir_context, message_.parameter_id());
if (!function || if (!function ||
fuzzerutil::FunctionIsEntryPoint(ir_context, function->result_id())) { fuzzerutil::FunctionIsEntryPoint(ir_context, function->result_id())) {
return false; return false;
@ -124,8 +107,8 @@ void TransformationReplaceParameterWithGlobal::Apply(
message_.global_variable_fresh_id()); message_.global_variable_fresh_id());
} }
auto* function = auto* function = fuzzerutil::GetFunctionFromParameterId(
GetFunctionFromParameterId(ir_context, message_.parameter_id()); ir_context, message_.parameter_id());
assert(function && "Function must exist"); assert(function && "Function must exist");
// Insert an OpLoad instruction right after OpVariable instructions. // Insert an OpLoad instruction right after OpVariable instructions.
@ -159,29 +142,23 @@ void TransformationReplaceParameterWithGlobal::Apply(
"Parameter must exist in the function"); "Parameter must exist in the function");
// Update all relevant OpFunctionCall instructions. // Update all relevant OpFunctionCall instructions.
ir_context->get_def_use_mgr()->ForEachUser( for (auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) {
function->result_id(), assert(fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore, inst) &&
[ir_context, parameter_index, this](opt::Instruction* inst) { "Can't insert OpStore right before the function call");
if (inst->opcode() != SpvOpFunctionCall) {
return;
}
assert(fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore, inst) && // Insert an OpStore before the OpFunctionCall. +1 since the first
"Can't insert OpStore right before the function call"); // operand of OpFunctionCall is an id of the function.
inst->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpStore, 0, 0,
opt::Instruction::OperandList{
{SPV_OPERAND_TYPE_ID, {message_.global_variable_fresh_id()}},
{SPV_OPERAND_TYPE_ID,
{inst->GetSingleWordInOperand(parameter_index + 1)}}}));
// Insert an OpStore before the OpFunctionCall. +1 since the first // +1 since the first operand of OpFunctionCall is an id of the
// operand of OpFunctionCall is an id of the function. // function.
inst->InsertBefore(MakeUnique<opt::Instruction>( inst->RemoveInOperand(parameter_index + 1);
ir_context, SpvOpStore, 0, 0, }
opt::Instruction::OperandList{
{SPV_OPERAND_TYPE_ID, {message_.global_variable_fresh_id()}},
{SPV_OPERAND_TYPE_ID,
{inst->GetSingleWordInOperand(parameter_index + 1)}}}));
// +1 since the first operand of OpFunctionCall is an id of the
// function.
inst->RemoveInOperand(parameter_index + 1);
});
// Remove the parameter from the function. // Remove the parameter from the function.
function->RemoveParameter(message_.parameter_id()); function->RemoveParameter(message_.parameter_id());
@ -201,7 +178,8 @@ void TransformationReplaceParameterWithGlobal::Apply(
} }
} }
if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1) { if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1 &&
fuzzerutil::FindFunctionType(ir_context, type_ids) == 0) {
// Change the old type in place. +1 since the first operand is the result // Change the old type in place. +1 since the first operand is the result
// type id of the function. // type id of the function.
old_function_type->RemoveInOperand(parameter_index + 1); old_function_type->RemoveInOperand(parameter_index + 1);

View File

@ -0,0 +1,319 @@
// Copyright (c) 2020 Vasyl Teliman
//
// 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_replace_params_with_struct.h"
#include <vector>
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
TransformationReplaceParamsWithStruct::TransformationReplaceParamsWithStruct(
const protobufs::TransformationReplaceParamsWithStruct& message)
: message_(message) {}
TransformationReplaceParamsWithStruct::TransformationReplaceParamsWithStruct(
const std::vector<uint32_t>& parameter_id, uint32_t fresh_function_type_id,
uint32_t fresh_parameter_id,
const std::unordered_map<uint32_t, uint32_t>&
caller_id_to_fresh_composite_id) {
message_.set_fresh_function_type_id(fresh_function_type_id);
message_.set_fresh_parameter_id(fresh_parameter_id);
for (auto id : parameter_id) {
message_.add_parameter_id(id);
}
message_.mutable_caller_id_to_fresh_composite_id()->insert(
caller_id_to_fresh_composite_id.begin(),
caller_id_to_fresh_composite_id.end());
}
bool TransformationReplaceParamsWithStruct::IsApplicable(
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
std::vector<uint32_t> parameter_id(message_.parameter_id().begin(),
message_.parameter_id().end());
// Check that |parameter_id| is neither empty nor it has duplicates.
if (parameter_id.empty() || fuzzerutil::HasDuplicates(parameter_id)) {
return false;
}
// All ids must correspond to valid parameters of the same function.
// The function can't be an entry-point function.
// fuzzerutil::GetFunctionFromParameterId requires a valid id.
if (!ir_context->get_def_use_mgr()->GetDef(parameter_id[0])) {
return false;
}
const auto* function =
fuzzerutil::GetFunctionFromParameterId(ir_context, parameter_id[0]);
if (!function ||
fuzzerutil::FunctionIsEntryPoint(ir_context, function->result_id())) {
return false;
}
// Compute all ids of the function's parameters.
std::unordered_set<uint32_t> all_parameter_ids;
for (const auto* param :
fuzzerutil::GetParameters(ir_context, function->result_id())) {
all_parameter_ids.insert(param->result_id());
}
// Check that all elements in |parameter_id| are valid.
for (auto id : parameter_id) {
// fuzzerutil::GetFunctionFromParameterId requires a valid id.
if (!ir_context->get_def_use_mgr()->GetDef(id)) {
return false;
}
// Check that |id| is a result id of one of the |function|'s parameters.
if (!all_parameter_ids.count(id)) {
return false;
}
// Check that the parameter with result id |id| has supported type.
const auto* type = ir_context->get_type_mgr()->GetType(
fuzzerutil::GetTypeId(ir_context, id));
assert(type && "Parameter has invalid type");
if (!IsParameterTypeSupported(*type)) {
return false;
}
}
// We already know that the function has at least |parameter_id.size()|
// parameters.
// Check that a relevant OpTypeStruct exists in the module.
if (!MaybeGetRequiredStructType(ir_context)) {
return false;
}
// Check that |callee_id_to_fresh_composite_id| is valid.
for (const auto* inst :
fuzzerutil::GetCallers(ir_context, function->result_id())) {
// Check that the callee is present in the map. It's ok if the map contains
// more ids that there are callees (those ids will not be used).
if (!message_.caller_id_to_fresh_composite_id().contains(
inst->result_id())) {
return false;
}
}
// Check that all fresh ids are unique and fresh.
std::vector<uint32_t> fresh_ids = {message_.fresh_function_type_id(),
message_.fresh_parameter_id()};
for (const auto& entry : message_.caller_id_to_fresh_composite_id()) {
fresh_ids.push_back(entry.second);
}
return !fuzzerutil::HasDuplicates(fresh_ids) &&
std::all_of(fresh_ids.begin(), fresh_ids.end(),
[ir_context](uint32_t id) {
return fuzzerutil::IsFreshId(ir_context, id);
});
}
void TransformationReplaceParamsWithStruct::Apply(
opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
auto* function = fuzzerutil::GetFunctionFromParameterId(
ir_context, message_.parameter_id(0));
assert(function &&
"All parameters' ids should've been checked in the IsApplicable");
// Get a type id of the OpTypeStruct used as a type id of the new parameter.
auto struct_type_id = MaybeGetRequiredStructType(ir_context);
assert(struct_type_id &&
"IsApplicable should've guaranteed that this value isn't equal to 0");
// Add new parameter to the function.
function->AddParameter(MakeUnique<opt::Instruction>(
ir_context, SpvOpFunctionParameter, struct_type_id,
message_.fresh_parameter_id(), opt::Instruction::OperandList()));
fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_parameter_id());
// Compute indices of replaced parameters. This will be used to adjust
// OpFunctionCall instructions and create OpCompositeConstruct instructions at
// every call site.
std::vector<uint32_t> indices_of_replaced_params;
{
// We want to destroy |params| after the loop because it will contain
// dangling pointers when we remove parameters from the function.
auto params = fuzzerutil::GetParameters(ir_context, function->result_id());
for (auto id : message_.parameter_id()) {
auto it = std::find_if(params.begin(), params.end(),
[id](const opt::Instruction* param) {
return param->result_id() == id;
});
assert(it != params.end() && "Parameter's id is invalid");
indices_of_replaced_params.push_back(
static_cast<uint32_t>(it - params.begin()));
}
}
// Update all function calls.
for (auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) {
// Create a list of operands for the OpCompositeConstruct instruction.
opt::Instruction::OperandList composite_components;
for (auto index : indices_of_replaced_params) {
// +1 since the first in operand to OpFunctionCall is the result id of
// the function.
composite_components.emplace_back(
std::move(inst->GetInOperand(index + 1)));
}
// Remove arguments from the function call. We do it in a separate loop
// and in reverse order to make sure we have removed correct operands.
for (auto it = indices_of_replaced_params.rbegin();
it != indices_of_replaced_params.rend(); ++it) {
// +1 since the first in operand to OpFunctionCall is the result id of
// the function.
inst->RemoveInOperand(*it + 1);
}
// Insert OpCompositeConstruct before the function call.
auto fresh_composite_id =
message_.caller_id_to_fresh_composite_id().at(inst->result_id());
inst->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpCompositeConstruct, struct_type_id, fresh_composite_id,
std::move(composite_components)));
// Add a new operand to the OpFunctionCall instruction.
inst->AddOperand({SPV_OPERAND_TYPE_ID, {fresh_composite_id}});
fuzzerutil::UpdateModuleIdBound(ir_context, fresh_composite_id);
}
// Insert OpCompositeExtract instructions into the entry point block of the
// function and remove replaced parameters.
for (int i = 0; i < message_.parameter_id_size(); ++i) {
const auto* param_inst =
ir_context->get_def_use_mgr()->GetDef(message_.parameter_id(i));
assert(param_inst && "Parameter id is invalid");
// Skip all OpVariable instructions.
auto iter = function->begin()->begin();
while (iter != function->begin()->end() &&
!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCompositeExtract,
iter)) {
++iter;
}
assert(fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCompositeExtract,
iter) &&
"Can't extract parameter's value from the structure");
// Insert OpCompositeExtract instructions to unpack parameters' values from
// the struct type.
iter.InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpCompositeExtract, param_inst->type_id(),
param_inst->result_id(),
opt::Instruction::OperandList{
{SPV_OPERAND_TYPE_ID, {message_.fresh_parameter_id()}},
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {static_cast<uint32_t>(i)}}}));
function->RemoveParameter(param_inst->result_id());
}
// Update function's type.
auto* old_function_type = fuzzerutil::GetFunctionType(ir_context, function);
assert(old_function_type && "Function has invalid type");
std::vector<uint32_t> type_ids = {
// Result type of the function.
old_function_type->GetSingleWordInOperand(0)};
// +1 since the first in operand to OpTypeFunction is the result type id
// of the function.
for (uint32_t i = 1; i < old_function_type->NumInOperands(); ++i) {
if (std::find(indices_of_replaced_params.begin(),
indices_of_replaced_params.end(),
i - 1) == indices_of_replaced_params.end()) {
type_ids.push_back(old_function_type->GetSingleWordInOperand(i));
}
}
type_ids.push_back(struct_type_id);
if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1 &&
fuzzerutil::FindFunctionType(ir_context, type_ids) == 0) {
// Update |old_function_type| in place.
opt::Instruction::OperandList replaced_operands;
for (auto id : type_ids) {
replaced_operands.push_back({SPV_OPERAND_TYPE_ID, {id}});
}
old_function_type->SetInOperands(std::move(replaced_operands));
// Make sure domination rules are satisfied.
old_function_type->RemoveFromList();
ir_context->AddType(std::unique_ptr<opt::Instruction>(old_function_type));
} else {
// Create a new function type or use an existing one.
function->DefInst().SetInOperand(
1, {fuzzerutil::FindOrCreateFunctionType(
ir_context, message_.fresh_function_type_id(), type_ids)});
}
// Make sure our changes are analyzed
ir_context->InvalidateAnalysesExceptFor(
opt::IRContext::Analysis::kAnalysisNone);
}
protobufs::Transformation TransformationReplaceParamsWithStruct::ToMessage()
const {
protobufs::Transformation result;
*result.mutable_replace_params_with_struct() = message_;
return result;
}
bool TransformationReplaceParamsWithStruct::IsParameterTypeSupported(
const opt::analysis::Type& param_type) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
// Consider adding support for more types of parameters.
switch (param_type.kind()) {
case opt::analysis::Type::kBool:
case opt::analysis::Type::kInteger:
case opt::analysis::Type::kFloat:
case opt::analysis::Type::kArray:
case opt::analysis::Type::kVector:
case opt::analysis::Type::kMatrix:
return true;
case opt::analysis::Type::kStruct:
return std::all_of(param_type.AsStruct()->element_types().begin(),
param_type.AsStruct()->element_types().end(),
[](const opt::analysis::Type* type) {
return IsParameterTypeSupported(*type);
});
default:
return false;
}
}
uint32_t TransformationReplaceParamsWithStruct::MaybeGetRequiredStructType(
opt::IRContext* ir_context) const {
std::vector<uint32_t> component_type_ids;
for (auto id : message_.parameter_id()) {
component_type_ids.push_back(fuzzerutil::GetTypeId(ir_context, id));
}
return fuzzerutil::MaybeGetStructType(ir_context, component_type_ids);
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,83 @@
// Copyright (c) 2020 Vasyl Teliman
//
// 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_REPLACE_PARAMS_WITH_STRUCT_H_
#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_PARAMS_WITH_STRUCT_H_
#include <unordered_map>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation.h"
#include "source/fuzz/transformation_context.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
class TransformationReplaceParamsWithStruct : public Transformation {
public:
explicit TransformationReplaceParamsWithStruct(
const protobufs::TransformationReplaceParamsWithStruct& message);
TransformationReplaceParamsWithStruct(
const std::vector<uint32_t>& parameter_id,
uint32_t fresh_function_type_id, uint32_t fresh_parameter_id,
const std::unordered_map<uint32_t, uint32_t>&
caller_id_to_fresh_composite_id);
// - Each element of |parameter_id| is a valid result id of some
// OpFunctionParameter instruction. All parameter ids must correspond to
// parameters of the same function. That function may not be an entry-point
// function.
// - Types of all parameters must be supported by this transformation (see
// IsParameterTypeSupported method).
// - |parameter_id| may not be empty or contain duplicates.
// - There must exist an OpTypeStruct instruction containing types of all
// replaced parameters. Type of the i'th component of the struct is equal
// to the type of the instruction with result id |parameter_id[i]|.
// - |caller_id_to_fresh_composite_id| should contain a key for at least every
// result id of an OpFunctionCall instruction that calls the function.
// - |fresh_function_type_id|, |fresh_parameter_id|,
// |caller_id_to_fresh_composite_id| are all fresh and unique ids.
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// - Creates a new function parameter with result id |fresh_parameter_id|.
// Parameter's type is OpTypeStruct with each components type equal to the
// type of the replaced parameter.
// - OpCompositeConstruct with result id from |fresh_composite_id| is inserted
// before each OpFunctionCall instruction.
// - OpCompositeExtract with result id equal to the result id of the replaced
// parameter is created in the function.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
protobufs::Transformation ToMessage() const override;
// Returns true if parameter's type is supported by this transformation.
static bool IsParameterTypeSupported(const opt::analysis::Type& param_type);
private:
// Returns a result id of the OpTypeStruct instruction required by this
// transformation (see docs on the IsApplicable method to learn more).
uint32_t MaybeGetRequiredStructType(opt::IRContext* ir_context) const;
protobufs::TransformationReplaceParamsWithStruct message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_PARAMS_WITH_STRUCT_H_

View File

@ -71,6 +71,7 @@ if (${SPIRV_BUILD_FUZZER})
transformation_replace_constant_with_uniform_test.cpp transformation_replace_constant_with_uniform_test.cpp
transformation_replace_id_with_synonym_test.cpp transformation_replace_id_with_synonym_test.cpp
transformation_replace_linear_algebra_instruction_test.cpp transformation_replace_linear_algebra_instruction_test.cpp
transformation_replace_params_with_struct_test.cpp
transformation_set_function_control_test.cpp transformation_set_function_control_test.cpp
transformation_set_loop_control_test.cpp transformation_set_loop_control_test.cpp
transformation_set_memory_operands_mask_test.cpp transformation_set_memory_operands_mask_test.cpp

View File

@ -90,6 +90,11 @@ TEST(TransformationPermuteFunctionParametersTest, BasicTest) {
%84 = OpConstant %14 5 %84 = OpConstant %14 5
%90 = OpConstant %6 3 %90 = OpConstant %6 3
%98 = OpConstant %6 4 %98 = OpConstant %6 4
%206 = OpTypeFunction %2 %14 %16
%223 = OpTypeFunction %2 %6 %8
%224 = OpTypeFunction %2 %8 %6
%233 = OpTypeFunction %2 %42 %24
%234 = OpTypeFunction %2 %24 %42
%4 = OpFunction %2 None %3 %4 = OpFunction %2 None %3
%5 = OpLabel %5 = OpLabel
%66 = OpVariable %15 Function %66 = OpVariable %15 Function
@ -148,6 +153,8 @@ TEST(TransformationPermuteFunctionParametersTest, BasicTest) {
%70 = OpLabel %70 = OpLabel
OpReturn OpReturn
OpFunctionEnd OpFunctionEnd
; adjust type of the function in-place
%12 = OpFunction %8 None %9 %12 = OpFunction %8 None %9
%10 = OpFunctionParameter %7 %10 = OpFunctionParameter %7
%11 = OpFunctionParameter %7 %11 = OpFunctionParameter %7
@ -192,6 +199,53 @@ TEST(TransformationPermuteFunctionParametersTest, BasicTest) {
OpReturnValue %61 OpReturnValue %61
OpFunctionEnd OpFunctionEnd
; create a new function type
%200 = OpFunction %2 None %206
%207 = OpFunctionParameter %14
%208 = OpFunctionParameter %16
%202 = OpLabel
OpReturn
OpFunctionEnd
%203 = OpFunction %2 None %206
%209 = OpFunctionParameter %14
%210 = OpFunctionParameter %16
%205 = OpLabel
OpReturn
OpFunctionEnd
; reuse an existing function type
%211 = OpFunction %2 None %223
%212 = OpFunctionParameter %6
%213 = OpFunctionParameter %8
%214 = OpLabel
OpReturn
OpFunctionEnd
%215 = OpFunction %2 None %224
%216 = OpFunctionParameter %8
%217 = OpFunctionParameter %6
%218 = OpLabel
OpReturn
OpFunctionEnd
%219 = OpFunction %2 None %224
%220 = OpFunctionParameter %8
%221 = OpFunctionParameter %6
%222 = OpLabel
OpReturn
OpFunctionEnd
; don't adjust the type of the function if it creates a duplicate
%225 = OpFunction %2 None %233
%226 = OpFunctionParameter %42
%227 = OpFunctionParameter %24
%228 = OpLabel
OpReturn
OpFunctionEnd
%229 = OpFunction %2 None %234
%230 = OpFunctionParameter %24
%231 = OpFunctionParameter %42
%232 = OpLabel
OpReturn
OpFunctionEnd
)"; )";
const auto env = SPV_ENV_UNIVERSAL_1_3; const auto env = SPV_ENV_UNIVERSAL_1_3;
@ -250,6 +304,27 @@ TEST(TransformationPermuteFunctionParametersTest, BasicTest) {
transformation.Apply(context.get(), &transformation_context); transformation.Apply(context.get(), &transformation_context);
ASSERT_TRUE(IsValid(env, context.get())); ASSERT_TRUE(IsValid(env, context.get()));
} }
{
TransformationPermuteFunctionParameters transformation(200, 107, {1, 0});
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
ASSERT_TRUE(IsValid(env, context.get()));
}
{
TransformationPermuteFunctionParameters transformation(219, 108, {1, 0});
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
ASSERT_TRUE(IsValid(env, context.get()));
}
{
TransformationPermuteFunctionParameters transformation(229, 109, {1, 0});
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
ASSERT_TRUE(IsValid(env, context.get()));
}
std::string after_transformation = R"( std::string after_transformation = R"(
OpCapability Shader OpCapability Shader
@ -320,6 +395,12 @@ TEST(TransformationPermuteFunctionParametersTest, BasicTest) {
%84 = OpConstant %14 5 %84 = OpConstant %14 5
%90 = OpConstant %6 3 %90 = OpConstant %6 3
%98 = OpConstant %6 4 %98 = OpConstant %6 4
%206 = OpTypeFunction %2 %14 %16
%223 = OpTypeFunction %2 %6 %8
%224 = OpTypeFunction %2 %8 %6
%233 = OpTypeFunction %2 %42 %24
%234 = OpTypeFunction %2 %24 %42
%107 = OpTypeFunction %2 %16 %14
%4 = OpFunction %2 None %3 %4 = OpFunction %2 None %3
%5 = OpLabel %5 = OpLabel
%66 = OpVariable %15 Function %66 = OpVariable %15 Function
@ -421,6 +502,48 @@ TEST(TransformationPermuteFunctionParametersTest, BasicTest) {
%61 = OpFOrdLessThan %24 %59 %60 %61 = OpFOrdLessThan %24 %59 %60
OpReturnValue %61 OpReturnValue %61
OpFunctionEnd OpFunctionEnd
%200 = OpFunction %2 None %107
%208 = OpFunctionParameter %16
%207 = OpFunctionParameter %14
%202 = OpLabel
OpReturn
OpFunctionEnd
%203 = OpFunction %2 None %206
%209 = OpFunctionParameter %14
%210 = OpFunctionParameter %16
%205 = OpLabel
OpReturn
OpFunctionEnd
%211 = OpFunction %2 None %223
%212 = OpFunctionParameter %6
%213 = OpFunctionParameter %8
%214 = OpLabel
OpReturn
OpFunctionEnd
%215 = OpFunction %2 None %224
%216 = OpFunctionParameter %8
%217 = OpFunctionParameter %6
%218 = OpLabel
OpReturn
OpFunctionEnd
%219 = OpFunction %2 None %223
%221 = OpFunctionParameter %6
%220 = OpFunctionParameter %8
%222 = OpLabel
OpReturn
OpFunctionEnd
%225 = OpFunction %2 None %233
%226 = OpFunctionParameter %42
%227 = OpFunctionParameter %24
%228 = OpLabel
OpReturn
OpFunctionEnd
%229 = OpFunction %2 None %233
%231 = OpFunctionParameter %42
%230 = OpFunctionParameter %24
%232 = OpLabel
OpReturn
OpFunctionEnd
)"; )";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));

View File

@ -38,6 +38,10 @@ TEST(TransformationReplaceParameterWithGlobalTest, BasicTest) {
%10 = OpTypeVector %8 2 %10 = OpTypeVector %8 2
%11 = OpTypePointer Private %10 %11 = OpTypePointer Private %10
%12 = OpTypeBool %12 = OpTypeBool
%71 = OpTypeFunction %2 %6
%83 = OpTypeFunction %2 %6 %12
%93 = OpTypeFunction %2 %10
%94 = OpTypeFunction %2 %8 %10
%40 = OpTypePointer Function %12 %40 = OpTypePointer Function %12
%13 = OpTypeStruct %6 %8 %13 = OpTypeStruct %6 %8
%14 = OpTypePointer Private %13 %14 = OpTypePointer Private %13
@ -53,6 +57,8 @@ TEST(TransformationReplaceParameterWithGlobalTest, BasicTest) {
%33 = OpFunctionCall %2 %20 %22 %23 %26 %28 %41 %27 %33 = OpFunctionCall %2 %20 %22 %23 %26 %28 %41 %27
OpReturn OpReturn
OpFunctionEnd OpFunctionEnd
; adjust type of the function in-place
%20 = OpFunction %2 None %15 %20 = OpFunction %2 None %15
%16 = OpFunctionParameter %6 %16 = OpFunctionParameter %6
%17 = OpFunctionParameter %8 %17 = OpFunctionParameter %8
@ -63,6 +69,45 @@ TEST(TransformationReplaceParameterWithGlobalTest, BasicTest) {
%21 = OpLabel %21 = OpLabel
OpReturn OpReturn
OpFunctionEnd OpFunctionEnd
; reuse an existing function type
%70 = OpFunction %2 None %71
%72 = OpFunctionParameter %6
%73 = OpLabel
OpReturn
OpFunctionEnd
%74 = OpFunction %2 None %71
%75 = OpFunctionParameter %6
%76 = OpLabel
OpReturn
OpFunctionEnd
; create a new function type
%77 = OpFunction %2 None %83
%78 = OpFunctionParameter %6
%84 = OpFunctionParameter %12
%79 = OpLabel
OpReturn
OpFunctionEnd
%80 = OpFunction %2 None %83
%81 = OpFunctionParameter %6
%85 = OpFunctionParameter %12
%82 = OpLabel
OpReturn
OpFunctionEnd
; don't adjust the type of the function if it creates a duplicate
%86 = OpFunction %2 None %93
%87 = OpFunctionParameter %10
%89 = OpLabel
OpReturn
OpFunctionEnd
%90 = OpFunction %2 None %94
%91 = OpFunctionParameter %8
%95 = OpFunctionParameter %10
%92 = OpLabel
OpReturn
OpFunctionEnd
)"; )";
const auto env = SPV_ENV_UNIVERSAL_1_3; const auto env = SPV_ENV_UNIVERSAL_1_3;
@ -131,6 +176,26 @@ TEST(TransformationReplaceParameterWithGlobalTest, BasicTest) {
transformation.IsApplicable(context.get(), transformation_context)); transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context); transformation.Apply(context.get(), &transformation_context);
} }
{
TransformationReplaceParameterWithGlobal transformation(58, 75, 59);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
}
{
TransformationReplaceParameterWithGlobal transformation(60, 81, 61);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
}
{
TransformationReplaceParameterWithGlobal transformation(62, 91, 63);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
}
ASSERT_TRUE(IsValid(env, context.get()));
std::string expected_shader = R"( std::string expected_shader = R"(
OpCapability Shader OpCapability Shader
@ -150,6 +215,10 @@ TEST(TransformationReplaceParameterWithGlobalTest, BasicTest) {
%10 = OpTypeVector %8 2 %10 = OpTypeVector %8 2
%11 = OpTypePointer Private %10 %11 = OpTypePointer Private %10
%12 = OpTypeBool %12 = OpTypeBool
%71 = OpTypeFunction %2 %6
%83 = OpTypeFunction %2 %6 %12
%93 = OpTypeFunction %2 %10
%94 = OpTypeFunction %2 %8 %10
%40 = OpTypePointer Function %12 %40 = OpTypePointer Function %12
%13 = OpTypeStruct %6 %8 %13 = OpTypeStruct %6 %8
%14 = OpTypePointer Private %13 %14 = OpTypePointer Private %13
@ -163,6 +232,10 @@ TEST(TransformationReplaceParameterWithGlobalTest, BasicTest) {
%53 = OpVariable %9 Private %23 %53 = OpVariable %9 Private %23
%55 = OpVariable %11 Private %26 %55 = OpVariable %11 Private %26
%57 = OpVariable %14 Private %28 %57 = OpVariable %14 Private %28
%59 = OpVariable %7 Private %22
%61 = OpVariable %7 Private %22
%60 = OpTypeFunction %2 %12
%63 = OpVariable %9 Private %23
%4 = OpFunction %2 None %3 %4 = OpFunction %2 None %3
%5 = OpLabel %5 = OpLabel
%41 = OpVariable %40 Function %27 %41 = OpVariable %40 Function %27
@ -183,6 +256,39 @@ TEST(TransformationReplaceParameterWithGlobalTest, BasicTest) {
%16 = OpLoad %6 %51 %16 = OpLoad %6 %51
OpReturn OpReturn
OpFunctionEnd OpFunctionEnd
%70 = OpFunction %2 None %71
%72 = OpFunctionParameter %6
%73 = OpLabel
OpReturn
OpFunctionEnd
%74 = OpFunction %2 None %3
%76 = OpLabel
%75 = OpLoad %6 %59
OpReturn
OpFunctionEnd
%77 = OpFunction %2 None %83
%78 = OpFunctionParameter %6
%84 = OpFunctionParameter %12
%79 = OpLabel
OpReturn
OpFunctionEnd
%80 = OpFunction %2 None %60
%85 = OpFunctionParameter %12
%82 = OpLabel
%81 = OpLoad %6 %61
OpReturn
OpFunctionEnd
%86 = OpFunction %2 None %93
%87 = OpFunctionParameter %10
%89 = OpLabel
OpReturn
OpFunctionEnd
%90 = OpFunction %2 None %93
%95 = OpFunctionParameter %10
%92 = OpLabel
%91 = OpLoad %8 %63
OpReturn
OpFunctionEnd
)"; )";
ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));

View File

@ -0,0 +1,339 @@
// Copyright (c) 2020 Vasyl Teliman
//
// 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_replace_params_with_struct.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
TEST(TransformationReplaceParamsWithStructTest, BasicTest) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpMemberDecorate %13 0 RelaxedPrecision
OpDecorate %16 RelaxedPrecision
%2 = OpTypeVoid
%6 = OpTypeInt 32 1
%3 = OpTypeFunction %2
%7 = OpTypePointer Private %6
%8 = OpTypeFloat 32
%9 = OpTypePointer Private %8
%10 = OpTypeVector %8 2
%11 = OpTypePointer Private %10
%12 = OpTypeBool
%51 = OpTypeFunction %2 %12
%64 = OpTypeStruct %6
%63 = OpTypeFunction %2 %64
%65 = OpTypeFunction %2 %6
%75 = OpTypeStruct %8
%76 = OpTypeFunction %2 %75
%77 = OpTypeFunction %2 %8
%40 = OpTypePointer Function %12
%13 = OpTypeStruct %6 %8
%45 = OpTypeStruct %6 %10 %13
%46 = OpTypeStruct %12
%47 = OpTypeStruct %8 %45 %46
%14 = OpTypePointer Private %13
%15 = OpTypeFunction %2 %6 %8 %10 %13 %40 %12
%22 = OpConstant %6 0
%23 = OpConstant %8 0
%26 = OpConstantComposite %10 %23 %23
%27 = OpConstantTrue %12
%28 = OpConstantComposite %13 %22 %23
%4 = OpFunction %2 None %3
%5 = OpLabel
%41 = OpVariable %40 Function %27
%33 = OpFunctionCall %2 %20 %22 %23 %26 %28 %41 %27
OpReturn
OpFunctionEnd
; adjust type of the function in-place
%20 = OpFunction %2 None %15
%16 = OpFunctionParameter %6
%17 = OpFunctionParameter %8
%18 = OpFunctionParameter %10
%19 = OpFunctionParameter %13
%42 = OpFunctionParameter %40
%43 = OpFunctionParameter %12
%21 = OpLabel
OpReturn
OpFunctionEnd
; create a new function type
%50 = OpFunction %2 None %51
%52 = OpFunctionParameter %12
%53 = OpLabel
OpReturn
OpFunctionEnd
%54 = OpFunction %2 None %51
%55 = OpFunctionParameter %12
%56 = OpLabel
OpReturn
OpFunctionEnd
; reuse an existing function type
%57 = OpFunction %2 None %63
%58 = OpFunctionParameter %64
%59 = OpLabel
OpReturn
OpFunctionEnd
%60 = OpFunction %2 None %65
%61 = OpFunctionParameter %6
%62 = OpLabel
OpReturn
OpFunctionEnd
%66 = OpFunction %2 None %65
%67 = OpFunctionParameter %6
%68 = OpLabel
OpReturn
OpFunctionEnd
; don't adjust the type of the function if it creates a duplicate
%69 = OpFunction %2 None %76
%70 = OpFunctionParameter %75
%71 = OpLabel
OpReturn
OpFunctionEnd
%72 = OpFunction %2 None %77
%73 = OpFunctionParameter %8
%74 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
spvtools::ValidatorOptions validator_options;
TransformationContext transformation_context(&fact_manager,
validator_options);
// |parameter_id| is empty.
ASSERT_FALSE(
TransformationReplaceParamsWithStruct({}, 90, 91, {{33, 92}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
// |parameter_id| has duplicates.
ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 16, 17}, 90, 91,
{{33, 92}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
// |parameter_id| has invalid values.
ASSERT_FALSE(TransformationReplaceParamsWithStruct({21, 16, 17}, 90, 91,
{{33, 92}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 90, 17}, 90, 91,
{{33, 92}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
// Parameter's belong to different functions.
ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 17, 52}, 90, 91,
{{33, 92}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
// Parameter has unsupported type.
ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 17, 42, 43}, 90, 91,
{{33, 92}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
// OpTypeStruct does not exist in the module.
ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 43}, 90, 91,
{{33, 92}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
// |caller_id_to_fresh_composite_id| misses values.
ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 17}, 90, 91,
{{33, 92}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
// All fresh ids must be unique.
ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 17}, 90, 90,
{{33, 92}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 17}, 90, 91,
{{33, 90}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 17}, 90, 91,
{{33, 92}, {90, 92}})
.IsApplicable(context.get(), transformation_context));
// All 'fresh' ids must be fresh.
ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 17}, 90, 91,
{{33, 33}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 17}, 33, 91,
{{33, 92}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 17}, 90, 33,
{{33, 92}, {90, 93}})
.IsApplicable(context.get(), transformation_context));
{
TransformationReplaceParamsWithStruct transformation({16, 18, 19}, 90, 91,
{{33, 92}, {90, 93}});
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
}
{
TransformationReplaceParamsWithStruct transformation({43}, 93, 94,
{{33, 95}});
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
}
{
TransformationReplaceParamsWithStruct transformation({17, 91, 94}, 96, 97,
{{33, 98}});
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
}
{
TransformationReplaceParamsWithStruct transformation({55}, 99, 100, {{}});
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
}
{
TransformationReplaceParamsWithStruct transformation({61}, 101, 102, {{}});
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
}
{
TransformationReplaceParamsWithStruct transformation({73}, 103, 104, {{}});
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
}
ASSERT_TRUE(IsValid(env, context.get()));
std::string expected_shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpMemberDecorate %13 0 RelaxedPrecision
OpDecorate %16 RelaxedPrecision
%2 = OpTypeVoid
%6 = OpTypeInt 32 1
%3 = OpTypeFunction %2
%7 = OpTypePointer Private %6
%8 = OpTypeFloat 32
%9 = OpTypePointer Private %8
%10 = OpTypeVector %8 2
%11 = OpTypePointer Private %10
%12 = OpTypeBool
%51 = OpTypeFunction %2 %12
%64 = OpTypeStruct %6
%63 = OpTypeFunction %2 %64
%65 = OpTypeFunction %2 %6
%75 = OpTypeStruct %8
%76 = OpTypeFunction %2 %75
%77 = OpTypeFunction %2 %8
%40 = OpTypePointer Function %12
%13 = OpTypeStruct %6 %8
%45 = OpTypeStruct %6 %10 %13
%46 = OpTypeStruct %12
%47 = OpTypeStruct %8 %45 %46
%14 = OpTypePointer Private %13
%22 = OpConstant %6 0
%23 = OpConstant %8 0
%26 = OpConstantComposite %10 %23 %23
%27 = OpConstantTrue %12
%28 = OpConstantComposite %13 %22 %23
%15 = OpTypeFunction %2 %40 %47
%99 = OpTypeFunction %2 %46
%4 = OpFunction %2 None %3
%5 = OpLabel
%41 = OpVariable %40 Function %27
%92 = OpCompositeConstruct %45 %22 %26 %28
%95 = OpCompositeConstruct %46 %27
%98 = OpCompositeConstruct %47 %23 %92 %95
%33 = OpFunctionCall %2 %20 %41 %98
OpReturn
OpFunctionEnd
%20 = OpFunction %2 None %15
%42 = OpFunctionParameter %40
%97 = OpFunctionParameter %47
%21 = OpLabel
%94 = OpCompositeExtract %46 %97 2
%91 = OpCompositeExtract %45 %97 1
%17 = OpCompositeExtract %8 %97 0
%43 = OpCompositeExtract %12 %94 0
%19 = OpCompositeExtract %13 %91 2
%18 = OpCompositeExtract %10 %91 1
%16 = OpCompositeExtract %6 %91 0
OpReturn
OpFunctionEnd
%50 = OpFunction %2 None %51
%52 = OpFunctionParameter %12
%53 = OpLabel
OpReturn
OpFunctionEnd
%54 = OpFunction %2 None %99
%100 = OpFunctionParameter %46
%56 = OpLabel
%55 = OpCompositeExtract %12 %100 0
OpReturn
OpFunctionEnd
%57 = OpFunction %2 None %63
%58 = OpFunctionParameter %64
%59 = OpLabel
OpReturn
OpFunctionEnd
%60 = OpFunction %2 None %63
%102 = OpFunctionParameter %64
%62 = OpLabel
%61 = OpCompositeExtract %6 %102 0
OpReturn
OpFunctionEnd
%66 = OpFunction %2 None %65
%67 = OpFunctionParameter %6
%68 = OpLabel
OpReturn
OpFunctionEnd
%69 = OpFunction %2 None %76
%70 = OpFunctionParameter %75
%71 = OpLabel
OpReturn
OpFunctionEnd
%72 = OpFunction %2 None %76
%104 = OpFunctionParameter %75
%74 = OpLabel
%73 = OpCompositeExtract %8 %104 0
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools