• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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