spirv-fuzz: fuzzer pass to adjust memory access operands (#2968)
A new pass that gives spirv-fuzz the ability to adjust the memory
operand masks associated with memory access instructions (such as
OpLoad and OpCopy Memory).
Fixes #2940.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 587a05a..39bf1d3 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -41,6 +41,7 @@
fuzzer_pass_add_useful_constructs.h
fuzzer_pass_adjust_function_controls.h
fuzzer_pass_adjust_loop_controls.h
+ fuzzer_pass_adjust_memory_operands_masks.h
fuzzer_pass_adjust_selection_controls.h
fuzzer_pass_apply_id_synonyms.h
fuzzer_pass_construct_composites.h
@@ -74,6 +75,7 @@
transformation_replace_id_with_synonym.h
transformation_set_function_control.h
transformation_set_loop_control.h
+ transformation_set_memory_operands_mask.h
transformation_set_selection_control.h
transformation_split_block.h
uniform_buffer_element_descriptor.h
@@ -91,6 +93,7 @@
fuzzer_pass_add_useful_constructs.cpp
fuzzer_pass_adjust_function_controls.cpp
fuzzer_pass_adjust_loop_controls.cpp
+ fuzzer_pass_adjust_memory_operands_masks.cpp
fuzzer_pass_adjust_selection_controls.cpp
fuzzer_pass_apply_id_synonyms.cpp
fuzzer_pass_construct_composites.cpp
@@ -123,6 +126,7 @@
transformation_replace_id_with_synonym.cpp
transformation_set_function_control.cpp
transformation_set_loop_control.cpp
+ transformation_set_memory_operands_mask.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 d07c173..01b4258 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -18,6 +18,7 @@
#include <memory>
#include <sstream>
+#include "fuzzer_pass_adjust_memory_operands_masks.h"
#include "source/fuzz/fact_manager.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
@@ -173,14 +174,17 @@
// Now apply some passes that it does not make sense to apply repeatedly,
// as they do not unlock other passes.
std::vector<std::unique_ptr<FuzzerPass>> final_passes;
- MaybeAddPass<FuzzerPassAdjustFunctionControls>(&passes, ir_context.get(),
- &fact_manager, &fuzzer_context,
- transformation_sequence_out);
- MaybeAddPass<FuzzerPassAdjustLoopControls>(&passes, ir_context.get(),
+ MaybeAddPass<FuzzerPassAdjustFunctionControls>(
+ &final_passes, ir_context.get(), &fact_manager, &fuzzer_context,
+ transformation_sequence_out);
+ MaybeAddPass<FuzzerPassAdjustLoopControls>(&final_passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
+ MaybeAddPass<FuzzerPassAdjustMemoryOperandsMasks>(
+ &final_passes, ir_context.get(), &fact_manager, &fuzzer_context,
+ transformation_sequence_out);
MaybeAddPass<FuzzerPassAdjustSelectionControls>(
- &passes, ir_context.get(), &fact_manager, &fuzzer_context,
+ &final_passes, ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out);
for (auto& pass : final_passes) {
pass->Apply();
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 68903d0..356cb35 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -30,6 +30,8 @@
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingFunctionControl = {20,
70};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingLoopControl = {20, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfAdjustingMemoryOperandsMask = {20,
+ 90};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingSelectionControl = {20,
90};
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
@@ -72,6 +74,8 @@
ChooseBetweenMinAndMax(kChanceOfAdjustingFunctionControl);
chance_of_adjusting_loop_control_ =
ChooseBetweenMinAndMax(kChanceOfAdjustingLoopControl);
+ chance_of_adjusting_memory_operands_mask_ =
+ ChooseBetweenMinAndMax(kChanceOfAdjustingMemoryOperandsMask);
chance_of_adjusting_selection_control_ =
ChooseBetweenMinAndMax(kChanceOfAdjustingSelectionControl);
chance_of_constructing_composite_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index fcf2c9f..c8242e6 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -71,6 +71,9 @@
uint32_t GetChanceOfAdjustingLoopControl() {
return chance_of_adjusting_loop_control_;
}
+ uint32_t GetChanceOfAdjustingMemoryOperandsMask() {
+ return chance_of_adjusting_memory_operands_mask_;
+ }
uint32_t GetChanceOfAdjustingSelectionControl() {
return chance_of_adjusting_selection_control_;
}
@@ -112,6 +115,7 @@
uint32_t chance_of_adding_no_contraction_decoration_;
uint32_t chance_of_adjusting_function_control_;
uint32_t chance_of_adjusting_loop_control_;
+ uint32_t chance_of_adjusting_memory_operands_mask_;
uint32_t chance_of_adjusting_selection_control_;
uint32_t chance_of_constructing_composite_;
uint32_t chance_of_copying_object_;
diff --git a/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp b/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp
new file mode 100644
index 0000000..a9d4b32
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp
@@ -0,0 +1,113 @@
+// 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_memory_operands_masks.h"
+
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_set_memory_operands_mask.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAdjustMemoryOperandsMasks::FuzzerPassAdjustMemoryOperandsMasks(
+ opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations)
+ : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassAdjustMemoryOperandsMasks::~FuzzerPassAdjustMemoryOperandsMasks() =
+ default;
+
+void FuzzerPassAdjustMemoryOperandsMasks::Apply() {
+ // Consider every block in every function.
+ for (auto& function : *GetIRContext()->module()) {
+ for (auto& block : function) {
+ // Consider every instruction in this block, using an explicit iterator so
+ // that when we find an instruction of interest we can search backwards to
+ // create an id descriptor for it.
+ for (auto inst_it = block.cbegin(); inst_it != block.cend(); ++inst_it) {
+ if (!TransformationSetMemoryOperandsMask::IsMemoryAccess(*inst_it)) {
+ // We are only interested in memory access instructions.
+ continue;
+ }
+
+ std::vector<uint32_t> indices_of_available_masks_to_adjust;
+ // All memory instructions have at least one memory operands mask.
+ indices_of_available_masks_to_adjust.push_back(0);
+ // From SPIR-V 1.4 onwards, OpCopyMemory and OpCopyMemorySized have a
+ // second mask.
+ switch (inst_it->opcode()) {
+ case SpvOpCopyMemory:
+ case SpvOpCopyMemorySized:
+ if (TransformationSetMemoryOperandsMask::
+ MultipleMemoryOperandMasksAreSupported(GetIRContext())) {
+ indices_of_available_masks_to_adjust.push_back(1);
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Consider the available masks
+ for (auto mask_index : indices_of_available_masks_to_adjust) {
+ // Randomly decide whether to adjust this mask.
+ if (!GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()
+ ->GetChanceOfAdjustingMemoryOperandsMask())) {
+ continue;
+ }
+ // Get the existing mask, using None if there was no mask present at
+ // all.
+ auto existing_mask_in_operand_index =
+ TransformationSetMemoryOperandsMask::GetInOperandIndexForMask(
+ *inst_it, mask_index);
+ auto existing_mask =
+ existing_mask_in_operand_index < inst_it->NumInOperands()
+ ? inst_it->GetSingleWordOperand(
+ existing_mask_in_operand_index)
+ : static_cast<uint32_t>(SpvMemoryAccessMaskNone);
+
+ // There are two things we can do to a mask:
+ // - add Volatile if not already present
+ // - toggle Nontemporal
+ // The following ensures that we do at least one of these
+ bool add_volatile = !(existing_mask & SpvMemoryAccessVolatileMask) &&
+ GetFuzzerContext()->ChooseEven();
+ bool toggle_nontemporal =
+ !add_volatile || GetFuzzerContext()->ChooseEven();
+
+ // These bitwise operations use '|' to add Volatile if desired, and
+ // '^' to toggle Nontemporal if desired.
+ uint32_t new_mask =
+ (existing_mask | (add_volatile ? SpvMemoryAccessVolatileMask
+ : SpvMemoryAccessMaskNone)) ^
+ (toggle_nontemporal ? SpvMemoryAccessNontemporalMask
+ : SpvMemoryAccessMaskNone);
+
+ TransformationSetMemoryOperandsMask transformation(
+ MakeInstructionDescriptor(block, inst_it), new_mask, mask_index);
+ 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_memory_operands_masks.h b/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h
new file mode 100644
index 0000000..c3d7118
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h
@@ -0,0 +1,40 @@
+// 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_MEMORY_OPERANDS_MASKS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_MEMORY_OPERANDS_MASKS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass to adjust the memory operand masks in memory access
+// instructions.
+class FuzzerPassAdjustMemoryOperandsMasks : public FuzzerPass {
+ public:
+ FuzzerPassAdjustMemoryOperandsMasks(
+ opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations);
+
+ ~FuzzerPassAdjustMemoryOperandsMasks();
+
+ void Apply() override;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_MEMORY_OPERANDS_MASKS_H_
diff --git a/source/fuzz/instruction_descriptor.cpp b/source/fuzz/instruction_descriptor.cpp
index bd48d48..2b4217a 100644
--- a/source/fuzz/instruction_descriptor.cpp
+++ b/source/fuzz/instruction_descriptor.cpp
@@ -66,5 +66,40 @@
return result;
}
+protobufs::InstructionDescriptor MakeInstructionDescriptor(
+ const opt::BasicBlock& block,
+ const opt::BasicBlock::const_iterator& inst_it) {
+ const SpvOp opcode =
+ inst_it->opcode(); // The opcode of the instruction being described.
+ uint32_t skip_count = 0; // The number of these opcodes we have skipped when
+ // searching backwards.
+
+ // Consider instructions in the block in reverse order, starting from
+ // |inst_it|.
+ for (opt::BasicBlock::const_iterator backwards_iterator = inst_it;;
+ --backwards_iterator) {
+ if (backwards_iterator->HasResultId()) {
+ // As soon as we find an instruction with a result id, we can return a
+ // descriptor for |inst_it|.
+ return MakeInstructionDescriptor(backwards_iterator->result_id(), opcode,
+ skip_count);
+ }
+ if (backwards_iterator != inst_it &&
+ backwards_iterator->opcode() == opcode) {
+ // We are skipping over an instruction with the same opcode as |inst_it|;
+ // we increase our skip count to reflect this.
+ skip_count++;
+ }
+ if (backwards_iterator == block.begin()) {
+ // We exit the loop when we reach the start of the block, but only after
+ // we have processed the first instruction in the block.
+ break;
+ }
+ }
+ // We did not find an instruction inside the block with a result id, so we use
+ // the block's label's id.
+ return MakeInstructionDescriptor(block.id(), opcode, skip_count);
+}
+
} // namespace fuzz
} // namespace spvtools
diff --git a/source/fuzz/instruction_descriptor.h b/source/fuzz/instruction_descriptor.h
index 1164318..c0febca 100644
--- a/source/fuzz/instruction_descriptor.h
+++ b/source/fuzz/instruction_descriptor.h
@@ -16,6 +16,7 @@
#define SOURCE_FUZZ_INSTRUCTION_DESCRIPTOR_H_
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/basic_block.h"
#include "source/opt/ir_context.h"
namespace spvtools {
@@ -34,6 +35,14 @@
uint32_t base_instruction_result_id, SpvOp target_instruction_opcode,
uint32_t num_opcodes_to_ignore);
+// Returns an instruction descriptor that describing the instruction at
+// |inst_it|, which must be inside |block|. The descriptor will be with
+// respect to the first instruction at or before |inst_it| that has a result
+// id.
+protobufs::InstructionDescriptor MakeInstructionDescriptor(
+ const opt::BasicBlock& block,
+ const opt::BasicBlock::const_iterator& inst_it);
+
} // namespace fuzz
} // namespace spvtools
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 53a28f5..6d0299d 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -190,6 +190,7 @@
TransformationSetLoopControl set_loop_control = 17;
TransformationSetFunctionControl set_function_control = 18;
TransformationAddNoContractionDecoration add_no_contraction_decoration = 19;
+ TransformationSetMemoryOperandsMask set_memory_operands_mask = 20;
// Add additional option using the next available number.
}
}
@@ -464,6 +465,25 @@
}
+message TransformationSetMemoryOperandsMask {
+
+ // A transformation that sets the memory operands mask of a memory access
+ // instruction.
+
+ // A descriptor for a memory access instruction, e.g. an OpLoad
+ InstructionDescriptor memory_access_instruction = 1;
+
+ // A mask of memory operands to be applied to the instruction. It must be the
+ // same as the original mask, except that Volatile can be added, and
+ // Nontemporal can be added or removed.
+ uint32 memory_operands_mask = 2;
+
+ // Some memory access instructions allow more than one mask to be specified;
+ // this field indicates which mask should be set
+ uint32 memory_operands_mask_index = 3;
+
+}
+
message TransformationSetSelectionControl {
// A transformation that sets the selection control operand of an
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 9d0841f..ddc0b07 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -33,6 +33,7 @@
#include "source/fuzz/transformation_replace_id_with_synonym.h"
#include "source/fuzz/transformation_set_function_control.h"
#include "source/fuzz/transformation_set_loop_control.h"
+#include "source/fuzz/transformation_set_memory_operands_mask.h"
#include "source/fuzz/transformation_set_selection_control.h"
#include "source/fuzz/transformation_split_block.h"
#include "source/util/make_unique.h"
@@ -94,6 +95,9 @@
case protobufs::Transformation::TransformationCase::kSetLoopControl:
return MakeUnique<TransformationSetLoopControl>(
message.set_loop_control());
+ case protobufs::Transformation::TransformationCase::kSetMemoryOperandsMask:
+ return MakeUnique<TransformationSetMemoryOperandsMask>(
+ message.set_memory_operands_mask());
case protobufs::Transformation::TransformationCase::kSetSelectionControl:
return MakeUnique<TransformationSetSelectionControl>(
message.set_selection_control());
diff --git a/source/fuzz/transformation_set_memory_operands_mask.cpp b/source/fuzz/transformation_set_memory_operands_mask.cpp
new file mode 100644
index 0000000..a14e1a6
--- /dev/null
+++ b/source/fuzz/transformation_set_memory_operands_mask.cpp
@@ -0,0 +1,201 @@
+// 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_memory_operands_mask.h"
+
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+namespace {
+
+const uint32_t kOpLoadMemoryOperandsMaskIndex = 1;
+const uint32_t kOpStoreMemoryOperandsMaskIndex = 2;
+const uint32_t kOpCopyMemoryFirstMemoryOperandsMaskIndex = 2;
+const uint32_t kOpCopyMemorySizedFirstMemoryOperandsMaskIndex = 3;
+
+} // namespace
+
+TransformationSetMemoryOperandsMask::TransformationSetMemoryOperandsMask(
+ const spvtools::fuzz::protobufs::TransformationSetMemoryOperandsMask&
+ message)
+ : message_(message) {}
+
+TransformationSetMemoryOperandsMask::TransformationSetMemoryOperandsMask(
+ const protobufs::InstructionDescriptor& memory_access_instruction,
+ uint32_t memory_operands_mask, uint32_t memory_operands_mask_index) {
+ *message_.mutable_memory_access_instruction() = memory_access_instruction;
+ message_.set_memory_operands_mask(memory_operands_mask);
+ message_.set_memory_operands_mask_index(memory_operands_mask_index);
+}
+
+bool TransformationSetMemoryOperandsMask::IsApplicable(
+ opt::IRContext* context,
+ const spvtools::fuzz::FactManager& /*unused*/) const {
+ if (message_.memory_operands_mask_index() != 0) {
+ // The following conditions should never be violated, even if
+ // transformations end up being replayed in a different way to the manner in
+ // which they were applied during fuzzing, hence why these are assertions
+ // rather than applicability checks.
+ assert(message_.memory_operands_mask_index() == 1);
+ assert(message_.memory_access_instruction().target_instruction_opcode() ==
+ SpvOpCopyMemory ||
+ message_.memory_access_instruction().target_instruction_opcode() ==
+ SpvOpCopyMemorySized);
+ assert(MultipleMemoryOperandMasksAreSupported(context));
+ }
+
+ auto instruction =
+ FindInstruction(message_.memory_access_instruction(), context);
+ if (!instruction) {
+ return false;
+ }
+ if (!IsMemoryAccess(*instruction)) {
+ return false;
+ }
+
+ auto original_mask_in_operand_index = GetInOperandIndexForMask(
+ *instruction, message_.memory_operands_mask_index());
+ assert(original_mask_in_operand_index != 0 &&
+ "The given mask index is not valid.");
+ uint32_t original_mask =
+ original_mask_in_operand_index < instruction->NumInOperands()
+ ? instruction->GetSingleWordInOperand(original_mask_in_operand_index)
+ : static_cast<uint32_t>(SpvMemoryAccessMaskNone);
+ uint32_t new_mask = message_.memory_operands_mask();
+
+ // Volatile must not be removed
+ if ((original_mask & SpvMemoryAccessVolatileMask) &&
+ !(new_mask & SpvMemoryAccessVolatileMask)) {
+ return false;
+ }
+
+ // Nontemporal can be added or removed, and no other flag is allowed to
+ // change. We do this by checking that the masks are equal once we set
+ // their Volatile and Nontemporal flags to the same value (this works
+ // because valid manipulation of Volatile is checked above, and the manner
+ // in which Nontemporal is manipulated does not matter).
+ return (original_mask | SpvMemoryAccessVolatileMask |
+ SpvMemoryAccessNontemporalMask) ==
+ (new_mask | SpvMemoryAccessVolatileMask |
+ SpvMemoryAccessNontemporalMask);
+}
+
+void TransformationSetMemoryOperandsMask::Apply(
+ opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+ auto instruction =
+ FindInstruction(message_.memory_access_instruction(), context);
+ auto original_mask_in_operand_index = GetInOperandIndexForMask(
+ *instruction, message_.memory_operands_mask_index());
+ // Either add a new operand, if no mask operand was already present, or
+ // replace an existing mask operand.
+ if (original_mask_in_operand_index >= instruction->NumInOperands()) {
+ instruction->AddOperand(
+ {SPV_OPERAND_TYPE_MEMORY_ACCESS, {message_.memory_operands_mask()}});
+
+ } else {
+ instruction->SetInOperand(original_mask_in_operand_index,
+ {message_.memory_operands_mask()});
+ }
+}
+
+protobufs::Transformation TransformationSetMemoryOperandsMask::ToMessage()
+ const {
+ protobufs::Transformation result;
+ *result.mutable_set_memory_operands_mask() = message_;
+ return result;
+}
+
+bool TransformationSetMemoryOperandsMask::IsMemoryAccess(
+ const opt::Instruction& instruction) {
+ switch (instruction.opcode()) {
+ case SpvOpLoad:
+ case SpvOpStore:
+ case SpvOpCopyMemory:
+ case SpvOpCopyMemorySized:
+ return true;
+ default:
+ return false;
+ }
+}
+
+uint32_t TransformationSetMemoryOperandsMask::GetInOperandIndexForMask(
+ const opt::Instruction& instruction, uint32_t mask_index) {
+ // Get the input operand index associated with the first memory operands mask
+ // for the instruction.
+ uint32_t first_mask_in_operand_index = 0;
+ switch (instruction.opcode()) {
+ case SpvOpLoad:
+ first_mask_in_operand_index = kOpLoadMemoryOperandsMaskIndex;
+ break;
+ case SpvOpStore:
+ first_mask_in_operand_index = kOpStoreMemoryOperandsMaskIndex;
+ break;
+ case SpvOpCopyMemory:
+ first_mask_in_operand_index = kOpCopyMemoryFirstMemoryOperandsMaskIndex;
+ break;
+ case SpvOpCopyMemorySized:
+ first_mask_in_operand_index =
+ kOpCopyMemorySizedFirstMemoryOperandsMaskIndex;
+ break;
+ default:
+ assert(false && "Unknown memory instruction.");
+ break;
+ }
+ // If we are looking for the input operand index of the first mask, return it.
+ if (mask_index == 0) {
+ return first_mask_in_operand_index;
+ }
+ assert(mask_index == 1 && "Memory operands mask index must be 0 or 1.");
+
+ // We are looking for the input operand index of the second mask. This is a
+ // little complicated because, depending on the contents of the first mask,
+ // there may be some input operands separating the two masks.
+ uint32_t first_mask =
+ instruction.GetSingleWordInOperand(first_mask_in_operand_index);
+
+ // Consider each bit that might have an associated extra input operand, and
+ // count how many there are expected to be.
+ uint32_t first_mask_extra_operand_count = 0;
+ for (auto mask_bit :
+ {SpvMemoryAccessAlignedMask, SpvMemoryAccessMakePointerAvailableMask,
+ SpvMemoryAccessMakePointerAvailableKHRMask,
+ SpvMemoryAccessMakePointerVisibleMask,
+ SpvMemoryAccessMakePointerVisibleKHRMask}) {
+ if (first_mask & mask_bit) {
+ first_mask_extra_operand_count++;
+ }
+ }
+ return first_mask_in_operand_index + first_mask_extra_operand_count + 1;
+}
+
+bool TransformationSetMemoryOperandsMask::
+ MultipleMemoryOperandMasksAreSupported(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_memory_operands_mask.h b/source/fuzz/transformation_set_memory_operands_mask.h
new file mode 100644
index 0000000..20ae145
--- /dev/null
+++ b/source/fuzz/transformation_set_memory_operands_mask.h
@@ -0,0 +1,76 @@
+// 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_MEMORY_OPERANDS_MASK_H_
+#define SOURCE_FUZZ_TRANSFORMATION_SET_MEMORY_OPERANDS_MASK_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 TransformationSetMemoryOperandsMask : public Transformation {
+ public:
+ explicit TransformationSetMemoryOperandsMask(
+ const protobufs::TransformationSetMemoryOperandsMask& message);
+
+ TransformationSetMemoryOperandsMask(
+ const protobufs::InstructionDescriptor& memory_access_instruction,
+ uint32_t memory_operands_mask, uint32_t memory_operands_mask_index);
+
+ // - |message_.memory_access_instruction| must describe a memory access
+ // instruction.
+ // - |message_.memory_operands_mask_index| must be suitable for this memory
+ // access instruction, e.g. it must be 0 in the case of OpLoad, and may be
+ // 1 in the case of OpCopyMemory if the SPIR-V version is 1.4 or higher.
+ // - |message_.memory_operands_mask| must be identical to the original memory
+ // operands mask, except that Volatile may be added, and Nontemporal may be
+ // toggled.
+ bool IsApplicable(opt::IRContext* context,
+ const FactManager& fact_manager) const override;
+
+ // Replaces the operands mask identified by
+ // |message_.memory_operands_mask_index| in the instruction described by
+ // |message_.memory_access_instruction| with |message_.memory_operands_mask|,
+ // creating an input operand for the mask if no such operand was present.
+ void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+ protobufs::Transformation ToMessage() const override;
+
+ // Helper function that determines whether |instruction| is a memory
+ // instruction (e.g. OpLoad).
+ static bool IsMemoryAccess(const opt::Instruction& instruction);
+
+ // Does the version of SPIR-V being used support multiple memory operand
+ // masks on relevant memory access instructions?
+ static bool MultipleMemoryOperandMasksAreSupported(opt::IRContext* context);
+
+ // Helper function to get the input operand index associated with mask number
+ // |mask_index|. This is a bit tricky if there are multiple masks, because the
+ // index associated with the second mask depends on whether the first mask
+ // includes any flags such as Aligned that have corresponding operands.
+ static uint32_t GetInOperandIndexForMask(const opt::Instruction& instruction,
+ uint32_t mask_index);
+
+ private:
+ protobufs::TransformationSetMemoryOperandsMask message_;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_TRANSFORMATION_SET_MEMORY_OPERANDS_MASK_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index cd22426..5b913b9 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -20,6 +20,7 @@
fact_manager_test.cpp
fuzz_test_util.cpp
fuzzer_pass_add_useful_constructs_test.cpp
+ instruction_descriptor_test.cpp
transformation_add_constant_boolean_test.cpp
transformation_add_constant_scalar_test.cpp
transformation_add_dead_break_test.cpp
@@ -37,6 +38,7 @@
transformation_replace_id_with_synonym_test.cpp
transformation_set_function_control_test.cpp
transformation_set_loop_control_test.cpp
+ transformation_set_memory_operands_mask_test.cpp
transformation_set_selection_control_test.cpp
transformation_split_block_test.cpp
uniform_buffer_element_descriptor_test.cpp)
diff --git a/test/fuzz/instruction_descriptor_test.cpp b/test/fuzz/instruction_descriptor_test.cpp
new file mode 100644
index 0000000..5165cfb
--- /dev/null
+++ b/test/fuzz/instruction_descriptor_test.cpp
@@ -0,0 +1,69 @@
+// 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/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(InstructionDescriptorTest, BasicTest) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %10 = OpTypeInt 32 1
+ %11 = OpTypePointer Function %10
+ %13 = OpConstant %10 2
+ %32 = OpConstant %10 0
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %164 = OpVariable %11 Function
+ %165 = OpVariable %11 Function
+ OpBranch %16
+ %16 = OpLabel
+ OpStore %164 %32
+ OpStore %165 %13
+ 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()));
+
+ for (auto& function : *context->module()) {
+ for (auto& block : function) {
+ for (auto inst_it = block.cbegin(); inst_it != block.cend(); ++inst_it) {
+ ASSERT_EQ(&*inst_it,
+ FindInstruction(MakeInstructionDescriptor(block, inst_it),
+ context.get()));
+ }
+ }
+ }
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools
diff --git a/test/fuzz/transformation_set_memory_operands_mask_test.cpp b/test/fuzz/transformation_set_memory_operands_mask_test.cpp
new file mode 100644
index 0000000..ad4dc25
--- /dev/null
+++ b/test/fuzz/transformation_set_memory_operands_mask_test.cpp
@@ -0,0 +1,432 @@
+// 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_memory_operands_mask.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationSetMemoryOperandsMaskTest, PreSpirv14) {
+ 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 %7 "Point3D"
+ OpMemberName %7 0 "x"
+ OpMemberName %7 1 "y"
+ OpMemberName %7 2 "z"
+ OpName %12 "global_points"
+ OpName %15 "block"
+ OpMemberName %15 0 "in_points"
+ OpMemberName %15 1 "in_point"
+ OpName %17 ""
+ OpName %133 "local_points"
+ OpMemberDecorate %7 0 Offset 0
+ OpMemberDecorate %7 1 Offset 4
+ OpMemberDecorate %7 2 Offset 8
+ OpDecorate %10 ArrayStride 16
+ OpMemberDecorate %15 0 Offset 0
+ OpMemberDecorate %15 1 Offset 192
+ OpDecorate %15 Block
+ OpDecorate %17 DescriptorSet 0
+ OpDecorate %17 Binding 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeStruct %6 %6 %6
+ %8 = OpTypeInt 32 0
+ %9 = OpConstant %8 12
+ %10 = OpTypeArray %7 %9
+ %11 = OpTypePointer Private %10
+ %12 = OpVariable %11 Private
+ %15 = OpTypeStruct %10 %7
+ %16 = OpTypePointer Uniform %15
+ %17 = OpVariable %16 Uniform
+ %18 = OpTypeInt 32 1
+ %19 = OpConstant %18 0
+ %20 = OpTypePointer Uniform %10
+ %24 = OpTypePointer Private %7
+ %27 = OpTypePointer Private %6
+ %30 = OpConstant %18 1
+ %132 = OpTypePointer Function %10
+ %135 = OpTypePointer Uniform %7
+ %145 = OpTypePointer Function %7
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %133 = OpVariable %132 Function
+ %21 = OpAccessChain %20 %17 %19
+ OpCopyMemory %12 %21 Aligned 16
+ OpCopyMemory %133 %12 Volatile
+ %136 = OpAccessChain %135 %17 %30
+ %138 = OpAccessChain %24 %12 %19
+ OpCopyMemory %138 %136 None
+ %146 = OpAccessChain %145 %133 %30
+ %147 = OpLoad %7 %146 Volatile|Nontemporal|Aligned 16
+ %148 = OpAccessChain %24 %12 %19
+ OpStore %148 %147 Nontemporal
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+
+ // Not OK: the instruction is not a memory access.
+ ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+ MakeInstructionDescriptor(21, SpvOpAccessChain, 0),
+ SpvMemoryAccessMaskNone, 0)
+ .IsApplicable(context.get(), fact_manager));
+
+ // Not OK to remove Aligned
+ ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+ MakeInstructionDescriptor(147, SpvOpLoad, 0),
+ SpvMemoryAccessVolatileMask | SpvMemoryAccessNontemporalMask,
+ 0)
+ .IsApplicable(context.get(), fact_manager));
+
+ TransformationSetMemoryOperandsMask transformation1(
+ MakeInstructionDescriptor(147, SpvOpLoad, 0),
+ SpvMemoryAccessAlignedMask | SpvMemoryAccessVolatileMask, 0);
+ ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+ transformation1.Apply(context.get(), &fact_manager);
+
+ // Not OK to remove Aligned
+ ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+ MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
+ SpvMemoryAccessMaskNone, 0)
+ .IsApplicable(context.get(), fact_manager));
+
+ // OK: leaves the mask as is
+ ASSERT_TRUE(TransformationSetMemoryOperandsMask(
+ MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
+ SpvMemoryAccessAlignedMask, 0)
+ .IsApplicable(context.get(), fact_manager));
+
+ // OK: adds Nontemporal and Volatile
+ TransformationSetMemoryOperandsMask transformation2(
+ MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
+ SpvMemoryAccessAlignedMask | SpvMemoryAccessNontemporalMask |
+ SpvMemoryAccessVolatileMask,
+ 0);
+ ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+ transformation2.Apply(context.get(), &fact_manager);
+
+ // Not OK to remove Volatile
+ ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+ MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
+ SpvMemoryAccessNontemporalMask, 0)
+ .IsApplicable(context.get(), fact_manager));
+
+ // Not OK to add Aligned
+ ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+ MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
+ SpvMemoryAccessAlignedMask | SpvMemoryAccessVolatileMask, 0)
+ .IsApplicable(context.get(), fact_manager));
+
+ // OK: adds Nontemporal
+ TransformationSetMemoryOperandsMask transformation3(
+ MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
+ SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
+ ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+ transformation3.Apply(context.get(), &fact_manager);
+
+ // OK: adds Nontemporal and Volatile
+ TransformationSetMemoryOperandsMask transformation4(
+ MakeInstructionDescriptor(138, SpvOpCopyMemory, 0),
+ SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
+ ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
+ transformation4.Apply(context.get(), &fact_manager);
+
+ // OK: removes Nontemporal, adds Volatile
+ TransformationSetMemoryOperandsMask transformation5(
+ MakeInstructionDescriptor(148, SpvOpStore, 0),
+ SpvMemoryAccessVolatileMask, 0);
+ ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
+ transformation5.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"
+ OpName %7 "Point3D"
+ OpMemberName %7 0 "x"
+ OpMemberName %7 1 "y"
+ OpMemberName %7 2 "z"
+ OpName %12 "global_points"
+ OpName %15 "block"
+ OpMemberName %15 0 "in_points"
+ OpMemberName %15 1 "in_point"
+ OpName %17 ""
+ OpName %133 "local_points"
+ OpMemberDecorate %7 0 Offset 0
+ OpMemberDecorate %7 1 Offset 4
+ OpMemberDecorate %7 2 Offset 8
+ OpDecorate %10 ArrayStride 16
+ OpMemberDecorate %15 0 Offset 0
+ OpMemberDecorate %15 1 Offset 192
+ OpDecorate %15 Block
+ OpDecorate %17 DescriptorSet 0
+ OpDecorate %17 Binding 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeStruct %6 %6 %6
+ %8 = OpTypeInt 32 0
+ %9 = OpConstant %8 12
+ %10 = OpTypeArray %7 %9
+ %11 = OpTypePointer Private %10
+ %12 = OpVariable %11 Private
+ %15 = OpTypeStruct %10 %7
+ %16 = OpTypePointer Uniform %15
+ %17 = OpVariable %16 Uniform
+ %18 = OpTypeInt 32 1
+ %19 = OpConstant %18 0
+ %20 = OpTypePointer Uniform %10
+ %24 = OpTypePointer Private %7
+ %27 = OpTypePointer Private %6
+ %30 = OpConstant %18 1
+ %132 = OpTypePointer Function %10
+ %135 = OpTypePointer Uniform %7
+ %145 = OpTypePointer Function %7
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %133 = OpVariable %132 Function
+ %21 = OpAccessChain %20 %17 %19
+ OpCopyMemory %12 %21 Aligned|Nontemporal|Volatile 16
+ OpCopyMemory %133 %12 Nontemporal|Volatile
+ %136 = OpAccessChain %135 %17 %30
+ %138 = OpAccessChain %24 %12 %19
+ OpCopyMemory %138 %136 Nontemporal|Volatile
+ %146 = OpAccessChain %145 %133 %30
+ %147 = OpLoad %7 %146 Aligned|Volatile 16
+ %148 = OpAccessChain %24 %12 %19
+ OpStore %148 %147 Volatile
+ OpReturn
+ OpFunctionEnd
+ )";
+ ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationSetMemoryOperandsMaskTest, Spirv14) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %12 %17
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %7 "Point3D"
+ OpMemberName %7 0 "x"
+ OpMemberName %7 1 "y"
+ OpMemberName %7 2 "z"
+ OpName %12 "global_points"
+ OpName %15 "block"
+ OpMemberName %15 0 "in_points"
+ OpMemberName %15 1 "in_point"
+ OpName %17 ""
+ OpName %133 "local_points"
+ OpMemberDecorate %7 0 Offset 0
+ OpMemberDecorate %7 1 Offset 4
+ OpMemberDecorate %7 2 Offset 8
+ OpDecorate %10 ArrayStride 16
+ OpMemberDecorate %15 0 Offset 0
+ OpMemberDecorate %15 1 Offset 192
+ OpDecorate %15 Block
+ OpDecorate %17 DescriptorSet 0
+ OpDecorate %17 Binding 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeStruct %6 %6 %6
+ %8 = OpTypeInt 32 0
+ %9 = OpConstant %8 12
+ %10 = OpTypeArray %7 %9
+ %11 = OpTypePointer Private %10
+ %12 = OpVariable %11 Private
+ %15 = OpTypeStruct %10 %7
+ %16 = OpTypePointer Uniform %15
+ %17 = OpVariable %16 Uniform
+ %18 = OpTypeInt 32 1
+ %19 = OpConstant %18 0
+ %20 = OpTypePointer Uniform %10
+ %24 = OpTypePointer Private %7
+ %27 = OpTypePointer Private %6
+ %30 = OpConstant %18 1
+ %132 = OpTypePointer Function %10
+ %135 = OpTypePointer Uniform %7
+ %145 = OpTypePointer Function %7
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %133 = OpVariable %132 Function
+ %21 = OpAccessChain %20 %17 %19
+ OpCopyMemory %12 %21 Aligned 16 Nontemporal|Aligned 16
+ OpCopyMemory %133 %12 Volatile
+ %136 = OpAccessChain %135 %17 %30
+ %138 = OpAccessChain %24 %12 %19
+ OpCopyMemory %138 %136 None Aligned 16
+ OpCopyMemory %138 %136 Aligned 16
+ %146 = OpAccessChain %145 %133 %30
+ %147 = OpLoad %7 %146 Volatile|Nontemporal|Aligned 16
+ %148 = OpAccessChain %24 %12 %19
+ OpStore %148 %147 Nontemporal
+ 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;
+
+ TransformationSetMemoryOperandsMask transformation1(
+ MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
+ SpvMemoryAccessAlignedMask | SpvMemoryAccessVolatileMask, 1);
+ // Bad: cannot remove aligned
+ ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+ MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
+ SpvMemoryAccessVolatileMask, 1)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+ transformation1.Apply(context.get(), &fact_manager);
+
+ TransformationSetMemoryOperandsMask transformation2(
+ MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
+ SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 1);
+ // Bad: cannot remove volatile
+ ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+ MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
+ SpvMemoryAccessNontemporalMask, 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+ transformation2.Apply(context.get(), &fact_manager);
+
+ TransformationSetMemoryOperandsMask transformation3(
+ MakeInstructionDescriptor(138, SpvOpCopyMemory, 0),
+ SpvMemoryAccessAlignedMask | SpvMemoryAccessNontemporalMask, 1);
+ // Bad: the first mask is None, so Aligned cannot be added to it.
+ ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+ MakeInstructionDescriptor(138, SpvOpCopyMemory, 0),
+ SpvMemoryAccessAlignedMask | SpvMemoryAccessNontemporalMask,
+ 0)
+ .IsApplicable(context.get(), fact_manager));
+ ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+ transformation3.Apply(context.get(), &fact_manager);
+
+ TransformationSetMemoryOperandsMask transformation4(
+ MakeInstructionDescriptor(138, SpvOpCopyMemory, 1),
+ SpvMemoryAccessVolatileMask, 1);
+ ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
+ transformation4.Apply(context.get(), &fact_manager);
+
+ TransformationSetMemoryOperandsMask transformation5(
+ MakeInstructionDescriptor(147, SpvOpLoad, 0),
+ SpvMemoryAccessVolatileMask | SpvMemoryAccessAlignedMask, 0);
+ ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
+ transformation5.Apply(context.get(), &fact_manager);
+
+ TransformationSetMemoryOperandsMask transformation6(
+ MakeInstructionDescriptor(148, SpvOpStore, 0), SpvMemoryAccessMaskNone,
+ 0);
+ ASSERT_TRUE(transformation6.IsApplicable(context.get(), fact_manager));
+ transformation6.Apply(context.get(), &fact_manager);
+
+ std::string after_transformation = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %12 %17
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %7 "Point3D"
+ OpMemberName %7 0 "x"
+ OpMemberName %7 1 "y"
+ OpMemberName %7 2 "z"
+ OpName %12 "global_points"
+ OpName %15 "block"
+ OpMemberName %15 0 "in_points"
+ OpMemberName %15 1 "in_point"
+ OpName %17 ""
+ OpName %133 "local_points"
+ OpMemberDecorate %7 0 Offset 0
+ OpMemberDecorate %7 1 Offset 4
+ OpMemberDecorate %7 2 Offset 8
+ OpDecorate %10 ArrayStride 16
+ OpMemberDecorate %15 0 Offset 0
+ OpMemberDecorate %15 1 Offset 192
+ OpDecorate %15 Block
+ OpDecorate %17 DescriptorSet 0
+ OpDecorate %17 Binding 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeStruct %6 %6 %6
+ %8 = OpTypeInt 32 0
+ %9 = OpConstant %8 12
+ %10 = OpTypeArray %7 %9
+ %11 = OpTypePointer Private %10
+ %12 = OpVariable %11 Private
+ %15 = OpTypeStruct %10 %7
+ %16 = OpTypePointer Uniform %15
+ %17 = OpVariable %16 Uniform
+ %18 = OpTypeInt 32 1
+ %19 = OpConstant %18 0
+ %20 = OpTypePointer Uniform %10
+ %24 = OpTypePointer Private %7
+ %27 = OpTypePointer Private %6
+ %30 = OpConstant %18 1
+ %132 = OpTypePointer Function %10
+ %135 = OpTypePointer Uniform %7
+ %145 = OpTypePointer Function %7
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %133 = OpVariable %132 Function
+ %21 = OpAccessChain %20 %17 %19
+ OpCopyMemory %12 %21 Aligned 16 Aligned|Volatile 16
+ OpCopyMemory %133 %12 Volatile Nontemporal|Volatile
+ %136 = OpAccessChain %135 %17 %30
+ %138 = OpAccessChain %24 %12 %19
+ OpCopyMemory %138 %136 None Aligned|Nontemporal 16
+ OpCopyMemory %138 %136 Aligned 16 Volatile
+ %146 = OpAccessChain %145 %133 %30
+ %147 = OpLoad %7 %146 Volatile|Aligned 16
+ %148 = OpAccessChain %24 %12 %19
+ OpStore %148 %147 None
+ OpReturn
+ OpFunctionEnd
+ )";
+ ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools