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