Updated spirv-tools.

This commit is contained in:
Бранимир Караџић 2020-08-14 20:34:35 -07:00
parent a9e1d1a8ce
commit f16ab74475
74 changed files with 3119 additions and 321 deletions

View File

@ -1 +1 @@
"v2020.5", "SPIRV-Tools v2020.5 276598ad50d33f1d1a56311520b17390a6bed635"
"v2020.5", "SPIRV-Tools v2020.5 1a2bfbb06fe9545a7aa26e85e4b92b876ca316fd"

View File

@ -414,7 +414,7 @@ if(ENABLE_SPIRV_TOOLS_INSTALL)
install(FILES ${CMAKE_BINARY_DIR}/${SPIRV_TOOLS}Config.cmake DESTINATION ${PACKAGE_DIR})
endif(ENABLE_SPIRV_TOOLS_INSTALL)
if(MSVC)
if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")))
# Enable parallel builds across four cores for this lib
add_definitions(/MP4)
endif()

View File

@ -50,6 +50,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_add_synonyms.h
fuzzer_pass_add_loads.h
fuzzer_pass_add_local_variables.h
fuzzer_pass_add_loop_preheaders.h
fuzzer_pass_add_no_contraction_decorations.h
fuzzer_pass_add_parameters.h
fuzzer_pass_add_relaxed_decorations.h
@ -67,13 +68,17 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_invert_comparison_operators.h
fuzzer_pass_interchange_signedness_of_integer_operands.h
fuzzer_pass_interchange_zero_like_constants.h
fuzzer_pass_make_vector_operations_dynamic.h
fuzzer_pass_merge_blocks.h
fuzzer_pass_obfuscate_constants.h
fuzzer_pass_outline_functions.h
fuzzer_pass_permute_blocks.h
fuzzer_pass_permute_function_parameters.h
fuzzer_pass_permute_instructions.h
fuzzer_pass_permute_phi_operands.h
fuzzer_pass_propagate_instructions_up.h
fuzzer_pass_push_ids_through_variables.h
fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
fuzzer_pass_replace_copy_memories_with_loads_stores.h
fuzzer_pass_replace_copy_objects_with_stores_loads.h
fuzzer_pass_replace_linear_algebra_instructions.h
@ -132,13 +137,17 @@ if(SPIRV_BUILD_FUZZER)
transformation_function_call.h
transformation_invert_comparison_operator.h
transformation_load.h
transformation_make_vector_operation_dynamic.h
transformation_merge_blocks.h
transformation_move_block_down.h
transformation_move_instruction_down.h
transformation_outline_function.h
transformation_permute_function_parameters.h
transformation_permute_phi_operands.h
transformation_propagate_instruction_up.h
transformation_push_id_through_variable.h
transformation_record_synonymous_constants.h
transformation_replace_add_sub_mul_with_carrying_extended.h
transformation_replace_boolean_constant_with_constant_binary.h
transformation_replace_constant_with_uniform.h
transformation_replace_copy_memory_with_load_store.h
@ -181,6 +190,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_add_synonyms.cpp
fuzzer_pass_add_loads.cpp
fuzzer_pass_add_local_variables.cpp
fuzzer_pass_add_loop_preheaders.cpp
fuzzer_pass_add_no_contraction_decorations.cpp
fuzzer_pass_add_parameters.cpp
fuzzer_pass_add_relaxed_decorations.cpp
@ -198,13 +208,17 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_invert_comparison_operators.cpp
fuzzer_pass_interchange_signedness_of_integer_operands.cpp
fuzzer_pass_interchange_zero_like_constants.cpp
fuzzer_pass_make_vector_operations_dynamic.cpp
fuzzer_pass_merge_blocks.cpp
fuzzer_pass_obfuscate_constants.cpp
fuzzer_pass_outline_functions.cpp
fuzzer_pass_permute_blocks.cpp
fuzzer_pass_permute_function_parameters.cpp
fuzzer_pass_permute_instructions.cpp
fuzzer_pass_permute_phi_operands.cpp
fuzzer_pass_propagate_instructions_up.cpp
fuzzer_pass_push_ids_through_variables.cpp
fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
fuzzer_pass_replace_copy_memories_with_loads_stores.cpp
fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
fuzzer_pass_replace_linear_algebra_instructions.cpp
@ -262,13 +276,17 @@ if(SPIRV_BUILD_FUZZER)
transformation_function_call.cpp
transformation_invert_comparison_operator.cpp
transformation_load.cpp
transformation_make_vector_operation_dynamic.cpp
transformation_merge_blocks.cpp
transformation_move_block_down.cpp
transformation_move_instruction_down.cpp
transformation_outline_function.cpp
transformation_permute_function_parameters.cpp
transformation_permute_phi_operands.cpp
transformation_propagate_instruction_up.cpp
transformation_push_id_through_variable.cpp
transformation_record_synonymous_constants.cpp
transformation_replace_add_sub_mul_with_carrying_extended.cpp
transformation_replace_boolean_constant_with_constant_binary.cpp
transformation_replace_constant_with_uniform.cpp
transformation_replace_copy_memory_with_load_store.cpp
@ -292,7 +310,7 @@ if(SPIRV_BUILD_FUZZER)
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
)
if(MSVC)
if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")))
# Enable parallel builds across four cores for this lib
add_definitions(/MP4)
endif()

View File

@ -32,6 +32,7 @@
#include "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h"
#include "source/fuzz/fuzzer_pass_add_loads.h"
#include "source/fuzz/fuzzer_pass_add_local_variables.h"
#include "source/fuzz/fuzzer_pass_add_loop_preheaders.h"
#include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
#include "source/fuzz/fuzzer_pass_add_parameters.h"
#include "source/fuzz/fuzzer_pass_add_stores.h"
@ -49,12 +50,15 @@
#include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h"
#include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h"
#include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
#include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h"
#include "source/fuzz/fuzzer_pass_merge_blocks.h"
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
#include "source/fuzz/fuzzer_pass_outline_functions.h"
#include "source/fuzz/fuzzer_pass_permute_blocks.h"
#include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
#include "source/fuzz/fuzzer_pass_permute_instructions.h"
#include "source/fuzz/fuzzer_pass_permute_phi_operands.h"
#include "source/fuzz/fuzzer_pass_propagate_instructions_up.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_parameter_with_global.h"
@ -239,6 +243,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassAddLocalVariables>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddLoopPreheaders>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddParameters>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
@ -266,6 +273,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassInvertComparisonOperators>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassMakeVectorOperationsDynamic>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassMergeBlocks>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
@ -281,6 +291,12 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassPermuteFunctionParameters>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassPermuteInstructions>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassPropagateInstructionsUp>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassPushIdsThroughVariables>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);

View File

@ -38,6 +38,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfAddingImageSampleUnusedComponents =
{20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingLoad = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingLocalVariable = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingLoopPreheader = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingMatrixType = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = {
5, 70};
@ -72,13 +73,20 @@ const std::pair<uint32_t, uint32_t>
const std::pair<uint32_t, uint32_t> kChanceOfInvertingComparisonOperators = {
20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfMakingDonorLivesafe = {40, 60};
const std::pair<uint32_t, uint32_t> kChanceOfMakingVectorOperationDynamic = {
20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95};
const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfOutliningFunction = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfPermutingInstructions = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfPermutingParameters = {30, 90};
const std::pair<uint32_t, uint32_t> kChanceOfPermutingPhiOperands = {30, 90};
const std::pair<uint32_t, uint32_t> kChanceOfPropagatingInstructionsUp = {20,
70};
const std::pair<uint32_t, uint32_t> kChanceOfPushingIdThroughVariable = {5, 50};
const std::pair<uint32_t, uint32_t>
kChanceOfReplacingAddSubMulWithCarryingExtended = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyMemoryWithLoadStore =
{20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyObjectWithStoreLoad =
@ -158,6 +166,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
chance_of_adding_global_variable_ =
ChooseBetweenMinAndMax(kChanceOfAddingGlobalVariable);
chance_of_adding_load_ = ChooseBetweenMinAndMax(kChanceOfAddingLoad);
chance_of_adding_loop_preheader_ =
ChooseBetweenMinAndMax(kChanceOfAddingLoopPreheader);
chance_of_adding_image_sample_unused_components_ =
ChooseBetweenMinAndMax(kChanceOfAddingImageSampleUnusedComponents);
chance_of_adding_local_variable_ =
@ -207,6 +217,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfInvertingComparisonOperators);
chance_of_making_donor_livesafe_ =
ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe);
chance_of_making_vector_operation_dynamic_ =
ChooseBetweenMinAndMax(kChanceOfMakingVectorOperationDynamic);
chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks);
chance_of_moving_block_down_ =
ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
@ -214,12 +226,18 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant);
chance_of_outlining_function_ =
ChooseBetweenMinAndMax(kChanceOfOutliningFunction);
chance_of_permuting_instructions_ =
ChooseBetweenMinAndMax(kChanceOfPermutingInstructions);
chance_of_permuting_parameters_ =
ChooseBetweenMinAndMax(kChanceOfPermutingParameters);
chance_of_permuting_phi_operands_ =
ChooseBetweenMinAndMax(kChanceOfPermutingPhiOperands);
chance_of_propagating_instructions_up_ =
ChooseBetweenMinAndMax(kChanceOfPropagatingInstructionsUp);
chance_of_pushing_id_through_variable_ =
ChooseBetweenMinAndMax(kChanceOfPushingIdThroughVariable);
chance_of_replacing_add_sub_mul_with_carrying_extended_ =
ChooseBetweenMinAndMax(kChanceOfReplacingAddSubMulWithCarryingExtended);
chance_of_replacing_copy_memory_with_load_store_ =
ChooseBetweenMinAndMax(kChanceOfReplacingCopyMemoryWithLoadStore);
chance_of_replacing_copyobject_with_store_load_ =

View File

