spirv-fuzz: Add fuzzer pass to change loop controls (#2949)
A new pass that allows the fuzzer to change the 'loop control' operand
(and associated literal operands) of OpLoopMerge instructions.
Fixes #2938.
Fixes #2943.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index dbb4964..c711d05 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -38,6 +38,7 @@
fuzzer_pass_add_dead_breaks.h
fuzzer_pass_add_dead_continues.h
fuzzer_pass_add_useful_constructs.h
+ fuzzer_pass_adjust_loop_controls.h
fuzzer_pass_adjust_selection_controls.h
fuzzer_pass_apply_id_synonyms.h
fuzzer_pass_construct_composites.h
@@ -67,6 +68,7 @@
transformation_replace_boolean_constant_with_constant_binary.h
transformation_replace_constant_with_uniform.h
transformation_replace_id_with_synonym.h
+ transformation_set_loop_control.h
transformation_set_selection_control.h
transformation_split_block.h
uniform_buffer_element_descriptor.h
@@ -81,6 +83,7 @@
fuzzer_pass_add_dead_breaks.cpp
fuzzer_pass_add_dead_continues.cpp
fuzzer_pass_add_useful_constructs.cpp
+ fuzzer_pass_adjust_loop_controls.cpp
fuzzer_pass_adjust_selection_controls.cpp
fuzzer_pass_apply_id_synonyms.cpp
fuzzer_pass_construct_composites.cpp
@@ -109,6 +112,7 @@
transformation_replace_boolean_constant_with_constant_binary.cpp
transformation_replace_constant_with_uniform.cpp
transformation_replace_id_with_synonym.cpp
+ transformation_set_loop_control.cpp
transformation_set_selection_control.cpp
transformation_split_block.cpp
uniform_buffer_element_descriptor.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index fbeafb3..a034c0f 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -23,6 +23,7 @@
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
#include "source/fuzz/fuzzer_pass_add_dead_continues.h"
#include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
+#include "source/fuzz/fuzzer_pass_adjust_loop_controls.h"
#include "source/fuzz/fuzzer_pass_adjust_selection_controls.h"
#include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
#include "source/fuzz/fuzzer_pass_construct_composites.h"
@@ -170,6 +171,11 @@
// Now apply some passes that it does not make sense to apply repeatedly,
// as they do not unlock other passes.
if (fuzzer_context.ChooseEven()) {
+ FuzzerPassAdjustLoopControls(ir_context.get(), &fact_manager,
+ &fuzzer_context, transformation_sequence_out)
+ .Apply();
+ }
+ if (fuzzer_context.ChooseEven()) {
FuzzerPassAdjustSelectionControls(ir_context.get(), &fact_manager,
&fuzzer_context,
transformation_sequence_out)
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 2328621..5a49e5e 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -25,6 +25,7 @@
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80};
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadContinue = {5, 80};
+const std::pair<uint32_t, uint32_t> kChanceOfAdjustingLoopControl = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingSelectionControl = {20,
90};
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
@@ -34,6 +35,11 @@
const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
+// Default limits for various quantities that are chosen during fuzzing.
+// Keep them in alphabetical order.
+const uint32_t kDefaultMaxLoopControlPartialCount = 100;
+const uint32_t kDefaultMaxLoopControlPeelCount = 100;
+
// Default functions for controlling how deep to go during recursive
// generation/transformation. Keep them in alphabetical order.
@@ -56,6 +62,8 @@
ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak);
chance_of_adding_dead_continue_ =
ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue);
+ chance_of_adjusting_loop_control_ =
+ ChooseBetweenMinAndMax(kChanceOfAdjustingLoopControl);
chance_of_adjusting_selection_control_ =
ChooseBetweenMinAndMax(kChanceOfAdjustingSelectionControl);
chance_of_constructing_composite_ =
@@ -68,6 +76,8 @@
chance_of_replacing_id_with_synonym_ =
ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
+ max_loop_control_partial_count_ = kDefaultMaxLoopControlPartialCount;
+ max_loop_control_peel_count_ = kDefaultMaxLoopControlPeelCount;
}
FuzzerContext::~FuzzerContext() = default;
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index eabe346..5e1b1d2 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -62,6 +62,9 @@
uint32_t GetChanceOfAddingDeadContinue() {
return chance_of_adding_dead_continue_;
}
+ uint32_t GetChanceOfAdjustingLoopControl() {
+ return chance_of_adjusting_loop_control_;
+ }
uint32_t GetChanceOfAdjustingSelectionControl() {
return chance_of_adjusting_selection_control_;
}
@@ -77,6 +80,12 @@
return chance_of_replacing_id_with_synonym_;
}
uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; }
+ uint32_t GetRandomLoopControlPeelCount() {
+ return random_generator_->RandomUint32(max_loop_control_peel_count_);
+ }
+ uint32_t GetRandomLoopControlPartialCount() {
+ return random_generator_->RandomUint32(max_loop_control_partial_count_);
+ }
// Functions to control how deeply to recurse.
// Keep them in alphabetical order.
@@ -94,6 +103,7 @@
// Keep them in alphabetical order.
uint32_t chance_of_adding_dead_break_;
uint32_t chance_of_adding_dead_continue_;
+ uint32_t chance_of_adjusting_loop_control_;
uint32_t chance_of_adjusting_selection_control_;
uint32_t chance_of_constructing_composite_;
uint32_t chance_of_copying_object_;
@@ -102,6 +112,12 @@
uint32_t chance_of_replacing_id_with_synonym_;
uint32_t chance_of_splitting_block_;
+ // Limits associated with various quantities for which random values are
+ // chosen during fuzzing.
+ // Keep them in alphabetical order.
+ uint32_t max_loop_control_partial_count_;
+ uint32_t max_loop_control_peel_count_;
+
// Functions to determine with what probability to go deeper when generating
// or mutating constructs recursively.
const std::function<bool(uint32_t, RandomGenerator*)>&
diff --git a/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp b/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp
new file mode 100644
index 0000000..65b8d95
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp
@@ -0,0 +1,121 @@
+// Copyright (c) 2019 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_adjust_loop_controls.h"
+
+#include "source/fuzz/transformation_set_loop_control.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAdjustLoopControls::FuzzerPassAdjustLoopControls(
+ opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations)
+ : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
+
+FuzzerPassAdjustLoopControls::~FuzzerPassAdjustLoopControls() = default;
+
+void FuzzerPassAdjustLoopControls::Apply() {
+ // Consider every merge instruction in the module (via looking through all
+ // functions and blocks).
+ for (auto& function : *GetIRContext()->module()) {
+ for (auto& block : function) {
+ if (auto merge_inst = block.GetMergeInst()) {
+ // Ignore the instruction if it is not a loop merge.
+ if (merge_inst->opcode() != SpvOpLoopMerge) {
+ continue;
+ }
+
+ // Decide randomly whether to adjust this loop merge.
+ if (!GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()->GetChanceOfAdjustingLoopControl())) {
+ continue;
+ }
+
+ uint32_t existing_mask = merge_inst->GetSingleWordOperand(
+ TransformationSetLoopControl::kLoopControlMaskInOperandIndex);
+
+ // First, set the new mask to one of None, Unroll or DontUnroll.
+ std::vector<uint32_t> basic_masks = {SpvLoopControlMaskNone,
+ SpvLoopControlUnrollMask,
+ SpvLoopControlDontUnrollMask};
+ uint32_t new_mask =
+ basic_masks[GetFuzzerContext()->RandomIndex(basic_masks)];
+
+ // For the loop controls that depend on guarantees about what the loop
+ // does, check which of these were present in the existing mask and
+ // randomly decide whether to keep them. They are just hints, so
+ // removing them should not change the semantics of the module.
+ for (auto mask_bit :
+ {SpvLoopControlDependencyInfiniteMask,
+ SpvLoopControlDependencyLengthMask,
+ SpvLoopControlMinIterationsMask, SpvLoopControlMaxIterationsMask,
+ SpvLoopControlIterationMultipleMask}) {
+ if ((existing_mask & mask_bit) && GetFuzzerContext()->ChooseEven()) {
+ // The mask bits we are considering are not available in all SPIR-V
+ // versions. However, we only include a mask bit if it was present
+ // in the original loop control mask, and we work under the
+ // assumption that we are transforming a valid module, thus we don't
+ // need to actually check whether the SPIR-V version being used
+ // supports these loop control mask bits.
+ new_mask |= mask_bit;
+ }
+ }
+
+ // We use 0 for peel count and partial count in the case that we choose
+ // not to set these controls.
+ uint32_t peel_count = 0;
+ uint32_t partial_count = 0;
+
+ // PeelCount and PartialCount are not compatible with DontUnroll, so
+ // we check whether DontUnroll is set.
+ if (!(new_mask & SpvLoopControlDontUnrollMask)) {
+ // If PeelCount is supported by this SPIR-V version, randomly choose
+ // whether to set it. If it was set in the original mask and is not
+ // selected for setting here, that amounts to dropping it.
+ if (TransformationSetLoopControl::PeelCountIsSupported(
+ GetIRContext()) &&
+ GetFuzzerContext()->ChooseEven()) {
+ new_mask |= SpvLoopControlPeelCountMask;
+ // The peel count is chosen randomly - if PeelCount was already set
+ // this will overwrite whatever peel count was previously used.
+ peel_count = GetFuzzerContext()->GetRandomLoopControlPeelCount();
+ }
+ // Similar, but for PartialCount.
+ if (TransformationSetLoopControl::PartialCountIsSupported(
+ GetIRContext()) &&
+ GetFuzzerContext()->ChooseEven()) {
+ new_mask |= SpvLoopControlPartialCountMask;
+ partial_count =
+ GetFuzzerContext()->GetRandomLoopControlPartialCount();
+ }
+ }
+
+ // Apply the transformation and add it to the output transformation
+ // sequence.
+ TransformationSetLoopControl transformation(block.id(), new_mask,
+ peel_count, partial_count);
+ assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
+ "Transformation should be applicable by construction.");
+ transformation.Apply(GetIRContext(), GetFactManager());
+ *GetTransformations()->add_transformation() =
+ transformation.ToMessage();
+ }
+ }
+ }
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_adjust_loop_controls.h b/source/fuzz/fuzzer_pass_adjust_loop_controls.h
new file mode 100644
index 0000000..e945606
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_adjust_loop_controls.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2019 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_ADJUST_LOOP_CONTROLS_
+#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A pass that adjusts the loop controls on OpLoopMerge instructions.
+class FuzzerPassAdjustLoopControls : public FuzzerPass {
+ public:
+ FuzzerPassAdjustLoopControls(
+ opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations);
+
+ ~FuzzerPassAdjustLoopControls() override;
+
+ void Apply() override;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 0989502..80bb4df 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -23,8 +23,8 @@
message IdUseDescriptor {
- // Describes a use of an id as an input operand to an instruction in some block
- // of a function.
+ // Describes a use of an id as an input operand to an instruction in some
+ // block of a function.
// Example:
// - id_of_interest = 42
@@ -167,7 +167,8 @@
TransformationAddTypeFloat add_type_float = 6;
TransformationAddTypeInt add_type_int = 7;
TransformationAddDeadBreak add_dead_break = 8;
- TransformationReplaceBooleanConstantWithConstantBinary replace_boolean_constant_with_constant_binary = 9;
+ TransformationReplaceBooleanConstantWithConstantBinary
+ replace_boolean_constant_with_constant_binary = 9;
TransformationAddTypePointer add_type_pointer = 10;
TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11;
TransformationAddDeadContinue add_dead_continue = 12;
@@ -175,6 +176,7 @@
TransformationReplaceIdWithSynonym replace_id_with_synonym = 14;
TransformationSetSelectionControl set_selection_control = 15;
TransformationConstructComposite construct_composite = 16;
+ TransformationSetLoopControl set_loop_control = 17;
// Add additional option using the next available number.
}
}
@@ -410,18 +412,42 @@
uint32 fresh_id_for_temporary = 3;
}
+message TransformationSetLoopControl {
+
+ // A transformation that sets the loop control operand of an OpLoopMerge
+ // instruction.
+
+ // The id of a basic block that should contain OpLoopMerge
+ uint32 block_id = 1;
+
+ // The value to which the 'loop control' operand should be set.
+ // This must be a legal loop control mask.
+ uint32 loop_control = 2;
+
+ // Provides a peel count value for the loop. Used if and only if the
+ // PeelCount bit is set. Must be zero if the PeelCount bit is not set (can
+ // still be zero if this bit is set).
+ uint32 peel_count = 3;
+
+ // Provides a partial count value for the loop. Used if and only if the
+ // PartialCount bit is set. Must be zero if the PartialCount bit is not set
+ // (can still be zero if this bit is set).
+ uint32 partial_count = 4;
+
+}
+
message TransformationSetSelectionControl {
// A transformation that sets the selection control operand of an
// OpSelectionMerge instruction.
- // The id of a basic block that should contain OpSelectionMerge.
+ // The id of a basic block that should contain OpSelectionMerge
uint32 block_id = 1;
// The value to which the 'selection control' operand should be set.
// Although technically 'selection control' is a literal mask that can be
// some combination of 'None', 'Flatten' and 'DontFlatten', the combination
- // 'Flatten | DontFlatten' does not make sense.
+ // 'Flatten | DontFlatten' does not make sense and is not allowed here.
uint32 selection_control = 2;
}
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 18c6abc..8b93b6a 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -30,6 +30,7 @@
#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_id_with_synonym.h"
+#include "source/fuzz/transformation_set_loop_control.h"
#include "source/fuzz/transformation_set_selection_control.h"
#include "source/fuzz/transformation_split_block.h"
#include "source/util/make_unique.h"
@@ -81,6 +82,9 @@
case protobufs::Transformation::TransformationCase::kReplaceIdWithSynonym:
return MakeUnique<TransformationReplaceIdWithSynonym>(
message.replace_id_with_synonym());
+ case protobufs::Transformation::TransformationCase::kSetLoopControl:
+ return MakeUnique<TransformationSetLoopControl>(
+ message.set_loop_control());
case protobufs::Transformation::TransformationCase::kSetSelectionControl:
return MakeUnique<TransformationSetSelectionControl>(
message.set_selection_control());
diff --git a/source/fuzz/transformation_set_loop_control.cpp b/source/fuzz/transformation_set_loop_control.cpp
new file mode 100644
index 0000000..9062f17
--- /dev/null
+++ b/source/fuzz/transformation_set_loop_control.cpp
@@ -0,0 +1,216 @@
+// Copyright (c) 2019 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_set_loop_control.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationSetLoopControl::TransformationSetLoopControl(
+ const spvtools::fuzz::protobufs::TransformationSetLoopControl& message)
+ : message_(message) {}
+
+TransformationSetLoopControl::TransformationSetLoopControl(
+ uint32_t block_id, uint32_t loop_control, uint32_t peel_count,
+ uint32_t partial_count) {
+ message_.set_block_id(block_id);
+ message_.set_loop_control(loop_control);
+ message_.set_peel_count(peel_count);
+ message_.set_partial_count(partial_count);
+}
+
+bool TransformationSetLoopControl::IsApplicable(
+ opt::IRContext* context, const FactManager& /*unused*/) const {
+ // |message_.block_id| must identify a block that ends with OpLoopMerge.
+ auto block = context->get_instr_block(message_.block_id());
+ if (!block) {
+ return false;
+ }
+ auto merge_inst = block->GetMergeInst();
+ if (!merge_inst || merge_inst->opcode() != SpvOpLoopMerge) {
+ return false;
+ }
+
+ // We sanity-check that the transformation does not try to set any meaningless
+ // bits of the loop control mask.
+ uint32_t all_loop_control_mask_bits_set =
+ SpvLoopControlUnrollMask | SpvLoopControlDontUnrollMask |
+ SpvLoopControlDependencyInfiniteMask |
+ SpvLoopControlDependencyLengthMask | SpvLoopControlMinIterationsMask |
+ SpvLoopControlMaxIterationsMask | SpvLoopControlIterationMultipleMask |
+ SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask;
+
+ // The variable is only used in an assertion; the following keeps release-mode
+ // compilers happy.
+ (void)(all_loop_control_mask_bits_set);
+
+ // No additional bits should be set.
+ assert(!(message_.loop_control() & ~all_loop_control_mask_bits_set));
+
+ // Grab the loop control mask currently associated with the OpLoopMerge
+ // instruction.
+ auto existing_loop_control_mask =
+ merge_inst->GetSingleWordInOperand(kLoopControlMaskInOperandIndex);
+
+ // Check that there is no attempt to set one of the loop controls that
+ // requires guarantees to hold.
+ for (SpvLoopControlMask mask :
+ {SpvLoopControlDependencyInfiniteMask,
+ SpvLoopControlDependencyLengthMask, SpvLoopControlMinIterationsMask,
+ SpvLoopControlMaxIterationsMask, SpvLoopControlIterationMultipleMask}) {
+ // We have a problem if this loop control bit was not set in the original
+ // loop control mask but is set by the transformation.
+ if (LoopControlBitIsAddedByTransformation(mask,
+ existing_loop_control_mask)) {
+ return false;
+ }
+ }
+
+ if ((message_.loop_control() &
+ (SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask)) &&
+ !(PeelCountIsSupported(context) && PartialCountIsSupported(context))) {
+ // At least one of PeelCount or PartialCount is used, but the SPIR-V version
+ // in question does not support these loop controls.
+ return false;
+ }
+
+ if (message_.peel_count() > 0 &&
+ !(message_.loop_control() & SpvLoopControlPeelCountMask)) {
+ // Peel count provided, but peel count mask bit not set.
+ return false;
+ }
+
+ if (message_.partial_count() > 0 &&
+ !(message_.loop_control() & SpvLoopControlPartialCountMask)) {
+ // Partial count provided, but partial count mask bit not set.
+ return false;
+ }
+
+ // We must not set both 'don't unroll' and one of 'peel count' or 'partial
+ // count'.
+ return !((message_.loop_control() & SpvLoopControlDontUnrollMask) &&
+ (message_.loop_control() &
+ (SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask)));
+}
+
+void TransformationSetLoopControl::Apply(opt::IRContext* context,
+ FactManager* /*unused*/) const {
+ // Grab the loop merge instruction and its associated loop control mask.
+ auto merge_inst =
+ context->get_instr_block(message_.block_id())->GetMergeInst();
+ auto existing_loop_control_mask =
+ merge_inst->GetSingleWordInOperand(kLoopControlMaskInOperandIndex);
+
+ // We are going to replace the OpLoopMerge's operands with this list.
+ opt::Instruction::OperandList new_operands;
+ // We add the existing merge block and continue target ids.
+ new_operands.push_back(merge_inst->GetInOperand(0));
+ new_operands.push_back(merge_inst->GetInOperand(1));
+ // We use the loop control mask from the transformation.
+ new_operands.push_back(
+ {SPV_OPERAND_TYPE_LOOP_CONTROL, {message_.loop_control()}});
+
+ // It remains to determine what literals to provide, in association with
+ // the new loop control mask.
+ //
+ // For the loop controls that require guarantees to hold about the number
+ // of loop iterations, we need to keep, from the original OpLoopMerge, any
+ // literals associated with loop control bits that are still set.
+
+ uint32_t literal_index = 0; // Indexes into the literals from the original
+ // instruction.
+ for (SpvLoopControlMask mask :
+ {SpvLoopControlDependencyLengthMask, SpvLoopControlMinIterationsMask,
+ SpvLoopControlMaxIterationsMask, SpvLoopControlIterationMultipleMask}) {
+ // Check whether the bit was set in the original loop control mask.
+ if (existing_loop_control_mask & mask) {
+ // Check whether the bit is set in the new loop control mask.
+ if (message_.loop_control() & mask) {
+ // Add the associated literal to our sequence of replacement operands.
+ new_operands.push_back(
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER,
+ {merge_inst->GetSingleWordInOperand(
+ kLoopControlFirstLiteralInOperandIndex + literal_index)}});
+ }
+ // Increment our index into the original loop control mask's literals,
+ // whether or not the bit was set in the new mask.
+ literal_index++;
+ }
+ }
+
+ // If PeelCount is set in the new mask, |message_.peel_count| provides the
+ // associated peel count.
+ if (message_.loop_control() & SpvLoopControlPeelCountMask) {
+ new_operands.push_back(
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.peel_count()}});
+ }
+
+ // Similar, but for PartialCount.
+ if (message_.loop_control() & SpvLoopControlPartialCountMask) {
+ new_operands.push_back(
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.partial_count()}});
+ }
+
+ // Replace the input operands of the OpLoopMerge with the new operands we have
+ // accumulated.
+ merge_inst->SetInOperands(std::move(new_operands));
+}
+
+protobufs::Transformation TransformationSetLoopControl::ToMessage() const {
+ protobufs::Transformation result;
+ *result.mutable_set_loop_control() = message_;
+ return result;
+}
+
+bool TransformationSetLoopControl::LoopControlBitIsAddedByTransformation(
+ SpvLoopControlMask loop_control_single_bit_mask,
+ uint32_t existing_loop_control_mask) const {
+ return !(loop_control_single_bit_mask & existing_loop_control_mask) &&
+ (loop_control_single_bit_mask & message_.loop_control());
+}
+
+bool TransformationSetLoopControl::PartialCountIsSupported(
+ opt::IRContext* context) {
+ // TODO(afd): We capture the universal environments for which this loop
+ // control is definitely not supported. The check should be refined on
+ // demand for other target environments.
+ switch (context->grammar().target_env()) {
+ case SPV_ENV_UNIVERSAL_1_0:
+ case SPV_ENV_UNIVERSAL_1_1:
+ case SPV_ENV_UNIVERSAL_1_2:
+ case SPV_ENV_UNIVERSAL_1_3:
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool TransformationSetLoopControl::PeelCountIsSupported(
+ opt::IRContext* context) {
+ // TODO(afd): We capture the universal environments for which this loop
+ // control is definitely not supported. The check should be refined on
+ // demand for other target environments.
+ switch (context->grammar().target_env()) {
+ case SPV_ENV_UNIVERSAL_1_0:
+ case SPV_ENV_UNIVERSAL_1_1:
+ case SPV_ENV_UNIVERSAL_1_2:
+ case SPV_ENV_UNIVERSAL_1_3:
+ return false;
+ default:
+ return true;
+ }
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/transformation_set_loop_control.h b/source/fuzz/transformation_set_loop_control.h
new file mode 100644
index 0000000..28b148c
--- /dev/null
+++ b/source/fuzz/transformation_set_loop_control.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2019 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_SET_LOOP_CONTROL_H_
+#define SOURCE_FUZZ_TRANSFORMATION_SET_LOOP_CONTROL_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationSetLoopControl : public Transformation {
+ public:
+ const static uint32_t kLoopControlMaskInOperandIndex = 2;
+ const static uint32_t kLoopControlFirstLiteralInOperandIndex = 3;
+
+ explicit TransformationSetLoopControl(
+ const protobufs::TransformationSetLoopControl& message);
+
+ TransformationSetLoopControl(uint32_t block_id, uint32_t loop_control,
+ uint32_t peel_count, uint32_t partial_count);
+
+ // - |message_.block_id| must be a block containing an OpLoopMerge
+ // instruction.
+ // - |message_.loop_control| must be a legal loop control mask that
+ // only uses controls available in the SPIR-V version associated with
+ // |context|, and must not add loop controls that are only valid in the
+ // presence of guarantees about what the loop does (e.g. MinIterations).
+ // - |message_.peel_count| (respectively |message_.partial_count|) must be
+ // zero PeelCount (respectively PartialCount) is set in
+ // |message_.loop_control|.
+ bool IsApplicable(opt::IRContext* context,
+ const FactManager& fact_manager) const override;
+
+ // - The loop control operand of the OpLoopMergeInstruction in
+ // |message_.block_id| is overwritten with |message_.loop_control|.
+ // - The literals associated with the loop control are updated to reflect any
+ // controls with associated literals that have been removed (e.g.
+ // MinIterations), and any that have been added (PeelCount and/or
+ // PartialCount).
+ void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+ protobufs::Transformation ToMessage() const override;
+
+ // Does the version of SPIR-V being used support the PartialCount loop
+ // control?
+ static bool PartialCountIsSupported(opt::IRContext* context);
+
+ // Does the version of SPIR-V being used support the PeelCount loop control?
+ static bool PeelCountIsSupported(opt::IRContext* context);
+
+ private:
+ // Returns true if and only if |loop_single_bit_mask| is *not* set in
+ // |existing_loop_control| but *is* set in |message_.loop_control|.
+ bool LoopControlBitIsAddedByTransformation(
+ SpvLoopControlMask loop_control_single_bit_mask,
+ uint32_t existing_loop_control_mask) const;
+
+ protobufs::TransformationSetLoopControl message_;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_TRANSFORMATION_SET_LOOP_CONTROL_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index ca51b48..f4405e8 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -34,6 +34,7 @@
transformation_replace_boolean_constant_with_constant_binary_test.cpp
transformation_replace_constant_with_uniform_test.cpp
transformation_replace_id_with_synonym_test.cpp
+ transformation_set_loop_control_test.cpp
transformation_set_selection_control_test.cpp
transformation_split_block_test.cpp
uniform_buffer_element_descriptor_test.cpp)
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
index c2a1984..fed6cfe 100644
--- a/test/fuzz/fuzzer_replayer_test.cpp
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -31,7 +31,7 @@
void RunFuzzerAndReplayer(const std::string& shader,
const protobufs::FactSequence& initial_facts,
uint32_t initial_seed, uint32_t num_runs) {
- const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto env = SPV_ENV_UNIVERSAL_1_5;
std::vector<uint32_t> binary_in;
SpirvTools t(env);
@@ -305,7 +305,7 @@
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
- OpEntryPoint Fragment %4 "main" %16 %139
+ OpEntryPoint Fragment %4 "main" %16 %139 %25 %68
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
@@ -605,7 +605,7 @@
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
- OpEntryPoint Fragment %4 "main" %68 %100
+ OpEntryPoint Fragment %4 "main" %68 %100 %24
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
diff --git a/test/fuzz/fuzzer_shrinker_test.cpp b/test/fuzz/fuzzer_shrinker_test.cpp
index 84608f3..6485070 100644
--- a/test/fuzz/fuzzer_shrinker_test.cpp
+++ b/test/fuzz/fuzzer_shrinker_test.cpp
@@ -154,7 +154,7 @@
void RunFuzzerAndShrinker(const std::string& shader,
const protobufs::FactSequence& initial_facts,
uint32_t seed) {
- const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto env = SPV_ENV_UNIVERSAL_1_5;
std::vector<uint32_t> binary_in;
SpirvTools t(env);
@@ -434,7 +434,7 @@
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
- OpEntryPoint Fragment %4 "main" %16 %139
+ OpEntryPoint Fragment %4 "main" %16 %139 %25 %68
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
@@ -732,7 +732,7 @@
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
- OpEntryPoint Fragment %4 "main" %68 %100
+ OpEntryPoint Fragment %4 "main" %68 %100 %24
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
diff --git a/test/fuzz/transformation_set_loop_control_test.cpp b/test/fuzz/transformation_set_loop_control_test.cpp
new file mode 100644
index 0000000..83953ec
--- /dev/null
+++ b/test/fuzz/transformation_set_loop_control_test.cpp
@@ -0,0 +1,968 @@
+// Copyright (c) 2019 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_set_loop_control.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationSetLoopControlTest, VariousScenarios) {
+ // This test features loops with various different controls, and goes through
+ // a number of acceptable and unacceptable transformations to those controls.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 100
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %22 = OpVariable %7 Function
+ %32 = OpVariable %7 Function
+ %42 = OpVariable %7 Function
+ %52 = OpVariable %7 Function
+ %62 = OpVariable %7 Function
+ %72 = OpVariable %7 Function
+ %82 = OpVariable %7 Function
+ %92 = OpVariable %7 Function
+ %102 = OpVariable %7 Function
+ %112 = OpVariable %7 Function
+ %122 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %132 = OpPhi %6 %9 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %132 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %6 %132 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %22 %9
+ OpBranch %23
+ %23 = OpLabel
+ %133 = OpPhi %6 %9 %12 %31 %26
+ OpLoopMerge %25 %26 Unroll
+ OpBranch %27
+ %27 = OpLabel
+ %29 = OpSLessThan %17 %133 %16
+ OpBranchConditional %29 %24 %25
+ %24 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %31 = OpIAdd %6 %133 %20
+ OpStore %22 %31
+ OpBranch %23
+ %25 = OpLabel
+ OpStore %32 %9
+ OpBranch %33
+ %33 = OpLabel
+ %134 = OpPhi %6 %9 %25 %41 %36
+ OpLoopMerge %35 %36 DontUnroll
+ OpBranch %37
+ %37 = OpLabel
+ %39 = OpSLessThan %17 %134 %16
+ OpBranchConditional %39 %34 %35
+ %34 = OpLabel
+ OpBranch %36
+ %36 = OpLabel
+ %41 = OpIAdd %6 %134 %20
+ OpStore %32 %41
+ OpBranch %33
+ %35 = OpLabel
+ OpStore %42 %9
+ OpBranch %43
+ %43 = OpLabel
+ %135 = OpPhi %6 %9 %35 %51 %46
+ OpLoopMerge %45 %46 DependencyInfinite
+ OpBranch %47
+ %47 = OpLabel
+ %49 = OpSLessThan %17 %135 %16
+ OpBranchConditional %49 %44 %45
+ %44 = OpLabel
+ OpBranch %46
+ %46 = OpLabel
+ %51 = OpIAdd %6 %135 %20
+ OpStore %42 %51
+ OpBranch %43
+ %45 = OpLabel
+ OpStore %52 %9
+ OpBranch %53
+ %53 = OpLabel
+ %136 = OpPhi %6 %9 %45 %61 %56
+ OpLoopMerge %55 %56 DependencyLength 3
+ OpBranch %57
+ %57 = OpLabel
+ %59 = OpSLessThan %17 %136 %16
+ OpBranchConditional %59 %54 %55
+ %54 = OpLabel
+ OpBranch %56
+ %56 = OpLabel
+ %61 = OpIAdd %6 %136 %20
+ OpStore %52 %61
+ OpBranch %53
+ %55 = OpLabel
+ OpStore %62 %9
+ OpBranch %63
+ %63 = OpLabel
+ %137 = OpPhi %6 %9 %55 %71 %66
+ OpLoopMerge %65 %66 MinIterations 10
+ OpBranch %67
+ %67 = OpLabel
+ %69 = OpSLessThan %17 %137 %16
+ OpBranchConditional %69 %64 %65
+ %64 = OpLabel
+ OpBranch %66
+ %66 = OpLabel
+ %71 = OpIAdd %6 %137 %20
+ OpStore %62 %71
+ OpBranch %63
+ %65 = OpLabel
+ OpStore %72 %9
+ OpBranch %73
+ %73 = OpLabel
+ %138 = OpPhi %6 %9 %65 %81 %76
+ OpLoopMerge %75 %76 MaxIterations 50
+ OpBranch %77
+ %77 = OpLabel
+ %79 = OpSLessThan %17 %138 %16
+ OpBranchConditional %79 %74 %75
+ %74 = OpLabel
+ OpBranch %76
+ %76 = OpLabel
+ %81 = OpIAdd %6 %138 %20
+ OpStore %72 %81
+ OpBranch %73
+ %75 = OpLabel
+ OpStore %82 %9
+ OpBranch %83
+ %83 = OpLabel
+ %139 = OpPhi %6 %9 %75 %91 %86
+ OpLoopMerge %85 %86 IterationMultiple 4
+ OpBranch %87
+ %87 = OpLabel
+ %89 = OpSLessThan %17 %139 %16
+ OpBranchConditional %89 %84 %85
+ %84 = OpLabel
+ OpBranch %86
+ %86 = OpLabel
+ %91 = OpIAdd %6 %139 %20
+ OpStore %82 %91
+ OpBranch %83
+ %85 = OpLabel
+ OpStore %92 %9
+ OpBranch %93
+ %93 = OpLabel
+ %140 = OpPhi %6 %9 %85 %101 %96
+ OpLoopMerge %95 %96 PeelCount 2
+ OpBranch %97
+ %97 = OpLabel
+ %99 = OpSLessThan %17 %140 %16
+ OpBranchConditional %99 %94 %95
+ %94 = OpLabel
+ OpBranch %96
+ %96 = OpLabel
+ %101 = OpIAdd %6 %140 %20
+ OpStore %92 %101
+ OpBranch %93
+ %95 = OpLabel
+ OpStore %102 %9
+ OpBranch %103
+ %103 = OpLabel
+ %141 = OpPhi %6 %9 %95 %111 %106
+ OpLoopMerge %105 %106 PartialCount 3
+ OpBranch %107
+ %107 = OpLabel
+ %109 = OpSLessThan %17 %141 %16
+ OpBranchConditional %109 %104 %105
+ %104 = OpLabel
+ OpBranch %106
+ %106 = OpLabel
+ %111 = OpIAdd %6 %141 %20
+ OpStore %102 %111
+ OpBranch %103
+ %105 = OpLabel
+ OpStore %112 %9
+ OpBranch %113
+ %113 = OpLabel
+ %142 = OpPhi %6 %9 %105 %121 %116
+ OpLoopMerge %115 %116 Unroll|PeelCount|PartialCount 3 4
+ OpBranch %117
+ %117 = OpLabel
+ %119 = OpSLessThan %17 %142 %16
+ OpBranchConditional %119 %114 %115
+ %114 = OpLabel
+ OpBranch %116
+ %116 = OpLabel
+ %121 = OpIAdd %6 %142 %20
+ OpStore %112 %121
+ OpBranch %113
+ %115 = OpLabel
+ OpStore %122 %9
+ OpBranch %123
+ %123 = OpLabel
+ %143 = OpPhi %6 %9 %115 %131 %126
+ OpLoopMerge %125 %126 DependencyLength|MinIterations|MaxIterations|IterationMultiple|PeelCount|PartialCount 2 5 90 4 7 14
+ OpBranch %127
+ %127 = OpLabel
+ %129 = OpSLessThan %17 %143 %16
+ OpBranchConditional %129 %124 %125
+ %124 = OpLabel
+ OpBranch %126
+ %126 = OpLabel
+ %131 = OpIAdd %6 %143 %20
+ OpStore %122 %131
+ OpBranch %123
+ %125 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+
+ // These are the loop headers together with the selection controls of their
+ // merge instructions:
+ // %10 None
+ // %23 Unroll
+ // %33 DontUnroll
+ // %43 DependencyInfinite
+ // %53 DependencyLength 3
+ // %63 MinIterations 10
+ // %73 MaxIterations 50
+ // %83 IterationMultiple 4
+ // %93 PeelCount 2
+ // %103 PartialCount 3
+ // %113 Unroll|PeelCount|PartialCount 3 4
+ // %123
+ // DependencyLength|MinIterations|MaxIterations|IterationMultiple|PeelCount|PartialCount
+ // 2 5 90 4 7 14
+
+ ASSERT_TRUE(TransformationSetLoopControl(10, SpvLoopControlMaskNone, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(10, SpvLoopControlUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(10, SpvLoopControlDontUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(TransformationSetLoopControl(
+ 10, SpvLoopControlDependencyInfiniteMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(10, SpvLoopControlDependencyLengthMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(10, SpvLoopControlMinIterationsMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(10, SpvLoopControlMaxIterationsMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(TransformationSetLoopControl(
+ 10, SpvLoopControlIterationMultipleMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(10, SpvLoopControlPeelCountMask, 3, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(10, SpvLoopControlPeelCountMask, 3, 3)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(10, SpvLoopControlPartialCountMask, 0, 3)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(10, SpvLoopControlPartialCountMask, 3, 3)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(
+ 10,
+ SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
+ 3, 3)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(10,
+ SpvLoopControlUnrollMask |
+ SpvLoopControlPeelCountMask |
+ SpvLoopControlPartialCountMask,
+ 3, 3)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(TransformationSetLoopControl(10,
+ SpvLoopControlDontUnrollMask |
+ SpvLoopControlPeelCountMask |
+ SpvLoopControlPartialCountMask,
+ 3, 3)
+ .IsApplicable(context.get(), fact_manager));
+
+ ASSERT_TRUE(TransformationSetLoopControl(23, SpvLoopControlMaskNone, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(23, SpvLoopControlUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(23, SpvLoopControlDontUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(
+ 23,
+ SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
+ 3, 3)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(23, SpvLoopControlMaxIterationsMask, 2, 3)
+ .IsApplicable(context.get(), fact_manager));
+
+ ASSERT_TRUE(TransformationSetLoopControl(33, SpvLoopControlMaskNone, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(33, SpvLoopControlUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(33, SpvLoopControlDontUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(33, SpvLoopControlMinIterationsMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(
+ 33, SpvLoopControlUnrollMask | SpvLoopControlPeelCountMask, 5, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(TransformationSetLoopControl(33,
+ SpvLoopControlDontUnrollMask |
+ SpvLoopControlPartialCountMask,
+ 0, 10)
+ .IsApplicable(context.get(), fact_manager));
+
+ ASSERT_TRUE(TransformationSetLoopControl(43, SpvLoopControlMaskNone, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(43, SpvLoopControlUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(43, SpvLoopControlDontUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(
+ 43,
+ SpvLoopControlMaskNone | SpvLoopControlDependencyInfiniteMask,
+ 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(
+ 43, SpvLoopControlUnrollMask | SpvLoopControlDependencyInfiniteMask,
+ 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(
+ 43,
+ SpvLoopControlDontUnrollMask | SpvLoopControlDependencyInfiniteMask,
+ 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(43,
+ SpvLoopControlDependencyInfiniteMask |
+ SpvLoopControlDependencyLengthMask,
+ 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(
+ 43, SpvLoopControlUnrollMask | SpvLoopControlPeelCountMask, 5, 0)
+ .IsApplicable(context.get(), fact_manager));
+
+ ASSERT_TRUE(TransformationSetLoopControl(53, SpvLoopControlMaskNone, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(53, SpvLoopControlUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(53, SpvLoopControlDontUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(53, SpvLoopControlMaxIterationsMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(
+ 53, SpvLoopControlMaskNone | SpvLoopControlDependencyLengthMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(
+ 53, SpvLoopControlUnrollMask | SpvLoopControlDependencyInfiniteMask,
+ 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(
+ 53, SpvLoopControlDontUnrollMask | SpvLoopControlDependencyLengthMask,
+ 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(53,
+ SpvLoopControlDependencyInfiniteMask |
+ SpvLoopControlDependencyLengthMask,
+ 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(
+ 53,
+ SpvLoopControlUnrollMask | SpvLoopControlDependencyLengthMask |
+ SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
+ 5, 3)
+ .IsApplicable(context.get(), fact_manager));
+
+ ASSERT_TRUE(TransformationSetLoopControl(63, SpvLoopControlMaskNone, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(63, SpvLoopControlUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(63, SpvLoopControlDontUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(63,
+ SpvLoopControlUnrollMask |
+ SpvLoopControlMinIterationsMask |
+ SpvLoopControlPeelCountMask |
+ SpvLoopControlPartialCountMask,
+ 5, 3)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(63,
+ SpvLoopControlUnrollMask |
+ SpvLoopControlMinIterationsMask |
+ SpvLoopControlPeelCountMask,
+ 23, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(TransformationSetLoopControl(
+ 63,
+ SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
+ SpvLoopControlPeelCountMask,
+ 2, 23)
+ .IsApplicable(context.get(), fact_manager));
+
+ ASSERT_TRUE(TransformationSetLoopControl(73, SpvLoopControlMaskNone, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(73, SpvLoopControlUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(73, SpvLoopControlDontUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(TransformationSetLoopControl(
+ 73,
+ SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
+ SpvLoopControlPeelCountMask |
+ SpvLoopControlPartialCountMask,
+ 5, 3)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(73,
+ SpvLoopControlUnrollMask |
+ SpvLoopControlMaxIterationsMask |
+ SpvLoopControlPeelCountMask,
+ 23, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(TransformationSetLoopControl(
+ 73,
+ SpvLoopControlUnrollMask | SpvLoopControlMaxIterationsMask |
+ SpvLoopControlPeelCountMask,
+ 2, 23)
+ .IsApplicable(context.get(), fact_manager));
+
+ ASSERT_TRUE(TransformationSetLoopControl(83, SpvLoopControlMaskNone, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(83, SpvLoopControlUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(83, SpvLoopControlDontUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(TransformationSetLoopControl(
+ 83,
+ SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
+ SpvLoopControlPeelCountMask |
+ SpvLoopControlPartialCountMask,
+ 5, 3)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(83,
+ SpvLoopControlUnrollMask |
+ SpvLoopControlIterationMultipleMask |
+ SpvLoopControlPeelCountMask,
+ 23, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(83,
+ SpvLoopControlUnrollMask |
+ SpvLoopControlIterationMultipleMask |
+ SpvLoopControlPeelCountMask,
+ 2, 23)
+ .IsApplicable(context.get(), fact_manager));
+
+ ASSERT_TRUE(TransformationSetLoopControl(93, SpvLoopControlMaskNone, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(93, SpvLoopControlUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(93, SpvLoopControlDontUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(93, SpvLoopControlPeelCountMask, 8, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(93, SpvLoopControlPeelCountMask, 8, 8)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(93, SpvLoopControlPartialCountMask, 0, 8)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(
+ 93,
+ SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
+ 16, 8)
+ .IsApplicable(context.get(), fact_manager));
+
+ ASSERT_TRUE(TransformationSetLoopControl(103, SpvLoopControlMaskNone, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(103, SpvLoopControlUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(103, SpvLoopControlDontUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(103, SpvLoopControlPartialCountMask, 0, 60)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(TransformationSetLoopControl(103,
+ SpvLoopControlDontUnrollMask |
+ SpvLoopControlPartialCountMask,
+ 0, 60)
+ .IsApplicable(context.get(), fact_manager));
+
+ ASSERT_TRUE(TransformationSetLoopControl(113, SpvLoopControlMaskNone, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(113, SpvLoopControlUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(113, SpvLoopControlDontUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(113, SpvLoopControlPeelCountMask, 12, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(
+ 113,
+ SpvLoopControlIterationMultipleMask | SpvLoopControlPeelCountMask, 12,
+ 0)
+ .IsApplicable(context.get(), fact_manager));
+
+ ASSERT_TRUE(TransformationSetLoopControl(123, SpvLoopControlMaskNone, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(123, SpvLoopControlUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(123, SpvLoopControlDontUnrollMask, 0, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(
+ TransformationSetLoopControl(
+ 123,
+ SpvLoopControlMinIterationsMask | SpvLoopControlMaxIterationsMask |
+ SpvLoopControlIterationMultipleMask |
+ SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
+ 7, 8)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(TransformationSetLoopControl(123,
+ SpvLoopControlUnrollMask |
+ SpvLoopControlMinIterationsMask |
+ SpvLoopControlMaxIterationsMask |
+ SpvLoopControlPartialCountMask,
+ 0, 9)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(TransformationSetLoopControl(
+ 123,
+ SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
+ SpvLoopControlMaxIterationsMask |
+ SpvLoopControlPartialCountMask,
+ 7, 9)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_FALSE(
+ TransformationSetLoopControl(
+ 123,
+ SpvLoopControlDontUnrollMask | SpvLoopControlMinIterationsMask |
+ SpvLoopControlMaxIterationsMask | SpvLoopControlPartialCountMask,
+ 7, 9)
+ .IsApplicable(context.get(), fact_manager));
+
+ TransformationSetLoopControl(10,
+ SpvLoopControlUnrollMask |
+ SpvLoopControlPeelCountMask |
+ SpvLoopControlPartialCountMask,
+ 3, 3)
+ .Apply(context.get(), &fact_manager);
+ TransformationSetLoopControl(23, SpvLoopControlDontUnrollMask, 0, 0)
+ .Apply(context.get(), &fact_manager);
+ TransformationSetLoopControl(33, SpvLoopControlUnrollMask, 0, 0)
+ .Apply(context.get(), &fact_manager);
+ TransformationSetLoopControl(
+ 43, SpvLoopControlDontUnrollMask | SpvLoopControlDependencyInfiniteMask,
+ 0, 0)
+ .Apply(context.get(), &fact_manager);
+ TransformationSetLoopControl(53, SpvLoopControlMaskNone, 0, 0)
+ .Apply(context.get(), &fact_manager);
+ TransformationSetLoopControl(63,
+ SpvLoopControlUnrollMask |
+ SpvLoopControlMinIterationsMask |
+ SpvLoopControlPeelCountMask,
+ 23, 0)
+ .Apply(context.get(), &fact_manager);
+ TransformationSetLoopControl(73,
+ SpvLoopControlUnrollMask |
+ SpvLoopControlMaxIterationsMask |
+ SpvLoopControlPeelCountMask,
+ 23, 0)
+ .Apply(context.get(), &fact_manager);
+ TransformationSetLoopControl(83, SpvLoopControlDontUnrollMask, 0, 0)
+ .Apply(context.get(), &fact_manager);
+ TransformationSetLoopControl(
+ 93, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 16, 8)
+ .Apply(context.get(), &fact_manager);
+ TransformationSetLoopControl(103, SpvLoopControlPartialCountMask, 0, 60)
+ .Apply(context.get(), &fact_manager);
+ TransformationSetLoopControl(113, SpvLoopControlPeelCountMask, 12, 0)
+ .Apply(context.get(), &fact_manager);
+ TransformationSetLoopControl(
+ 123,
+ SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
+ SpvLoopControlMaxIterationsMask | SpvLoopControlPartialCountMask,
+ 0, 9)
+ .Apply(context.get(), &fact_manager);
+
+ std::string after_transformation = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 100
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %22 = OpVariable %7 Function
+ %32 = OpVariable %7 Function
+ %42 = OpVariable %7 Function
+ %52 = OpVariable %7 Function
+ %62 = OpVariable %7 Function
+ %72 = OpVariable %7 Function
+ %82 = OpVariable %7 Function
+ %92 = OpVariable %7 Function
+ %102 = OpVariable %7 Function
+ %112 = OpVariable %7 Function
+ %122 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %132 = OpPhi %6 %9 %5 %21 %13
+ OpLoopMerge %12 %13 Unroll|PeelCount|PartialCount 3 3
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %132 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %6 %132 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %22 %9
+ OpBranch %23
+ %23 = OpLabel
+ %133 = OpPhi %6 %9 %12 %31 %26
+ OpLoopMerge %25 %26 DontUnroll
+ OpBranch %27
+ %27 = OpLabel
+ %29 = OpSLessThan %17 %133 %16
+ OpBranchConditional %29 %24 %25
+ %24 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %31 = OpIAdd %6 %133 %20
+ OpStore %22 %31
+ OpBranch %23
+ %25 = OpLabel
+ OpStore %32 %9
+ OpBranch %33
+ %33 = OpLabel
+ %134 = OpPhi %6 %9 %25 %41 %36
+ OpLoopMerge %35 %36 Unroll
+ OpBranch %37
+ %37 = OpLabel
+ %39 = OpSLessThan %17 %134 %16
+ OpBranchConditional %39 %34 %35
+ %34 = OpLabel
+ OpBranch %36
+ %36 = OpLabel
+ %41 = OpIAdd %6 %134 %20
+ OpStore %32 %41
+ OpBranch %33
+ %35 = OpLabel
+ OpStore %42 %9
+ OpBranch %43
+ %43 = OpLabel
+ %135 = OpPhi %6 %9 %35 %51 %46
+ OpLoopMerge %45 %46 DontUnroll|DependencyInfinite
+ OpBranch %47
+ %47 = OpLabel
+ %49 = OpSLessThan %17 %135 %16
+ OpBranchConditional %49 %44 %45
+ %44 = OpLabel
+ OpBranch %46
+ %46 = OpLabel
+ %51 = OpIAdd %6 %135 %20
+ OpStore %42 %51
+ OpBranch %43
+ %45 = OpLabel
+ OpStore %52 %9
+ OpBranch %53
+ %53 = OpLabel
+ %136 = OpPhi %6 %9 %45 %61 %56
+ OpLoopMerge %55 %56 None
+ OpBranch %57
+ %57 = OpLabel
+ %59 = OpSLessThan %17 %136 %16
+ OpBranchConditional %59 %54 %55
+ %54 = OpLabel
+ OpBranch %56
+ %56 = OpLabel
+ %61 = OpIAdd %6 %136 %20
+ OpStore %52 %61
+ OpBranch %53
+ %55 = OpLabel
+ OpStore %62 %9
+ OpBranch %63
+ %63 = OpLabel
+ %137 = OpPhi %6 %9 %55 %71 %66
+ OpLoopMerge %65 %66 Unroll|MinIterations|PeelCount 10 23
+ OpBranch %67
+ %67 = OpLabel
+ %69 = OpSLessThan %17 %137 %16
+ OpBranchConditional %69 %64 %65
+ %64 = OpLabel
+ OpBranch %66
+ %66 = OpLabel
+ %71 = OpIAdd %6 %137 %20
+ OpStore %62 %71
+ OpBranch %63
+ %65 = OpLabel
+ OpStore %72 %9
+ OpBranch %73
+ %73 = OpLabel
+ %138 = OpPhi %6 %9 %65 %81 %76
+ OpLoopMerge %75 %76 Unroll|MaxIterations|PeelCount 50 23
+ OpBranch %77
+ %77 = OpLabel
+ %79 = OpSLessThan %17 %138 %16
+ OpBranchConditional %79 %74 %75
+ %74 = OpLabel
+ OpBranch %76
+ %76 = OpLabel
+ %81 = OpIAdd %6 %138 %20
+ OpStore %72 %81
+ OpBranch %73
+ %75 = OpLabel
+ OpStore %82 %9
+ OpBranch %83
+ %83 = OpLabel
+ %139 = OpPhi %6 %9 %75 %91 %86
+ OpLoopMerge %85 %86 DontUnroll
+ OpBranch %87
+ %87 = OpLabel
+ %89 = OpSLessThan %17 %139 %16
+ OpBranchConditional %89 %84 %85
+ %84 = OpLabel
+ OpBranch %86
+ %86 = OpLabel
+ %91 = OpIAdd %6 %139 %20
+ OpStore %82 %91
+ OpBranch %83
+ %85 = OpLabel
+ OpStore %92 %9
+ OpBranch %93
+ %93 = OpLabel
+ %140 = OpPhi %6 %9 %85 %101 %96
+ OpLoopMerge %95 %96 PeelCount|PartialCount 16 8
+ OpBranch %97
+ %97 = OpLabel
+ %99 = OpSLessThan %17 %140 %16
+ OpBranchConditional %99 %94 %95
+ %94 = OpLabel
+ OpBranch %96
+ %96 = OpLabel
+ %101 = OpIAdd %6 %140 %20
+ OpStore %92 %101
+ OpBranch %93
+ %95 = OpLabel
+ OpStore %102 %9
+ OpBranch %103
+ %103 = OpLabel
+ %141 = OpPhi %6 %9 %95 %111 %106
+ OpLoopMerge %105 %106 PartialCount 60
+ OpBranch %107
+ %107 = OpLabel
+ %109 = OpSLessThan %17 %141 %16
+ OpBranchConditional %109 %104 %105
+ %104 = OpLabel
+ OpBranch %106
+ %106 = OpLabel
+ %111 = OpIAdd %6 %141 %20
+ OpStore %102 %111
+ OpBranch %103
+ %105 = OpLabel
+ OpStore %112 %9
+ OpBranch %113
+ %113 = OpLabel
+ %142 = OpPhi %6 %9 %105 %121 %116
+ OpLoopMerge %115 %116 PeelCount 12
+ OpBranch %117
+ %117 = OpLabel
+ %119 = OpSLessThan %17 %142 %16
+ OpBranchConditional %119 %114 %115
+ %114 = OpLabel
+ OpBranch %116
+ %116 = OpLabel
+ %121 = OpIAdd %6 %142 %20
+ OpStore %112 %121
+ OpBranch %113
+ %115 = OpLabel
+ OpStore %122 %9
+ OpBranch %123
+ %123 = OpLabel
+ %143 = OpPhi %6 %9 %115 %131 %126
+ OpLoopMerge %125 %126 Unroll|MinIterations|MaxIterations|PartialCount 5 90 9
+ OpBranch %127
+ %127 = OpLabel
+ %129 = OpSLessThan %17 %143 %16
+ OpBranchConditional %129 %124 %125
+ %124 = OpLabel
+ OpBranch %126
+ %126 = OpLabel
+ %131 = OpIAdd %6 %143 %20
+ OpStore %122 %131
+ OpBranch %123
+ %125 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationSetLoopControlTest, CheckSPIRVVersionsRespected) {
+ // This test checks that we do not allow introducing PeelCount and
+ // PartialCount loop controls if the SPIR-V version being used does not
+ // support them.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %8 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %18 = OpSLessThan %17 %15 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %19 = OpLoad %6 %8
+ %21 = OpIAdd %6 %19 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto consumer = nullptr;
+ const auto context_1_0 =
+ BuildModule(SPV_ENV_UNIVERSAL_1_0, consumer, shader, kFuzzAssembleOption);
+ const auto context_1_1 =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, consumer, shader, kFuzzAssembleOption);
+ const auto context_1_2 =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, consumer, shader, kFuzzAssembleOption);
+ const auto context_1_3 =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, consumer, shader, kFuzzAssembleOption);
+ const auto context_1_4 =
+ BuildModule(SPV_ENV_UNIVERSAL_1_4, consumer, shader, kFuzzAssembleOption);
+ const auto context_1_5 =
+ BuildModule(SPV_ENV_UNIVERSAL_1_5, consumer, shader, kFuzzAssembleOption);
+
+ FactManager fact_manager;
+
+ TransformationSetLoopControl set_peel_and_partial(
+ 10, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 4, 4);
+
+ // PeelCount and PartialCount were introduced in SPIRV 1.4, so are not valid
+ // in the context of older versions.
+ ASSERT_FALSE(
+ set_peel_and_partial.IsApplicable(context_1_0.get(), fact_manager));
+ ASSERT_FALSE(
+ set_peel_and_partial.IsApplicable(context_1_1.get(), fact_manager));
+ ASSERT_FALSE(
+ set_peel_and_partial.IsApplicable(context_1_2.get(), fact_manager));
+ ASSERT_FALSE(
+ set_peel_and_partial.IsApplicable(context_1_3.get(), fact_manager));
+
+ ASSERT_TRUE(
+ set_peel_and_partial.IsApplicable(context_1_4.get(), fact_manager));
+ ASSERT_TRUE(
+ set_peel_and_partial.IsApplicable(context_1_5.get(), fact_manager));
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools