1 // Copyright (c) 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "source/fuzz/transformation_set_memory_operands_mask.h"
16
17 #include "source/fuzz/instruction_descriptor.h"
18
19 namespace spvtools {
20 namespace fuzz {
21
22 namespace {
23
24 const uint32_t kOpLoadMemoryOperandsMaskIndex = 1;
25 const uint32_t kOpStoreMemoryOperandsMaskIndex = 2;
26 const uint32_t kOpCopyMemoryFirstMemoryOperandsMaskIndex = 2;
27 const uint32_t kOpCopyMemorySizedFirstMemoryOperandsMaskIndex = 3;
28
29 } // namespace
30
TransformationSetMemoryOperandsMask(protobufs::TransformationSetMemoryOperandsMask message)31 TransformationSetMemoryOperandsMask::TransformationSetMemoryOperandsMask(
32 protobufs::TransformationSetMemoryOperandsMask message)
33 : message_(std::move(message)) {}
34
TransformationSetMemoryOperandsMask(const protobufs::InstructionDescriptor & memory_access_instruction,uint32_t memory_operands_mask,uint32_t memory_operands_mask_index)35 TransformationSetMemoryOperandsMask::TransformationSetMemoryOperandsMask(
36 const protobufs::InstructionDescriptor& memory_access_instruction,
37 uint32_t memory_operands_mask, uint32_t memory_operands_mask_index) {
38 *message_.mutable_memory_access_instruction() = memory_access_instruction;
39 message_.set_memory_operands_mask(memory_operands_mask);
40 message_.set_memory_operands_mask_index(memory_operands_mask_index);
41 }
42
IsApplicable(opt::IRContext * ir_context,const TransformationContext &) const43 bool TransformationSetMemoryOperandsMask::IsApplicable(
44 opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
45 if (message_.memory_operands_mask_index() != 0) {
46 // The following conditions should never be violated, even if
47 // transformations end up being replayed in a different way to the manner in
48 // which they were applied during fuzzing, hence why these are assertions
49 // rather than applicability checks.
50 assert(message_.memory_operands_mask_index() == 1);
51 assert(
52 spv::Op(
53 message_.memory_access_instruction().target_instruction_opcode()) ==
54 spv::Op::OpCopyMemory ||
55 spv::Op(
56 message_.memory_access_instruction().target_instruction_opcode()) ==
57 spv::Op::OpCopyMemorySized);
58 assert(MultipleMemoryOperandMasksAreSupported(ir_context) &&
59 "Multiple memory operand masks are not supported for this SPIR-V "
60 "version.");
61 }
62
63 auto instruction =
64 FindInstruction(message_.memory_access_instruction(), ir_context);
65 if (!instruction) {
66 return false;
67 }
68 if (!IsMemoryAccess(*instruction)) {
69 return false;
70 }
71
72 auto original_mask_in_operand_index = GetInOperandIndexForMask(
73 *instruction, message_.memory_operands_mask_index());
74 assert(original_mask_in_operand_index != 0 &&
75 "The given mask index is not valid.");
76 uint32_t original_mask =
77 original_mask_in_operand_index < instruction->NumInOperands()
78 ? instruction->GetSingleWordInOperand(original_mask_in_operand_index)
79 : static_cast<uint32_t>(spv::MemoryAccessMask::MaskNone);
80 uint32_t new_mask = message_.memory_operands_mask();
81
82 // Volatile must not be removed
83 if ((original_mask & uint32_t(spv::MemoryAccessMask::Volatile)) &&
84 !(new_mask & uint32_t(spv::MemoryAccessMask::Volatile))) {
85 return false;
86 }
87
88 // Nontemporal can be added or removed, and no other flag is allowed to
89 // change. We do this by checking that the masks are equal once we set
90 // their Volatile and Nontemporal flags to the same value (this works
91 // because valid manipulation of Volatile is checked above, and the manner
92 // in which Nontemporal is manipulated does not matter).
93 return (original_mask | uint32_t(spv::MemoryAccessMask::Volatile) |
94 uint32_t(spv::MemoryAccessMask::Nontemporal)) ==
95 (new_mask | uint32_t(spv::MemoryAccessMask::Volatile) |
96 uint32_t(spv::MemoryAccessMask::Nontemporal));
97 }
98
Apply(opt::IRContext * ir_context,TransformationContext *) const99 void TransformationSetMemoryOperandsMask::Apply(
100 opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
101 auto instruction =
102 FindInstruction(message_.memory_access_instruction(), ir_context);
103 auto original_mask_in_operand_index = GetInOperandIndexForMask(
104 *instruction, message_.memory_operands_mask_index());
105 // Either add a new operand, if no mask operand was already present, or
106 // replace an existing mask operand.
107 if (original_mask_in_operand_index >= instruction->NumInOperands()) {
108 // Add first memory operand if it's missing.
109 if (message_.memory_operands_mask_index() == 1 &&
110 GetInOperandIndexForMask(*instruction, 0) >=
111 instruction->NumInOperands()) {
112 instruction->AddOperand({SPV_OPERAND_TYPE_MEMORY_ACCESS,
113 {uint32_t(spv::MemoryAccessMask::MaskNone)}});
114 }
115
116 instruction->AddOperand(
117 {SPV_OPERAND_TYPE_MEMORY_ACCESS, {message_.memory_operands_mask()}});
118
119 } else {
120 instruction->SetInOperand(original_mask_in_operand_index,
121 {message_.memory_operands_mask()});
122 }
123 }
124
ToMessage() const125 protobufs::Transformation TransformationSetMemoryOperandsMask::ToMessage()
126 const {
127 protobufs::Transformation result;
128 *result.mutable_set_memory_operands_mask() = message_;
129 return result;
130 }
131
IsMemoryAccess(const opt::Instruction & instruction)132 bool TransformationSetMemoryOperandsMask::IsMemoryAccess(
133 const opt::Instruction& instruction) {
134 switch (instruction.opcode()) {
135 case spv::Op::OpLoad:
136 case spv::Op::OpStore:
137 case spv::Op::OpCopyMemory:
138 case spv::Op::OpCopyMemorySized:
139 return true;
140 default:
141 return false;
142 }
143 }
144
GetInOperandIndexForMask(const opt::Instruction & instruction,uint32_t mask_index)145 uint32_t TransformationSetMemoryOperandsMask::GetInOperandIndexForMask(
146 const opt::Instruction& instruction, uint32_t mask_index) {
147 // Get the input operand index associated with the first memory operands mask
148 // for the instruction.
149 uint32_t first_mask_in_operand_index = 0;
150 switch (instruction.opcode()) {
151 case spv::Op::OpLoad:
152 first_mask_in_operand_index = kOpLoadMemoryOperandsMaskIndex;
153 break;
154 case spv::Op::OpStore:
155 first_mask_in_operand_index = kOpStoreMemoryOperandsMaskIndex;
156 break;
157 case spv::Op::OpCopyMemory:
158 first_mask_in_operand_index = kOpCopyMemoryFirstMemoryOperandsMaskIndex;
159 break;
160 case spv::Op::OpCopyMemorySized:
161 first_mask_in_operand_index =
162 kOpCopyMemorySizedFirstMemoryOperandsMaskIndex;
163 break;
164 default:
165 assert(false && "Unknown memory instruction.");
166 break;
167 }
168 // If we are looking for the input operand index of the first mask, return it.
169 // This will also return a correct value if the operand is missing.
170 if (mask_index == 0) {
171 return first_mask_in_operand_index;
172 }
173 assert(mask_index == 1 && "Memory operands mask index must be 0 or 1.");
174
175 // Memory mask operands are optional. Thus, if the second operand exists,
176 // its index will be >= |first_mask_in_operand_index + 1|. We can reason as
177 // follows to separate the cases where the index of the second operand is
178 // equal to |first_mask_in_operand_index + 1|:
179 // - If the first memory operand doesn't exist, its value is equal to None.
180 // This means that it doesn't have additional operands following it and the
181 // condition in the if statement below will be satisfied.
182 // - If the first memory operand exists and has no additional memory operands
183 // following it, the condition in the if statement below will be satisfied
184 // and we will return the correct value from the function.
185 if (first_mask_in_operand_index + 1 >= instruction.NumInOperands()) {
186 return first_mask_in_operand_index + 1;
187 }
188
189 // We are looking for the input operand index of the second mask. This is a
190 // little complicated because, depending on the contents of the first mask,
191 // there may be some input operands separating the two masks.
192 uint32_t first_mask =
193 instruction.GetSingleWordInOperand(first_mask_in_operand_index);
194
195 // Consider each bit that might have an associated extra input operand, and
196 // count how many there are expected to be.
197 uint32_t first_mask_extra_operand_count = 0;
198 for (auto mask_bit : {spv::MemoryAccessMask::Aligned,
199 spv::MemoryAccessMask::MakePointerAvailable,
200 spv::MemoryAccessMask::MakePointerAvailableKHR,
201 spv::MemoryAccessMask::MakePointerVisible,
202 spv::MemoryAccessMask::MakePointerVisibleKHR}) {
203 if (first_mask & uint32_t(mask_bit)) {
204 first_mask_extra_operand_count++;
205 }
206 }
207 return first_mask_in_operand_index + first_mask_extra_operand_count + 1;
208 }
209
210 bool TransformationSetMemoryOperandsMask::
MultipleMemoryOperandMasksAreSupported(opt::IRContext * ir_context)211 MultipleMemoryOperandMasksAreSupported(opt::IRContext* ir_context) {
212 // TODO(afd): We capture the environments for which this loop control is
213 // definitely not supported. The check should be refined on demand for other
214 // target environments.
215 switch (ir_context->grammar().target_env()) {
216 case SPV_ENV_UNIVERSAL_1_0:
217 case SPV_ENV_UNIVERSAL_1_1:
218 case SPV_ENV_UNIVERSAL_1_2:
219 case SPV_ENV_UNIVERSAL_1_3:
220 case SPV_ENV_VULKAN_1_0:
221 case SPV_ENV_VULKAN_1_1:
222 return false;
223 default:
224 return true;
225 }
226 }
227
GetFreshIds() const228 std::unordered_set<uint32_t> TransformationSetMemoryOperandsMask::GetFreshIds()
229 const {
230 return std::unordered_set<uint32_t>();
231 }
232
233 } // namespace fuzz
234 } // namespace spvtools
235