@ -136,6 +136,9 @@ class FuzzerContext {
uint32_t GetChanceOfAddingLocalVariable() {
return chance_of_adding_local_variable_;
}
uint32_t GetChanceOfAddingLoopPreheader() {
return chance_of_adding_loop_preheader_;
}
uint32_t GetChanceOfAddingMatrixType() {
return chance_of_adding_matrix_type_;
}
@ -198,6 +201,9 @@ class FuzzerContext {
uint32_t ChanceOfMakingDonorLivesafe() {
return chance_of_making_donor_livesafe_;
}
uint32_t GetChanceOfMakingVectorOperationDynamic() {
return chance_of_making_vector_operation_dynamic_;
}
uint32_t GetChanceOfMergingBlocks() { return chance_of_merging_blocks_; }
uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
uint32_t GetChanceOfObfuscatingConstant() {
@ -206,15 +212,24 @@ class FuzzerContext {
uint32_t GetChanceOfOutliningFunction() {
return chance_of_outlining_function_;
}
uint32_t GetChanceOfPermutingInstructions() {
return chance_of_permuting_instructions_;
}
uint32_t GetChanceOfPermutingParameters() {
return chance_of_permuting_parameters_;
}
uint32_t GetChanceOfPermutingPhiOperands() {
return chance_of_permuting_phi_operands_;
}
uint32_t GetChanceOfPropagatingInstructionsUp() {
return chance_of_propagating_instructions_up_;
}
uint32_t GetChanceOfPushingIdThroughVariable() {
return chance_of_pushing_id_through_variable_;
}
uint32_t GetChanceOfReplacingAddSubMulWithCarryingExtended() {
return chance_of_replacing_add_sub_mul_with_carrying_extended_;
}
uint32_t GetChanceOfReplacingCopyMemoryWithLoadStore() {
return chance_of_replacing_copy_memory_with_load_store_;
}
@ -336,6 +351,7 @@ class FuzzerContext {
uint32_t chance_of_adding_image_sample_unused_components_;
uint32_t chance_of_adding_load_;
uint32_t chance_of_adding_local_variable_;
uint32_t chance_of_adding_loop_preheader_;
uint32_t chance_of_adding_matrix_type_;
uint32_t chance_of_adding_no_contraction_decoration_;
uint32_t chance_of_adding_parameters;
@ -360,13 +376,17 @@ class FuzzerContext {
uint32_t chance_of_interchanging_zero_like_constants_;
uint32_t chance_of_inverting_comparison_operators_;
uint32_t chance_of_making_donor_livesafe_;
uint32_t chance_of_making_vector_operation_dynamic_;
uint32_t chance_of_merging_blocks_;
uint32_t chance_of_moving_block_down_;
uint32_t chance_of_obfuscating_constant_;
uint32_t chance_of_outlining_function_;
uint32_t chance_of_permuting_instructions_;
uint32_t chance_of_permuting_parameters_;
uint32_t chance_of_permuting_phi_operands_;
uint32_t chance_of_propagating_instructions_up_;
uint32_t chance_of_pushing_id_through_variable_;
uint32_t chance_of_replacing_add_sub_mul_with_carrying_extended_;
uint32_t chance_of_replacing_copy_memory_with_load_store_;
uint32_t chance_of_replacing_copyobject_with_store_load_;
uint32_t chance_of_replacing_id_with_synonym_;

View File

@ -24,6 +24,7 @@
#include "source/fuzz/transformation_add_constant_null.h"
#include "source/fuzz/transformation_add_constant_scalar.h"
#include "source/fuzz/transformation_add_global_undef.h"
#include "source/fuzz/transformation_add_loop_preheader.h"
#include "source/fuzz/transformation_add_type_boolean.h"
#include "source/fuzz/transformation_add_type_float.h"
#include "source/fuzz/transformation_add_type_function.h"
@ -533,5 +534,66 @@ void FuzzerPass::MaybeAddUseToReplace(
std::make_pair(id_use_descriptor, replacement_id));
}
opt::BasicBlock* FuzzerPass::GetOrCreateSimpleLoopPreheader(
uint32_t header_id) {
auto header_block = fuzzerutil::MaybeFindBlock(GetIRContext(), header_id);
assert(header_block && header_block->IsLoopHeader() &&
"|header_id| should be the label id of a loop header");
auto predecessors = GetIRContext()->cfg()->preds(header_id);
assert(predecessors.size() >= 2 &&
"The block |header_id| should be reachable.");
auto function = header_block->GetParent();
if (predecessors.size() == 2) {
// The header has a single out-of-loop predecessor, which could be a
// preheader.
opt::BasicBlock* maybe_preheader;
if (GetIRContext()->GetDominatorAnalysis(function)->Dominates(
header_id, predecessors[0])) {
// The first predecessor is the back-edge block, because the header
// dominates it, so the second one is out of the loop.
maybe_preheader = &*function->FindBlock(predecessors[1]);
} else {
// The first predecessor is out of the loop.
maybe_preheader = &*function->FindBlock(predecessors[0]);
}
// |maybe_preheader| is a preheader if it branches unconditionally to
// the header. We also require it not to be a loop header.
if (maybe_preheader->terminator()->opcode() == SpvOpBranch &&
!maybe_preheader->IsLoopHeader()) {
return maybe_preheader;
}
}
// We need to add a preheader.
// Get a fresh id for the preheader.
uint32_t preheader_id = GetFuzzerContext()->GetFreshId();
// Get a fresh id for each OpPhi instruction, if there is more than one
// out-of-loop predecessor.
std::vector<uint32_t> phi_ids;
if (predecessors.size() > 2) {
header_block->ForEachPhiInst(
[this, &phi_ids](opt::Instruction* /* unused */) {
phi_ids.push_back(GetFuzzerContext()->GetFreshId());
});
}
// Add the preheader.
ApplyTransformation(
TransformationAddLoopPreheader(header_id, preheader_id, phi_ids));
// Make the newly-created preheader the new entry block.
return &*function->FindBlock(preheader_id);
}
} // namespace fuzz
} // namespace spvtools

View File

@ -283,6 +283,16 @@ class FuzzerPass {
std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
uses_to_replace);
// Returns the preheader of the loop with header |header_id|, which satisfies
// all of the following conditions:
// - It is the only out-of-loop predecessor of the header
// - It unconditionally branches to the header
// - It is not a loop header itself
// If such preheader does not exist, a new one is added and returned.
// Requires |header_id| to be the label id of a loop header block that is
// reachable in the CFG (and thus has at least 2 predecessors).
opt::BasicBlock* GetOrCreateSimpleLoopPreheader(uint32_t header_id);
private:
opt::IRContext* ir_context_;
TransformationContext* transformation_context_;

View File

@ -21,6 +21,26 @@
namespace spvtools {
namespace fuzz {
namespace {
bool IsBitWidthSupported(opt::IRContext* ir_context, uint32_t bit_width) {
switch (bit_width) {
case 32:
return true;
case 64:
return ir_context->get_feature_mgr()->HasCapability(
SpvCapabilityFloat64) &&
ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt64);
case 16:
return ir_context->get_feature_mgr()->HasCapability(
SpvCapabilityFloat16) &&
ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt16);
default:
return false;
}
}
} // namespace
FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions(
opt::IRContext* ir_context, TransformationContext* transformation_context,
@ -76,8 +96,22 @@ void FuzzerPassAddEquationInstructions::Apply() {
switch (opcode) {
case SpvOpConvertSToF:
case SpvOpConvertUToF: {
auto candidate_instructions =
GetIntegerInstructions(available_instructions);
std::vector<const opt::Instruction*> candidate_instructions;
for (const auto* inst :
GetIntegerInstructions(available_instructions)) {
const auto* type =
GetIRContext()->get_type_mgr()->GetType(inst->type_id());
assert(type && "|inst| has invalid type");
if (const auto* vector_type = type->AsVector()) {
type = vector_type->element_type();
}
if (IsBitWidthSupported(GetIRContext(),
type->AsInteger()->width())) {
candidate_instructions.push_back(inst);
}
}
if (candidate_instructions.empty()) {
break;
@ -112,20 +146,8 @@ void FuzzerPassAddEquationInstructions::Apply() {
return;
}
case SpvOpBitcast: {
std::vector<const opt::Instruction*> candidate_instructions;
for (const auto* inst : available_instructions) {
const auto* type =
GetIRContext()->get_type_mgr()->GetType(inst->type_id());
assert(type && "Instruction has invalid type");
if ((type->AsVector() &&
(type->AsVector()->element_type()->AsInteger() ||
type->AsVector()->element_type()->AsFloat())) ||
type->AsInteger() || type->AsFloat()) {
// We support OpBitcast for only scalars or vectors of
// numerical type.
candidate_instructions.push_back(inst);
}
}
const auto candidate_instructions =
GetNumericalInstructions(available_instructions);
if (!candidate_instructions.empty()) {
const auto* operand_inst =
@ -356,5 +378,36 @@ FuzzerPassAddEquationInstructions::RestrictToElementBitWidth(
return result;
}
std::vector<opt::Instruction*>
FuzzerPassAddEquationInstructions::GetNumericalInstructions(
const std::vector<opt::Instruction*>& instructions) const {
std::vector<opt::Instruction*> result;
for (auto* inst : instructions) {
const auto* type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
assert(type && "Instruction has invalid type");
if (const auto* vector_type = type->AsVector()) {
type = vector_type->element_type();
}
if (!type->AsInteger() && !type->AsFloat()) {
// Only numerical scalars or vectors of numerical components are
// supported.
continue;
}
if (!IsBitWidthSupported(GetIRContext(), type->AsInteger()
? type->AsInteger()->width()
: type->AsFloat()->width())) {
continue;
}
result.push_back(inst);
}
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -51,6 +51,14 @@ class FuzzerPassAddEquationInstructions : public FuzzerPass {
std::vector<opt::Instruction*> GetBooleanInstructions(
const std::vector<opt::Instruction*>& instructions) const;
// Yields those instructions in |instructions| that have a scalar numerical or
// a vector of numerical components type. Only 16, 32 and 64-bit numericals
// are supported if both OpTypeInt and OpTypeFloat instructions can be created
// with the specified width (e.g. for 16-bit types both Float16 and Int16
// capabilities must be present).
std::vector<opt::Instruction*> GetNumericalInstructions(
const std::vector<opt::Instruction*>& instructions) const;
// Requires that |instructions| are scalars or vectors of some type. Returns
// only those instructions whose width is |width|. If |width| is 1 this means
// the scalars.

View File

@ -0,0 +1,66 @@
// 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_loop_preheaders.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_add_loop_preheader.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddLoopPreheaders::FuzzerPassAddLoopPreheaders(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddLoopPreheaders::~FuzzerPassAddLoopPreheaders() = default;
void FuzzerPassAddLoopPreheaders::Apply() {
for (auto& function : *GetIRContext()->module()) {
// Keep track of all the loop headers we want to add a preheader to.
std::vector<uint32_t> loop_header_ids_to_consider;
for (auto& block : function) {
// We only care about loop headers.
if (!block.IsLoopHeader()) {
continue;
}
// Randomly decide whether to consider this header.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingLoopPreheader())) {
continue;
}
// We exclude loop headers with just one predecessor (the back-edge block)
// because they are unreachable.
if (GetIRContext()->cfg()->preds(block.id()).size() < 2) {
continue;
}
loop_header_ids_to_consider.push_back(block.id());
}
for (uint32_t header_id : loop_header_ids_to_consider) {
// If not already present, add a preheader which is not also a loop
// header.
GetOrCreateSimpleLoopPreheader(header_id);
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,43 @@
// 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_LOOP_PREHEADERS_H
#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOOP_PREHEADERS_H
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass that randomly adds simple loop preheaders to loops that do not
// have one. A simple loop preheader is a block that:
// - is the only out-of-loop predecessor of the header
// - branches unconditionally to the header
// - is not a loop header itself
class FuzzerPassAddLoopPreheaders : public FuzzerPass {
public:
FuzzerPassAddLoopPreheaders(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddLoopPreheaders();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_LOOP_PREHEADERS_H

View File

@ -54,12 +54,12 @@ void FuzzerPassCopyObjects::Apply() {
return;
}
std::vector<opt::Instruction*> relevant_instructions =
FindAvailableInstructions(
const auto relevant_instructions = FindAvailableInstructions(
function, block, inst_it,
[this](opt::IRContext* ir_context, opt::Instruction* inst) {
return fuzzerutil::CanMakeSynonymOf(
ir_context, *GetTransformationContext(), inst);
return TransformationAddSynonym::IsInstructionValid(
ir_context, *GetTransformationContext(), inst,
protobufs::TransformationAddSynonym::COPY_OBJECT);
});
// At this point, |relevant_instructions| contains all the instructions

View File

@ -84,6 +84,16 @@ void FuzzerPassDonateModules::Apply() {
void FuzzerPassDonateModules::DonateSingleModule(
opt::IRContext* donor_ir_context, bool make_livesafe) {
// Check that the donated module has capabilities, supported by the recipient
// module.
for (const auto& capability_inst : donor_ir_context->capabilities()) {
auto capability =
static_cast<SpvCapability>(capability_inst.GetSingleWordInOperand(0));
if (!GetIRContext()->get_feature_mgr()->HasCapability(capability)) {
return;
}
}
// The ids used by the donor module may very well clash with ids defined in
// the recipient module. Furthermore, some instructions defined in the donor
// module will be equivalent to instructions defined in the recipient module,
@ -646,12 +656,13 @@ void FuzzerPassDonateModules::HandleFunctions(
}
});
if (make_livesafe) {
// Make the function livesafe and then add it.
AddLivesafeFunction(*function_to_donate, donor_ir_context,
*original_id_to_donated_id, donated_instructions);
} else {
// Add the function in a non-livesafe manner.
// If |make_livesafe| is true, try to add the function in a livesafe manner.
// Otherwise (if |make_lifesafe| is false or an attempt to make the function
// livesafe has failed), add the function in a non-livesafe manner.
if (!make_livesafe ||
!MaybeAddLivesafeFunction(*function_to_donate, donor_ir_context,
*original_id_to_donated_id,
donated_instructions)) {
ApplyTransformation(TransformationAddFunction(donated_instructions));
}
}
@ -773,6 +784,7 @@ bool FuzzerPassDonateModules::IsBasicType(
const opt::Instruction& instruction) const {
switch (instruction.opcode()) {
case SpvOpTypeArray:
case SpvOpTypeBool:
case SpvOpTypeFloat:
case SpvOpTypeInt:
case SpvOpTypeMatrix:
@ -1007,7 +1019,96 @@ void FuzzerPassDonateModules::PrepareInstructionForDonation(
input_operands));
}
void FuzzerPassDonateModules::AddLivesafeFunction(
bool FuzzerPassDonateModules::CreateLoopLimiterInfo(
opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header,
const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
protobufs::LoopLimiterInfo* out) {
assert(loop_header.IsLoopHeader() && "|loop_header| is not a loop header");
// Grab the loop header's id, mapped to its donated value.
out->set_loop_header_id(original_id_to_donated_id.at(loop_header.id()));
// Get fresh ids that will be used to load the loop limiter, increment
// it, compare it with the loop limit, and an id for a new block that
// will contain the loop's original terminator.
out->set_load_id(GetFuzzerContext()->GetFreshId());
out->set_increment_id(GetFuzzerContext()->GetFreshId());
out->set_compare_id(GetFuzzerContext()->GetFreshId());
out->set_logical_op_id(GetFuzzerContext()->GetFreshId());
// We are creating a branch from the back-edge block to the merge block. Thus,
// if merge block has any OpPhi instructions, we might need to adjust
// them.
// Note that the loop might have an unreachable back-edge block. This means
// that the loop can't iterate, so we don't need to adjust anything.
const auto back_edge_block_id = TransformationAddFunction::GetBackEdgeBlockId(
donor_ir_context, loop_header.id());
if (!back_edge_block_id) {
return true;
}
auto* back_edge_block = donor_ir_context->cfg()->block(back_edge_block_id);
assert(back_edge_block && "|back_edge_block_id| is invalid");
const auto* merge_block =
donor_ir_context->cfg()->block(loop_header.MergeBlockId());
assert(merge_block && "Loop header has invalid merge block id");
// We don't need to adjust anything if there is already a branch from
// the back-edge block to the merge block.
if (back_edge_block->IsSuccessor(merge_block)) {
return true;
}
// Adjust OpPhi instructions in the |merge_block|.
for (const auto& inst : *merge_block) {
if (inst.opcode() != SpvOpPhi) {
break;
}
// There is no simple way to ensure that a chosen operand for the OpPhi
// instruction will never cause any problems (e.g. if we choose an
// integer id, it might have a zero value when we branch from the back
// edge block. This might cause a division by 0 later in the function.).
// Thus, we ignore possible problems and proceed as follows:
// - if any of the existing OpPhi operands dominates the back-edge
// block - use it
// - if OpPhi has a basic type (see IsBasicType method) - create
// a zero constant
// - otherwise, we can't add a livesafe function.
uint32_t suitable_operand_id = 0;
for (uint32_t i = 0; i < inst.NumInOperands(); i += 2) {
auto dependency_inst_id = inst.GetSingleWordInOperand(i);
if (fuzzerutil::IdIsAvailableBeforeInstruction(
donor_ir_context, back_edge_block->terminator(),
dependency_inst_id)) {
suitable_operand_id = original_id_to_donated_id.at(dependency_inst_id);
break;
}
}
if (suitable_operand_id == 0 &&
IsBasicType(
*donor_ir_context->get_def_use_mgr()->GetDef(inst.type_id()))) {
// We mark this constant as irrelevant so that we can replace it
// with more interesting value later.
suitable_operand_id = FindOrCreateZeroConstant(
original_id_to_donated_id.at(inst.type_id()), true);
}
if (suitable_operand_id == 0) {
return false;
}
out->add_phi_id(suitable_operand_id);
}
return true;
}
bool FuzzerPassDonateModules::MaybeAddLivesafeFunction(
const opt::Function& function_to_donate, opt::IRContext* donor_ir_context,
const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
const std::vector<protobufs::Instruction>& donated_instructions) {
@ -1035,16 +1136,13 @@ void FuzzerPassDonateModules::AddLivesafeFunction(
for (auto& block : function_to_donate) {
if (block.IsLoopHeader()) {
protobufs::LoopLimiterInfo loop_limiter;
// Grab the loop header's id, mapped to its donated value.
loop_limiter.set_loop_header_id(original_id_to_donated_id.at(block.id()));
// Get fresh ids that will be used to load the loop limiter, increment
// it, compare it with the loop limit, and an id for a new block that
// will contain the loop's original terminator.
loop_limiter.set_load_id(GetFuzzerContext()->GetFreshId());
loop_limiter.set_increment_id(GetFuzzerContext()->GetFreshId());
loop_limiter.set_compare_id(GetFuzzerContext()->GetFreshId());
loop_limiter.set_logical_op_id(GetFuzzerContext()->GetFreshId());
loop_limiters.emplace_back(loop_limiter);
if (!CreateLoopLimiterInfo(donor_ir_context, block,
original_id_to_donated_id, &loop_limiter)) {
return false;
}
loop_limiters.emplace_back(std::move(loop_limiter));
}
}
@ -1157,6 +1255,7 @@ void FuzzerPassDonateModules::AddLivesafeFunction(
ApplyTransformation(TransformationAddFunction(
donated_instructions, loop_limiter_variable_id, loop_limit, loop_limiters,
kill_unreachable_return_value_id, access_chain_clamping_info));
return true;
}
} // namespace fuzz

View File

@ -128,14 +128,24 @@ class FuzzerPassDonateModules : public FuzzerPass {
std::map<uint32_t, uint32_t>* original_id_to_donated_id,
std::vector<protobufs::Instruction>* donated_instructions);
// Tries to create a protobufs::LoopLimiterInfo given a loop header basic
// block. Returns true if successful and outputs loop limiter into the |out|
// variable. Otherwise, returns false. |out| contains an undefined value when
// this function returns false.
bool CreateLoopLimiterInfo(
opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header,
const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
protobufs::LoopLimiterInfo* out);
// Requires that |donated_instructions| represents a prepared version of the
// instructions of |function_to_donate| (which comes from |donor_ir_context|)
// ready for donation, and |original_id_to_donated_id| maps ids from
// |donor_ir_context| to their corresponding ids in the recipient module.
//
// Adds a livesafe version of the function, based on |donated_instructions|,
// to the recipient module.
void AddLivesafeFunction(
// Attempts to add a livesafe version of the function, based on
// |donated_instructions|, to the recipient module. Returns true if the
// donation was successful, false otherwise.
bool MaybeAddLivesafeFunction(
const opt::Function& function_to_donate, opt::IRContext* donor_ir_context,
const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
const std::vector<protobufs::Instruction>& donated_instructions);

View File

@ -91,6 +91,13 @@ void FuzzerPassInterchangeSignednessOfIntegerOperands::Apply() {
uint32_t FuzzerPassInterchangeSignednessOfIntegerOperands::
FindOrCreateToggledIntegerConstant(uint32_t id) {
// |id| must not be a specialization constant because we do not know the value
// of specialization constants.
if (opt::IsSpecConstantInst(
GetIRContext()->get_def_use_mgr()->GetDef(id)->opcode())) {
return 0;
}
auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(id);
// This pass only toggles integer constants.

View File

@ -34,6 +34,12 @@ FuzzerPassInterchangeZeroLikeConstants::
uint32_t FuzzerPassInterchangeZeroLikeConstants::FindOrCreateToggledConstant(
opt::Instruction* declaration) {
// |declaration| must not be a specialization constant because we do not know
// the value of specialization constants.
if (opt::IsSpecConstantInst(declaration->opcode())) {
return 0;
}
auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
declaration->result_id());

View File

@ -0,0 +1,71 @@
// Copyright (c) 2020 André Perez Maselco
//
// 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_make_vector_operations_dynamic.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_make_vector_operation_dynamic.h"
namespace spvtools {
namespace fuzz {
FuzzerPassMakeVectorOperationsDynamic::FuzzerPassMakeVectorOperationsDynamic(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassMakeVectorOperationsDynamic::
~FuzzerPassMakeVectorOperationsDynamic() = default;
void FuzzerPassMakeVectorOperationsDynamic::Apply() {
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
for (auto& instruction : block) {
// Randomly decide whether to try applying the transformation.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfMakingVectorOperationDynamic())) {
continue;
}
// |instruction| must be a vector operation.
if (!TransformationMakeVectorOperationDynamic::IsVectorOperation(
GetIRContext(), &instruction)) {
continue;
}
// Make sure |instruction| has only one indexing operand.
assert(instruction.NumInOperands() ==
(instruction.opcode() == SpvOpCompositeExtract ? 2 : 3) &&
"FuzzerPassMakeVectorOperationsDynamic: the composite "
"instruction must have "
"only one indexing operand.");
// Applies the make vector operation dynamic transformation.
ApplyTransformation(TransformationMakeVectorOperationDynamic(
instruction.result_id(),
FindOrCreateIntegerConstant(
{instruction.GetSingleWordInOperand(
instruction.opcode() == SpvOpCompositeExtract ? 1 : 2)},
32, GetFuzzerContext()->ChooseEven(), false)));
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,40 @@
// Copyright (c) 2020 André Perez Maselco
//
// 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_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_
#define SOURCE_FUZZ_FUZZER_PASS_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Looks for OpCompositeExtract/Insert instructions on vectors, and replaces
// them with OpVectorExtract/InsertDynamic.
class FuzzerPassMakeVectorOperationsDynamic : public FuzzerPass {
public:
FuzzerPassMakeVectorOperationsDynamic(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassMakeVectorOperationsDynamic() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_

View File

@ -47,32 +47,13 @@ void FuzzerPassOutlineFunctions::Apply() {
for (auto& block : *function) {
blocks.push_back(&block);
}
auto entry_block = blocks[GetFuzzerContext()->RandomIndex(blocks)];
auto entry_block = MaybeGetEntryBlockSuitableForOutlining(
blocks[GetFuzzerContext()->RandomIndex(blocks)]);
// If the entry block starts with OpPhi, try to split it.
if (entry_block->begin()->opcode() == SpvOpPhi) {
// Find the first non-OpPhi instruction.
opt::Instruction* non_phi_inst = nullptr;
for (auto& instruction : *entry_block) {
if (instruction.opcode() != SpvOpPhi) {
non_phi_inst = &instruction;
break;
}
}
assert(non_phi_inst && "|non_phi_inst| must've been initialized");
// If the split was not applicable, the transformation will not work.
uint32_t new_block_id = GetFuzzerContext()->GetFreshId();
if (!MaybeApplyTransformation(TransformationSplitBlock(
MakeInstructionDescriptor(non_phi_inst->result_id(),
non_phi_inst->opcode(), 0),
new_block_id))) {
return;
}
// The new entry block is the newly-created block.
entry_block = &*function->FindBlock(new_block_id);
if (!entry_block) {
// The chosen block is not suitable to be the entry block of a region that
// will be outlined.
continue;
}
auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function);
@ -83,16 +64,26 @@ void FuzzerPassOutlineFunctions::Apply() {
postdominates_entry_block != nullptr;
postdominates_entry_block = postdominator_analysis->ImmediateDominator(
postdominates_entry_block)) {
// Consider the block if it is dominated by the entry block, ignore it if
// it is a continue target.
if (dominator_analysis->Dominates(entry_block,
postdominates_entry_block)) {
postdominates_entry_block) &&
!GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock(
postdominates_entry_block->id())) {
candidate_exit_blocks.push_back(postdominates_entry_block);
}
}
if (candidate_exit_blocks.empty()) {
continue;
}
auto exit_block = candidate_exit_blocks[GetFuzzerContext()->RandomIndex(
candidate_exit_blocks)];
auto exit_block = MaybeGetExitBlockSuitableForOutlining(
candidate_exit_blocks[GetFuzzerContext()->RandomIndex(
candidate_exit_blocks)]);
if (!exit_block) {
// The block chosen is not suitable
continue;
}
auto region_blocks = TransformationOutlineFunction::GetRegionBlocks(
GetIRContext(), entry_block, exit_block);
@ -122,5 +113,84 @@ void FuzzerPassOutlineFunctions::Apply() {
}
}
opt::BasicBlock*
FuzzerPassOutlineFunctions::MaybeGetEntryBlockSuitableForOutlining(
opt::BasicBlock* entry_block) {
// If the entry block is a loop header, we need to get or create its
// preheader and make it the entry block, if possible.
if (entry_block->IsLoopHeader()) {
auto predecessors =
GetIRContext()->cfg()->preds(entry_block->GetLabel()->result_id());
if (predecessors.size() < 2) {
// The header only has one predecessor (the back-edge block) and thus
// it is unreachable. The block cannot be adjusted to be suitable for
// outlining.
return nullptr;
}
// Get or create a suitable preheader and make it become the entry block.
entry_block =
GetOrCreateSimpleLoopPreheader(entry_block->GetLabel()->result_id());
}
assert(!entry_block->IsLoopHeader() &&
"The entry block cannot be a loop header at this point.");
// If the entry block starts with OpPhi or OpVariable, try to split it.
if (entry_block->begin()->opcode() == SpvOpPhi ||
entry_block->begin()->opcode() == SpvOpVariable) {
// Find the first non-OpPhi and non-OpVariable instruction.
auto non_phi_or_var_inst = &*entry_block->begin();
while (non_phi_or_var_inst->opcode() == SpvOpPhi ||
non_phi_or_var_inst->opcode() == SpvOpVariable) {
non_phi_or_var_inst = non_phi_or_var_inst->NextNode();
}
// Split the block.
uint32_t new_block_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationSplitBlock(
MakeInstructionDescriptor(GetIRContext(), non_phi_or_var_inst),
new_block_id));
// The new entry block is the newly-created block.
entry_block = &*entry_block->GetParent()->FindBlock(new_block_id);
}
return entry_block;
}
opt::BasicBlock*
FuzzerPassOutlineFunctions::MaybeGetExitBlockSuitableForOutlining(
opt::BasicBlock* exit_block) {
// The exit block must not be a continue target.
assert(!GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock(
exit_block->id()) &&
"A candidate exit block cannot be a continue target.");
// If the exit block is a merge block, try to split it and return the second
// block in the pair as the exit block.
if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(
exit_block->id())) {
uint32_t new_block_id = GetFuzzerContext()->GetFreshId();
// Find the first non-OpPhi instruction, after which to split.
auto split_before = &*exit_block->begin();
while (split_before->opcode() == SpvOpPhi) {
split_before = split_before->NextNode();
}
if (!MaybeApplyTransformation(TransformationSplitBlock(
MakeInstructionDescriptor(GetIRContext(), split_before),
new_block_id))) {
return nullptr;
}
return &*exit_block->GetParent()->FindBlock(new_block_id);
}
return exit_block;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -32,6 +32,31 @@ class FuzzerPassOutlineFunctions : public FuzzerPass {
~FuzzerPassOutlineFunctions();
void Apply() override;
// Returns a block suitable to be an entry block for a region that can be
// outlined, i.e. a block that is not a loop header and that does not start
// with OpPhi or OpVariable. In particular, it returns:
// - |entry_block| if it is suitable
// - otherwise, a block found by:
// - looking for or creating a new preheader, if |entry_block| is a loop
// header
// - splitting the candidate entry block, if it starts with OpPhi or
// OpVariable.
// Returns nullptr if a suitable block cannot be found following the
// instructions above.
opt::BasicBlock* MaybeGetEntryBlockSuitableForOutlining(
opt::BasicBlock* entry_block);
// Returns:
// - |exit_block| if it is not a merge block
// - the second block obtained by splitting |exit_block|, if |exit_block| is a
// merge block.
// Assumes that |exit_block| is not a continue target.
// The block returned by this function should be suitable to be the exit block
// of a region that can be outlined.
// Returns nullptr if |exit_block| is a merge block and it cannot be split.
opt::BasicBlock* MaybeGetExitBlockSuitableForOutlining(
opt::BasicBlock* exit_block);
};
} // namespace fuzz

View File

@ -0,0 +1,64 @@
// 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_permute_instructions.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_move_instruction_down.h"
namespace spvtools {
namespace fuzz {
FuzzerPassPermuteInstructions::FuzzerPassPermuteInstructions(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassPermuteInstructions::~FuzzerPassPermuteInstructions() = default;
void FuzzerPassPermuteInstructions::Apply() {
// We are iterating over all instructions in all basic blocks.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// We need to collect all instructions in the block into a separate vector
// since application of the transformation below might invalidate
// iterators.
std::vector<opt::Instruction*> instructions;
for (auto& instruction : block) {
instructions.push_back(&instruction);
}
// We consider all instructions in reverse to increase the possible number
// of applied transformations.
for (auto it = instructions.rbegin(); it != instructions.rend(); ++it) {
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfPermutingInstructions())) {
continue;
}
while (MaybeApplyTransformation(TransformationMoveInstructionDown(
MakeInstructionDescriptor(GetIRContext(), *it)))) {
// Apply the transformation as many times as possible.
}
}
}
}
}
} // 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_PERMUTE_INSTRUCTIONS_H_
#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_INSTRUCTIONS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Permutes instructions in every block of all while preserving the module's
// semantics.
class FuzzerPassPermuteInstructions : public FuzzerPass {
public:
FuzzerPassPermuteInstructions(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassPermuteInstructions() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_INSTRUCTIONS_H_

View File

@ -0,0 +1,64 @@
// 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_propagate_instructions_up.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_propagate_instruction_up.h"
namespace spvtools {
namespace fuzz {
FuzzerPassPropagateInstructionsUp::FuzzerPassPropagateInstructionsUp(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassPropagateInstructionsUp::~FuzzerPassPropagateInstructionsUp() =
default;
void FuzzerPassPropagateInstructionsUp::Apply() {
for (const auto& function : *GetIRContext()->module()) {
for (const auto& block : function) {
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfPropagatingInstructionsUp())) {
continue;
}
if (TransformationPropagateInstructionUp::IsApplicableToBlock(
GetIRContext(), block.id())) {
std::map<uint32_t, uint32_t> fresh_ids;
for (auto id : GetIRContext()->cfg()->preds(block.id())) {
auto& fresh_id = fresh_ids[id];
if (!fresh_id) {
// Create a fresh id if it hasn't been created yet. |fresh_id| will
// be default-initialized to 0 in this case.
fresh_id = GetFuzzerContext()->GetFreshId();
}
}
ApplyTransformation(
TransformationPropagateInstructionUp(block.id(), fresh_ids));
}
}
}
}
} // 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_PROPAGATE_INSTRUCTIONS_UP_H_
#define SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_UP_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Decides whether to propagate instructions from some block into its
// predecessors.
class FuzzerPassPropagateInstructionsUp : public FuzzerPass {
public:
FuzzerPassPropagateInstructionsUp(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassPropagateInstructionsUp() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_UP_H_

View File

@ -0,0 +1,76 @@
// 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_replace_adds_subs_muls_with_carrying_extended.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h"
namespace spvtools {
namespace fuzz {
namespace {
const uint32_t kArithmeticInstructionIndexLeftInOperand = 0;
} // namespace
FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::
FuzzerPassReplaceAddsSubsMulsWithCarryingExtended(
opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::
~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() = default;
void FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::Apply() {
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
for (auto& instruction : block) {
// Randomly decide whether to apply the transformation.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfReplacingAddSubMulWithCarryingExtended())) {
continue;
}
// Check if the transformation can be applied to this instruction.
if (!TransformationReplaceAddSubMulWithCarryingExtended::
IsInstructionSuitable(GetIRContext(), instruction)) {
continue;
}
// Get the operand type id. We know that both operands have the same
// type.
uint32_t operand_type_id =
GetIRContext()
->get_def_use_mgr()
->GetDef(instruction.GetSingleWordInOperand(
kArithmeticInstructionIndexLeftInOperand))
->type_id();
// Ensure the required struct type exists. The struct type is based on
// the operand type.
FindOrCreateStructType({operand_type_id, operand_type_id});
ApplyTransformation(TransformationReplaceAddSubMulWithCarryingExtended(
GetFuzzerContext()->GetFreshId(), instruction.result_id()));
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,42 @@
// 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 SPIRV_TOOLS_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H
#define SPIRV_TOOLS_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass that replaces instructions OpIAdd, OpISub, OpIMul with pairs of
// instructions. The first one (OpIAddCarry, OpISubBorrow, OpUMulExtended,
// OpSMulExtended) computes the result into a struct. The second one extracts
// the appropriate component from the struct to yield the original result.
class FuzzerPassReplaceAddsSubsMulsWithCarryingExtended : public FuzzerPass {
public:
FuzzerPassReplaceAddsSubsMulsWithCarryingExtended(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SPIRV_TOOLS_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H

View File

@ -36,23 +36,27 @@ FuzzerPassReplaceLinearAlgebraInstructions::
void FuzzerPassReplaceLinearAlgebraInstructions::Apply() {
// For each instruction, checks whether it is a linear algebra instruction. In
// this case, the transformation is randomly applied.
GetIRContext()->module()->ForEachInst([this](opt::Instruction* instruction) {
if (!spvOpcodeIsLinearAlgebra(instruction->opcode())) {
return;
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
for (auto& instruction : block) {
if (!spvOpcodeIsLinearAlgebra(instruction.opcode())) {
continue;
}
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfReplacingLinearAlgebraInstructions())) {
return;
continue;
}
ApplyTransformation(TransformationReplaceLinearAlgebraInstruction(
GetFuzzerContext()->GetFreshIds(
TransformationReplaceLinearAlgebraInstruction::
GetRequiredFreshIdCount(GetIRContext(), instruction)),
MakeInstructionDescriptor(GetIRContext(), instruction)));
});
GetRequiredFreshIdCount(GetIRContext(), &instruction)),
MakeInstructionDescriptor(GetIRContext(), &instruction)));
}
}
}
}
} // namespace fuzz

View File

@ -826,6 +826,12 @@ uint32_t UpdateFunctionType(opt::IRContext* ir_context, uint32_t function_id,
operand_ids.insert(operand_ids.end(), parameter_type_ids.begin(),
parameter_type_ids.end());
// A trivial case - we change nothing.
if (FindFunctionType(ir_context, operand_ids) ==
old_function_type->result_id()) {
return old_function_type->result_id();
}
if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1 &&
FindFunctionType(ir_context, operand_ids) == 0) {
// We can change |old_function_type| only if it's used once in the module
@ -849,17 +855,16 @@ uint32_t UpdateFunctionType(opt::IRContext* ir_context, uint32_t function_id,
// existing one or create a new one.
auto type_id = FindOrCreateFunctionType(
ir_context, new_function_type_result_id, operand_ids);
assert(type_id != old_function_type->result_id() &&
"We should've handled this case above");
if (type_id != old_function_type->result_id()) {
function->DefInst().SetInOperand(1, {type_id});
// DefUseManager hasn't been updated yet, so if the following condition is
// true, then |old_function_type| will have no users when this function
// returns. We might as well remove it.
if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1) {
old_function_type->RemoveFromList();
delete old_function_type;
}
ir_context->KillInst(old_function_type);
}
return type_id;
@ -1312,6 +1317,31 @@ MapToRepeatedUInt32Pair(const std::map<uint32_t, uint32_t>& data) {
return result;
}
opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context,
uint32_t block_id,
SpvOp opcode) {
// CFG::block uses std::map::at which throws an exception when |block_id| is
// invalid. The error message is unhelpful, though. Thus, we test that
// |block_id| is valid here.
const auto* label_inst = ir_context->get_def_use_mgr()->GetDef(block_id);
(void)label_inst; // Make compilers happy in release mode.
assert(label_inst && label_inst->opcode() == SpvOpLabel &&
"|block_id| is invalid");
auto* block = ir_context->cfg()->block(block_id);
auto it = block->rbegin();
assert(it != block->rend() && "Basic block can't be empty");
if (block->GetMergeInst()) {
++it;
assert(it != block->rend() &&
"|block| must have at least two instructions:"
"terminator and a merge instruction");
}
return CanInsertOpcodeBeforeInstruction(opcode, &*it) ? &*it : nullptr;
}
} // namespace fuzzerutil
} // namespace fuzz

View File

@ -316,6 +316,10 @@ opt::Function* GetFunctionFromParameterId(opt::IRContext* ir_context,
// more users, it is removed from the module. Returns the result id of the
// OpTypeFunction instruction that is used as a type of the function with
// |function_id|.
//
// CAUTION: When the old type of the function is removed from the module, its
// memory is deallocated. Be sure not to use any pointers to the old
// type when this function returns.
uint32_t UpdateFunctionType(opt::IRContext* ir_context, uint32_t function_id,
uint32_t new_function_type_result_id,
uint32_t return_type_id,
@ -483,6 +487,12 @@ std::map<uint32_t, uint32_t> RepeatedUInt32PairToMap(
google::protobuf::RepeatedPtrField<protobufs::UInt32Pair>
MapToRepeatedUInt32Pair(const std::map<uint32_t, uint32_t>& data);
// Returns the last instruction in |block_id| before which an instruction with
// opcode |opcode| can be inserted, or nullptr if there is no such instruction.
opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context,
uint32_t block_id,
SpvOp opcode);
} // namespace fuzzerutil
} // namespace fuzz

View File

@ -52,7 +52,7 @@ protobufs::IdUseDescriptor MakeIdUseDescriptorFromUse(
opt::IRContext* context, opt::Instruction* inst,
uint32_t in_operand_index) {
const auto& in_operand = inst->GetInOperand(in_operand_index);
assert(in_operand.type == SPV_OPERAND_TYPE_ID);
assert(spvIsInIdType(in_operand.type));
return MakeIdUseDescriptor(in_operand.words[0],
MakeInstructionDescriptor(context, inst),
in_operand_index);

View File

@ -408,6 +408,10 @@ message Transformation {
TransformationReplaceCopyMemoryWithLoadStore replace_copy_memory_with_load_store = 61;
TransformationReplaceLoadStoreWithCopyMemory replace_load_store_with_copy_memory = 62;
TransformationAddLoopPreheader add_loop_preheader = 63;
TransformationMoveInstructionDown move_instruction_down = 64;
TransformationMakeVectorOperationDynamic make_vector_operation_dynamic = 65;
TransformationReplaceAddSubMulWithCarryingExtended replace_add_sub_mul_with_carrying_extended = 66;
TransformationPropagateInstructionUp propagate_instruction_up = 67;
// Add additional option using the next available number.
}
}
@ -1105,6 +1109,21 @@ message TransformationLoad {
}
message TransformationMakeVectorOperationDynamic {
// A transformation that replaces the OpCompositeExtract and OpCompositeInsert
// instructions with the OpVectorExtractDynamic and OpVectorInsertDynamic instructions.
// The composite instruction result id.
uint32 instruction_result_id = 1;
// The OpCompositeExtract/Insert instructions accept integer literals as indices to the composite object.
// However, the OpVectorInsert/ExtractDynamic instructions require its single index to be an integer instruction.
// This is the result id of the integer instruction.
uint32 constant_index_id = 2;
}
message TransformationMergeBlocks {
// A transformation that merges a block with its predecessor.
@ -1124,6 +1143,15 @@ message TransformationMoveBlockDown {
uint32 block_id = 1;
}
message TransformationMoveInstructionDown {
// Swaps |instruction| with the next instruction in the block.
// The instruction to move down.
InstructionDescriptor instruction = 1;
}
message TransformationOutlineFunction {
// A transformation that outlines a single-entry single-exit region of a
@ -1212,6 +1240,26 @@ message TransformationPermutePhiOperands {
}
message TransformationPropagateInstructionUp {
// Propagates an instruction in the block into the block's predecessors.
// Concretely, this transformation clones some particular instruction from
// the |block_id| into every block's predecessor and replaces the original
// instruction with OpPhi. Take a look at the transformation class to learn
// more about how we choose what instruction to propagate.
// Id of the block to propagate an instruction from.
uint32 block_id = 1;
// A map from the id of some predecessor of the |block_id| to the fresh id.
// The map contains a fresh id for at least every predecessor of the |block_id|.
// The instruction is propagated by creating a number of clones - one clone for
// each predecessor. Fresh ids from this field are used as result ids of cloned
// instructions.
repeated UInt32Pair predecessor_id_to_fresh_id = 2;
}
message TransformationPushIdThroughVariable {
// A transformation that makes |value_synonym_id| and |value_id| to be
@ -1259,6 +1307,24 @@ message TransformationRecordSynonymousConstants {
}
message TransformationReplaceAddSubMulWithCarryingExtended {
// Replaces OpIAdd with OpIAddCarry, OpISub with OpISubBorrow, OpIMul
// with OpUMulExtended or OpSMulExtended (depending on the signedness
// of the operands) and stores the result into a |struct_fresh_id|.
// In the original instruction the result type id and the type ids of
// the operands must be the same. Then the transformation extracts
// the first element of the result into the original |result_id|.
// This value is the same as the result of the original instruction.
// The fresh id of the intermediate result.
uint32 struct_fresh_id = 1;
// The result id of the original instruction.
uint32 result_id = 2;
}
message TransformationReplaceParameterWithGlobal {
// Removes parameter with result id |parameter_id| from its function

View File

@ -54,13 +54,17 @@
#include "source/fuzz/transformation_function_call.h"
#include "source/fuzz/transformation_invert_comparison_operator.h"
#include "source/fuzz/transformation_load.h"
#include "source/fuzz/transformation_make_vector_operation_dynamic.h"
#include "source/fuzz/transformation_merge_blocks.h"
#include "source/fuzz/transformation_move_block_down.h"
#include "source/fuzz/transformation_move_instruction_down.h"
#include "source/fuzz/transformation_outline_function.h"
#include "source/fuzz/transformation_permute_function_parameters.h"
#include "source/fuzz/transformation_permute_phi_operands.h"
#include "source/fuzz/transformation_propagate_instruction_up.h"
#include "source/fuzz/transformation_push_id_through_variable.h"
#include "source/fuzz/transformation_record_synonymous_constants.h"
#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h"
#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
#include "source/fuzz/transformation_replace_constant_with_uniform.h"
#include "source/fuzz/transformation_replace_copy_memory_with_load_store.h"
@ -190,10 +194,17 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
message.invert_comparison_operator());
case protobufs::Transformation::TransformationCase::kLoad:
return MakeUnique<TransformationLoad>(message.load());
case protobufs::Transformation::TransformationCase::
kMakeVectorOperationDynamic:
return MakeUnique<TransformationMakeVectorOperationDynamic>(
message.make_vector_operation_dynamic());
case protobufs::Transformation::TransformationCase::kMergeBlocks:
return MakeUnique<TransformationMergeBlocks>(message.merge_blocks());
case protobufs::Transformation::TransformationCase::kMoveBlockDown:
return MakeUnique<TransformationMoveBlockDown>(message.move_block_down());
case protobufs::Transformation::TransformationCase::kMoveInstructionDown:
return MakeUnique<TransformationMoveInstructionDown>(
message.move_instruction_down());
case protobufs::Transformation::TransformationCase::kOutlineFunction:
return MakeUnique<TransformationOutlineFunction>(
message.outline_function());
@ -204,6 +215,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
case protobufs::Transformation::TransformationCase::kPermutePhiOperands:
return MakeUnique<TransformationPermutePhiOperands>(
message.permute_phi_operands());
case protobufs::Transformation::TransformationCase::kPropagateInstructionUp:
return MakeUnique<TransformationPropagateInstructionUp>(
message.propagate_instruction_up());
case protobufs::Transformation::TransformationCase::kPushIdThroughVariable:
return MakeUnique<TransformationPushIdThroughVariable>(
message.push_id_through_variable());
@ -212,9 +226,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
return MakeUnique<TransformationRecordSynonymousConstants>(
message.record_synonymous_constants());
case protobufs::Transformation::TransformationCase::
kReplaceParameterWithGlobal:
return MakeUnique<TransformationReplaceParameterWithGlobal>(
message.replace_parameter_with_global());
kReplaceAddSubMulWithCarryingExtended:
return MakeUnique<TransformationReplaceAddSubMulWithCarryingExtended>(
message.replace_add_sub_mul_with_carrying_extended());
case protobufs::Transformation::TransformationCase::
kReplaceBooleanConstantWithConstantBinary:
return MakeUnique<TransformationReplaceBooleanConstantWithConstantBinary>(
@ -242,6 +256,10 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
kReplaceLoadStoreWithCopyMemory:
return MakeUnique<TransformationReplaceLoadStoreWithCopyMemory>(
message.replace_load_store_with_copy_memory());
case protobufs::Transformation::TransformationCase::
kReplaceParameterWithGlobal:
return MakeUnique<TransformationReplaceParameterWithGlobal>(
message.replace_parameter_with_global());
case protobufs::Transformation::TransformationCase::
kReplaceParamsWithStruct:
return MakeUnique<TransformationReplaceParamsWithStruct>(

View File

@ -363,6 +363,22 @@ bool TransformationAddFunction::TryToMakeFunctionLivesafe(
return true;
}
uint32_t TransformationAddFunction::GetBackEdgeBlockId(
opt::IRContext* ir_context, uint32_t loop_header_block_id) {
const auto* loop_header_block =
ir_context->cfg()->block(loop_header_block_id);
assert(loop_header_block && "|loop_header_block_id| is invalid");
for (auto pred : ir_context->cfg()->preds(loop_header_block_id)) {
if (ir_context->GetDominatorAnalysis(loop_header_block->GetParent())
->Dominates(loop_header_block_id, pred)) {
return pred;
}
}
return 0;
}
bool TransformationAddFunction::TryToAddLoopLimiters(
opt::IRContext* ir_context, opt::Function* added_function) const {
// Collect up all the loop headers so that we can subsequently add loop
@ -474,14 +490,8 @@ bool TransformationAddFunction::TryToAddLoopLimiters(
for (auto loop_header : loop_headers) {
// Look for the loop's back-edge block. This is a predecessor of the loop
// header that is dominated by the loop header.
uint32_t back_edge_block_id = 0;
for (auto pred : ir_context->cfg()->preds(loop_header->id())) {
if (ir_context->GetDominatorAnalysis(added_function)
->Dominates(loop_header->id(), pred)) {
back_edge_block_id = pred;
break;
}
}
const auto back_edge_block_id =
GetBackEdgeBlockId(ir_context, loop_header->id());
if (!back_edge_block_id) {
// The loop's back-edge block must be unreachable. This means that the
// loop cannot iterate, so there is no need to make it lifesafe; we can
@ -692,9 +702,7 @@ bool TransformationAddFunction::TryToAddLoopLimiters(
back_edge_block_terminator->SetOpcode(SpvOpBranchConditional);
back_edge_block_terminator->SetInOperands(opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {loop_limiter_info.compare_id()}},
{SPV_OPERAND_TYPE_ID, {loop_header->MergeBlockId()}
},
{SPV_OPERAND_TYPE_ID, {loop_header->MergeBlockId()}},
{SPV_OPERAND_TYPE_ID, {loop_header->id()}}}));
}

View File

@ -65,6 +65,12 @@ class TransformationAddFunction : public Transformation {
opt::IRContext* ir_context, const opt::Instruction& composite_type_inst,
uint32_t index_id);
// Returns id of the back-edge block, given the corresponding
// |loop_header_block_id|. |loop_header_block_id| must be the id of a loop
// header block. Returns 0 if the loop has no back-edge block.
static uint32_t GetBackEdgeBlockId(opt::IRContext* ir_context,
uint32_t loop_header_block_id);
private:
// Attempts to create a function from the series of instructions in
// |message_.instruction| and add it to |ir_context|.

View File

@ -14,8 +14,6 @@
#include "source/fuzz/transformation_add_parameter.h"
#include <source/spirv_constant.h>
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
@ -72,13 +70,13 @@ void TransformationAddParameter::Apply(
auto* function = fuzzerutil::FindFunction(ir_context, message_.function_id());
assert(function && "Can't find the function");
auto parameter_type_id =
const auto new_parameter_type_id =
fuzzerutil::GetTypeId(ir_context, message_.initializer_id());
assert(parameter_type_id != 0 && "Initializer has invalid type");
assert(new_parameter_type_id != 0 && "Initializer has invalid type");
// Add new parameters to the function.
function->AddParameter(MakeUnique<opt::Instruction>(
ir_context, SpvOpFunctionParameter, parameter_type_id,
ir_context, SpvOpFunctionParameter, new_parameter_type_id,
message_.parameter_fresh_id(), opt::Instruction::OperandList()));
fuzzerutil::UpdateModuleIdBound(ir_context, message_.parameter_fresh_id());
@ -92,43 +90,30 @@ void TransformationAddParameter::Apply(
message_.parameter_fresh_id());
// Fix all OpFunctionCall instructions.
ir_context->get_def_use_mgr()->ForEachUser(
&function->DefInst(), [this](opt::Instruction* call) {
if (call->opcode() != SpvOpFunctionCall ||
call->GetSingleWordInOperand(0) != message_.function_id()) {
return;
for (auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) {
inst->AddOperand({SPV_OPERAND_TYPE_ID, {message_.initializer_id()}});
}
call->AddOperand({SPV_OPERAND_TYPE_ID, {message_.initializer_id()}});
});
// Update function's type.
{
// We use a separate scope here since |old_function_type| might become a
// dangling pointer after the call to the fuzzerutil::UpdateFunctionType.
auto* old_function_type = fuzzerutil::GetFunctionType(ir_context, function);
const auto* old_function_type =
fuzzerutil::GetFunctionType(ir_context, function);
assert(old_function_type && "Function must have a valid type");
if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1) {
// Adjust existing function type if it is used only by this function.
old_function_type->AddOperand({SPV_OPERAND_TYPE_ID, {parameter_type_id}});
// We must make sure that all dependencies of |old_function_type| are
// evaluated before |old_function_type| (i.e. the domination rules are not
// broken). Thus, we move |old_function_type| to the end of the list of all
// types in the module.
old_function_type->RemoveFromList();
ir_context->AddType(std::unique_ptr<opt::Instruction>(old_function_type));
} else {
// Otherwise, either create a new type or use an existing one.
std::vector<uint32_t> type_ids;
type_ids.reserve(old_function_type->NumInOperands() + 1);
for (uint32_t i = 0, n = old_function_type->NumInOperands(); i < n; ++i) {
type_ids.push_back(old_function_type->GetSingleWordInOperand(i));
std::vector<uint32_t> parameter_type_ids;
for (uint32_t i = 1; i < old_function_type->NumInOperands(); ++i) {
parameter_type_ids.push_back(
old_function_type->GetSingleWordInOperand(i));
}
type_ids.push_back(parameter_type_id);
parameter_type_ids.push_back(new_parameter_type_id);
function->DefInst().SetInOperand(
1, {fuzzerutil::FindOrCreateFunctionType(
ir_context, message_.function_type_fresh_id(), type_ids)});
fuzzerutil::UpdateFunctionType(
ir_context, function->result_id(), message_.function_type_fresh_id(),
old_function_type->GetSingleWordInOperand(0), parameter_type_ids);
}
// Make sure our changes are analyzed.

View File

@ -36,6 +36,28 @@ bool TransformationAddTypeFloat::IsApplicable(
return false;
}
// Checks float type width capabilities.
switch (message_.width()) {
case 16:
// The Float16 capability must be present.
if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityFloat16)) {
return false;
}
break;
case 32:
// No capabilities needed.
break;
case 64:
// The Float64 capability must be present.
if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityFloat64)) {
return false;
}
break;
default:
assert(false && "Unexpected float type width");
return false;
}
// Applicable if there is no float type with this width already declared in
// the module.
return fuzzerutil::MaybeGetFloatType(ir_context, message_.width()) == 0;

View File

@ -38,6 +38,34 @@ bool TransformationAddTypeInt::IsApplicable(
return false;
}
// Checks integer type width capabilities.
switch (message_.width()) {
case 8:
// The Int8 capability must be present.
if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt8)) {
return false;
}
break;
case 16:
// The Int16 capability must be present.
if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt16)) {
return false;
}
break;
case 32:
// No capabilities needed.
break;
case 64:
// The Int64 capability must be present.
if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt64)) {
return false;
}
break;
default:
assert(false && "Unexpected integer type width");
return false;
}
// Applicable if there is no int type with this width and signedness already
// declared in the module.
return fuzzerutil::MaybeGetIntegerType(ir_context, message_.width(),

View File

@ -0,0 +1,111 @@
// Copyright (c) 2020 André Perez Maselco
//
// 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_make_vector_operation_dynamic.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
namespace spvtools {
namespace fuzz {
TransformationMakeVectorOperationDynamic::
TransformationMakeVectorOperationDynamic(
const spvtools::fuzz::protobufs::
TransformationMakeVectorOperationDynamic& message)
: message_(message) {}
TransformationMakeVectorOperationDynamic::
TransformationMakeVectorOperationDynamic(uint32_t instruction_result_id,
uint32_t constant_index_id) {
message_.set_instruction_result_id(instruction_result_id);
message_.set_constant_index_id(constant_index_id);
}
bool TransformationMakeVectorOperationDynamic::IsApplicable(
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
// |instruction| must be a vector operation.
auto instruction =
ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id());
if (!IsVectorOperation(ir_context, instruction)) {
return false;
}
// |constant_index_instruction| must be defined as an integer instruction.
auto constant_index_instruction =
ir_context->get_def_use_mgr()->GetDef(message_.constant_index_id());
if (!constant_index_instruction || !constant_index_instruction->type_id() ||
!ir_context->get_type_mgr()
->GetType(constant_index_instruction->type_id())
->AsInteger()) {
return false;
}
return true;
}
void TransformationMakeVectorOperationDynamic::Apply(
opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
auto instruction =
ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id());
// The OpVectorInsertDynamic instruction has the vector and component operands
// in reverse order in relation to the OpCompositeInsert corresponding
// operands.
if (instruction->opcode() == SpvOpCompositeInsert) {
std::swap(instruction->GetInOperand(0), instruction->GetInOperand(1));
}
// Sets the literal operand to the equivalent constant.
instruction->SetInOperand(
instruction->opcode() == SpvOpCompositeExtract ? 1 : 2,
{message_.constant_index_id()});
// Sets the |instruction| opcode to the corresponding vector dynamic opcode.
instruction->SetOpcode(instruction->opcode() == SpvOpCompositeExtract
? SpvOpVectorExtractDynamic
: SpvOpVectorInsertDynamic);
}
protobufs::Transformation TransformationMakeVectorOperationDynamic::ToMessage()
const {
protobufs::Transformation result;
*result.mutable_make_vector_operation_dynamic() = message_;
return result;
}
bool TransformationMakeVectorOperationDynamic::IsVectorOperation(
opt::IRContext* ir_context, opt::Instruction* instruction) {
// |instruction| must be defined and must be an OpCompositeExtract/Insert
// instruction.
if (!instruction || (instruction->opcode() != SpvOpCompositeExtract &&
instruction->opcode() != SpvOpCompositeInsert)) {
return false;
}
// The composite must be a vector.
auto composite_instruction =
ir_context->get_def_use_mgr()->GetDef(instruction->GetSingleWordInOperand(
instruction->opcode() == SpvOpCompositeExtract ? 0 : 1));
if (!ir_context->get_type_mgr()
->GetType(composite_instruction->type_id())
->AsVector()) {
return false;
}
return true;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,63 @@
// Copyright (c) 2020 André Perez Maselco
//
// 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_MAKE_VECTOR_OPERATION_DYNAMIC_H_
#define SOURCE_FUZZ_TRANSFORMATION_MAKE_VECTOR_OPERATION_DYNAMIC_H_
#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 TransformationMakeVectorOperationDynamic : public Transformation {
public:
explicit TransformationMakeVectorOperationDynamic(
const protobufs::TransformationMakeVectorOperationDynamic& message);
TransformationMakeVectorOperationDynamic(uint32_t instruction_result_id,
uint32_t constant_index_id);
// - |message_.instruction_result_id| must be the result id of an
// OpCompositeExtract/Insert instruction such that the composite operand is a
// vector.
// - |message_.constant_index_id| must be the result id of an integer
// instruction such that its value equals the indexing literal of the
// OpCompositeExtract/Insert instruction.
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// Replaces the OpCompositeExtract and OpCompositeInsert instructions with the
// OpVectorExtractDynamic and OpVectorInsertDynamic instructions.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
protobufs::Transformation ToMessage() const override;
// Checks |instruction| is defined, is an OpCompositeExtract/Insert
// instruction and the composite operand is a vector.
static bool IsVectorOperation(opt::IRContext* ir_context,
opt::Instruction* instruction);
private:
protobufs::TransformationMakeVectorOperationDynamic message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_MAKE_VECTOR_OPERATION_DYNAMIC_H_

View File

@ -0,0 +1,219 @@
// 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_move_instruction_down.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
namespace spvtools {
namespace fuzz {
TransformationMoveInstructionDown::TransformationMoveInstructionDown(
const protobufs::TransformationMoveInstructionDown& message)
: message_(message) {}
TransformationMoveInstructionDown::TransformationMoveInstructionDown(
const protobufs::InstructionDescriptor& instruction) {
*message_.mutable_instruction() = instruction;
}
bool TransformationMoveInstructionDown::IsApplicable(
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
// |instruction| must be valid.
auto* inst = FindInstruction(message_.instruction(), ir_context);
if (!inst) {
return false;
}
// Instruction's opcode must be supported by this transformation.
if (!IsOpcodeSupported(inst->opcode())) {
return false;
}
auto* inst_block = ir_context->get_instr_block(inst);
assert(inst_block &&
"Global instructions and function parameters are not supported");
auto inst_it = fuzzerutil::GetIteratorForInstruction(inst_block, inst);
assert(inst_it != inst_block->end() &&
"Can't get an iterator for the instruction");
// |instruction| can't be the last instruction in the block.
auto successor_it = ++inst_it;
if (successor_it == inst_block->end()) {
return false;
}
// Check that we can insert |instruction| after |inst_it|.
auto successors_successor_it = ++inst_it;
if (successors_successor_it == inst_block->end() ||
!fuzzerutil::CanInsertOpcodeBeforeInstruction(inst->opcode(),
successors_successor_it)) {
return false;
}
// Check that |instruction|'s successor doesn't depend on the |instruction|.
if (inst->result_id()) {
for (uint32_t i = 0; i < successor_it->NumInOperands(); ++i) {
const auto& operand = successor_it->GetInOperand(i);
if (operand.type == SPV_OPERAND_TYPE_ID &&
operand.words[0] == inst->result_id()) {
return false;
}
}
}
return true;
}
void TransformationMoveInstructionDown::Apply(
opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
auto* inst = FindInstruction(message_.instruction(), ir_context);
assert(inst &&
"The instruction should've been validated in the IsApplicable");
auto inst_it = fuzzerutil::GetIteratorForInstruction(
ir_context->get_instr_block(inst), inst);
// Move the instruction down in the block.
inst->InsertAfter(&*++inst_it);
ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisNone);
}
protobufs::Transformation TransformationMoveInstructionDown::ToMessage() const {
protobufs::Transformation result;
*result.mutable_move_instruction_down() = message_;
return result;
}
bool TransformationMoveInstructionDown::IsOpcodeSupported(SpvOp opcode) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605):
// We only support "simple" instructions that work don't with memory.
// We should extend this so that we support the ones that modify the memory
// too.
switch (opcode) {
case SpvOpNop:
case SpvOpUndef:
case SpvOpAccessChain:
case SpvOpInBoundsAccessChain:
case SpvOpArrayLength:
case SpvOpVectorExtractDynamic:
case SpvOpVectorInsertDynamic:
case SpvOpVectorShuffle:
case SpvOpCompositeConstruct:
case SpvOpCompositeExtract:
case SpvOpCompositeInsert:
case SpvOpCopyObject:
case SpvOpTranspose:
case SpvOpConvertFToU:
case SpvOpConvertFToS:
case SpvOpConvertSToF:
case SpvOpConvertUToF:
case SpvOpUConvert:
case SpvOpSConvert:
case SpvOpFConvert:
case SpvOpQuantizeToF16:
case SpvOpSatConvertSToU:
case SpvOpSatConvertUToS:
case SpvOpBitcast:
case SpvOpSNegate:
case SpvOpFNegate:
case SpvOpIAdd:
case SpvOpFAdd:
case SpvOpISub:
case SpvOpFSub:
case SpvOpIMul:
case SpvOpFMul:
case SpvOpUDiv:
case SpvOpSDiv:
case SpvOpFDiv:
case SpvOpUMod:
case SpvOpSRem:
case SpvOpSMod:
case SpvOpFRem:
case SpvOpFMod:
case SpvOpVectorTimesScalar:
case SpvOpMatrixTimesScalar:
case SpvOpVectorTimesMatrix:
case SpvOpMatrixTimesVector:
case SpvOpMatrixTimesMatrix:
case SpvOpOuterProduct:
case SpvOpDot:
case SpvOpIAddCarry:
case SpvOpISubBorrow:
case SpvOpUMulExtended:
case SpvOpSMulExtended:
case SpvOpAny:
case SpvOpAll:
case SpvOpIsNan:
case SpvOpIsInf:
case SpvOpIsFinite:
case SpvOpIsNormal:
case SpvOpSignBitSet:
case SpvOpLessOrGreater:
case SpvOpOrdered:
case SpvOpUnordered:
case SpvOpLogicalEqual:
case SpvOpLogicalNotEqual:
case SpvOpLogicalOr:
case SpvOpLogicalAnd:
case SpvOpLogicalNot:
case SpvOpSelect:
case SpvOpIEqual:
case SpvOpINotEqual:
case SpvOpUGreaterThan:
case SpvOpSGreaterThan:
case SpvOpUGreaterThanEqual:
case SpvOpSGreaterThanEqual:
case SpvOpULessThan:
case SpvOpSLessThan:
case SpvOpULessThanEqual:
case SpvOpSLessThanEqual:
case SpvOpFOrdEqual:
case SpvOpFUnordEqual:
case SpvOpFOrdNotEqual:
case SpvOpFUnordNotEqual:
case SpvOpFOrdLessThan:
case SpvOpFUnordLessThan:
case SpvOpFOrdGreaterThan:
case SpvOpFUnordGreaterThan:
case SpvOpFOrdLessThanEqual:
case SpvOpFUnordLessThanEqual:
case SpvOpFOrdGreaterThanEqual:
case SpvOpFUnordGreaterThanEqual:
case SpvOpShiftRightLogical:
case SpvOpShiftRightArithmetic:
case SpvOpShiftLeftLogical:
case SpvOpBitwiseOr:
case SpvOpBitwiseXor:
case SpvOpBitwiseAnd:
case SpvOpNot:
case SpvOpBitFieldInsert:
case SpvOpBitFieldSExtract:
case SpvOpBitFieldUExtract:
case SpvOpBitReverse:
case SpvOpBitCount:
case SpvOpCopyLogical:
case SpvOpPtrEqual:
case SpvOpPtrNotEqual:
return true;
default:
return false;
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,62 @@
// 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_MOVE_INSTRUCTION_DOWN_H_
#define SOURCE_FUZZ_TRANSFORMATION_MOVE_INSTRUCTION_DOWN_H_
#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 TransformationMoveInstructionDown : public Transformation {
public:
explicit TransformationMoveInstructionDown(
const protobufs::TransformationMoveInstructionDown& message);
explicit TransformationMoveInstructionDown(
const protobufs::InstructionDescriptor& instruction);
// - |instruction| should be a descriptor of a valid instruction in the module
// - |instruction|'s opcode should be supported by this transformation
// - neither |instruction| nor its successor may be the last instruction in
// the block
// - |instruction|'s successor may not be dependent on the |instruction|
// - it should be possible to insert |instruction|'s opcode after its
// successor
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// Swaps |instruction| with its successor.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
protobufs::Transformation ToMessage() const override;
private:
// Returns true if the |opcode| is supported by this transformation (i.e.
// we can move an instruction with this opcode).
static bool IsOpcodeSupported(SpvOp opcode);
protobufs::TransformationMoveInstructionDown message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_MOVE_INSTRUCTION_DOWN_H_

View File

@ -0,0 +1,402 @@
// 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_propagate_instruction_up.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
namespace spvtools {
namespace fuzz {
namespace {
uint32_t GetResultIdFromLabelId(const opt::Instruction& phi_inst,
uint32_t label_id) {
assert(phi_inst.opcode() == SpvOpPhi && "|phi_inst| is not an OpPhi");
for (uint32_t i = 1; i < phi_inst.NumInOperands(); i += 2) {
if (phi_inst.GetSingleWordInOperand(i) == label_id) {
return phi_inst.GetSingleWordInOperand(i - 1);
}
}
return 0;
}
bool ContainsPointers(const opt::analysis::Type& type) {
switch (type.kind()) {
case opt::analysis::Type::kPointer:
return true;
case opt::analysis::Type::kStruct:
return std::any_of(type.AsStruct()->element_types().begin(),
type.AsStruct()->element_types().end(),
[](const opt::analysis::Type* element_type) {
return ContainsPointers(*element_type);
});
default:
return false;
}
}
bool HasValidDependencies(opt::IRContext* ir_context, opt::Instruction* inst) {
const auto* inst_block = ir_context->get_instr_block(inst);
assert(inst_block &&
"This function shouldn't be applied to global instructions or function"
"parameters");
for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
const auto& operand = inst->GetInOperand(i);
if (operand.type != SPV_OPERAND_TYPE_ID) {
// Consider only <id> operands.
continue;
}
auto* dependency = ir_context->get_def_use_mgr()->GetDef(operand.words[0]);
assert(dependency && "Operand has invalid id");
if (ir_context->get_instr_block(dependency) == inst_block &&
dependency->opcode() != SpvOpPhi) {
// |dependency| is "valid" if it's an OpPhi from the same basic block or
// an instruction from a different basic block.
return false;
}
}
return true;
}
} // namespace
TransformationPropagateInstructionUp::TransformationPropagateInstructionUp(
const protobufs::TransformationPropagateInstructionUp& message)
: message_(message) {}
TransformationPropagateInstructionUp::TransformationPropagateInstructionUp(
uint32_t block_id,
const std::map<uint32_t, uint32_t>& predecessor_id_to_fresh_id) {
message_.set_block_id(block_id);
*message_.mutable_predecessor_id_to_fresh_id() =
fuzzerutil::MapToRepeatedUInt32Pair(predecessor_id_to_fresh_id);
}
bool TransformationPropagateInstructionUp::IsApplicable(
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
// Check that we can apply this transformation to the |block_id|.
if (!IsApplicableToBlock(ir_context, message_.block_id())) {
return false;
}
const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap(
message_.predecessor_id_to_fresh_id());
for (auto id : ir_context->cfg()->preds(message_.block_id())) {
// Each predecessor must have a fresh id in the |predecessor_id_to_fresh_id|
// map.
if (!predecessor_id_to_fresh_id.count(id)) {
return false;
}
}
std::vector<uint32_t> maybe_fresh_ids;
maybe_fresh_ids.reserve(predecessor_id_to_fresh_id.size());
for (const auto& entry : predecessor_id_to_fresh_id) {
maybe_fresh_ids.push_back(entry.second);
}
// All ids must be unique and fresh.
return !fuzzerutil::HasDuplicates(maybe_fresh_ids) &&
std::all_of(maybe_fresh_ids.begin(), maybe_fresh_ids.end(),
[ir_context](uint32_t id) {
return fuzzerutil::IsFreshId(ir_context, id);
});
}
void TransformationPropagateInstructionUp::Apply(
opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
auto* inst = GetInstructionToPropagate(ir_context, message_.block_id());
assert(inst &&
"The block must have at least one supported instruction to propagate");
assert(inst->result_id() && inst->type_id() &&
"|inst| must have a result id and a type id");
opt::Instruction::OperandList op_phi_operands;
const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap(
message_.predecessor_id_to_fresh_id());
std::unordered_set<uint32_t> visited_predecessors;
for (auto predecessor_id : ir_context->cfg()->preds(message_.block_id())) {
// A block can have multiple identical predecessors.
if (visited_predecessors.count(predecessor_id)) {
continue;
}
visited_predecessors.insert(predecessor_id);
auto new_result_id = predecessor_id_to_fresh_id.at(predecessor_id);
// Compute InOperands for the OpPhi instruction to be inserted later.
op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {new_result_id}});
op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {predecessor_id}});
// Create a clone of the |inst| to be inserted into the |predecessor_id|.
std::unique_ptr<opt::Instruction> clone(inst->Clone(ir_context));
clone->SetResultId(new_result_id);
fuzzerutil::UpdateModuleIdBound(ir_context, new_result_id);
// Adjust |clone|'s operands to account for possible dependencies on OpPhi
// instructions from the same basic block.
for (uint32_t i = 0; i < clone->NumInOperands(); ++i) {
auto& operand = clone->GetInOperand(i);
if (operand.type != SPV_OPERAND_TYPE_ID) {
// Consider only ids.
continue;
}
const auto* dependency_inst =
ir_context->get_def_use_mgr()->GetDef(operand.words[0]);
assert(dependency_inst && "|clone| depends on an invalid id");
if (ir_context->get_instr_block(dependency_inst->result_id()) !=
ir_context->cfg()->block(message_.block_id())) {
// We don't need to adjust anything if |dependency_inst| is from a
// different block, a global instruction or a function parameter.
continue;
}
assert(dependency_inst->opcode() == SpvOpPhi &&
"Propagated instruction can depend only on OpPhis from the same "
"basic block or instructions from different basic blocks");
auto new_id = GetResultIdFromLabelId(*dependency_inst, predecessor_id);
assert(new_id && "OpPhi instruction is missing a predecessor");
operand.words[0] = new_id;
}
auto* insert_before_inst = fuzzerutil::GetLastInsertBeforeInstruction(
ir_context, predecessor_id, clone->opcode());
assert(insert_before_inst && "Can't insert |clone| into |predecessor_id");
insert_before_inst->InsertBefore(std::move(clone));
}
// Insert an OpPhi instruction into the basic block of |inst|.
ir_context->get_instr_block(inst)->begin()->InsertBefore(
MakeUnique<opt::Instruction>(ir_context, SpvOpPhi, inst->type_id(),
inst->result_id(),
std::move(op_phi_operands)));
// Remove |inst| from the basic block.
ir_context->KillInst(inst);
// We have changed the module so most analyzes are now invalid.
ir_context->InvalidateAnalysesExceptFor(
opt::IRContext::Analysis::kAnalysisNone);
}
protobufs::Transformation TransformationPropagateInstructionUp::ToMessage()
const {
protobufs::Transformation result;
*result.mutable_propagate_instruction_up() = message_;
return result;
}
bool TransformationPropagateInstructionUp::IsOpcodeSupported(SpvOp opcode) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605):
// We only support "simple" instructions that don't work with memory.
// We should extend this so that we support the ones that modify the memory
// too.
switch (opcode) {
case SpvOpUndef:
case SpvOpAccessChain:
case SpvOpInBoundsAccessChain:
case SpvOpArrayLength:
case SpvOpVectorExtractDynamic:
case SpvOpVectorInsertDynamic:
case SpvOpVectorShuffle:
case SpvOpCompositeConstruct:
case SpvOpCompositeExtract:
case SpvOpCompositeInsert:
case SpvOpCopyObject:
case SpvOpTranspose:
case SpvOpConvertFToU:
case SpvOpConvertFToS:
case SpvOpConvertSToF:
case SpvOpConvertUToF:
case SpvOpUConvert:
case SpvOpSConvert:
case SpvOpFConvert:
case SpvOpQuantizeToF16:
case SpvOpSatConvertSToU:
case SpvOpSatConvertUToS:
case SpvOpBitcast:
case SpvOpSNegate:
case SpvOpFNegate:
case SpvOpIAdd:
case SpvOpFAdd:
case SpvOpISub:
case SpvOpFSub:
case SpvOpIMul:
case SpvOpFMul:
case SpvOpUDiv:
case SpvOpSDiv:
case SpvOpFDiv:
case SpvOpUMod:
case SpvOpSRem:
case SpvOpSMod:
case SpvOpFRem:
case SpvOpFMod:
case SpvOpVectorTimesScalar:
case SpvOpMatrixTimesScalar:
case SpvOpVectorTimesMatrix:
case SpvOpMatrixTimesVector:
case SpvOpMatrixTimesMatrix:
case SpvOpOuterProduct:
case SpvOpDot:
case SpvOpIAddCarry:
case SpvOpISubBorrow:
case SpvOpUMulExtended:
case SpvOpSMulExtended:
case SpvOpAny:
case SpvOpAll:
case SpvOpIsNan:
case SpvOpIsInf:
case SpvOpIsFinite:
case SpvOpIsNormal:
case SpvOpSignBitSet:
case SpvOpLessOrGreater:
case SpvOpOrdered:
case SpvOpUnordered:
case SpvOpLogicalEqual:
case SpvOpLogicalNotEqual:
case SpvOpLogicalOr:
case SpvOpLogicalAnd:
case SpvOpLogicalNot:
case SpvOpSelect:
case SpvOpIEqual:
case SpvOpINotEqual:
case SpvOpUGreaterThan:
case SpvOpSGreaterThan:
case SpvOpUGreaterThanEqual:
case SpvOpSGreaterThanEqual:
case SpvOpULessThan:
case SpvOpSLessThan:
case SpvOpULessThanEqual:
case SpvOpSLessThanEqual:
case SpvOpFOrdEqual:
case SpvOpFUnordEqual:
case SpvOpFOrdNotEqual:
case SpvOpFUnordNotEqual:
case SpvOpFOrdLessThan:
case SpvOpFUnordLessThan:
case SpvOpFOrdGreaterThan:
case SpvOpFUnordGreaterThan:
case SpvOpFOrdLessThanEqual:
case SpvOpFUnordLessThanEqual:
case SpvOpFOrdGreaterThanEqual:
case SpvOpFUnordGreaterThanEqual:
case SpvOpShiftRightLogical:
case SpvOpShiftRightArithmetic:
case SpvOpShiftLeftLogical:
case SpvOpBitwiseOr:
case SpvOpBitwiseXor:
case SpvOpBitwiseAnd:
case SpvOpNot:
case SpvOpBitFieldInsert:
case SpvOpBitFieldSExtract:
case SpvOpBitFieldUExtract:
case SpvOpBitReverse:
case SpvOpBitCount:
case SpvOpCopyLogical:
case SpvOpPtrEqual:
case SpvOpPtrNotEqual:
return true;
default:
return false;
}
}
opt::Instruction*
TransformationPropagateInstructionUp::GetInstructionToPropagate(
opt::IRContext* ir_context, uint32_t block_id) {
auto* block = ir_context->cfg()->block(block_id);
assert(block && "|block_id| is invalid");
for (auto& inst : *block) {
// We look for the first instruction in the block that satisfies the
// following rules:
// - it's not an OpPhi
// - it must be supported by this transformation
// - it may depend only on instructions from different basic blocks or on
// OpPhi instructions from the same basic block.
if (inst.opcode() == SpvOpPhi || !IsOpcodeSupported(inst.opcode()) ||
!inst.type_id() || !inst.result_id()) {
continue;
}
const auto* inst_type = ir_context->get_type_mgr()->GetType(inst.type_id());
assert(inst_type && "|inst| has invalid type");
if (!ir_context->get_feature_mgr()->HasCapability(
SpvCapabilityVariablePointersStorageBuffer) &&
ContainsPointers(*inst_type)) {
// OpPhi supports pointer operands only with VariablePointers or
// VariablePointersStorageBuffer capabilities.
//
// Note that VariablePointers capability implicitly declares
// VariablePointersStorageBuffer capability.
continue;
}
if (!HasValidDependencies(ir_context, &inst)) {
continue;
}
return &inst;
}
return nullptr;
}
bool TransformationPropagateInstructionUp::IsApplicableToBlock(
opt::IRContext* ir_context, uint32_t block_id) {
// Check that |block_id| is valid.
const auto* label_inst = ir_context->get_def_use_mgr()->GetDef(block_id);
if (!label_inst || label_inst->opcode() != SpvOpLabel) {
return false;
}
// Check that |block| has predecessors.
const auto& predecessors = ir_context->cfg()->preds(block_id);
if (predecessors.empty()) {
return false;
}
// The block must contain an instruction to propagate.
const auto* inst_to_propagate =
GetInstructionToPropagate(ir_context, block_id);
if (!inst_to_propagate) {
return false;
}
// We should be able to insert |inst_to_propagate| into every predecessor of
// |block|.
return std::all_of(predecessors.begin(), predecessors.end(),
[ir_context, inst_to_propagate](uint32_t predecessor_id) {
return fuzzerutil::GetLastInsertBeforeInstruction(
ir_context, predecessor_id,
inst_to_propagate->opcode()) != nullptr;
});
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,89 @@
// 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_PROPAGATE_INSTRUCTION_UP_H_
#define SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_UP_H_
#include <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 TransformationPropagateInstructionUp : public Transformation {
public:
explicit TransformationPropagateInstructionUp(
const protobufs::TransformationPropagateInstructionUp& message);
TransformationPropagateInstructionUp(
uint32_t block_id,
const std::map<uint32_t, uint32_t>& predecessor_id_to_fresh_id);
// - |block_id| must be a valid result id of some OpLabel instruction.
// - |block_id| must have at least one predecessor
// - |block_id| must contain an instruction that can be propagated using this
// transformation
// - the instruction can be propagated if:
// - it's not an OpPhi
// - it is supported by this transformation
// - it depends only on instructions from different basic blocks or on
// OpPhi instructions from the same basic block
// - it should be possible to insert the propagated instruction at the end of
// each |block_id|'s predecessor
// - |predecessor_id_to_fresh_id| must have an entry for at least every
// predecessor of |block_id|
// - each value in the |predecessor_id_to_fresh_id| map must be a fresh id
// - all fresh ids in the |predecessor_id_to_fresh_id| must be unique
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// Inserts a copy of the propagated instruction into each |block_id|'s
// predecessor. Replaces the original instruction with an OpPhi referring
// inserted copies.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
protobufs::Transformation ToMessage() const override;
// Returns true if this transformation can be applied to the block with id
// |block_id|. Concretely, returns true iff:
// - |block_id| is a valid id of some block in the module
// - |block_id| has predecessors
// - |block_id| contains an instruction that can be propagated
// - it is possible to insert the propagated instruction into every
// |block_id|'s predecessor
static bool IsApplicableToBlock(opt::IRContext* ir_context,
uint32_t block_id);
private:
// Returns the instruction that will be propagated into the predecessors of
// the |block_id|. Returns nullptr if no such an instruction exists.
static opt::Instruction* GetInstructionToPropagate(opt::IRContext* ir_context,
uint32_t block_id);
// Returns true if |opcode| is supported by this transformation.
static bool IsOpcodeSupported(SpvOp opcode);
protobufs::TransformationPropagateInstructionUp message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_UP_H_

View File

@ -0,0 +1,232 @@
// 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_replace_add_sub_mul_with_carrying_extended.h"
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
namespace {
const uint32_t kOpCompositeExtractIndexLowOrderBits = 0;
const uint32_t kArithmeticInstructionIndexLeftInOperand = 0;
const uint32_t kArithmeticInstructionIndexRightInOperand = 1;
} // namespace
TransformationReplaceAddSubMulWithCarryingExtended::
TransformationReplaceAddSubMulWithCarryingExtended(
const spvtools::fuzz::protobufs::
TransformationReplaceAddSubMulWithCarryingExtended& message)
: message_(message) {}
TransformationReplaceAddSubMulWithCarryingExtended::
TransformationReplaceAddSubMulWithCarryingExtended(uint32_t struct_fresh_id,
uint32_t result_id) {
message_.set_struct_fresh_id(struct_fresh_id);
message_.set_result_id(result_id);
}
bool TransformationReplaceAddSubMulWithCarryingExtended::IsApplicable(
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
// |message_.struct_fresh_id| must be fresh.
if (!fuzzerutil::IsFreshId(ir_context, message_.struct_fresh_id())) {
return false;
}
// |message_.result_id| must refer to a suitable OpIAdd, OpISub or OpIMul
// instruction. The instruction must be defined.
auto instruction =
ir_context->get_def_use_mgr()->GetDef(message_.result_id());
if (instruction == nullptr) {
return false;
}
if (!TransformationReplaceAddSubMulWithCarryingExtended::
IsInstructionSuitable(ir_context, *instruction)) {
return false;
}
// The struct type for holding the intermediate result must exist in the
// module. The struct type is based on the operand type.
uint32_t operand_type_id = ir_context->get_def_use_mgr()
->GetDef(instruction->GetSingleWordInOperand(
kArithmeticInstructionIndexLeftInOperand))
->type_id();
uint32_t struct_type_id = fuzzerutil::MaybeGetStructType(
ir_context, {operand_type_id, operand_type_id});
if (struct_type_id == 0) {
return false;
}
return true;
}
void TransformationReplaceAddSubMulWithCarryingExtended::Apply(
opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
// |message_.struct_fresh_id| must be fresh.
assert(fuzzerutil::IsFreshId(ir_context, message_.struct_fresh_id()) &&
"|message_.struct_fresh_id| must be fresh");
// Get the signedness of an operand if it is an int or the signedness of a
// component if it is a vector.
auto type_id =
ir_context->get_def_use_mgr()->GetDef(message_.result_id())->type_id();
auto type = ir_context->get_type_mgr()->GetType(type_id);
bool operand_is_signed;
if (type->kind() == opt::analysis::Type::kVector) {
auto operand_type = type->AsVector()->element_type();
operand_is_signed = operand_type->AsInteger()->IsSigned();
} else {
operand_is_signed = type->AsInteger()->IsSigned();
}
auto original_instruction =
ir_context->get_def_use_mgr()->GetDef(message_.result_id());
fuzzerutil::UpdateModuleIdBound(ir_context, message_.struct_fresh_id());
// Determine the opcode of the new instruction that computes the result into a
// struct.
SpvOp new_instruction_opcode;
switch (original_instruction->opcode()) {
case SpvOpIAdd:
new_instruction_opcode = SpvOpIAddCarry;
break;
case SpvOpISub:
new_instruction_opcode = SpvOpISubBorrow;
break;
case SpvOpIMul:
if (!operand_is_signed) {
new_instruction_opcode = SpvOpUMulExtended;
} else {
new_instruction_opcode = SpvOpSMulExtended;
}
break;
default:
assert(false && "The instruction has an unsupported opcode.");
return;
}
// Get the type of struct type id holding the intermediate result based on the
// operand type.
uint32_t operand_type_id =
ir_context->get_def_use_mgr()
->GetDef(original_instruction->GetSingleWordInOperand(
kArithmeticInstructionIndexLeftInOperand))
->type_id();
uint32_t struct_type_id = fuzzerutil::MaybeGetStructType(
ir_context, {operand_type_id, operand_type_id});
// Avoid unused variables in release mode.
(void)struct_type_id;
assert(struct_type_id && "The struct type must exist in the module.");
// Insert the new instruction that computes the result into a struct before
// the |original_instruction|.
original_instruction->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, new_instruction_opcode, struct_type_id,
message_.struct_fresh_id(),
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID,
{original_instruction->GetSingleWordInOperand(
kArithmeticInstructionIndexLeftInOperand)}},
{SPV_OPERAND_TYPE_ID,
{original_instruction->GetSingleWordInOperand(
kArithmeticInstructionIndexRightInOperand)}}})));
// Insert the OpCompositeExtract after the added instruction. This instruction
// takes the first component of the struct which represents low-order bits of
// the operation. This is the original result.
original_instruction->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpCompositeExtract, original_instruction->type_id(),
message_.result_id(),
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {message_.struct_fresh_id()}},
{SPV_OPERAND_TYPE_LITERAL_INTEGER,
{kOpCompositeExtractIndexLowOrderBits}}})));
// Remove the original instruction.
ir_context->KillInst(original_instruction);
// We have modified the module so most analyzes are now invalid.
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}
bool TransformationReplaceAddSubMulWithCarryingExtended::IsInstructionSuitable(
opt::IRContext* ir_context, const opt::Instruction& instruction) {
auto instruction_opcode = instruction.opcode();
// Only instructions OpIAdd, OpISub, OpIMul are supported.
switch (instruction_opcode) {
case SpvOpIAdd:
case SpvOpISub:
case SpvOpIMul:
break;
default:
return false;
}
uint32_t operand_1_type_id =
ir_context->get_def_use_mgr()
->GetDef(instruction.GetSingleWordInOperand(
kArithmeticInstructionIndexLeftInOperand))
->type_id();
uint32_t operand_2_type_id =
ir_context->get_def_use_mgr()
->GetDef(instruction.GetSingleWordInOperand(
kArithmeticInstructionIndexRightInOperand))
->type_id();
uint32_t result_type_id = instruction.type_id();
// Both type ids of the operands and the result type ids must be equal.
if (operand_1_type_id != operand_2_type_id) {
return false;
}
if (operand_2_type_id != result_type_id) {
return false;
}
// In case of OpIAdd and OpISub, the type must be unsigned.
auto type = ir_context->get_type_mgr()->GetType(instruction.type_id());
switch (instruction_opcode) {
case SpvOpIAdd:
case SpvOpISub: {
// In case of OpIAdd and OpISub if the operand is a vector, the component
// type must be unsigned. Otherwise (if the operand is an int), the
// operand must be unsigned.
bool operand_is_signed =
type->AsVector()
? type->AsVector()->element_type()->AsInteger()->IsSigned()
: type->AsInteger()->IsSigned();
if (operand_is_signed) {
return false;
}
} break;
default:
break;
}
return true;
}
protobufs::Transformation
TransformationReplaceAddSubMulWithCarryingExtended::ToMessage() const {
protobufs::Transformation result;
*result.mutable_replace_add_sub_mul_with_carrying_extended() = message_;
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,69 @@
// 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 SPIRV_TOOLS_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H
#define SPIRV_TOOLS_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H
#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 TransformationReplaceAddSubMulWithCarryingExtended
: public Transformation {
public:
explicit TransformationReplaceAddSubMulWithCarryingExtended(
const protobufs::TransformationReplaceAddSubMulWithCarryingExtended&
message);
explicit TransformationReplaceAddSubMulWithCarryingExtended(
uint32_t struct_fresh_id, uint32_t result_id);
// - |message_.struct_fresh_id| must be fresh.
// - |message_.result_id| must refer to an OpIAdd or OpISub or OpIMul
// instruction. In this instruction the result type id and the type ids of
// the operands must be the same.
// - The type of struct holding the intermediate result must exists in the
// module.
// - For OpIAdd, OpISub both operands must be unsigned.
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// A transformation that replaces instructions OpIAdd, OpISub, OpIMul with
// pairs of instructions. The first one (OpIAddCarry, OpISubBorrow,
// OpUMulExtended, OpSMulExtended) computes the result into a struct. The
// second one extracts the appropriate component from the struct to yield the
// original result.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
protobufs::Transformation ToMessage() const override;
// Checks if an OpIAdd, OpISub or OpIMul instruction can be used by the
// transformation.
bool static IsInstructionSuitable(opt::IRContext* ir_context,
const opt::Instruction& instruction);
private:
protobufs::TransformationReplaceAddSubMulWithCarryingExtended message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SPIRV_TOOLS_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H

View File

@ -90,6 +90,40 @@ TransformationReplaceConstantWithUniform::MakeLoadInstruction(
operands_for_load);
}
opt::Instruction*
TransformationReplaceConstantWithUniform::GetInsertBeforeInstruction(
opt::IRContext* ir_context) const {
auto* result =
FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
if (!result) {
return nullptr;
}
// The use might be in an OpPhi instruction.
if (result->opcode() == SpvOpPhi) {
// OpPhi instructions must be the first instructions in a block. Thus, we
// can't insert above the OpPhi instruction. Given the predecessor block
// that corresponds to the id use, get the last instruction in that block
// above which we can insert OpAccessChain and OpLoad.
return fuzzerutil::GetLastInsertBeforeInstruction(
ir_context,
result->GetSingleWordInOperand(
message_.id_use_descriptor().in_operand_index() + 1),
SpvOpLoad);
}
// The only operand that we could've replaced in the OpBranchConditional is
// the condition id. But that operand has a boolean type and uniform variables
// can't store booleans (see the spec on OpTypeBool). Thus, |result| can't be
// an OpBranchConditional.
assert(result->opcode() != SpvOpBranchConditional &&
"OpBranchConditional has no operands to replace");
assert(fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, result) &&
"We should be able to insert OpLoad and OpAccessChain at this point");
return result;
}
bool TransformationReplaceConstantWithUniform::IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const {
@ -188,6 +222,12 @@ bool TransformationReplaceConstantWithUniform::IsApplicable(
}
}
// Once all checks are completed, we should be able to safely insert
// OpAccessChain and OpLoad into the module.
assert(GetInsertBeforeInstruction(ir_context) &&
"There must exist an instruction that we can use to insert "
"OpAccessChain and OpLoad above");
return true;
}
@ -195,7 +235,7 @@ void TransformationReplaceConstantWithUniform::Apply(
spvtools::opt::IRContext* ir_context,
TransformationContext* /*unused*/) const {
// Get the instruction that contains the id use we wish to replace.
auto instruction_containing_constant_use =
auto* instruction_containing_constant_use =
FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
assert(instruction_containing_constant_use &&
"Precondition requires that the id use can be found.");
@ -210,12 +250,17 @@ void TransformationReplaceConstantWithUniform::Apply(
->GetDef(message_.id_use_descriptor().id_of_interest())
->type_id();
// Get an instruction that will be used to insert OpAccessChain and OpLoad.
auto* insert_before_inst = GetInsertBeforeInstruction(ir_context);
assert(insert_before_inst &&
"There must exist an insertion point for OpAccessChain and OpLoad");
// Add an access chain instruction to target the uniform element.
instruction_containing_constant_use->InsertBefore(
insert_before_inst->InsertBefore(
MakeAccessChainInstruction(ir_context, constant_type_id));
// Add a load from this access chain.
instruction_containing_constant_use->InsertBefore(
insert_before_inst->InsertBefore(
MakeLoadInstruction(ir_context, constant_type_id));
// Adjust the instruction containing the usage of the constant so that this

View File

@ -84,6 +84,11 @@ class TransformationReplaceConstantWithUniform : public Transformation {
std::unique_ptr<opt::Instruction> MakeLoadInstruction(
spvtools::opt::IRContext* ir_context, uint32_t constant_type_id) const;
// OpAccessChain and OpLoad will be inserted above the instruction returned
// by this function. Returns nullptr if no such instruction is present.
opt::Instruction* GetInsertBeforeInstruction(
opt::IRContext* ir_context) const;
protobufs::TransformationReplaceConstantWithUniform message_;
};

View File

@ -59,14 +59,6 @@ bool TransformationReplaceCopyObjectWithStoreLoad::IsApplicable(
return false;
}
// It must be valid to insert the OpStore and OpLoad instruction before it.
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore,
copy_object_instruction) ||
!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad,
copy_object_instruction)) {
return false;
}
// A pointer type instruction pointing to the value type must be defined.
auto pointer_type_id = fuzzerutil::MaybeGetPointerType(
ir_context, copy_object_instruction->type_id(),

View File

@ -109,7 +109,7 @@ protobufs::Transformation TransformationReplaceIdWithSynonym::ToMessage()
bool TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
opt::IRContext* ir_context, opt::Instruction* use_instruction,
uint32_t use_in_operand_index) {
if (use_instruction->opcode() == SpvOpAccessChain &&
if (spvOpcodeIsAccessChain(use_instruction->opcode()) &&
use_in_operand_index > 0) {
// This is an access chain index. If the (sub-)object being accessed by the
// given index has struct type then we cannot replace the use with a

View File

@ -45,26 +45,18 @@ bool TransformationReplaceLoadStoreWithCopyMemory::IsApplicable(
opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
// This transformation is only applicable to the pair of OpLoad and OpStore
// instructions.
if (message_.load_instruction_descriptor().target_instruction_opcode() !=
SpvOpLoad) {
return false;
}
if (message_.store_instruction_descriptor().target_instruction_opcode() !=
SpvOpStore) {
return false;
}
// The OpLoad instruction must be defined.
auto load_instruction =
FindInstruction(message_.load_instruction_descriptor(), ir_context);
if (!load_instruction) {
if (!load_instruction || load_instruction->opcode() != SpvOpLoad) {
return false;
}
// The OpStore instruction must be defined.
auto store_instruction =
FindInstruction(message_.store_instruction_descriptor(), ir_context);
if (!store_instruction) {
if (!store_instruction || store_instruction->opcode() != SpvOpStore) {
return false;
}

View File

@ -101,7 +101,8 @@ bool TransformationReplaceParamsWithStruct::IsApplicable(
return false;
}
auto caller_id_to_fresh_composite_id = fuzzerutil::RepeatedUInt32PairToMap(
const auto caller_id_to_fresh_composite_id =
fuzzerutil::RepeatedUInt32PairToMap(
message_.caller_id_to_fresh_composite_id());
// Check that |callee_id_to_fresh_composite_id| is valid.
@ -151,23 +152,11 @@ void TransformationReplaceParamsWithStruct::Apply(
// 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()));
}
}
const auto indices_of_replaced_params =
ComputeIndicesOfReplacedParameters(ir_context);
auto caller_id_to_fresh_composite_id = fuzzerutil::RepeatedUInt32PairToMap(
const auto caller_id_to_fresh_composite_id =
fuzzerutil::RepeatedUInt32PairToMap(
message_.caller_id_to_fresh_composite_id());
// Update all function calls.
@ -182,12 +171,13 @@ void TransformationReplaceParamsWithStruct::Apply(
}
// 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) {
// and in decreasing order to make sure we have removed correct operands.
for (auto index : std::set<uint32_t, std::greater<uint32_t>>(
indices_of_replaced_params.begin(),
indices_of_replaced_params.end())) {
// +1 since the first in operand to OpFunctionCall is the result id of
// the function.
inst->RemoveInOperand(*it + 1);
inst->RemoveInOperand(index + 1);
}
// Insert OpCompositeConstruct before the function call.
@ -305,5 +295,30 @@ uint32_t TransformationReplaceParamsWithStruct::MaybeGetRequiredStructType(
return fuzzerutil::MaybeGetStructType(ir_context, component_type_ids);
}
std::vector<uint32_t>
TransformationReplaceParamsWithStruct::ComputeIndicesOfReplacedParameters(
opt::IRContext* ir_context) const {
assert(!message_.parameter_id().empty() &&
"There must be at least one parameter to replace");
const auto* function = fuzzerutil::GetFunctionFromParameterId(
ir_context, message_.parameter_id(0));
assert(function && "|parameter_id|s are invalid");
std::vector<uint32_t> result;
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");
result.push_back(static_cast<uint32_t>(it - params.begin()));
}
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -73,6 +73,12 @@ class TransformationReplaceParamsWithStruct : public Transformation {
// transformation (see docs on the IsApplicable method to learn more).
uint32_t MaybeGetRequiredStructType(opt::IRContext* ir_context) const;
// Returns a vector of indices of parameters to replace. Concretely, i'th
// element is the index of the parameter with result id |parameter_id[i]| in
// its function.
std::vector<uint32_t> ComputeIndicesOfReplacedParameters(
opt::IRContext* ir_context) const;
protobufs::TransformationReplaceParamsWithStruct message_;
};

View File

@ -719,3 +719,15 @@ std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode) {
return {};
}
}
bool spvOpcodeIsAccessChain(SpvOp opcode) {
switch (opcode) {
case SpvOpAccessChain:
case SpvOpInBoundsAccessChain:
case SpvOpPtrAccessChain:
case SpvOpInBoundsPtrAccessChain:
return true;
default:
return false;
}
}

View File

@ -144,4 +144,7 @@ bool spvOpcodeIsImageSample(SpvOp opcode);
// operands for |opcode|.
std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode);
// Returns true for opcodes that represents access chain instructions.
bool spvOpcodeIsAccessChain(SpvOp opcode);
#endif // SOURCE_OPCODE_H_

View File

@ -226,7 +226,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
wrap_opkill.cpp
)
if(MSVC)
if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")))
# Enable parallel builds across four cores for this lib
add_definitions(/MP4)
endif()

View File

@ -22,6 +22,7 @@
#include "source/cfa.h"
#include "source/latest_version_glsl_std_450_header.h"
#include "source/opt/eliminate_dead_functions_util.h"
#include "source/opt/iterator.h"
#include "source/opt/reflect.h"
#include "source/spirv_constant.h"
@ -727,8 +728,8 @@ bool AggressiveDCEPass::EliminateDeadFunctions() {
funcIter != get_module()->end();) {
if (live_function_set.count(&*funcIter) == 0) {
modified = true;
EliminateFunction(&*funcIter);
funcIter = funcIter.Erase();
funcIter =
eliminatedeadfunctionsutil::EliminateFunction(context(), &funcIter);
} else {
++funcIter;
}
@ -737,12 +738,6 @@ bool AggressiveDCEPass::EliminateDeadFunctions() {
return modified;
}
void AggressiveDCEPass::EliminateFunction(Function* func) {
// Remove all of the instruction in the function body
func->ForEachInst([this](Instruction* inst) { context()->KillInst(inst); },
true);
}
bool AggressiveDCEPass::ProcessGlobalValues() {
// Remove debug and annotation statements referencing dead instructions.
// This must be done before killing the instructions, otherwise there are

View File

@ -127,9 +127,6 @@ class AggressiveDCEPass : public MemPass {
// Erases functions that are unreachable from the entry points of the module.
bool EliminateDeadFunctions();
// Removes |func| from the module and deletes all its instructions.
void EliminateFunction(Function* func);
// For function |func|, mark all Stores to non-function-scope variables
// and block terminating instructions as live. Recursively mark the values
// they use. When complete, mark any non-live instructions to be deleted.

View File

@ -196,6 +196,7 @@ bool DeadInsertElimPass::EliminateDeadInsertsOnePass(Function* func) {
}
const uint32_t id = ii->result_id();
get_def_use_mgr()->ForEachUser(id, [&ii, this](Instruction* user) {
if (user->IsOpenCL100DebugInstr()) return;
switch (user->opcode()) {
case SpvOpCompositeInsert:
case SpvOpPhi:

View File

@ -21,9 +21,35 @@ namespace eliminatedeadfunctionsutil {
Module::iterator EliminateFunction(IRContext* context,
Module::iterator* func_iter) {
bool first_func = *func_iter == context->module()->begin();
bool seen_func_end = false;
(*func_iter)
->ForEachInst([context](Instruction* inst) { context->KillInst(inst); },
true);
->ForEachInst(
[context, first_func, func_iter, &seen_func_end](Instruction* inst) {
if (inst->opcode() == SpvOpFunctionEnd) {
seen_func_end = true;
}
// Move non-semantic instructions to the previous function or
// global values if this is the first function.
if (seen_func_end && inst->opcode() == SpvOpExtInst) {
assert(inst->IsNonSemanticInstruction());
std::unique_ptr<Instruction> clone(inst->Clone(context));
context->ForgetUses(inst);
context->AnalyzeDefUse(clone.get());
if (first_func) {
context->AddGlobalValue(std::move(clone));
} else {
auto prev_func_iter = *func_iter;
--prev_func_iter;
prev_func_iter->AddNonSemanticInstruction(std::move(clone));
}
inst->ToNop();
} else {
context->KillNonSemanticInfo(inst);
context->KillInst(inst);
}
},
true, true);
return func_iter->Erase();
}

View File

@ -1467,7 +1467,7 @@ FoldingRule CompositeConstructFeedingExtract() {
type_mgr->GetType(element_def->type_id())->AsVector();
if (element_type) {
uint32_t vector_size = element_type->element_count();
if (vector_size < element_index) {
if (vector_size <= element_index) {
// The element we want comes after this vector.
element_index -= vector_size;
} else {

View File

@ -47,31 +47,40 @@ Function* Function::Clone(IRContext* ctx) const {
}
clone->SetFunctionEnd(std::unique_ptr<Instruction>(EndInst()->Clone(ctx)));
clone->non_semantic_.reserve(non_semantic_.size());
for (auto& non_semantic : non_semantic_) {
clone->AddNonSemanticInstruction(
std::unique_ptr<Instruction>(non_semantic->Clone(ctx)));
}
return clone;
}
void Function::ForEachInst(const std::function<void(Instruction*)>& f,
bool run_on_debug_line_insts) {
bool run_on_debug_line_insts,
bool run_on_non_semantic_insts) {
WhileEachInst(
[&f](Instruction* inst) {
f(inst);
return true;
},
run_on_debug_line_insts);
run_on_debug_line_insts, run_on_non_semantic_insts);
}
void Function::ForEachInst(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts) const {
bool run_on_debug_line_insts,
bool run_on_non_semantic_insts) const {
WhileEachInst(
[&f](const Instruction* inst) {
f(inst);
return true;
},
run_on_debug_line_insts);
run_on_debug_line_insts, run_on_non_semantic_insts);
}
bool Function::WhileEachInst(const std::function<bool(Instruction*)>& f,
bool run_on_debug_line_insts) {
bool run_on_debug_line_insts,
bool run_on_non_semantic_insts) {
if (def_inst_) {
if (!def_inst_->WhileEachInst(f, run_on_debug_line_insts)) {
return false;
@ -99,13 +108,26 @@ bool Function::WhileEachInst(const std::function<bool(Instruction*)>& f,
}
}
if (end_inst_) return end_inst_->WhileEachInst(f, run_on_debug_line_insts);
if (end_inst_) {
if (!end_inst_->WhileEachInst(f, run_on_debug_line_insts)) {
return false;
}
}
if (run_on_non_semantic_insts) {
for (auto& non_semantic : non_semantic_) {
if (!non_semantic->WhileEachInst(f, run_on_debug_line_insts)) {
return false;
}
}
}
return true;
}
bool Function::WhileEachInst(const std::function<bool(const Instruction*)>& f,
bool run_on_debug_line_insts) const {
bool run_on_debug_line_insts,
bool run_on_non_semantic_insts) const {
if (def_inst_) {
if (!static_cast<const Instruction*>(def_inst_.get())
->WhileEachInst(f, run_on_debug_line_insts)) {
@ -133,9 +155,21 @@ bool Function::WhileEachInst(const std::function<bool(const Instruction*)>& f,
}
}
if (end_inst_)
return static_cast<const Instruction*>(end_inst_.get())
->WhileEachInst(f, run_on_debug_line_insts);
if (end_inst_) {
if (!static_cast<const Instruction*>(end_inst_.get())
->WhileEachInst(f, run_on_debug_line_insts)) {
return false;
}
}
if (run_on_non_semantic_insts) {
for (auto& non_semantic : non_semantic_) {
if (!static_cast<const Instruction*>(non_semantic.get())
->WhileEachInst(f, run_on_debug_line_insts)) {
return false;
}
}
}
return true;
}

View File

@ -79,6 +79,11 @@ class Function {
// Saves the given function end instruction.
inline void SetFunctionEnd(std::unique_ptr<Instruction> end_inst);
// Add a non-semantic instruction that succeeds this function in the module.
// These instructions are maintained in the order they are added.
inline void AddNonSemanticInstruction(
std::unique_ptr<Instruction> non_semantic);
// Returns the given function end instruction.
inline Instruction* EndInst() { return end_inst_.get(); }
inline const Instruction* EndInst() const { return end_inst_.get(); }
@ -115,19 +120,24 @@ class Function {
}
// Runs the given function |f| on instructions in this function, in order,
// and optionally on debug line instructions that might precede them.
// and optionally on debug line instructions that might precede them and
// non-semantic instructions that succceed the function.
void ForEachInst(const std::function<void(Instruction*)>& f,
bool run_on_debug_line_insts = false);
bool run_on_debug_line_insts = false,
bool run_on_non_semantic_insts = false);
void ForEachInst(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts = false) const;
bool run_on_debug_line_insts = false,
bool run_on_non_semantic_insts = false) const;
// Runs the given function |f| on instructions in this function, in order,
// and optionally on debug line instructions that might precede them.
// If |f| returns false, iteration is terminated and this function returns
// false.
// and optionally on debug line instructions that might precede them and
// non-semantic instructions that succeed the function. If |f| returns
// false, iteration is terminated and this function returns false.
bool WhileEachInst(const std::function<bool(Instruction*)>& f,
bool run_on_debug_line_insts = false);
bool run_on_debug_line_insts = false,
bool run_on_non_semantic_insts = false);
bool WhileEachInst(const std::function<bool(const Instruction*)>& f,
bool run_on_debug_line_insts = false) const;
bool run_on_debug_line_insts = false,
bool run_on_non_semantic_insts = false) const;
// Runs the given function |f| on each parameter instruction in this function,
// in order, and optionally on debug line instructions that might precede
@ -172,6 +182,8 @@ class Function {
std::vector<std::unique_ptr<BasicBlock>> blocks_;
// The OpFunctionEnd instruction.
std::unique_ptr<Instruction> end_inst_;
// Non-semantic instructions succeeded by this function.
std::vector<std::unique_ptr<Instruction>> non_semantic_;
};
// Pretty-prints |func| to |str|. Returns |str|.
@ -235,6 +247,11 @@ inline void Function::SetFunctionEnd(std::unique_ptr<Instruction> end_inst) {
end_inst_ = std::move(end_inst);
}
inline void Function::AddNonSemanticInstruction(
std::unique_ptr<Instruction> non_semantic) {
non_semantic_.emplace_back(std::move(non_semantic));
}
} // namespace opt
} // namespace spvtools

View File

@ -183,8 +183,9 @@ void Instruction::ToBinaryWithoutAttachedDebugInsts(
std::vector<uint32_t>* binary) const {
const uint32_t num_words = 1 + NumOperandWords();
binary->push_back((num_words << 16) | static_cast<uint16_t>(opcode_));
for (const auto& operand : operands_)
for (const auto& operand : operands_) {
binary->insert(binary->end(), operand.words.begin(), operand.words.end());
}
}
void Instruction::ReplaceOperands(const OperandList& new_operands) {
@ -283,8 +284,7 @@ bool Instruction::IsVulkanStorageImage() const {
// Check if the image is sampled. If we do not know for sure that it is,
// then assume it is a storage image.
auto s = base_type->GetSingleWordInOperand(kTypeImageSampledIndex);
return s != 1;
return base_type->GetSingleWordInOperand(kTypeImageSampledIndex) != 1;
}
bool Instruction::IsVulkanSampledImage() const {
@ -318,8 +318,7 @@ bool Instruction::IsVulkanSampledImage() const {
// Check if the image is sampled. If we know for sure that it is,
// then return true.
auto s = base_type->GetSingleWordInOperand(kTypeImageSampledIndex);
return s == 1;
return base_type->GetSingleWordInOperand(kTypeImageSampledIndex) == 1;
}
bool Instruction::IsVulkanStorageTexelBuffer() const {
@ -502,16 +501,16 @@ uint32_t Instruction::GetTypeComponent(uint32_t element) const {
return subtype;
}
Instruction* Instruction::InsertBefore(std::unique_ptr<Instruction>&& i) {
i.get()->InsertBefore(this);
return i.release();
Instruction* Instruction::InsertBefore(std::unique_ptr<Instruction>&& inst) {
inst.get()->InsertBefore(this);
return inst.release();
}
Instruction* Instruction::InsertBefore(
std::vector<std::unique_ptr<Instruction>>&& list) {
Instruction* first_node = list.front().get();
for (auto& i : list) {
i.release()->InsertBefore(this);
for (auto& inst : list) {
inst.release()->InsertBefore(this);
}
list.clear();
return first_node;
@ -568,10 +567,13 @@ bool Instruction::IsValidBasePointer() const {
}
OpenCLDebugInfo100Instructions Instruction::GetOpenCL100DebugOpcode() const {
if (opcode() != SpvOpExtInst) return OpenCLDebugInfo100InstructionsMax;
if (!context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo())
if (opcode() != SpvOpExtInst) {
return OpenCLDebugInfo100InstructionsMax;
}
if (!context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) {
return OpenCLDebugInfo100InstructionsMax;
}
if (GetSingleWordInOperand(kExtInstSetIdInIdx) !=
context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) {
@ -622,6 +624,7 @@ bool Instruction::IsFoldableByFoldScalar() const {
if (!folder.IsFoldableOpcode(opcode())) {
return false;
}
Instruction* type = context()->get_def_use_mgr()->GetDef(type_id());
if (!folder.IsFoldableType(type)) {
return false;
@ -889,6 +892,16 @@ bool Instruction::IsOpcodeSafeToDelete() const {
}
}
bool Instruction::IsNonSemanticInstruction() const {
if (!HasResultId()) return false;
if (opcode() != SpvOpExtInst) return false;
auto import_inst =
context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(0));
std::string import_name = import_inst->GetInOperand(0).AsString();
return import_name.find("NonSemantic.") == 0;
}
void DebugScope::ToBinary(uint32_t type_id, uint32_t result_id,
uint32_t ext_set,
std::vector<uint32_t>* binary) const {

View File

@ -549,6 +549,9 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> {
return GetOpenCL100DebugOpcode() != OpenCLDebugInfo100InstructionsMax;
}
// Returns true if this instructions a non-semantic instruction.
bool IsNonSemanticInstruction() const;
// Dump this instruction on stderr. Useful when running interactive
// debuggers.
void Dump() const;
@ -749,21 +752,21 @@ inline void Instruction::ForEachInst(
}
inline void Instruction::ForEachId(const std::function<void(uint32_t*)>& f) {
for (auto& opnd : operands_)
if (spvIsIdType(opnd.type)) f(&opnd.words[0]);
for (auto& operand : operands_)
if (spvIsIdType(operand.type)) f(&operand.words[0]);
}
inline void Instruction::ForEachId(
const std::function<void(const uint32_t*)>& f) const {
for (const auto& opnd : operands_)
if (spvIsIdType(opnd.type)) f(&opnd.words[0]);
for (const auto& operand : operands_)
if (spvIsIdType(operand.type)) f(&operand.words[0]);
}
inline bool Instruction::WhileEachInId(
const std::function<bool(uint32_t*)>& f) {
for (auto& opnd : operands_) {
if (spvIsInIdType(opnd.type)) {
if (!f(&opnd.words[0])) return false;
for (auto& operand : operands_) {
if (spvIsInIdType(operand.type) && !f(&operand.words[0])) {
return false;
}
}
return true;
@ -771,9 +774,9 @@ inline bool Instruction::WhileEachInId(
inline bool Instruction::WhileEachInId(
const std::function<bool(const uint32_t*)>& f) const {
for (const auto& opnd : operands_) {
if (spvIsInIdType(opnd.type)) {
if (!f(&opnd.words[0])) return false;
for (const auto& operand : operands_) {
if (spvIsInIdType(operand.type) && !f(&operand.words[0])) {
return false;
}
}
return true;
@ -796,13 +799,13 @@ inline void Instruction::ForEachInId(
inline bool Instruction::WhileEachInOperand(
const std::function<bool(uint32_t*)>& f) {
for (auto& opnd : operands_) {
switch (opnd.type) {
for (auto& operand : operands_) {
switch (operand.type) {
case SPV_OPERAND_TYPE_RESULT_ID:
case SPV_OPERAND_TYPE_TYPE_ID:
break;
default:
if (!f(&opnd.words[0])) return false;
if (!f(&operand.words[0])) return false;
break;
}
}
@ -811,13 +814,13 @@ inline bool Instruction::WhileEachInOperand(
inline bool Instruction::WhileEachInOperand(
const std::function<bool(const uint32_t*)>& f) const {
for (const auto& opnd : operands_) {
switch (opnd.type) {
for (const auto& operand : operands_) {
switch (operand.type) {
case SPV_OPERAND_TYPE_RESULT_ID:
case SPV_OPERAND_TYPE_TYPE_ID:
break;
default:
if (!f(&opnd.words[0])) return false;
if (!f(&operand.words[0])) return false;
break;
}
}
@ -826,16 +829,16 @@ inline bool Instruction::WhileEachInOperand(
inline void Instruction::ForEachInOperand(
const std::function<void(uint32_t*)>& f) {
WhileEachInOperand([&f](uint32_t* op) {
f(op);
WhileEachInOperand([&f](uint32_t* operand) {
f(operand);
return true;
});
}
inline void Instruction::ForEachInOperand(
const std::function<void(const uint32_t*)>& f) const {
WhileEachInOperand([&f](const uint32_t* op) {
f(op);
WhileEachInOperand([&f](const uint32_t* operand) {
f(operand);
return true;
});
}

View File

@ -213,6 +213,30 @@ Instruction* IRContext::KillInst(Instruction* inst) {
return next_instruction;
}
void IRContext::KillNonSemanticInfo(Instruction* inst) {
if (!inst->HasResultId()) return;
std::vector<Instruction*> work_list;
std::vector<Instruction*> to_kill;
std::unordered_set<Instruction*> seen;
work_list.push_back(inst);
while (!work_list.empty()) {
auto* i = work_list.back();
work_list.pop_back();
get_def_use_mgr()->ForEachUser(
i, [&work_list, &to_kill, &seen](Instruction* user) {
if (user->IsNonSemanticInstruction() && seen.insert(user).second) {
work_list.push_back(user);
to_kill.push_back(user);
}
});
}
for (auto* dead : to_kill) {
KillInst(dead);
}
}
bool IRContext::KillDef(uint32_t id) {
Instruction* def = get_def_use_mgr()->GetDef(id);
if (def != nullptr) {

View File

@ -403,6 +403,9 @@ class IRContext {
// instruction exists.
Instruction* KillInst(Instruction* inst);
// Removes the non-semantic instruction tree that uses |inst|'s result id.
void KillNonSemanticInfo(Instruction* inst);
// Returns true if all of the given analyses are valid.
bool AreAnalysesValid(Analysis set) { return (set & valid_analyses_) == set; }

View File

@ -167,13 +167,22 @@ bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) {
} else if (IsTypeInst(opcode)) {
module_->AddType(std::move(spv_inst));
} else if (IsConstantInst(opcode) || opcode == SpvOpVariable ||
opcode == SpvOpUndef ||
(opcode == SpvOpExtInst &&
spvExtInstIsNonSemantic(inst->ext_inst_type))) {
opcode == SpvOpUndef) {
module_->AddGlobalValue(std::move(spv_inst));
} else if (opcode == SpvOpExtInst &&
spvExtInstIsDebugInfo(inst->ext_inst_type)) {
module_->AddExtInstDebugInfo(std::move(spv_inst));
} else if (opcode == SpvOpExtInst &&
spvExtInstIsNonSemantic(inst->ext_inst_type)) {
// If there are no functions, add the non-semantic instructions to the
// global values. Otherwise append it to the list of the last function.
auto func_begin = module_->begin();
auto func_end = module_->end();
if (func_begin == func_end) {
module_->AddGlobalValue(std::move(spv_inst));
} else {
(--func_end)->AddNonSemanticInstruction(std::move(spv_inst));
}
} else {
Errorf(consumer_, src, loc,
"Unhandled inst type (opcode: %d) found outside function "

View File

@ -77,6 +77,15 @@ void LocalAccessChainConvertPass::AppendConstantOperands(
bool LocalAccessChainConvertPass::ReplaceAccessChainLoad(
const Instruction* address_inst, Instruction* original_load) {
// Build and append load of variable in ptrInst
if (address_inst->NumInOperands() == 1) {
// An access chain with no indices is essentially a copy. All that is
// needed is to propagate the address.
context()->ReplaceAllUsesWith(
address_inst->result_id(),
address_inst->GetSingleWordInOperand(kAccessChainPtrIdInIdx));
return true;
}
std::vector<std::unique_ptr<Instruction>> new_inst;
uint32_t varId;
uint32_t varPteTypeId;
@ -109,6 +118,18 @@ bool LocalAccessChainConvertPass::ReplaceAccessChainLoad(
bool LocalAccessChainConvertPass::GenAccessChainStoreReplacement(
const Instruction* ptrInst, uint32_t valId,
std::vector<std::unique_ptr<Instruction>>* newInsts) {
if (ptrInst->NumInOperands() == 1) {
// An access chain with no indices is essentially a copy. However, we still
// have to create a new store because the old ones will be deleted.
BuildAndAppendInst(
SpvOpStore, 0, 0,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
{ptrInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx)}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}}},
newInsts);
return true;
}
// Build and append load of variable in ptrInst
uint32_t varId;
uint32_t varPteTypeId;
@ -246,11 +267,13 @@ Pass::Status LocalAccessChainConvertPass::ConvertLocalAccessChains(
if (!GenAccessChainStoreReplacement(ptrInst, valId, &newInsts)) {
return Status::Failure;
}
size_t num_of_instructions_to_skip = newInsts.size() - 1;
dead_instructions.push_back(&*ii);
++ii;
ii = ii.InsertBefore(std::move(newInsts));
for (size_t i = 0; i < num_of_instructions_to_skip; ++i) {
++ii;
++ii;
}
modified = true;
} break;
default:

View File

@ -191,14 +191,13 @@ bool Loop::GetInductionInitValue(const Instruction* induction,
if (!constant) return false;
if (value) {
const analysis::Integer* type =
constant->AsIntConstant()->type()->AsInteger();
if (type->IsSigned()) {
*value = constant->AsIntConstant()->GetS32BitValue();
} else {
*value = constant->AsIntConstant()->GetU32BitValue();
const analysis::Integer* type = constant->type()->AsInteger();
if (!type) {
return false;
}
*value = type->IsSigned() ? constant->GetSignExtendedValue()
: constant->GetZeroExtendedValue();
}
return true;
@ -682,22 +681,19 @@ bool Loop::FindNumberOfIterations(const Instruction* induction,
if (!upper_bound) return false;
// Must be integer because of the opcode on the condition.
int64_t condition_value = 0;
const analysis::Integer* type = upper_bound->type()->AsInteger();
const analysis::Integer* type =
upper_bound->AsIntConstant()->type()->AsInteger();
if (type->width() > 32) {
if (!type || type->width() > 64) {
return false;
}
if (type->IsSigned()) {
condition_value = upper_bound->AsIntConstant()->GetS32BitValue();
} else {
condition_value = upper_bound->AsIntConstant()->GetU32BitValue();
}
int64_t condition_value = type->IsSigned()
? upper_bound->GetSignExtendedValue()
: upper_bound->GetZeroExtendedValue();
// Find the instruction which is stepping through the loop.
//
// GetInductionStepOperation returns nullptr if |step_inst| is OpConstantNull.
Instruction* step_inst = GetInductionStepOperation(induction);
if (!step_inst) return false;

View File

@ -98,7 +98,10 @@ void Module::ForEachInst(const std::function<void(Instruction*)>& f,
DELEGATE(ext_inst_debuginfo_);
DELEGATE(annotations_);
DELEGATE(types_values_);
for (auto& i : functions_) i->ForEachInst(f, run_on_debug_line_insts);
for (auto& i : functions_) {
i->ForEachInst(f, run_on_debug_line_insts,
/* run_on_non_semantic_insts = */ true);
}
#undef DELEGATE
}
@ -120,8 +123,9 @@ void Module::ForEachInst(const std::function<void(const Instruction*)>& f,
for (auto& i : types_values_) DELEGATE(i);
for (auto& i : ext_inst_debuginfo_) DELEGATE(i);
for (auto& i : functions_) {
static_cast<const Function*>(i.get())->ForEachInst(f,
run_on_debug_line_insts);
static_cast<const Function*>(i.get())->ForEachInst(
f, run_on_debug_line_insts,
/* run_on_non_semantic_insts = */ true);
}
if (run_on_debug_line_insts) {
for (auto& i : trailing_dbg_line_info_) DELEGATE(i);

View File

@ -70,7 +70,7 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
simple_conditional_branch_to_branch_reduction_opportunity.cpp
)
if(MSVC)
if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")))
# Enable parallel builds across four cores for this lib
add_definitions(/MP4)
endif()

View File

@ -81,6 +81,7 @@ bool DoesDebugInfoOperandMatchExpectation(
const ValidationState_t& _,
const std::function<bool(OpenCLDebugInfo100Instructions)>& expectation,
const Instruction* inst, uint32_t word_index) {
if (inst->words().size() <= word_index) return false;
auto* debug_inst = _.FindDef(inst->word(word_index));
if (debug_inst->opcode() != SpvOpExtInst ||
debug_inst->ext_inst_type() != SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 ||
@ -167,9 +168,16 @@ spv_result_t ValidateOperandLexicalScope(
spv_result_t ValidateOperandDebugType(
ValidationState_t& _, const std::string& debug_inst_name,
const Instruction* inst, uint32_t word_index,
const std::function<std::string()>& ext_inst_name) {
const std::function<std::string()>& ext_inst_name,
bool allow_template_param) {
std::function<bool(OpenCLDebugInfo100Instructions)> expectation =
[](OpenCLDebugInfo100Instructions dbg_inst) {
[&allow_template_param](OpenCLDebugInfo100Instructions dbg_inst) {
if (allow_template_param &&
(dbg_inst == OpenCLDebugInfo100DebugTypeTemplateParameter ||
dbg_inst ==
OpenCLDebugInfo100DebugTypeTemplateTemplateParameter)) {
return true;
}
return OpenCLDebugInfo100DebugTypeBasic <= dbg_inst &&
dbg_inst <= OpenCLDebugInfo100DebugTypePtrToMember;
};
@ -636,6 +644,45 @@ spv_result_t ValidateClspvReflectionInstruction(ValidationState_t& _,
return SPV_SUCCESS;
}
bool IsConstIntScalarTypeWith32Or64Bits(ValidationState_t& _,
Instruction* instr) {
if (instr->opcode() != SpvOpConstant) return false;
if (!_.IsIntScalarType(instr->type_id())) return false;
uint32_t size_in_bits = _.GetBitWidth(instr->type_id());
return size_in_bits == 32 || size_in_bits == 64;
}
bool IsConstWithIntScalarType(ValidationState_t& _, const Instruction* inst,
uint32_t word_index) {
auto* int_scalar_const = _.FindDef(inst->word(word_index));
if (int_scalar_const->opcode() == SpvOpConstant &&
_.IsIntScalarType(int_scalar_const->type_id())) {
return true;
}
return false;
}
bool IsDebugVariableWithIntScalarType(ValidationState_t& _,
const Instruction* inst,
uint32_t word_index) {
auto* dbg_int_scalar_var = _.FindDef(inst->word(word_index));
if (OpenCLDebugInfo100Instructions(dbg_int_scalar_var->word(4)) ==
OpenCLDebugInfo100DebugLocalVariable ||
OpenCLDebugInfo100Instructions(dbg_int_scalar_var->word(4)) ==
OpenCLDebugInfo100DebugGlobalVariable) {
auto* dbg_type = _.FindDef(dbg_int_scalar_var->word(6));
if (OpenCLDebugInfo100Instructions(dbg_type->word(4)) ==
OpenCLDebugInfo100DebugTypeBasic &&
(OpenCLDebugInfo100DebugBaseTypeAttributeEncoding(dbg_type->word(7)) ==
OpenCLDebugInfo100Signed ||
OpenCLDebugInfo100DebugBaseTypeAttributeEncoding(dbg_type->word(7)) ==
OpenCLDebugInfo100Unsigned)) {
return true;
}
}
return false;
}
} // anonymous namespace
spv_result_t ValidateExtension(ValidationState_t& _, const Instruction* inst) {
@ -2678,17 +2725,53 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
break;
}
case OpenCLDebugInfo100DebugTypeArray: {
auto validate_base_type =
ValidateOperandDebugType(_, "Base Type", inst, 5, ext_inst_name);
auto validate_base_type = ValidateOperandDebugType(
_, "Base Type", inst, 5, ext_inst_name, false);
if (validate_base_type != SPV_SUCCESS) return validate_base_type;
for (uint32_t i = 6; i < num_words; ++i) {
CHECK_OPERAND("Component Count", SpvOpConstant, i);
bool invalid = false;
auto* component_count = _.FindDef(inst->word(i));
if (!_.IsIntScalarType(component_count->type_id()) ||
!component_count->word(3)) {
if (IsConstIntScalarTypeWith32Or64Bits(_, component_count)) {
// TODO: We need a spec discussion for the bindless array.
if (!component_count->word(3)) {
invalid = true;
}
} else if (component_count->words().size() > 6 &&
(OpenCLDebugInfo100Instructions(component_count->word(
4)) == OpenCLDebugInfo100DebugLocalVariable ||
OpenCLDebugInfo100Instructions(component_count->word(
4)) == OpenCLDebugInfo100DebugGlobalVariable)) {
auto* component_count_type = _.FindDef(component_count->word(6));
if (component_count_type->words().size() > 7) {
if (OpenCLDebugInfo100Instructions(component_count_type->word(
4)) != OpenCLDebugInfo100DebugTypeBasic ||
OpenCLDebugInfo100DebugBaseTypeAttributeEncoding(
component_count_type->word(7)) !=
OpenCLDebugInfo100Unsigned) {
invalid = true;
} else {
// DebugTypeBasic for DebugLocalVariable/DebugGlobalVariable
// must have Unsigned encoding and 32 or 64 as its size in bits.
Instruction* size_in_bits =
_.FindDef(component_count_type->word(6));
if (!_.IsIntScalarType(size_in_bits->type_id()) ||
(size_in_bits->word(3) != 32 &&
size_in_bits->word(3) != 64)) {
invalid = true;
}
}
} else {
invalid = true;
}
} else {
invalid = true;
}
if (invalid) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": Component Count must be positive "
<< "integer";
<< ext_inst_name() << ": Component Count must be "
<< "OpConstant with a 32- or 64-bits integer scalar type or "
<< "DebugGlobalVariable or DebugLocalVariable with a 32- or "
<< "64-bits unsigned integer scalar type";
}
}
break;
@ -2706,14 +2789,16 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
}
case OpenCLDebugInfo100DebugTypeFunction: {
auto* return_type = _.FindDef(inst->word(6));
// TODO: We need a spec discussion that we have to allow return and
// parameter types of a DebugTypeFunction to have template parameter.
if (return_type->opcode() != SpvOpTypeVoid) {
auto validate_return = ValidateOperandDebugType(
_, "Return Type", inst, 6, ext_inst_name);
_, "Return Type", inst, 6, ext_inst_name, true);
if (validate_return != SPV_SUCCESS) return validate_return;
}
for (uint32_t word_index = 7; word_index < num_words; ++word_index) {
auto validate_param = ValidateOperandDebugType(
_, "Parameter Types", inst, word_index, ext_inst_name);
_, "Parameter Types", inst, word_index, ext_inst_name, true);
if (validate_param != SPV_SUCCESS) return validate_param;
}
break;
@ -2727,7 +2812,7 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
},
inst, 6)) {
auto validate_underlying_type = ValidateOperandDebugType(
_, "Underlying Types", inst, 6, ext_inst_name);
_, "Underlying Types", inst, 6, ext_inst_name, false);
if (validate_underlying_type != SPV_SUCCESS)
return validate_underlying_type;
}
@ -2784,8 +2869,10 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
}
case OpenCLDebugInfo100DebugTypeMember: {
CHECK_OPERAND("Name", SpvOpString, 5);
// TODO: We need a spec discussion that we have to allow member types
// to have template parameter.
auto validate_type =
ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name);
ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, true);
if (validate_type != SPV_SUCCESS) return validate_type;
CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
CHECK_DEBUG_OPERAND("Parent", OpenCLDebugInfo100DebugTypeComposite, 10);
@ -2823,18 +2910,13 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
case OpenCLDebugInfo100DebugFunction: {
CHECK_OPERAND("Name", SpvOpString, 5);
auto validate_type =
ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name);
ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, false);
if (validate_type != SPV_SUCCESS) return validate_type;
CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
auto validate_parent =
ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
if (validate_parent != SPV_SUCCESS) return validate_parent;
CHECK_OPERAND("Linkage Name", SpvOpString, 11);
// TODO: The current OpenCL.100.DebugInfo spec says "Function
// is an OpFunction which is described by this instruction.".
// However, the function definition can be opted-out e.g.,
// inlining. We assume that Function operand can be a
// DebugInfoNone, but we must discuss it and update the spec.
if (!DoesDebugInfoOperandMatchExpectation(
_,
[](OpenCLDebugInfo100Instructions dbg_inst) {
@ -2852,7 +2934,7 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
case OpenCLDebugInfo100DebugFunctionDeclaration: {
CHECK_OPERAND("Name", SpvOpString, 5);
auto validate_type =
ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name);
ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, false);
if (validate_type != SPV_SUCCESS) return validate_type;
CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
auto validate_parent =
@ -2870,9 +2952,6 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
break;
}
case OpenCLDebugInfo100DebugScope: {
// TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/533): We are
// still in spec discussion about what must be "Scope" operand of
// DebugScope. Update this code if the conclusion is different.
auto validate_scope =
ValidateOperandLexicalScope(_, "Scope", inst, 5, ext_inst_name);
if (validate_scope != SPV_SUCCESS) return validate_scope;
@ -2884,8 +2963,10 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
}
case OpenCLDebugInfo100DebugLocalVariable: {
CHECK_OPERAND("Name", SpvOpString, 5);
// TODO: We need a spec discussion that we have to allow local variable
// types to have template parameter.
auto validate_type =
ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name);
ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, true);
if (validate_type != SPV_SUCCESS) return validate_type;
CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
auto validate_parent =
@ -2896,11 +2977,6 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
case OpenCLDebugInfo100DebugDeclare: {
CHECK_DEBUG_OPERAND("Local Variable",
OpenCLDebugInfo100DebugLocalVariable, 5);
// TODO: We must discuss DebugDeclare.Variable of
// OpenCL.100.DebugInfo. Currently, it says "Variable must be an id of
// OpVariable instruction which defines the local variable.", but we
// want to allow OpFunctionParameter as well.
auto* operand = _.FindDef(inst->word(6));
if (operand->opcode() != SpvOpVariable &&
operand->opcode() != SpvOpFunctionParameter) {
@ -2920,18 +2996,120 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) {
}
break;
}
case OpenCLDebugInfo100DebugTypeTemplate: {
if (!DoesDebugInfoOperandMatchExpectation(
_,
[](OpenCLDebugInfo100Instructions dbg_inst) {
return dbg_inst == OpenCLDebugInfo100DebugTypeComposite ||
dbg_inst == OpenCLDebugInfo100DebugFunction;
},
inst, 5)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected operand Target must be DebugTypeComposite "
<< "or DebugFunction";
}
for (uint32_t word_index = 6; word_index < num_words; ++word_index) {
if (!DoesDebugInfoOperandMatchExpectation(
_,
[](OpenCLDebugInfo100Instructions dbg_inst) {
return dbg_inst ==
OpenCLDebugInfo100DebugTypeTemplateParameter ||
dbg_inst ==
OpenCLDebugInfo100DebugTypeTemplateTemplateParameter;
},
inst, word_index)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected operand Parameters must be "
<< "DebugTypeTemplateParameter or "
<< "DebugTypeTemplateTemplateParameter";
}
}
break;
}
case OpenCLDebugInfo100DebugTypeTemplateParameter: {
CHECK_OPERAND("Name", SpvOpString, 5);
auto validate_actual_type = ValidateOperandDebugType(
_, "Actual Type", inst, 6, ext_inst_name, false);
if (validate_actual_type != SPV_SUCCESS) return validate_actual_type;
if (!DoesDebugInfoOperandMatchExpectation(
_,
[](OpenCLDebugInfo100Instructions dbg_inst) {
return dbg_inst == OpenCLDebugInfo100DebugInfoNone;
},
inst, 7)) {
CHECK_OPERAND("Value", SpvOpConstant, 7);
}
CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 8);
break;
}
case OpenCLDebugInfo100DebugGlobalVariable: {
CHECK_OPERAND("Name", SpvOpString, 5);
auto validate_type =
ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, false);
if (validate_type != SPV_SUCCESS) return validate_type;
CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
auto validate_scope =
ValidateOperandLexicalScope(_, "Scope", inst, 10, ext_inst_name);
if (validate_scope != SPV_SUCCESS) return validate_scope;
CHECK_OPERAND("Linkage Name", SpvOpString, 11);
if (!DoesDebugInfoOperandMatchExpectation(
_,
[](OpenCLDebugInfo100Instructions dbg_inst) {
return dbg_inst == OpenCLDebugInfo100DebugInfoNone;
},
inst, 12)) {
auto* operand = _.FindDef(inst->word(12));
if (operand->opcode() != SpvOpVariable &&
operand->opcode() != SpvOpConstant) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": "
<< "expected operand Variable must be a result id of "
"OpVariable or OpConstant or DebugInfoNone";
}
}
if (num_words == 15) {
CHECK_DEBUG_OPERAND("Static Member Declaration",
OpenCLDebugInfo100DebugTypeMember, 14);
}
break;
}
case OpenCLDebugInfo100DebugInlinedAt: {
auto validate_scope =
ValidateOperandLexicalScope(_, "Scope", inst, 6, ext_inst_name);
if (validate_scope != SPV_SUCCESS) return validate_scope;
if (num_words == 8) {
CHECK_DEBUG_OPERAND("Inlined", OpenCLDebugInfo100DebugInlinedAt, 7);
}
break;
}
case OpenCLDebugInfo100DebugValue: {
CHECK_DEBUG_OPERAND("Local Variable",
OpenCLDebugInfo100DebugLocalVariable, 5);
CHECK_DEBUG_OPERAND("Expression", OpenCLDebugInfo100DebugExpression, 7);
for (uint32_t word_index = 8; word_index < num_words; ++word_index) {
// TODO: The following code simply checks if it is a const int scalar
// or a DebugLocalVariable or DebugGlobalVariable, but we have to
// check it using the same validation for Indexes of OpAccessChain.
if (!IsConstWithIntScalarType(_, inst, word_index) &&
!IsDebugVariableWithIntScalarType(_, inst, word_index)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< ext_inst_name() << ": expected operand Indexes is "
<< "OpConstant, DebugGlobalVariable, or "
<< "type is OpConstant with an integer scalar type";
}
}
break;
}
// TODO: Add validation rules for remaining cases as well.
case OpenCLDebugInfo100DebugTypePtrToMember:
case OpenCLDebugInfo100DebugTypeTemplate:
case OpenCLDebugInfo100DebugTypeTemplateParameter:
case OpenCLDebugInfo100DebugTypeTemplateTemplateParameter:
case OpenCLDebugInfo100DebugTypeTemplateParameterPack:
case OpenCLDebugInfo100DebugGlobalVariable:
case OpenCLDebugInfo100DebugLexicalBlockDiscriminator:
case OpenCLDebugInfo100DebugInlinedAt:
case OpenCLDebugInfo100DebugInlinedVariable:
case OpenCLDebugInfo100DebugValue:
case OpenCLDebugInfo100DebugMacroDef:
case OpenCLDebugInfo100DebugMacroUndef:
case OpenCLDebugInfo100DebugImportedEntity:

View File

@ -437,6 +437,19 @@ spv_result_t GetLocationsForVariable(
spv_result_t ValidateLocations(ValidationState_t& _,
const Instruction* entry_point) {
// According to Vulkan 14.1 only the following execution models have
// locations assigned.
switch (entry_point->GetOperandAs<SpvExecutionModel>(0)) {
case SpvExecutionModelVertex:
case SpvExecutionModelTessellationControl:
case SpvExecutionModelTessellationEvaluation:
case SpvExecutionModelGeometry:
case SpvExecutionModelFragment:
break;
default:
return SPV_SUCCESS;
}
// Locations are stored as a combined location and component values.
std::unordered_set<uint32_t> input_locations;
std::unordered_set<uint32_t> output_locations_index0;