spirv-fuzz: Create synonym of int constant using a loop (#3790)
This transformation, given a constant integer (scalar or vector) C,
constants I and S of compatible type and scalar 32-bit integer constant
N, such that C = I - S*N, adds a loop which subtracts S from I, N
times, creating a synonym for C.
The related fuzzer pass randomly chooses constants to which to add
synonyms using this transformation, and the location where they should
be added.
Fixes #3616.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 733bc56..cea05cf 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -63,15 +63,16 @@
fuzzer_pass_add_function_calls.h
fuzzer_pass_add_global_variables.h
fuzzer_pass_add_image_sample_unused_components.h
- 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_loops_to_create_int_constant_synonyms.h
fuzzer_pass_add_no_contraction_decorations.h
fuzzer_pass_add_opphi_synonyms.h
fuzzer_pass_add_parameters.h
fuzzer_pass_add_relaxed_decorations.h
fuzzer_pass_add_stores.h
+ fuzzer_pass_add_synonyms.h
fuzzer_pass_add_vector_shuffle_instructions.h
fuzzer_pass_adjust_branch_weights.h
fuzzer_pass_adjust_function_controls.h
@@ -147,6 +148,7 @@
transformation_add_image_sample_unused_components.h
transformation_add_local_variable.h
transformation_add_loop_preheader.h
+ transformation_add_loop_to_create_int_constant_synonym.h
transformation_add_no_contraction_decoration.h
transformation_add_opphi_synonym.h
transformation_add_parameter.h
@@ -237,15 +239,16 @@
fuzzer_pass_add_function_calls.cpp
fuzzer_pass_add_global_variables.cpp
fuzzer_pass_add_image_sample_unused_components.cpp
- 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_loops_to_create_int_constant_synonyms.cpp
fuzzer_pass_add_no_contraction_decorations.cpp
fuzzer_pass_add_opphi_synonyms.cpp
fuzzer_pass_add_parameters.cpp
fuzzer_pass_add_relaxed_decorations.cpp
fuzzer_pass_add_stores.cpp
+ fuzzer_pass_add_synonyms.cpp
fuzzer_pass_add_vector_shuffle_instructions.cpp
fuzzer_pass_adjust_branch_weights.cpp
fuzzer_pass_adjust_function_controls.cpp
@@ -319,6 +322,7 @@
transformation_add_image_sample_unused_components.cpp
transformation_add_local_variable.cpp
transformation_add_loop_preheader.cpp
+ transformation_add_loop_to_create_int_constant_synonym.cpp
transformation_add_no_contraction_decoration.cpp
transformation_add_opphi_synonym.cpp
transformation_add_parameter.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 845c8c0..557ee55 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -35,6 +35,7 @@
#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_loops_to_create_int_constant_synonyms.h"
#include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h"
#include "source/fuzz/fuzzer_pass_add_parameters.h"
@@ -238,6 +239,8 @@
MaybeAddRepeatedPass<FuzzerPassAddLoads>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassAddLocalVariables>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassAddLoopPreheaders>(&pass_instances);
+ MaybeAddRepeatedPass<FuzzerPassAddLoopsToCreateIntConstantSynonyms>(
+ &pass_instances);
MaybeAddRepeatedPass<FuzzerPassAddOpPhiSynonyms>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassAddParameters>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassAddRelaxedDecorations>(&pass_instances);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 416ac71..9e9e78d 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -75,6 +75,8 @@
50, 50};
const std::pair<uint32_t, uint32_t> kChanceOfConstructingComposite = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfCreatingIntSynonymsUsingLoops = {
+ 5, 10};
const std::pair<uint32_t, uint32_t> kChanceOfDonatingAdditionalModule = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfDuplicatingRegionWithSelection = {
20, 50};
@@ -84,6 +86,8 @@
30, 70};
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
{50, 95};
+const std::pair<uint32_t, uint32_t>
+ kChanceOfHavingTwoBlocksInLoopToCreateIntSynonym = {50, 80};
const std::pair<uint32_t, uint32_t> kChanceOfInliningFunction = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfInterchangingZeroLikeConstants = {
10, 90};
@@ -244,6 +248,8 @@
chance_of_constructing_composite_ =
ChooseBetweenMinAndMax(kChanceOfConstructingComposite);
chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
+ chance_of_creating_int_synonyms_using_loops_ =
+ ChooseBetweenMinAndMax(kChanceOfCreatingIntSynonymsUsingLoops);
chance_of_donating_additional_module_ =
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
chance_of_duplicating_region_with_selection_ =
@@ -254,6 +260,8 @@
ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite);
chance_of_going_deeper_when_making_access_chain_ =
ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
+ chance_of_having_two_blocks_in_loop_to_create_int_synonym_ =
+ ChooseBetweenMinAndMax(kChanceOfHavingTwoBlocksInLoopToCreateIntSynonym);
chance_of_inlining_function_ =
ChooseBetweenMinAndMax(kChanceOfInliningFunction);
chance_of_interchanging_signedness_of_integer_operands_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 79042e7..b6f466f 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -204,6 +204,9 @@
return chance_of_constructing_composite_;
}
uint32_t GetChanceOfCopyingObject() { return chance_of_copying_object_; }
+ uint32_t GetChanceOfCreatingIntSynonymsUsingLoops() {
+ return chance_of_creating_int_synonyms_using_loops_;
+ }
uint32_t GetChanceOfDonatingAdditionalModule() {
return chance_of_donating_additional_module_;
}
@@ -219,6 +222,9 @@
uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
return chance_of_going_deeper_when_making_access_chain_;
}
+ uint32_t GetChanceOfHavingTwoBlocksInLoopToCreateIntSynonym() {
+ return chance_of_having_two_blocks_in_loop_to_create_int_synonym_;
+ }
uint32_t GetChanceOfInliningFunction() {
return chance_of_inlining_function_;
}
@@ -342,6 +348,9 @@
uint32_t GetRandomIndexForCompositeInsert(uint32_t number_of_components) {
return random_generator_->RandomUint32(number_of_components);
}
+ int64_t GetRandomValueForStepConstantInLoop() {
+ return random_generator_->RandomUint64(UINT64_MAX);
+ }
uint32_t GetRandomLoopControlPartialCount() {
return random_generator_->RandomUint32(max_loop_control_partial_count_);
}
@@ -351,6 +360,9 @@
uint32_t GetRandomLoopLimit() {
return random_generator_->RandomUint32(max_loop_limit_);
}
+ uint32_t GetRandomNumberOfLoopIterations(uint32_t max_num_iterations) {
+ return ChooseBetweenMinAndMax({1, max_num_iterations});
+ }
uint32_t GetRandomNumberOfNewParameters(uint32_t num_of_params) {
assert(num_of_params < GetMaximumNumberOfFunctionParameters());
return ChooseBetweenMinAndMax(
@@ -423,11 +435,13 @@
uint32_t chance_of_choosing_workgroup_storage_class_;
uint32_t chance_of_constructing_composite_;
uint32_t chance_of_copying_object_;
+ uint32_t chance_of_creating_int_synonyms_using_loops_;
uint32_t chance_of_donating_additional_module_;
uint32_t chance_of_duplicating_region_with_selection_;
uint32_t chance_of_flattening_conditional_branch_;
uint32_t chance_of_going_deeper_to_insert_in_composite_;
uint32_t chance_of_going_deeper_when_making_access_chain_;
+ uint32_t chance_of_having_two_blocks_in_loop_to_create_int_synonym_;
uint32_t chance_of_inlining_function_;
uint32_t chance_of_interchanging_signedness_of_integer_operands_;
uint32_t chance_of_interchanging_zero_like_constants_;
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index 96feef9..b1f39b7 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -35,6 +35,7 @@
#include "source/fuzz/transformation_add_type_pointer.h"
#include "source/fuzz/transformation_add_type_struct.h"
#include "source/fuzz/transformation_add_type_vector.h"
+#include "source/fuzz/transformation_split_block.h"
namespace spvtools {
namespace fuzz {
@@ -608,6 +609,29 @@
return &*function->FindBlock(preheader_id);
}
+opt::BasicBlock* FuzzerPass::SplitBlockAfterOpPhiOrOpVariable(
+ uint32_t block_id) {
+ auto block = fuzzerutil::MaybeFindBlock(GetIRContext(), block_id);
+ assert(block && "|block_id| must be a block label");
+ assert(!block->IsLoopHeader() && "|block_id| cannot be a loop header");
+
+ // Find the first non-OpPhi and non-OpVariable instruction.
+ auto non_phi_or_var_inst = &*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));
+
+ // We need to return the newly-created block.
+ return &*block->GetParent()->FindBlock(new_block_id);
+}
+
uint32_t FuzzerPass::FindOrCreateLocalVariable(
uint32_t pointer_type_id, uint32_t function_id,
bool pointee_value_is_irrelevant) {
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index 40471eb..a9aae09 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -293,6 +293,11 @@
// reachable in the CFG (and thus has at least 2 predecessors).
opt::BasicBlock* GetOrCreateSimpleLoopPreheader(uint32_t header_id);
+ // Returns the second block in the pair obtained by splitting |block_id| just
+ // after the last OpPhi or OpVariable instruction in it. Assumes that the
+ // block is not a loop header.
+ opt::BasicBlock* SplitBlockAfterOpPhiOrOpVariable(uint32_t block_id);
+
// Returns the id of an available local variable (storage class Function) with
// the fact PointeeValueIsIrrelevant set according to
// |pointee_value_is_irrelevant|. If there is no such variable, it creates one
diff --git a/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp
new file mode 100644
index 0000000..1b286dd
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp
@@ -0,0 +1,247 @@
+// 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_loops_to_create_int_constant_synonyms.h"
+
+#include "source/fuzz/call_graph.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+uint32_t kMaxNestingDepth = 4;
+} // namespace
+
+FuzzerPassAddLoopsToCreateIntConstantSynonyms::
+ FuzzerPassAddLoopsToCreateIntConstantSynonyms(
+ opt::IRContext* ir_context,
+ TransformationContext* transformation_context,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations)
+ : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+ transformations) {}
+
+FuzzerPassAddLoopsToCreateIntConstantSynonyms::
+ ~FuzzerPassAddLoopsToCreateIntConstantSynonyms() = default;
+
+void FuzzerPassAddLoopsToCreateIntConstantSynonyms::Apply() {
+ std::vector<uint32_t> constants;
+
+ // Choose the constants for which to create synonyms.
+ for (auto constant_def : GetIRContext()->GetConstants()) {
+ // Randomly decide whether to consider this constant.
+ if (!GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()->GetChanceOfCreatingIntSynonymsUsingLoops())) {
+ continue;
+ }
+
+ auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
+ constant_def->result_id());
+
+ // We only consider integer constants (scalar or vector).
+ if (!constant->AsIntConstant() &&
+ !(constant->AsVectorConstant() &&
+ constant->AsVectorConstant()->component_type()->AsInteger())) {
+ continue;
+ }
+
+ constants.push_back(constant_def->result_id());
+ }
+
+ std::vector<uint32_t> blocks;
+
+ // Get a list of all the blocks before which we can add a loop creating a new
+ // synonym. We cannot apply the transformation while iterating over the
+ // module, because we are going to add new blocks.
+ for (auto& function : *GetIRContext()->module()) {
+ // Consider all blocks reachable from the first block of the function.
+ GetIRContext()->cfg()->ForEachBlockInPostOrder(
+ &*function.begin(),
+ [&blocks](opt::BasicBlock* block) { blocks.push_back(block->id()); });
+ }
+
+ // Make sure that the module has an OpTypeBool instruction, and 32-bit signed
+ // integer constants 0 and 1, adding them if necessary.
+ FindOrCreateBoolType();
+ FindOrCreateIntegerConstant({0}, 32, true, false);
+ FindOrCreateIntegerConstant({1}, 32, true, false);
+
+ // Compute the call graph. We can use this for any further computation, since
+ // we are not adding or removing functions or function calls.
+ auto call_graph = CallGraph(GetIRContext());
+
+ // Consider each constant and each block.
+ for (uint32_t constant_id : constants) {
+ // Choose one of the blocks.
+ uint32_t block_id = blocks[GetFuzzerContext()->RandomIndex(blocks)];
+
+ // Adjust the block so that the transformation can be applied.
+ auto block = GetIRContext()->get_instr_block(block_id);
+
+ // If the block is a loop header, add a simple preheader. We can do this
+ // because we have excluded all the non-reachable headers.
+ if (block->IsLoopHeader()) {
+ block = GetOrCreateSimpleLoopPreheader(block->id());
+ block_id = block->id();
+ }
+
+ assert(!block->IsLoopHeader() &&
+ "The block cannot be a loop header at this point.");
+
+ // If the block is a merge block, a continue block or it does not have
+ // exactly 1 predecessor, split it after any OpPhi or OpVariable
+ // instructions.
+ if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(block->id()) ||
+ GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock(
+ block->id()) ||
+ GetIRContext()->cfg()->preds(block->id()).size() != 1) {
+ block = SplitBlockAfterOpPhiOrOpVariable(block->id());
+ block_id = block->id();
+ }
+
+ // Randomly decide the values for the number of iterations and the step
+ // value, and compute the initial value accordingly.
+
+ // The maximum number of iterations depends on the maximum possible loop
+ // nesting depth of the block, computed interprocedurally, i.e. also
+ // considering the possibility that the enclosing function is called inside
+ // a loop. It is:
+ // - 1 if the nesting depth is >= kMaxNestingDepth
+ // - 2^(kMaxNestingDepth - nesting_depth) otherwise
+ uint32_t max_nesting_depth =
+ call_graph.GetMaxCallNestingDepth(block->GetParent()->result_id()) +
+ GetIRContext()->GetStructuredCFGAnalysis()->LoopNestingDepth(
+ block->id());
+ uint32_t num_iterations =
+ max_nesting_depth >= kMaxNestingDepth
+ ? 1
+ : GetFuzzerContext()->GetRandomNumberOfLoopIterations(
+ 1u << (kMaxNestingDepth - max_nesting_depth));
+
+ // Find or create the corresponding constant containing the number of
+ // iterations.
+ uint32_t num_iterations_id =
+ FindOrCreateIntegerConstant({num_iterations}, 32, true, false);
+
+ // Find the other constants.
+ // We use 64-bit values and then use the bits that we need. We find the
+ // step value (S) randomly and then compute the initial value (I) using
+ // the equation I = C + S*N.
+ uint32_t initial_value_id = 0;
+ uint32_t step_value_id = 0;
+
+ // Get the content of the existing constant.
+ const auto constant =
+ GetIRContext()->get_constant_mgr()->FindDeclaredConstant(constant_id);
+ const auto constant_type_id =
+ GetIRContext()->get_def_use_mgr()->GetDef(constant_id)->type_id();
+
+ if (constant->AsIntConstant()) {
+ // The constant is a scalar integer.
+
+ std::tie(initial_value_id, step_value_id) =
+ FindSuitableStepAndInitialValueConstants(
+ constant->GetZeroExtendedValue(),
+ constant->type()->AsInteger()->width(),
+ constant->type()->AsInteger()->IsSigned(), num_iterations);
+ } else {
+ // The constant is a vector of integers.
+ assert(constant->AsVectorConstant() &&
+ constant->AsVectorConstant()->component_type()->AsInteger() &&
+ "If the program got here, the constant should be a vector of "
+ "integers.");
+
+ // Find a constant for each component of the initial value and the step
+ // values.
+ std::vector<uint32_t> initial_value_component_ids;
+ std::vector<uint32_t> step_value_component_ids;
+
+ // Get the value, width and signedness of the components.
+ std::vector<uint64_t> component_values;
+ for (auto component : constant->AsVectorConstant()->GetComponents()) {
+ component_values.push_back(component->GetZeroExtendedValue());
+ }
+ uint32_t bit_width =
+ constant->AsVectorConstant()->component_type()->AsInteger()->width();
+ uint32_t is_signed = constant->AsVectorConstant()
+ ->component_type()
+ ->AsInteger()
+ ->IsSigned();
+
+ for (uint64_t component_val : component_values) {
+ uint32_t initial_val_id;
+ uint32_t step_val_id;
+ std::tie(initial_val_id, step_val_id) =
+ FindSuitableStepAndInitialValueConstants(component_val, bit_width,
+ is_signed, num_iterations);
+ initial_value_component_ids.push_back(initial_val_id);
+ step_value_component_ids.push_back(step_val_id);
+ }
+
+ // Find or create the vector constants.
+ initial_value_id = FindOrCreateCompositeConstant(
+ initial_value_component_ids, constant_type_id, false);
+ step_value_id = FindOrCreateCompositeConstant(step_value_component_ids,
+ constant_type_id, false);
+ }
+
+ assert(initial_value_id && step_value_id &&
+ "|initial_value_id| and |step_value_id| should have been defined.");
+
+ // Randomly decide whether to have two blocks (or just one) in the new
+ // loop.
+ uint32_t additional_block_id =
+ GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()
+ ->GetChanceOfHavingTwoBlocksInLoopToCreateIntSynonym())
+ ? GetFuzzerContext()->GetFreshId()
+ : 0;
+
+ // Add the loop and create the synonym.
+ ApplyTransformation(TransformationAddLoopToCreateIntConstantSynonym(
+ constant_id, initial_value_id, step_value_id, num_iterations_id,
+ block_id, GetFuzzerContext()->GetFreshId(),
+ GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(),
+ GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(),
+ GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(),
+ additional_block_id));
+ }
+}
+
+std::pair<uint32_t, uint32_t> FuzzerPassAddLoopsToCreateIntConstantSynonyms::
+ FindSuitableStepAndInitialValueConstants(uint64_t constant_val,
+ uint32_t bit_width, bool is_signed,
+ uint32_t num_iterations) {
+ // Choose the step value randomly and compute the initial value accordingly.
+ // The result of |initial_value| could overflow, but this is OK, since
+ // the transformation takes overflows into consideration (the equation still
+ // holds as long as the last |bit_width| bits of C and of (I-S*N) match).
+ uint64_t step_value =
+ GetFuzzerContext()->GetRandomValueForStepConstantInLoop();
+ uint64_t initial_value = constant_val + step_value * num_iterations;
+
+ uint32_t initial_val_id = FindOrCreateIntegerConstant(
+ fuzzerutil::IntToWords(initial_value, bit_width, is_signed), bit_width,
+ is_signed, false);
+
+ uint32_t step_val_id = FindOrCreateIntegerConstant(
+ fuzzerutil::IntToWords(step_value, bit_width, is_signed), bit_width,
+ is_signed, false);
+
+ return {initial_val_id, step_val_id};
+}
+
+} // namespace fuzz
+} // namespace spvtools
\ No newline at end of file
diff --git a/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h
new file mode 100644
index 0000000..ee98c4e
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h
@@ -0,0 +1,53 @@
+// 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_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass that adds synonyms for integer, scalar or vector, constants, by
+// adding loops that compute the same value by subtracting a value S from an
+// initial value I, and for N times, so that C = I - S*N.
+class FuzzerPassAddLoopsToCreateIntConstantSynonyms : public FuzzerPass {
+ public:
+ FuzzerPassAddLoopsToCreateIntConstantSynonyms(
+ opt::IRContext* ir_context, TransformationContext* transformation_context,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations);
+
+ ~FuzzerPassAddLoopsToCreateIntConstantSynonyms();
+
+ void Apply() override;
+
+ private:
+ // Returns a pair (initial_val_id, step_val_id) such that both ids are
+ // integer scalar constants of the same type as the scalar integer constant
+ // identified by the given |constant_val|, |bit_width| and signedness, and
+ // such that, if I is the value of initial_val_id, S is the value of
+ // step_val_id and C is the value of the constant, the equation (C = I - S *
+ // num_iterations) holds, (only considering the last |bit_width| bits of each
+ // constant).
+ std::pair<uint32_t, uint32_t> FindSuitableStepAndInitialValueConstants(
+ uint64_t constant_val, uint32_t bit_width, bool is_signed,
+ uint32_t num_iterations);
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index b550a74..aca9587 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -1306,6 +1306,30 @@
UpdateModuleIdBound(ir_context, result_id);
}
+std::vector<uint32_t> IntToWords(uint64_t value, uint32_t width,
+ bool is_signed) {
+ assert(width <= 64 && "The bit width should not be more than 64 bits");
+
+ // Sign-extend or zero-extend the last |width| bits of |value|, depending on
+ // |is_signed|.
+ if (is_signed) {
+ // Sign-extend by shifting left and then shifting right, interpreting the
+ // integer as signed.
+ value = static_cast<int64_t>(value << (64 - width)) >> (64 - width);
+ } else {
+ // Zero-extend by shifting left and then shifting right, interpreting the
+ // integer as unsigned.
+ value = (value << (64 - width)) >> (64 - width);
+ }
+
+ std::vector<uint32_t> result;
+ result.push_back(static_cast<uint32_t>(value));
+ if (width > 32) {
+ result.push_back(static_cast<uint32_t>(value >> 32));
+ }
+ return result;
+}
+
bool TypesAreEqualUpToSign(opt::IRContext* ir_context, uint32_t type1_id,
uint32_t type2_id) {
if (type1_id == type2_id) {
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index b4b9057..981d229 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -474,6 +474,17 @@
void AddStructType(opt::IRContext* ir_context, uint32_t result_id,
const std::vector<uint32_t>& component_type_ids);
+// Returns a vector of words representing the integer |value|, only considering
+// the last |width| bits. The last |width| bits are sign-extended if the value
+// is signed, zero-extended if it is unsigned.
+// |width| must be <= 64.
+// If |width| <= 32, returns a vector containing one value. If |width| > 64,
+// returns a vector containing two values, with the first one representing the
+// lower-order word of the value and the second one representing the
+// higher-order word.
+std::vector<uint32_t> IntToWords(uint64_t value, uint32_t width,
+ bool is_signed);
+
// Returns a bit pattern that represents a floating-point |value|.
inline uint32_t FloatToWord(float value) {
uint32_t result;
diff --git a/source/fuzz/pass_management/repeated_pass_instances.h b/source/fuzz/pass_management/repeated_pass_instances.h
index fdcb055..57820a2 100644
--- a/source/fuzz/pass_management/repeated_pass_instances.h
+++ b/source/fuzz/pass_management/repeated_pass_instances.h
@@ -30,6 +30,7 @@
#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_loops_to_create_int_constant_synonyms.h"
#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h"
#include "source/fuzz/fuzzer_pass_add_parameters.h"
#include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h"
@@ -118,6 +119,7 @@
REPEATED_PASS_INSTANCE(AddLoads);
REPEATED_PASS_INSTANCE(AddLocalVariables);
REPEATED_PASS_INSTANCE(AddLoopPreheaders);
+ REPEATED_PASS_INSTANCE(AddLoopsToCreateIntConstantSynonyms);
REPEATED_PASS_INSTANCE(AddOpPhiSynonyms);
REPEATED_PASS_INSTANCE(AddParameters);
REPEATED_PASS_INSTANCE(AddRelaxedDecorations);
diff --git a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
index 253504b..8c03807 100644
--- a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
+++ b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
@@ -122,6 +122,10 @@
{pass_instances_->GetDuplicateRegionsWithSelections(),
pass_instances_->GetOutlineFunctions()});
}
+ if (&pass == pass_instances_->GetAddLoopsToCreateIntConstantSynonyms()) {
+ // - New synonyms can be applied
+ return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
+ }
if (&pass == pass_instances_->GetAddOpPhiSynonyms()) {
// - New synonyms can be applied
// - If OpPhi synonyms are introduced for blocks with dead predecessors, the
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index c616943..d052ccc 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -497,6 +497,7 @@
TransformationDuplicateRegionWithSelection duplicate_region_with_selection = 75;
TransformationFlattenConditionalBranch flatten_conditional_branch = 76;
TransformationAddBitInstructionSynonym add_bit_instruction_synonym = 77;
+ TransformationAddLoopToCreateIntConstantSynonym add_loop_to_create_int_constant_synonym = 78;
// Add additional option using the next available number.
}
}
@@ -847,6 +848,97 @@
}
+message TransformationAddLoopToCreateIntConstantSynonym {
+ // A transformation that uses a loop to create a synonym for an integer
+ // constant C (scalar or vector) using an initial value I, a step value S and
+ // a number of iterations N such that C = I - N * S. For each iteration, S is
+ // added to the total.
+ // The loop can be made up of one or two blocks, and it is inserted before a
+ // block with a single predecessor. In the one-block case, it is of the form:
+ //
+ // %loop_id = OpLabel
+ // %ctr_id = OpPhi %int %int_0 %pred %incremented_ctr_id %loop_id
+ // %temp_id = OpPhi %type_of_I %I %pred %eventual_syn_id %loop_id
+ // %eventual_syn_id = OpISub %type_of_I %temp_id %step_val_id
+ // %incremented_ctr_id = OpIAdd %int %ctr_id %int_1
+ // %cond_id = OpSLessThan %bool %incremented_ctr_id %num_iterations_id
+ // OpLoopMerge %block_after_loop_id %loop_id %none
+ // OpBranchConditional %cond_id %loop_id %block_after_loop_id
+ //
+ // A new OpPhi instruction is then added to %block_after_loop_id, as follows:
+ //
+ // %block_after_loop_id = OpLabel
+ // %syn_id = OpPhi %type_of_I %eventual_syn_id %loop_id
+ //
+ // This can be translated, assuming that N > 0, to:
+ // int syn = I;
+ // for (int ctr = 0; ctr < N; ctr++) syn = syn - S;
+ //
+ // All existing OpPhi instructions in %block_after_loop_id are also updated
+ // to reflect the fact that its predecessor is now %loop_id.
+
+ // The following are existing ids.
+
+ // The id of the integer constant C that we want a synonym of.
+ uint32 constant_id = 1;
+
+ // The id of the initial value integer constant I.
+ uint32 initial_val_id = 2;
+
+ // The id of the step value integer constant S.
+ uint32 step_val_id = 3;
+
+ // The id of the integer scalar constant, its value being the number of
+ // iterations N.
+ uint32 num_iterations_id = 4;
+
+ // The label id of the block before which the loop must be inserted.
+ uint32 block_after_loop_id = 5;
+
+
+ // The following are fresh ids.
+
+ // A fresh id for the synonym.
+ uint32 syn_id = 6;
+
+ // A fresh id for the label of the loop,
+ uint32 loop_id = 7;
+
+ // A fresh id for the counter.
+ uint32 ctr_id = 8;
+
+ // A fresh id taking the value I - S * ctr at the ctr-th iteration.
+ uint32 temp_id = 9;
+
+ // A fresh id taking the value I - S * (ctr + 1) at the ctr-th iteration, and
+ // thus I - S * N at the last iteration.
+ uint32 eventual_syn_id = 10;
+
+ // A fresh id for the incremented counter.
+ uint32 incremented_ctr_id = 11;
+
+ // A fresh id for the loop condition.
+ uint32 cond_id = 12;
+
+ // The instructions in the loop can also be laid out in two basic blocks, as follows:
+ //
+ // %loop_id = OpLabel
+ // %ctr_id = OpPhi %int %int_0 %pred %incremented_ctr_id %loop_id
+ // %temp_id = OpPhi %type_of_I %I %pred %eventual_syn_id %loop_id
+ // OpLoopMerge %block_after_loop_id %additional_block_id None
+ // OpBranch %additional_block_id
+ //
+ // %additional_block_id = OpLabel
+ // %eventual_syn_id = OpISub %type_of_I %temp_id %step_val_id
+ // %incremented_ctr_id = OpIAdd %int %ctr_id %int_1
+ // %cond_id = OpSLessThan %bool %incremented_ctr_id %num_iterations_id
+ // OpBranchConditional %cond_id %loop_id %block_after_loop_id
+
+ // A fresh id for the additional block. If this is 0, it means that only one
+ // block is to be created.
+ uint32 additional_block_id = 13;
+}
+
message TransformationAddNoContractionDecoration {
// Applies OpDecorate NoContraction to the given result id
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 0b253ff..7301a89 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -33,6 +33,7 @@
#include "source/fuzz/transformation_add_image_sample_unused_components.h"
#include "source/fuzz/transformation_add_local_variable.h"
#include "source/fuzz/transformation_add_loop_preheader.h"
+#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h"
#include "source/fuzz/transformation_add_no_contraction_decoration.h"
#include "source/fuzz/transformation_add_opphi_synonym.h"
#include "source/fuzz/transformation_add_parameter.h"
@@ -150,6 +151,10 @@
return MakeUnique<TransformationAddLoopPreheader>(
message.add_loop_preheader());
case protobufs::Transformation::TransformationCase::
+ kAddLoopToCreateIntConstantSynonym:
+ return MakeUnique<TransformationAddLoopToCreateIntConstantSynonym>(
+ message.add_loop_to_create_int_constant_synonym());
+ case protobufs::Transformation::TransformationCase::
kAddNoContractionDecoration:
return MakeUnique<TransformationAddNoContractionDecoration>(
message.add_no_contraction_decoration());
diff --git a/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp
new file mode 100644
index 0000000..fccfc34
--- /dev/null
+++ b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp
@@ -0,0 +1,427 @@
+// 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_add_loop_to_create_int_constant_synonym.h"
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+uint32_t kMaxNumOfIterations = 32;
+}
+
+TransformationAddLoopToCreateIntConstantSynonym::
+ TransformationAddLoopToCreateIntConstantSynonym(
+ const protobufs::TransformationAddLoopToCreateIntConstantSynonym&
+ message)
+ : message_(message) {}
+
+TransformationAddLoopToCreateIntConstantSynonym::
+ TransformationAddLoopToCreateIntConstantSynonym(
+ uint32_t constant_id, uint32_t initial_val_id, uint32_t step_val_id,
+ uint32_t num_iterations_id, uint32_t block_after_loop_id,
+ uint32_t syn_id, uint32_t loop_id, uint32_t ctr_id, uint32_t temp_id,
+ uint32_t eventual_syn_id, uint32_t incremented_ctr_id, uint32_t cond_id,
+ uint32_t additional_block_id) {
+ message_.set_constant_id(constant_id);
+ message_.set_initial_val_id(initial_val_id);
+ message_.set_step_val_id(step_val_id);
+ message_.set_num_iterations_id(num_iterations_id);
+ message_.set_block_after_loop_id(block_after_loop_id);
+ message_.set_syn_id(syn_id);
+ message_.set_loop_id(loop_id);
+ message_.set_ctr_id(ctr_id);
+ message_.set_temp_id(temp_id);
+ message_.set_eventual_syn_id(eventual_syn_id);
+ message_.set_incremented_ctr_id(incremented_ctr_id);
+ message_.set_cond_id(cond_id);
+ message_.set_additional_block_id(additional_block_id);
+}
+
+bool TransformationAddLoopToCreateIntConstantSynonym::IsApplicable(
+ opt::IRContext* ir_context,
+ const TransformationContext& transformation_context) const {
+ // Check that |message_.constant_id|, |message_.initial_val_id| and
+ // |message_.step_val_id| are existing constants.
+
+ auto constant = ir_context->get_constant_mgr()->FindDeclaredConstant(
+ message_.constant_id());
+ auto initial_val = ir_context->get_constant_mgr()->FindDeclaredConstant(
+ message_.initial_val_id());
+ auto step_val = ir_context->get_constant_mgr()->FindDeclaredConstant(
+ message_.step_val_id());
+
+ if (!constant || !initial_val || !step_val) {
+ return false;
+ }
+
+ // Check that the type of |constant| is integer scalar or vector with integer
+ // components.
+ if (!constant->AsIntConstant() &&
+ (!constant->AsVectorConstant() ||
+ !constant->type()->AsVector()->element_type()->AsInteger())) {
+ return false;
+ }
+
+ // Check that the component bit width of |constant| is <= 64.
+ // Consider the width of the constant if it is an integer, of a single
+ // component if it is a vector.
+ uint32_t bit_width =
+ constant->AsIntConstant()
+ ? constant->type()->AsInteger()->width()
+ : constant->type()->AsVector()->element_type()->AsInteger()->width();
+ if (bit_width > 64) {
+ return false;
+ }
+
+ auto constant_def =
+ ir_context->get_def_use_mgr()->GetDef(message_.constant_id());
+ auto initial_val_def =
+ ir_context->get_def_use_mgr()->GetDef(message_.initial_val_id());
+ auto step_val_def =
+ ir_context->get_def_use_mgr()->GetDef(message_.step_val_id());
+
+ // Check that |constant|, |initial_val| and |step_val| have the same type,
+ // with possibly different signedness.
+ if (!fuzzerutil::TypesAreEqualUpToSign(ir_context, constant_def->type_id(),
+ initial_val_def->type_id()) ||
+ !fuzzerutil::TypesAreEqualUpToSign(ir_context, constant_def->type_id(),
+ step_val_def->type_id())) {
+ return false;
+ }
+
+ // |message_.num_iterations_id| is an integer constant with bit width 32.
+ auto num_iterations = ir_context->get_constant_mgr()->FindDeclaredConstant(
+ message_.num_iterations_id());
+
+ if (!num_iterations || !num_iterations->AsIntConstant() ||
+ num_iterations->type()->AsInteger()->width() != 32) {
+ return false;
+ }
+
+ // Check that the number of iterations is > 0 and <= 32.
+ uint32_t num_iterations_value =
+ num_iterations->AsIntConstant()->GetU32BitValue();
+
+ if (num_iterations_value == 0 || num_iterations_value > kMaxNumOfIterations) {
+ return false;
+ }
+
+ // Check that the module contains 32-bit signed integer scalar constants of
+ // value 0 and 1.
+ if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context,
+ {0}, 32, true, false)) {
+ return false;
+ }
+
+ if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context,
+ {1}, 32, true, false)) {
+ return false;
+ }
+
+ // Check that the module contains the Bool type.
+ if (!fuzzerutil::MaybeGetBoolType(ir_context)) {
+ return false;
+ }
+
+ // Check that the equation C = I - S * N is satisfied.
+
+ // Collect the components in vectors (if the constants are scalars, these
+ // vectors will contain the constants themselves).
+ std::vector<const opt::analysis::Constant*> c_components;
+ std::vector<const opt::analysis::Constant*> i_components;
+ std::vector<const opt::analysis::Constant*> s_components;
+ if (constant->AsIntConstant()) {
+ c_components.emplace_back(constant);
+ i_components.emplace_back(initial_val);
+ s_components.emplace_back(step_val);
+ } else {
+ // It is a vector: get all the components.
+ c_components = constant->AsVectorConstant()->GetComponents();
+ i_components = initial_val->AsVectorConstant()->GetComponents();
+ s_components = step_val->AsVectorConstant()->GetComponents();
+ }
+
+ // Check the value of the components satisfy the equation.
+ for (uint32_t i = 0; i < c_components.size(); i++) {
+ // Use 64-bits integers to be able to handle constants of any width <= 64.
+ uint64_t c_value = c_components[i]->AsIntConstant()->GetZeroExtendedValue();
+ uint64_t i_value = i_components[i]->AsIntConstant()->GetZeroExtendedValue();
+ uint64_t s_value = s_components[i]->AsIntConstant()->GetZeroExtendedValue();
+
+ uint64_t result = i_value - s_value * num_iterations_value;
+
+ // Use bit shifts to ignore the first bits in excess (if there are any). By
+ // shifting left, we discard the first |64 - bit_width| bits. By shifting
+ // right, we move the bits back to their correct position.
+ result = (result << (64 - bit_width)) >> (64 - bit_width);
+
+ if (c_value != result) {
+ return false;
+ }
+ }
+
+ // Check that |message_.block_after_loop_id| is the label of a block.
+ auto block =
+ fuzzerutil::MaybeFindBlock(ir_context, message_.block_after_loop_id());
+
+ // Check that the block exists and has a single predecessor.
+ if (!block || ir_context->cfg()->preds(block->id()).size() != 1) {
+ return false;
+ }
+
+ // Check that the block is not a merge block.
+ if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) {
+ return false;
+ }
+
+ // Check that the block is not a continue block.
+ if (ir_context->GetStructuredCFGAnalysis()->IsContinueBlock(block->id())) {
+ return false;
+ }
+
+ // Check that the block is not a loop header.
+ if (block->IsLoopHeader()) {
+ return false;
+ }
+
+ // Check all the fresh ids.
+ std::set<uint32_t> fresh_ids_used;
+ for (uint32_t id : {message_.syn_id(), message_.loop_id(), message_.ctr_id(),
+ message_.temp_id(), message_.eventual_syn_id(),
+ message_.incremented_ctr_id(), message_.cond_id()}) {
+ if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
+ &fresh_ids_used)) {
+ return false;
+ }
+ }
+
+ // Check the additional block id if it is non-zero.
+ return !message_.additional_block_id() ||
+ CheckIdIsFreshAndNotUsedByThisTransformation(
+ message_.additional_block_id(), ir_context, &fresh_ids_used);
+}
+
+void TransformationAddLoopToCreateIntConstantSynonym::Apply(
+ opt::IRContext* ir_context,
+ TransformationContext* transformation_context) const {
+ // Find 32-bit signed integer constants 0 and 1.
+ uint32_t const_0_id = fuzzerutil::MaybeGetIntegerConstant(
+ ir_context, *transformation_context, {0}, 32, true, false);
+ auto const_0_def = ir_context->get_def_use_mgr()->GetDef(const_0_id);
+ uint32_t const_1_id = fuzzerutil::MaybeGetIntegerConstant(
+ ir_context, *transformation_context, {1}, 32, true, false);
+
+ // Retrieve the instruction defining the initial value constant.
+ auto initial_val_def =
+ ir_context->get_def_use_mgr()->GetDef(message_.initial_val_id());
+
+ // Retrieve the block before which we want to insert the loop.
+ auto block_after_loop =
+ ir_context->get_instr_block(message_.block_after_loop_id());
+
+ // Find the predecessor of the block.
+ uint32_t pred_id =
+ ir_context->cfg()->preds(message_.block_after_loop_id())[0];
+
+ // Get the id for the last block in the new loop. It will be
+ // |message_.additional_block_id| if this is non_zero, |message_.loop_id|
+ // otherwise.
+ uint32_t last_loop_block_id = message_.additional_block_id()
+ ? message_.additional_block_id()
+ : message_.loop_id();
+
+ // Create the loop header block.
+ std::unique_ptr<opt::BasicBlock> loop_block =
+ MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpLabel, 0, message_.loop_id(),
+ opt::Instruction::OperandList{}));
+
+ // Add OpPhi instructions to retrieve the current value of the counter and of
+ // the temporary variable that will be decreased at each operation.
+ loop_block->AddInstruction(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpPhi, const_0_def->type_id(), message_.ctr_id(),
+ opt::Instruction::OperandList{
+ {SPV_OPERAND_TYPE_ID, {const_0_id}},
+ {SPV_OPERAND_TYPE_ID, {pred_id}},
+ {SPV_OPERAND_TYPE_ID, {message_.incremented_ctr_id()}},
+ {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}}));
+
+ loop_block->AddInstruction(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpPhi, initial_val_def->type_id(), message_.temp_id(),
+ opt::Instruction::OperandList{
+ {SPV_OPERAND_TYPE_ID, {message_.initial_val_id()}},
+ {SPV_OPERAND_TYPE_ID, {pred_id}},
+ {SPV_OPERAND_TYPE_ID, {message_.eventual_syn_id()}},
+ {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}}));
+
+ // Collect the other instructions in a list. These will be added to an
+ // additional block if |message_.additional_block_id| is defined, to the loop
+ // header otherwise.
+ std::vector<std::unique_ptr<opt::Instruction>> other_instructions;
+
+ // Add an instruction to subtract the step value from the temporary value.
+ // The value of this id will converge to the constant in the last iteration.
+ other_instructions.push_back(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpISub, initial_val_def->type_id(),
+ message_.eventual_syn_id(),
+ opt::Instruction::OperandList{
+ {SPV_OPERAND_TYPE_ID, {message_.temp_id()}},
+ {SPV_OPERAND_TYPE_ID, {message_.step_val_id()}}}));
+
+ // Add an instruction to increment the counter.
+ other_instructions.push_back(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpIAdd, const_0_def->type_id(),
+ message_.incremented_ctr_id(),
+ opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {message_.ctr_id()}},
+ {SPV_OPERAND_TYPE_ID, {const_1_id}}}));
+
+ // Add an instruction to decide whether the condition holds.
+ other_instructions.push_back(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpSLessThan, fuzzerutil::MaybeGetBoolType(ir_context),
+ message_.cond_id(),
+ opt::Instruction::OperandList{
+ {SPV_OPERAND_TYPE_ID, {message_.incremented_ctr_id()}},
+ {SPV_OPERAND_TYPE_ID, {message_.num_iterations_id()}}}));
+
+ // Define the OpLoopMerge instruction for the loop header. The merge block is
+ // the existing block, the continue block is the last block in the loop
+ // (either the loop itself or the additional block).
+ std::unique_ptr<opt::Instruction> merge_inst = MakeUnique<opt::Instruction>(
+ ir_context, SpvOpLoopMerge, 0, 0,
+ opt::Instruction::OperandList{
+ {SPV_OPERAND_TYPE_ID, {message_.block_after_loop_id()}},
+ {SPV_OPERAND_TYPE_ID, {last_loop_block_id}},
+ {SPV_OPERAND_TYPE_LOOP_CONTROL, {SpvLoopControlMaskNone}}});
+
+ // Define a conditional branch instruction, branching to the loop header if
+ // the condition holds, and to the existing block otherwise. This instruction
+ // will be added to the last block in the loop.
+ std::unique_ptr<opt::Instruction> conditional_branch =
+ MakeUnique<opt::Instruction>(
+ ir_context, SpvOpBranchConditional, 0, 0,
+ opt::Instruction::OperandList{
+ {SPV_OPERAND_TYPE_ID, {message_.cond_id()}},
+ {SPV_OPERAND_TYPE_ID, {message_.loop_id()}},
+ {SPV_OPERAND_TYPE_ID, {message_.block_after_loop_id()}}});
+
+ if (message_.additional_block_id()) {
+ // If an id for the additional block is specified, create an additional
+ // block, containing the instructions in the list and a branching
+ // instruction.
+
+ std::unique_ptr<opt::BasicBlock> additional_block =
+ MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpLabel, 0, message_.additional_block_id(),
+ opt::Instruction::OperandList{}));
+
+ for (auto& instruction : other_instructions) {
+ additional_block->AddInstruction(std::move(instruction));
+ }
+
+ additional_block->AddInstruction(std::move(conditional_branch));
+
+ // Add the merge instruction to the header.
+ loop_block->AddInstruction(std::move(merge_inst));
+
+ // Add an unconditional branch from the header to the additional block.
+ loop_block->AddInstruction(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpBranch, 0, 0,
+ opt::Instruction::OperandList{
+ {SPV_OPERAND_TYPE_ID, {message_.additional_block_id()}}}));
+
+ // Insert the two loop blocks before the existing block.
+ block_after_loop->GetParent()->InsertBasicBlockBefore(std::move(loop_block),
+ block_after_loop);
+ block_after_loop->GetParent()->InsertBasicBlockBefore(
+ std::move(additional_block), block_after_loop);
+ } else {
+ // If no id for an additional block is specified, the loop will only be made
+ // up of one block, so we need to add all the instructions to it.
+
+ for (auto& instruction : other_instructions) {
+ loop_block->AddInstruction(std::move(instruction));
+ }
+
+ // Add the merge and conditional branch instructions.
+ loop_block->AddInstruction(std::move(merge_inst));
+ loop_block->AddInstruction(std::move(conditional_branch));
+
+ // Insert the header before the existing block.
+ block_after_loop->GetParent()->InsertBasicBlockBefore(std::move(loop_block),
+ block_after_loop);
+ }
+
+ // Update the branching instructions leading to this block.
+ ir_context->get_def_use_mgr()->ForEachUse(
+ message_.block_after_loop_id(),
+ [this](opt::Instruction* instruction, uint32_t operand_index) {
+ assert(instruction->opcode() != SpvOpLoopMerge &&
+ instruction->opcode() != SpvOpSelectionMerge &&
+ instruction->opcode() != SpvOpSwitch &&
+ "The block should not be referenced by OpLoopMerge, "
+ "OpSelectionMerge or OpSwitch instructions, by construction.");
+ // Replace all uses of the label inside branch instructions.
+ if (instruction->opcode() == SpvOpBranch ||
+ instruction->opcode() == SpvOpBranchConditional) {
+ instruction->SetOperand(operand_index, {message_.loop_id()});
+ }
+ });
+
+ // Update all the OpPhi instructions in the block after the loop: its
+ // predecessor is now the last block in the loop.
+ block_after_loop->ForEachPhiInst(
+ [last_loop_block_id](opt::Instruction* phi_inst) {
+ // Since the block only had one predecessor, the id of the predecessor
+ // is input operand 1.
+ phi_inst->SetInOperand(1, {last_loop_block_id});
+ });
+
+ // Add a new OpPhi instruction at the beginning of the block after the loop,
+ // defining the synonym of the constant. The type id will be the same as
+ // |message_.initial_value_id|, since this is the value that is decremented in
+ // the loop.
+ block_after_loop->begin()->InsertBefore(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpPhi, initial_val_def->type_id(), message_.syn_id(),
+ opt::Instruction::OperandList{
+ {SPV_OPERAND_TYPE_ID, {message_.eventual_syn_id()}},
+ {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}}));
+
+ // Update the module id bound with all the fresh ids used.
+ for (uint32_t id : {message_.syn_id(), message_.loop_id(), message_.ctr_id(),
+ message_.temp_id(), message_.eventual_syn_id(),
+ message_.incremented_ctr_id(), message_.cond_id(),
+ message_.cond_id(), message_.additional_block_id()}) {
+ fuzzerutil::UpdateModuleIdBound(ir_context, id);
+ }
+
+ // Since we changed the structure of the module, we need to invalidate all the
+ // analyses.
+ ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+ // Record that |message_.syn_id| is synonymous with |message_.constant_id|.
+ transformation_context->GetFactManager()->AddFactDataSynonym(
+ MakeDataDescriptor(message_.syn_id(), {}),
+ MakeDataDescriptor(message_.constant_id(), {}), ir_context);
+}
+
+protobufs::Transformation
+TransformationAddLoopToCreateIntConstantSynonym::ToMessage() const {
+ protobufs::Transformation result;
+ *result.mutable_add_loop_to_create_int_constant_synonym() = message_;
+ return result;
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h
new file mode 100644
index 0000000..2914029
--- /dev/null
+++ b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h
@@ -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 SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+class TransformationAddLoopToCreateIntConstantSynonym : public Transformation {
+ public:
+ explicit TransformationAddLoopToCreateIntConstantSynonym(
+ const protobufs::TransformationAddLoopToCreateIntConstantSynonym&
+ message);
+
+ TransformationAddLoopToCreateIntConstantSynonym(
+ uint32_t constant_id, uint32_t initial_val_id, uint32_t step_val_id,
+ uint32_t num_iterations_id, uint32_t block_after_loop_id, uint32_t syn_id,
+ uint32_t loop_id, uint32_t ctr_id, uint32_t temp_id,
+ uint32_t eventual_syn_id, uint32_t incremented_ctr_id, uint32_t cond_id,
+ uint32_t additional_block_id);
+
+ // - |message_.constant_id|, |message_.initial_value_id|,
+ // |message_.step_val_id| are integer constants (scalar or vectors) with the
+ // same type (with possibly different signedness, but same bit width, which
+ // must be <= 64). Let their value be C, I, S respectively.
+ // - |message_.num_iterations_id| is a 32-bit integer scalar constant, with
+ // value N > 0 and N <= 32.
+ // - The module contains 32-bit signed integer scalar constants of values 0
+ // and 1.
+ // - The module contains the boolean type.
+ // - C = I - S * N
+ // - |message_.block_after_loop_id| is the label of a block which has a single
+ // predecessor and which is not a merge block, a continue block or a loop
+ // header.
+ // - |message_.additional_block_id| is either 0 or a valid fresh id, distinct
+ // from the other fresh ids.
+ // - All of the other parameters are valid fresh ids.
+ bool IsApplicable(
+ opt::IRContext* ir_context,
+ const TransformationContext& transformation_context) const override;
+
+ // Adds a loop to the module, defining a synonym of an integer (scalar or
+ // vector) constant. This id is marked as synonym with the original constant.
+ void Apply(opt::IRContext* ir_context,
+ TransformationContext* transformation_context) const override;
+
+ protobufs::Transformation ToMessage() const override;
+
+ private:
+ protobufs::TransformationAddLoopToCreateIntConstantSynonym message_;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 66299f7..a918b24 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -46,6 +46,7 @@
transformation_add_image_sample_unused_components_test.cpp
transformation_add_local_variable_test.cpp
transformation_add_loop_preheader_test.cpp
+ transformation_add_loop_to_create_int_constant_synonym_test.cpp
transformation_add_no_contraction_decoration_test.cpp
transformation_add_opphi_synonym_test.cpp
transformation_add_parameter_test.cpp
diff --git a/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp b/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp
new file mode 100644
index 0000000..d88ffae
--- /dev/null
+++ b/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp
@@ -0,0 +1,957 @@
+// 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_add_loop_to_create_int_constant_synonym.h"
+
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest,
+ ConstantsNotSuitable) {
+ std::string shader = R"(
+ OpCapability Shader
+ OpCapability Int64
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %2 "main"
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %36 = OpTypeBool
+ %5 = OpTypeInt 32 1
+ %6 = OpConstant %5 -1
+ %7 = OpConstant %5 0
+ %8 = OpConstant %5 1
+ %9 = OpConstant %5 2
+ %10 = OpConstant %5 5
+ %11 = OpConstant %5 10
+ %12 = OpConstant %5 20
+ %13 = OpConstant %5 33
+ %14 = OpTypeVector %5 2
+ %15 = OpConstantComposite %14 %10 %11
+ %16 = OpConstantComposite %14 %12 %12
+ %17 = OpTypeVector %5 3
+ %18 = OpConstantComposite %17 %11 %7 %11
+ %19 = OpTypeInt 64 1
+ %20 = OpConstant %19 0
+ %21 = OpConstant %19 10
+ %22 = OpTypeVector %19 2
+ %23 = OpConstantComposite %22 %21 %20
+ %24 = OpTypeFloat 32
+ %25 = OpConstant %24 0
+ %26 = OpConstant %24 5
+ %27 = OpConstant %24 10
+ %28 = OpConstant %24 20
+ %29 = OpTypeVector %24 3
+ %30 = OpConstantComposite %29 %26 %27 %26
+ %31 = OpConstantComposite %29 %28 %28 %28
+ %32 = OpConstantComposite %29 %27 %25 %27
+ %2 = OpFunction %3 None %4
+ %33 = OpLabel
+ %34 = OpCopyObject %5 %11
+ OpBranch %35
+ %35 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_5;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // Reminder: the first four parameters of the constructor are the constants
+ // with values for C, I, S, N respectively.
+
+ // %70 does not correspond to an id in the module.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 70, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // %35 is not a constant.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 35, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // %27, %28 and %26 are not integer constants, but scalar floats.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 27, 28, 26, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // %32, %31 and %30 are not integer constants, but vector floats.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 32, 31, 30, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // %18=(10, 0, 10) has 3 components, while %16=(20, 20) and %15=(5, 10)
+ // have 2.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 18, 16, 15, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // %21 has bit width 64, while the width of %12 and %10 is 32.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 21, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // %13 has component width 64, while the component width of %16 and %15 is 32.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 13, 16, 15, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // %21 (N) is a 64-bit integer, not 32-bit.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 7, 7, 7, 21, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // %7 (N) has value 0, so N <= 0.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 7, 7, 7, 7, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // %6 (N) has value -1, so N <= 1.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 7, 7, 7, 6, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // %13 (N) has value 33, so N > 32.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 7, 7, 7, 6, 13, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // C(%11)=10, I(%12)=20, S(%10)=5, N(%8)=1, so C=I-S*N does not hold, as
+ // 20-5*1=15.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 11, 12, 10, 8, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // C(%15)=(5, 10), I(%16)=(20, 20), S(%15)=(5, 10), N(%8)=1, so C=I-S*N does
+ // not hold, as (20, 20)-1*(5, 10) = (15, 10).
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 15, 16, 15, 8, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest,
+ MissingConstantsOrBoolType) {
+ {
+ // The shader is missing the boolean type.
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeInt 32 1
+ %20 = OpConstant %5 0
+ %6 = OpConstant %5 1
+ %7 = OpConstant %5 2
+ %8 = OpConstant %5 5
+ %9 = OpConstant %5 10
+ %10 = OpConstant %5 20
+ %2 = OpFunction %3 None %4
+ %11 = OpLabel
+ OpBranch %12
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_5;
+ const auto consumer = nullptr;
+ const auto context =
+ BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+ }
+ {
+ // The shader is missing a 32-bit integer 0 constant.
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %20 = OpTypeBool
+ %5 = OpTypeInt 32 1
+ %6 = OpConstant %5 1
+ %7 = OpConstant %5 2
+ %8 = OpConstant %5 5
+ %9 = OpConstant %5 10
+ %10 = OpConstant %5 20
+ %2 = OpFunction %3 None %4
+ %11 = OpLabel
+ OpBranch %12
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_5;
+ const auto consumer = nullptr;
+ const auto context =
+ BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+ }
+ {
+ // The shader is missing a 32-bit integer 1 constant.
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %20 = OpTypeBool
+ %5 = OpTypeInt 32 1
+ %6 = OpConstant %5 0
+ %7 = OpConstant %5 2
+ %8 = OpConstant %5 5
+ %9 = OpConstant %5 10
+ %10 = OpConstant %5 20
+ %2 = OpFunction %3 None %4
+ %11 = OpLabel
+ OpBranch %12
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_5;
+ const auto consumer = nullptr;
+ const auto context =
+ BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+ }
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest, Simple) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %7 = OpTypeInt 32 1
+ %8 = OpConstant %7 0
+ %9 = OpConstant %7 1
+ %10 = OpConstant %7 2
+ %11 = OpConstant %7 5
+ %12 = OpConstant %7 10
+ %13 = OpConstant %7 20
+ %2 = OpFunction %3 None %4
+ %14 = OpLabel
+ OpBranch %15
+ %15 = OpLabel
+ %22 = OpPhi %7 %12 %14
+ OpSelectionMerge %16 None
+ OpBranchConditional %6 %17 %18
+ %17 = OpLabel
+ %23 = OpPhi %7 %13 %15
+ OpBranch %18
+ %18 = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ OpBranch %19
+ %19 = OpLabel
+ OpLoopMerge %20 %19 None
+ OpBranchConditional %6 %20 %19
+ %20 = OpLabel
+ OpBranch %21
+ %21 = OpLabel
+ OpBranch %24
+ %24 = OpLabel
+ OpLoopMerge %27 %25 None
+ OpBranch %25
+ %25 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ OpBranchConditional %6 %24 %27
+ %27 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_5;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // Block %14 has no predecessors.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 14, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // Block %18 has more than one predecessor.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 18, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // Block %16 is a merge block.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 16, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // Block %25 is a continue block.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 25, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // Block %19 has more than one predecessor.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 19, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // Block %20 is a merge block.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 20, 100, 101, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // Id %20 is supposed to be fresh, but it is not.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 15, 100, 20, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // Id %100 is used twice.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 15, 100, 100, 102, 103, 104, 105, 106, 107)
+ .IsApplicable(context.get(), transformation_context));
+
+ // Id %100 is used twice.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 15, 100, 101, 102, 103, 104, 105, 106, 100)
+ .IsApplicable(context.get(), transformation_context));
+
+ // Only the last id (for the additional block) is optional, so the other ones
+ // cannot be 0.
+ ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 15, 0, 101, 102, 103, 104, 105, 106, 100)
+ .IsApplicable(context.get(), transformation_context));
+
+ // This transformation will create a synonym of constant %12 from a 1-block
+ // loop.
+ auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 15, 100, 101, 102, 103, 104, 105, 106, 0);
+ ASSERT_TRUE(
+ transformation1.IsApplicable(context.get(), transformation_context));
+ transformation1.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+ MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {})));
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ // This transformation will create a synonym of constant %12 from a 2-block
+ // loop.
+ auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 17, 107, 108, 109, 110, 111, 112, 113, 114);
+ ASSERT_TRUE(
+ transformation2.IsApplicable(context.get(), transformation_context));
+ transformation2.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+ MakeDataDescriptor(12, {}), MakeDataDescriptor(107, {})));
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ // This transformation will create a synonym of constant %12 from a 2-block
+ // loop.
+ auto transformation3 = TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 13, 11, 10, 26, 115, 116, 117, 118, 119, 120, 121, 0);
+ ASSERT_TRUE(
+ transformation3.IsApplicable(context.get(), transformation_context));
+ transformation3.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+ MakeDataDescriptor(12, {}), MakeDataDescriptor(115, {})));
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string after_transformations = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %7 = OpTypeInt 32 1
+ %8 = OpConstant %7 0
+ %9 = OpConstant %7 1
+ %10 = OpConstant %7 2
+ %11 = OpConstant %7 5
+ %12 = OpConstant %7 10
+ %13 = OpConstant %7 20
+ %2 = OpFunction %3 None %4
+ %14 = OpLabel
+ OpBranch %101
+ %101 = OpLabel
+ %102 = OpPhi %7 %8 %14 %105 %101
+ %103 = OpPhi %7 %13 %14 %104 %101
+ %104 = OpISub %7 %103 %11
+ %105 = OpIAdd %7 %102 %9
+ %106 = OpSLessThan %5 %105 %10
+ OpLoopMerge %15 %101 None
+ OpBranchConditional %106 %101 %15
+ %15 = OpLabel
+ %100 = OpPhi %7 %104 %101
+ %22 = OpPhi %7 %12 %101
+ OpSelectionMerge %16 None
+ OpBranchConditional %6 %108 %18
+ %108 = OpLabel
+ %109 = OpPhi %7 %8 %15 %112 %114
+ %110 = OpPhi %7 %13 %15 %111 %114
+ OpLoopMerge %17 %114 None
+ OpBranch %114
+ %114 = OpLabel
+ %111 = OpISub %7 %110 %11
+ %112 = OpIAdd %7 %109 %9
+ %113 = OpSLessThan %5 %112 %10
+ OpBranchConditional %113 %108 %17
+ %17 = OpLabel
+ %107 = OpPhi %7 %111 %114
+ %23 = OpPhi %7 %13 %114
+ OpBranch %18
+ %18 = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ OpBranch %19
+ %19 = OpLabel
+ OpLoopMerge %20 %19 None
+ OpBranchConditional %6 %20 %19
+ %20 = OpLabel
+ OpBranch %21
+ %21 = OpLabel
+ OpBranch %24
+ %24 = OpLabel
+ OpLoopMerge %27 %25 None
+ OpBranch %25
+ %25 = OpLabel
+ OpBranch %116
+ %116 = OpLabel
+ %117 = OpPhi %7 %8 %25 %120 %116
+ %118 = OpPhi %7 %13 %25 %119 %116
+ %119 = OpISub %7 %118 %11
+ %120 = OpIAdd %7 %117 %9
+ %121 = OpSLessThan %5 %120 %10
+ OpLoopMerge %26 %116 None
+ OpBranchConditional %121 %116 %26
+ %26 = OpLabel
+ %115 = OpPhi %7 %119 %116
+ OpBranchConditional %6 %24 %27
+ %27 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest,
+ DifferentSignednessAndVectors) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %7 = OpTypeInt 32 1
+ %8 = OpConstant %7 0
+ %9 = OpConstant %7 1
+ %10 = OpConstant %7 2
+ %11 = OpConstant %7 5
+ %12 = OpConstant %7 10
+ %13 = OpConstant %7 20
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 0
+ %16 = OpConstant %14 5
+ %17 = OpConstant %14 10
+ %18 = OpConstant %14 20
+ %19 = OpTypeVector %7 2
+ %20 = OpTypeVector %14 2
+ %21 = OpConstantComposite %19 %12 %8
+ %22 = OpConstantComposite %20 %17 %15
+ %23 = OpConstantComposite %19 %13 %12
+ %24 = OpConstantComposite %19 %11 %11
+ %2 = OpFunction %3 None %4
+ %25 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ OpBranch %27
+ %27 = OpLabel
+ OpBranch %28
+ %28 = OpLabel
+ OpBranch %29
+ %29 = OpLabel
+ OpBranch %30
+ %30 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_5;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // These tests check that the transformation is applicable and is applied
+ // correctly with integers, scalar and vectors, of different signedness.
+
+ // %12 is a signed integer, %18 and %16 are unsigned integers.
+ auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 18, 16, 10, 26, 100, 101, 102, 103, 104, 105, 106, 0);
+ ASSERT_TRUE(
+ transformation1.IsApplicable(context.get(), transformation_context));
+ transformation1.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+ MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {})));
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ // %12 and %11 are signed integers, %18 is an unsigned integer.
+ auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 18, 11, 10, 27, 108, 109, 110, 111, 112, 113, 114, 0);
+ ASSERT_TRUE(
+ transformation2.IsApplicable(context.get(), transformation_context));
+ transformation2.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+ MakeDataDescriptor(12, {}), MakeDataDescriptor(108, {})));
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ // %17, %18 and %16 are all signed integers.
+ auto transformation3 = TransformationAddLoopToCreateIntConstantSynonym(
+ 17, 18, 16, 10, 28, 115, 116, 117, 118, 119, 120, 121, 0);
+ ASSERT_TRUE(
+ transformation3.IsApplicable(context.get(), transformation_context));
+ transformation3.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+ MakeDataDescriptor(17, {}), MakeDataDescriptor(115, {})));
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ // %22 is an unsigned integer vector, %23 and %24 are signed integer vectors.
+ auto transformation4 = TransformationAddLoopToCreateIntConstantSynonym(
+ 22, 23, 24, 10, 29, 122, 123, 124, 125, 126, 127, 128, 0);
+ ASSERT_TRUE(
+ transformation4.IsApplicable(context.get(), transformation_context));
+ transformation4.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+ MakeDataDescriptor(22, {}), MakeDataDescriptor(122, {})));
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ // %21, %23 and %24 are all signed integer vectors.
+ auto transformation5 = TransformationAddLoopToCreateIntConstantSynonym(
+ 21, 23, 24, 10, 30, 129, 130, 131, 132, 133, 134, 135, 0);
+ ASSERT_TRUE(
+ transformation5.IsApplicable(context.get(), transformation_context));
+ transformation5.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+ MakeDataDescriptor(21, {}), MakeDataDescriptor(129, {})));
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string after_transformations = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %7 = OpTypeInt 32 1
+ %8 = OpConstant %7 0
+ %9 = OpConstant %7 1
+ %10 = OpConstant %7 2
+ %11 = OpConstant %7 5
+ %12 = OpConstant %7 10
+ %13 = OpConstant %7 20
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 0
+ %16 = OpConstant %14 5
+ %17 = OpConstant %14 10
+ %18 = OpConstant %14 20
+ %19 = OpTypeVector %7 2
+ %20 = OpTypeVector %14 2
+ %21 = OpConstantComposite %19 %12 %8
+ %22 = OpConstantComposite %20 %17 %15
+ %23 = OpConstantComposite %19 %13 %12
+ %24 = OpConstantComposite %19 %11 %11
+ %2 = OpFunction %3 None %4
+ %25 = OpLabel
+ OpBranch %101
+ %101 = OpLabel
+ %102 = OpPhi %7 %8 %25 %105 %101
+ %103 = OpPhi %14 %18 %25 %104 %101
+ %104 = OpISub %14 %103 %16
+ %105 = OpIAdd %7 %102 %9
+ %106 = OpSLessThan %5 %105 %10
+ OpLoopMerge %26 %101 None
+ OpBranchConditional %106 %101 %26
+ %26 = OpLabel
+ %100 = OpPhi %14 %104 %101
+ OpBranch %109
+ %109 = OpLabel
+ %110 = OpPhi %7 %8 %26 %113 %109
+ %111 = OpPhi %14 %18 %26 %112 %109
+ %112 = OpISub %14 %111 %11
+ %113 = OpIAdd %7 %110 %9
+ %114 = OpSLessThan %5 %113 %10
+ OpLoopMerge %27 %109 None
+ OpBranchConditional %114 %109 %27
+ %27 = OpLabel
+ %108 = OpPhi %14 %112 %109
+ OpBranch %116
+ %116 = OpLabel
+ %117 = OpPhi %7 %8 %27 %120 %116
+ %118 = OpPhi %14 %18 %27 %119 %116
+ %119 = OpISub %14 %118 %16
+ %120 = OpIAdd %7 %117 %9
+ %121 = OpSLessThan %5 %120 %10
+ OpLoopMerge %28 %116 None
+ OpBranchConditional %121 %116 %28
+ %28 = OpLabel
+ %115 = OpPhi %14 %119 %116
+ OpBranch %123
+ %123 = OpLabel
+ %124 = OpPhi %7 %8 %28 %127 %123
+ %125 = OpPhi %19 %23 %28 %126 %123
+ %126 = OpISub %19 %125 %24
+ %127 = OpIAdd %7 %124 %9
+ %128 = OpSLessThan %5 %127 %10
+ OpLoopMerge %29 %123 None
+ OpBranchConditional %128 %123 %29
+ %29 = OpLabel
+ %122 = OpPhi %19 %126 %123
+ OpBranch %130
+ %130 = OpLabel
+ %131 = OpPhi %7 %8 %29 %134 %130
+ %132 = OpPhi %19 %23 %29 %133 %130
+ %133 = OpISub %19 %132 %24
+ %134 = OpIAdd %7 %131 %9
+ %135 = OpSLessThan %5 %134 %10
+ OpLoopMerge %30 %130 None
+ OpBranchConditional %135 %130 %30
+ %30 = OpLabel
+ %129 = OpPhi %19 %133 %130
+ OpReturn
+ OpFunctionEnd
+)";
+
+ ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest, 64BitConstants) {
+ std::string shader = R"(
+ OpCapability Shader
+ OpCapability Int64
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %7 = OpTypeInt 32 1
+ %8 = OpConstant %7 0
+ %9 = OpConstant %7 1
+ %10 = OpConstant %7 2
+ %11 = OpTypeInt 64 1
+ %12 = OpConstant %11 5
+ %13 = OpConstant %11 10
+ %14 = OpConstant %11 20
+ %15 = OpTypeVector %11 2
+ %16 = OpConstantComposite %15 %13 %13
+ %17 = OpConstantComposite %15 %14 %14
+ %18 = OpConstantComposite %15 %12 %12
+ %2 = OpFunction %3 None %4
+ %19 = OpLabel
+ OpBranch %20
+ %20 = OpLabel
+ OpBranch %21
+ %21 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_5;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // These tests check that the transformation can be applied, and is applied
+ // correctly, to 64-bit integer (scalar and vector) constants.
+
+ // 64-bit scalar integers.
+ auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+ 13, 14, 12, 10, 20, 100, 101, 102, 103, 104, 105, 106, 0);
+ ASSERT_TRUE(
+ transformation1.IsApplicable(context.get(), transformation_context));
+ transformation1.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+ MakeDataDescriptor(13, {}), MakeDataDescriptor(100, {})));
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ // 64-bit vector integers.
+ auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+ 16, 17, 18, 10, 21, 107, 108, 109, 110, 111, 112, 113, 0);
+ ASSERT_TRUE(
+ transformation2.IsApplicable(context.get(), transformation_context));
+ transformation2.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+ MakeDataDescriptor(16, {}), MakeDataDescriptor(107, {})));
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string after_transformations = R"(
+ OpCapability Shader
+ OpCapability Int64
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %7 = OpTypeInt 32 1
+ %8 = OpConstant %7 0
+ %9 = OpConstant %7 1
+ %10 = OpConstant %7 2
+ %11 = OpTypeInt 64 1
+ %12 = OpConstant %11 5
+ %13 = OpConstant %11 10
+ %14 = OpConstant %11 20
+ %15 = OpTypeVector %11 2
+ %16 = OpConstantComposite %15 %13 %13
+ %17 = OpConstantComposite %15 %14 %14
+ %18 = OpConstantComposite %15 %12 %12
+ %2 = OpFunction %3 None %4
+ %19 = OpLabel
+ OpBranch %101
+ %101 = OpLabel
+ %102 = OpPhi %7 %8 %19 %105 %101
+ %103 = OpPhi %11 %14 %19 %104 %101
+ %104 = OpISub %11 %103 %12
+ %105 = OpIAdd %7 %102 %9
+ %106 = OpSLessThan %5 %105 %10
+ OpLoopMerge %20 %101 None
+ OpBranchConditional %106 %101 %20
+ %20 = OpLabel
+ %100 = OpPhi %11 %104 %101
+ OpBranch %108
+ %108 = OpLabel
+ %109 = OpPhi %7 %8 %20 %112 %108
+ %110 = OpPhi %15 %17 %20 %111 %108
+ %111 = OpISub %15 %110 %18
+ %112 = OpIAdd %7 %109 %9
+ %113 = OpSLessThan %5 %112 %10
+ OpLoopMerge %21 %108 None
+ OpBranchConditional %113 %108 %21
+ %21 = OpLabel
+ %107 = OpPhi %15 %111 %108
+ OpReturn
+ OpFunctionEnd
+)";
+
+ ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest, Underflow) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %7 = OpTypeInt 32 1
+ %8 = OpConstant %7 0
+ %9 = OpConstant %7 1
+ %10 = OpConstant %7 2
+ %11 = OpConstant %7 20
+ %12 = OpConstant %7 -4
+ %13 = OpTypeInt 32 0
+ %14 = OpConstant %13 214748365
+ %15 = OpConstant %13 4294967256
+ %2 = OpFunction %3 None %4
+ %16 = OpLabel
+ OpBranch %17
+ %17 = OpLabel
+ OpBranch %18
+ %18 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_5;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // These tests check that underflows are taken into consideration when
+ // deciding if transformation is applicable.
+
+ // Subtracting 2147483648 20 times from 32-bit integer 0 underflows 2 times
+ // and the result is equivalent to -4.
+ auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+ 12, 8, 14, 11, 17, 100, 101, 102, 103, 104, 105, 106, 0);
+ ASSERT_TRUE(
+ transformation1.IsApplicable(context.get(), transformation_context));
+ transformation1.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+ MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {})));
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ // Subtracting 20 twice from 0 underflows and gives the unsigned integer
+ // 4294967256.
+ auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+ 15, 8, 11, 10, 18, 107, 108, 109, 110, 111, 112, 113, 0);
+ ASSERT_TRUE(
+ transformation2.IsApplicable(context.get(), transformation_context));
+ transformation2.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+ MakeDataDescriptor(15, {}), MakeDataDescriptor(107, {})));
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string after_transformations = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %7 = OpTypeInt 32 1
+ %8 = OpConstant %7 0
+ %9 = OpConstant %7 1
+ %10 = OpConstant %7 2
+ %11 = OpConstant %7 20
+ %12 = OpConstant %7 -4
+ %13 = OpTypeInt 32 0
+ %14 = OpConstant %13 214748365
+ %15 = OpConstant %13 4294967256
+ %2 = OpFunction %3 None %4
+ %16 = OpLabel
+ OpBranch %101
+ %101 = OpLabel
+ %102 = OpPhi %7 %8 %16 %105 %101
+ %103 = OpPhi %7 %8 %16 %104 %101
+ %104 = OpISub %7 %103 %14
+ %105 = OpIAdd %7 %102 %9
+ %106 = OpSLessThan %5 %105 %11
+ OpLoopMerge %17 %101 None
+ OpBranchConditional %106 %101 %17
+ %17 = OpLabel
+ %100 = OpPhi %7 %104 %101
+ OpBranch %108
+ %108 = OpLabel
+ %109 = OpPhi %7 %8 %17 %112 %108
+ %110 = OpPhi %7 %8 %17 %111 %108
+ %111 = OpISub %7 %110 %11
+ %112 = OpIAdd %7 %109 %9
+ %113 = OpSLessThan %5 %112 %10
+ OpLoopMerge %18 %108 None
+ OpBranchConditional %113 %108 %18
+ %18 = OpLabel
+ %107 = OpPhi %7 %111 %108
+ OpReturn
+ OpFunctionEnd
+)";
+
+ ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools