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(message_.memory_access_instruction().target_instruction_opcode() ==
52 SpvOpCopyMemory ||
53 message_.memory_access_instruction().target_instruction_opcode() ==
54 SpvOpCopyMemorySized);
55 assert(MultipleMemoryOperandMasksAreSupported(ir_context) &&
56 "Multiple memory operand masks are not supported for this SPIR-V "
57 "version.");
58 }
59
60 auto instruction =
61 FindInstruction(message_.memory_access_instruction(), ir_context);
62 if (!instruction) {
63 return false;
64 }
65 if (!IsMemoryAccess(*instruction)) {
66 return false;
67 }
68
69 auto original_mask_in_operand_index = GetInOperandIndexForMask(
70 *instruction, message_.memory_operands_mask_index());
71 assert(original_mask_in_operand_index != 0 &&
72 "The given mask index is not valid.");
73 uint32_t original_mask =
74 original_mask_in_operand_index < instruction->NumInOperands()
75 ? instruction->GetSingleWordInOperand(original_mask_in_operand_index)
76 : static_cast<uint32_t>(SpvMemoryAccessMaskNone);
77 uint32_t new_mask = message_.memory_operands_mask();
78
79 // Volatile must not be removed
80 if ((original_mask & SpvMemoryAccessVolatileMask) &&
81 !(new_mask & SpvMemoryAccessVolatileMask)) {
82 return false;
83 }
84
85 // Nontemporal can be added or removed, and no other flag is allowed to
86 // change. We do this by checking that the masks are equal once we set
87 // their Volatile and Nontemporal flags to the same value (this works
88 // because valid manipulation of Volatile is checked above, and the manner
89 // in which Nontemporal is manipulated does not matter).
90 return (original_mask | SpvMemoryAccessVolatileMask |
91 SpvMemoryAccessNontemporalMask) ==
92 (new_mask | SpvMemoryAccessVolatileMask |
93 SpvMemoryAccessNontemporalMask);
94 }
95
Apply(opt::IRContext * ir_context,TransformationContext *) const96 void TransformationSetMemoryOperandsMask::Apply(
97 opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
98 auto instruction =
99 FindInstruction(message_.memory_access_instruction(), ir_context);
100 auto original_mask_in_operand_index = GetInOperandIndexForMask(
101 *instruction, message_.memory_operands_mask_index());
102 // Either add a new operand, if no mask operand was already present, or
103 // replace an existing mask operand.
104 if (original_mask_in_operand_index >= instruction->NumInOperands()) {
105 // Add first memory operand if it's missing.
106 if (message_.memory_operands_mask_index() == 1 &&
107 GetInOperandIndexForMask(*instruction, 0) >=
108 instruction->NumInOperands()) {
109 instruction->AddOperand(
110 {SPV_OPERAND_TYPE_MEMORY_ACCESS, {SpvMemoryAccessMaskNone}});
111 }
112
113 instruction->AddOperand(
114 {SPV_OPERAND_TYPE_MEMORY_ACCESS, {message_.memory_operands_mask()}});
115
116 } else {
117 instruction->SetInOperand(original_mask_in_operand_index,
118 {message_.memory_operands_mask()});
119 }
120 }
121
ToMessage() const122 protobufs::Transformation TransformationSetMemoryOperandsMask::ToMessage()
123 const {
124 protobufs::Transformation result;
125 *result.mutable_set_memory_operands_mask() = message_;
126 return result;
127 }
128
IsMemoryAccess(const opt::Instruction & instruction)129 bool TransformationSetMemoryOperandsMask::IsMemoryAccess(
130 const opt::Instruction& instruction) {
131 switch (instruction.opcode()) {
132 case SpvOpLoad:
133 case SpvOpStore:
134 case SpvOpCopyMemory:
135 case SpvOpCopyMemorySized:
136 return true;
137 default:
138 return false;
139 }
140 }
141
GetInOperandIndexForMask(const opt::Instruction & instruction,uint32_t mask_index)142 uint32_t TransformationSetMemoryOperandsMask::GetInOperandIndexForMask(
143 const opt::Instruction& instruction, uint32_t mask_index) {
144 // Get the input operand index associated with the first memory operands mask
145 // for the instruction.
146 uint32_t first_mask_in_operand_index = 0;
147 switch (instruction.opcode()) {
148 case SpvOpLoad:
149 first_mask_in_operand_index = kOpLoadMemoryOperandsMaskIndex;
150 break;
151 case SpvOpStore:
152 first_mask_in_operand_index = kOpStoreMemoryOperandsMaskIndex;
153 break;
154 case SpvOpCopyMemory:
155 first_mask_in_operand_index = kOpCopyMemoryFirstMemoryOperandsMaskIndex;
156 break;
157 case SpvOpCopyMemorySized:
158 first_mask_in_operand_index =
159 kOpCopyMemorySizedFirstMemoryOperandsMaskIndex;
160 break;
161 default:
162 assert(false && "Unknown memory instruction.");
163 break;
164 }
165 // If we are looking for the input operand index of the first mask, return it.
166 // This will also return a correct value if the operand is missing.
167 if (mask_index == 0) {
168 return first_mask_in_operand_index;
169 }
170 assert(mask_index == 1 && "Memory operands mask index must be 0 or 1.");
171
172 // Memory mask operands are optional. Thus, if the second operand exists,
173 // its index will be >= |first_mask_in_operand_index + 1|. We can reason as
174 // follows to separate the cases where the index of the second operand is
175 // equal to |first_mask_in_operand_index + 1|:
176 // - If the first memory operand doesn't exist, its value is equal to None.
177 // This means that it doesn't have additional operands following it and the
178 // condition in the if statement below will be satisfied.
179 // - If the first memory operand exists and has no additional memory operands
180 // following it, the condition in the if statement below will be satisfied
181 // and we will return the correct value from the function.
182 if (first_mask_in_operand_index + 1 >= instruction.NumInOperands()) {
183 return first_mask_in_operand_index + 1;
184 }
185
186 // We are looking for the input operand index of the second mask. This is a
187 // little complicated because, depending on the contents of the first mask,
188 // there may be some input operands separating the two masks.
189 uint32_t first_mask =
190 instruction.GetSingleWordInOperand(first_mask_in_operand_index);
191
192 // Consider each bit that might have an associated extra input operand, and
193 // count how many there are expected to be.
194 uint32_t first_mask_extra_operand_count = 0;
195 for (auto mask_bit :
196 {SpvMemoryAccessAlignedMask, SpvMemoryAccessMakePointerAvailableMask,
197 SpvMemoryAccessMakePointerAvailableKHRMask,
198 SpvMemoryAccessMakePointerVisibleMask,
199 SpvMemoryAccessMakePointerVisibleKHRMask}) {
200 if (first_mask & mask_bit) {
201 first_mask_extra_operand_count++;
202 }
203 }
204 return first_mask_in_operand_index + first_mask_extra_operand_count + 1;
205 }
206
207 bool TransformationSetMemoryOperandsMask::
MultipleMemoryOperandMasksAreSupported(opt::IRContext * ir_context)208 MultipleMemoryOperandMasksAreSupported(opt::IRContext* ir_context) {
209 // TODO(afd): We capture the environments for which this loop control is
210 // definitely not supported. The check should be refined on demand for other
211 // target environments.
212 switch (ir_context->grammar().target_env()) {
213 case SPV_ENV_UNIVERSAL_1_0:
214 case SPV_ENV_UNIVERSAL_1_1:
215 case SPV_ENV_UNIVERSAL_1_2:
216 case SPV_ENV_UNIVERSAL_1_3:
217 case SPV_ENV_VULKAN_1_0:
218 case SPV_ENV_VULKAN_1_1:
219 return false;
220 default:
221 return true;
222 }
223 }
224
GetFreshIds() const225 std::unordered_set<uint32_t> TransformationSetMemoryOperandsMask::GetFreshIds()
226 const {
227 return std::unordered_set<uint32_t>();
228 }
229
230 } // namespace fuzz
231 } // namespace spvtools
232