• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2020 Vasyl Teliman
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_propagate_instruction_up.h"
16 
17 #include "source/fuzz/fuzzer_util.h"
18 #include "source/fuzz/instruction_descriptor.h"
19 
20 namespace spvtools {
21 namespace fuzz {
22 namespace {
23 
GetResultIdFromLabelId(const opt::Instruction & phi_inst,uint32_t label_id)24 uint32_t GetResultIdFromLabelId(const opt::Instruction& phi_inst,
25                                 uint32_t label_id) {
26   assert(phi_inst.opcode() == SpvOpPhi && "|phi_inst| is not an OpPhi");
27 
28   for (uint32_t i = 1; i < phi_inst.NumInOperands(); i += 2) {
29     if (phi_inst.GetSingleWordInOperand(i) == label_id) {
30       return phi_inst.GetSingleWordInOperand(i - 1);
31     }
32   }
33 
34   return 0;
35 }
36 
ContainsPointers(const opt::analysis::Type & type)37 bool ContainsPointers(const opt::analysis::Type& type) {
38   switch (type.kind()) {
39     case opt::analysis::Type::kPointer:
40       return true;
41     case opt::analysis::Type::kStruct:
42       return std::any_of(type.AsStruct()->element_types().begin(),
43                          type.AsStruct()->element_types().end(),
44                          [](const opt::analysis::Type* element_type) {
45                            return ContainsPointers(*element_type);
46                          });
47     default:
48       return false;
49   }
50 }
51 
HasValidDependencies(opt::IRContext * ir_context,opt::Instruction * inst)52 bool HasValidDependencies(opt::IRContext* ir_context, opt::Instruction* inst) {
53   const auto* inst_block = ir_context->get_instr_block(inst);
54   assert(inst_block &&
55          "This function shouldn't be applied to global instructions or function"
56          "parameters");
57 
58   for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
59     const auto& operand = inst->GetInOperand(i);
60     if (operand.type != SPV_OPERAND_TYPE_ID) {
61       // Consider only <id> operands.
62       continue;
63     }
64 
65     auto* dependency = ir_context->get_def_use_mgr()->GetDef(operand.words[0]);
66     assert(dependency && "Operand has invalid id");
67 
68     if (ir_context->get_instr_block(dependency) == inst_block &&
69         dependency->opcode() != SpvOpPhi) {
70       // |dependency| is "valid" if it's an OpPhi from the same basic block or
71       // an instruction from a different basic block.
72       return false;
73     }
74   }
75 
76   return true;
77 }
78 
79 }  // namespace
80 
TransformationPropagateInstructionUp(protobufs::TransformationPropagateInstructionUp message)81 TransformationPropagateInstructionUp::TransformationPropagateInstructionUp(
82     protobufs::TransformationPropagateInstructionUp message)
83     : message_(std::move(message)) {}
84 
TransformationPropagateInstructionUp(uint32_t block_id,const std::map<uint32_t,uint32_t> & predecessor_id_to_fresh_id)85 TransformationPropagateInstructionUp::TransformationPropagateInstructionUp(
86     uint32_t block_id,
87     const std::map<uint32_t, uint32_t>& predecessor_id_to_fresh_id) {
88   message_.set_block_id(block_id);
89   *message_.mutable_predecessor_id_to_fresh_id() =
90       fuzzerutil::MapToRepeatedUInt32Pair(predecessor_id_to_fresh_id);
91 }
92 
IsApplicable(opt::IRContext * ir_context,const TransformationContext &) const93 bool TransformationPropagateInstructionUp::IsApplicable(
94     opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
95   // Check that we can apply this transformation to the |block_id|.
96   if (!IsApplicableToBlock(ir_context, message_.block_id())) {
97     return false;
98   }
99 
100   const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap(
101       message_.predecessor_id_to_fresh_id());
102   for (auto id : ir_context->cfg()->preds(message_.block_id())) {
103     // Each predecessor must have a fresh id in the |predecessor_id_to_fresh_id|
104     // map.
105     if (!predecessor_id_to_fresh_id.count(id)) {
106       return false;
107     }
108   }
109 
110   std::vector<uint32_t> maybe_fresh_ids;
111   maybe_fresh_ids.reserve(predecessor_id_to_fresh_id.size());
112   for (const auto& entry : predecessor_id_to_fresh_id) {
113     maybe_fresh_ids.push_back(entry.second);
114   }
115 
116   // All ids must be unique and fresh.
117   return !fuzzerutil::HasDuplicates(maybe_fresh_ids) &&
118          std::all_of(maybe_fresh_ids.begin(), maybe_fresh_ids.end(),
119                      [ir_context](uint32_t id) {
120                        return fuzzerutil::IsFreshId(ir_context, id);
121                      });
122 }
123 
Apply(opt::IRContext * ir_context,TransformationContext *) const124 void TransformationPropagateInstructionUp::Apply(
125     opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
126   auto* inst = GetInstructionToPropagate(ir_context, message_.block_id());
127   assert(inst &&
128          "The block must have at least one supported instruction to propagate");
129   assert(inst->result_id() && inst->type_id() &&
130          "|inst| must have a result id and a type id");
131 
132   opt::Instruction::OperandList op_phi_operands;
133   const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap(
134       message_.predecessor_id_to_fresh_id());
135   std::unordered_set<uint32_t> visited_predecessors;
136   for (auto predecessor_id : ir_context->cfg()->preds(message_.block_id())) {
137     // A block can have multiple identical predecessors.
138     if (visited_predecessors.count(predecessor_id)) {
139       continue;
140     }
141 
142     visited_predecessors.insert(predecessor_id);
143 
144     auto new_result_id = predecessor_id_to_fresh_id.at(predecessor_id);
145 
146     // Compute InOperands for the OpPhi instruction to be inserted later.
147     op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {new_result_id}});
148     op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {predecessor_id}});
149 
150     // Create a clone of the |inst| to be inserted into the |predecessor_id|.
151     std::unique_ptr<opt::Instruction> clone(inst->Clone(ir_context));
152     clone->SetResultId(new_result_id);
153 
154     fuzzerutil::UpdateModuleIdBound(ir_context, new_result_id);
155 
156     // Adjust |clone|'s operands to account for possible dependencies on OpPhi
157     // instructions from the same basic block.
158     for (uint32_t i = 0; i < clone->NumInOperands(); ++i) {
159       auto& operand = clone->GetInOperand(i);
160       if (operand.type != SPV_OPERAND_TYPE_ID) {
161         // Consider only ids.
162         continue;
163       }
164 
165       const auto* dependency_inst =
166           ir_context->get_def_use_mgr()->GetDef(operand.words[0]);
167       assert(dependency_inst && "|clone| depends on an invalid id");
168 
169       if (ir_context->get_instr_block(dependency_inst->result_id()) !=
170           ir_context->cfg()->block(message_.block_id())) {
171         // We don't need to adjust anything if |dependency_inst| is from a
172         // different block, a global instruction or a function parameter.
173         continue;
174       }
175 
176       assert(dependency_inst->opcode() == SpvOpPhi &&
177              "Propagated instruction can depend only on OpPhis from the same "
178              "basic block or instructions from different basic blocks");
179 
180       auto new_id = GetResultIdFromLabelId(*dependency_inst, predecessor_id);
181       assert(new_id && "OpPhi instruction is missing a predecessor");
182       operand.words[0] = new_id;
183     }
184 
185     auto* insert_before_inst = fuzzerutil::GetLastInsertBeforeInstruction(
186         ir_context, predecessor_id, clone->opcode());
187     assert(insert_before_inst && "Can't insert |clone| into |predecessor_id");
188 
189     insert_before_inst->InsertBefore(std::move(clone));
190   }
191 
192   // Insert an OpPhi instruction into the basic block of |inst|.
193   ir_context->get_instr_block(inst)->begin()->InsertBefore(
194       MakeUnique<opt::Instruction>(ir_context, SpvOpPhi, inst->type_id(),
195                                    inst->result_id(),
196                                    std::move(op_phi_operands)));
197 
198   // Remove |inst| from the basic block.
199   ir_context->KillInst(inst);
200 
201   // We have changed the module so most analyzes are now invalid.
202   ir_context->InvalidateAnalysesExceptFor(
203       opt::IRContext::Analysis::kAnalysisNone);
204 }
205 
ToMessage() const206 protobufs::Transformation TransformationPropagateInstructionUp::ToMessage()
207     const {
208   protobufs::Transformation result;
209   *result.mutable_propagate_instruction_up() = message_;
210   return result;
211 }
212 
IsOpcodeSupported(SpvOp opcode)213 bool TransformationPropagateInstructionUp::IsOpcodeSupported(SpvOp opcode) {
214   // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605):
215   //  We only support "simple" instructions that don't work with memory.
216   //  We should extend this so that we support the ones that modify the memory
217   //  too.
218   switch (opcode) {
219     case SpvOpUndef:
220     case SpvOpAccessChain:
221     case SpvOpInBoundsAccessChain:
222     case SpvOpArrayLength:
223     case SpvOpVectorExtractDynamic:
224     case SpvOpVectorInsertDynamic:
225     case SpvOpVectorShuffle:
226     case SpvOpCompositeConstruct:
227     case SpvOpCompositeExtract:
228     case SpvOpCompositeInsert:
229     case SpvOpCopyObject:
230     case SpvOpTranspose:
231     case SpvOpConvertFToU:
232     case SpvOpConvertFToS:
233     case SpvOpConvertSToF:
234     case SpvOpConvertUToF:
235     case SpvOpUConvert:
236     case SpvOpSConvert:
237     case SpvOpFConvert:
238     case SpvOpQuantizeToF16:
239     case SpvOpSatConvertSToU:
240     case SpvOpSatConvertUToS:
241     case SpvOpBitcast:
242     case SpvOpSNegate:
243     case SpvOpFNegate:
244     case SpvOpIAdd:
245     case SpvOpFAdd:
246     case SpvOpISub:
247     case SpvOpFSub:
248     case SpvOpIMul:
249     case SpvOpFMul:
250     case SpvOpUDiv:
251     case SpvOpSDiv:
252     case SpvOpFDiv:
253     case SpvOpUMod:
254     case SpvOpSRem:
255     case SpvOpSMod:
256     case SpvOpFRem:
257     case SpvOpFMod:
258     case SpvOpVectorTimesScalar:
259     case SpvOpMatrixTimesScalar:
260     case SpvOpVectorTimesMatrix:
261     case SpvOpMatrixTimesVector:
262     case SpvOpMatrixTimesMatrix:
263     case SpvOpOuterProduct:
264     case SpvOpDot:
265     case SpvOpIAddCarry:
266     case SpvOpISubBorrow:
267     case SpvOpUMulExtended:
268     case SpvOpSMulExtended:
269     case SpvOpAny:
270     case SpvOpAll:
271     case SpvOpIsNan:
272     case SpvOpIsInf:
273     case SpvOpIsFinite:
274     case SpvOpIsNormal:
275     case SpvOpSignBitSet:
276     case SpvOpLessOrGreater:
277     case SpvOpOrdered:
278     case SpvOpUnordered:
279     case SpvOpLogicalEqual:
280     case SpvOpLogicalNotEqual:
281     case SpvOpLogicalOr:
282     case SpvOpLogicalAnd:
283     case SpvOpLogicalNot:
284     case SpvOpSelect:
285     case SpvOpIEqual:
286     case SpvOpINotEqual:
287     case SpvOpUGreaterThan:
288     case SpvOpSGreaterThan:
289     case SpvOpUGreaterThanEqual:
290     case SpvOpSGreaterThanEqual:
291     case SpvOpULessThan:
292     case SpvOpSLessThan:
293     case SpvOpULessThanEqual:
294     case SpvOpSLessThanEqual:
295     case SpvOpFOrdEqual:
296     case SpvOpFUnordEqual:
297     case SpvOpFOrdNotEqual:
298     case SpvOpFUnordNotEqual:
299     case SpvOpFOrdLessThan:
300     case SpvOpFUnordLessThan:
301     case SpvOpFOrdGreaterThan:
302     case SpvOpFUnordGreaterThan:
303     case SpvOpFOrdLessThanEqual:
304     case SpvOpFUnordLessThanEqual:
305     case SpvOpFOrdGreaterThanEqual:
306     case SpvOpFUnordGreaterThanEqual:
307     case SpvOpShiftRightLogical:
308     case SpvOpShiftRightArithmetic:
309     case SpvOpShiftLeftLogical:
310     case SpvOpBitwiseOr:
311     case SpvOpBitwiseXor:
312     case SpvOpBitwiseAnd:
313     case SpvOpNot:
314     case SpvOpBitFieldInsert:
315     case SpvOpBitFieldSExtract:
316     case SpvOpBitFieldUExtract:
317     case SpvOpBitReverse:
318     case SpvOpBitCount:
319     case SpvOpCopyLogical:
320     case SpvOpPtrEqual:
321     case SpvOpPtrNotEqual:
322       return true;
323     default:
324       return false;
325   }
326 }
327 
328 opt::Instruction*
GetInstructionToPropagate(opt::IRContext * ir_context,uint32_t block_id)329 TransformationPropagateInstructionUp::GetInstructionToPropagate(
330     opt::IRContext* ir_context, uint32_t block_id) {
331   auto* block = ir_context->cfg()->block(block_id);
332   assert(block && "|block_id| is invalid");
333 
334   for (auto& inst : *block) {
335     // We look for the first instruction in the block that satisfies the
336     // following rules:
337     // - it's not an OpPhi
338     // - it must be supported by this transformation
339     // - it may depend only on instructions from different basic blocks or on
340     //   OpPhi instructions from the same basic block.
341     if (inst.opcode() == SpvOpPhi || !IsOpcodeSupported(inst.opcode()) ||
342         !inst.type_id() || !inst.result_id()) {
343       continue;
344     }
345 
346     const auto* inst_type = ir_context->get_type_mgr()->GetType(inst.type_id());
347     assert(inst_type && "|inst| has invalid type");
348 
349     if (inst_type->AsSampledImage()) {
350       // OpTypeSampledImage cannot be used as an argument to OpPhi instructions,
351       // thus we cannot support this type.
352       continue;
353     }
354 
355     if (!ir_context->get_feature_mgr()->HasCapability(
356             SpvCapabilityVariablePointersStorageBuffer) &&
357         ContainsPointers(*inst_type)) {
358       // OpPhi supports pointer operands only with VariablePointers or
359       // VariablePointersStorageBuffer capabilities.
360       //
361       // Note that VariablePointers capability implicitly declares
362       // VariablePointersStorageBuffer capability.
363       continue;
364     }
365 
366     if (!HasValidDependencies(ir_context, &inst)) {
367       continue;
368     }
369 
370     return &inst;
371   }
372 
373   return nullptr;
374 }
375 
IsApplicableToBlock(opt::IRContext * ir_context,uint32_t block_id)376 bool TransformationPropagateInstructionUp::IsApplicableToBlock(
377     opt::IRContext* ir_context, uint32_t block_id) {
378   // Check that |block_id| is valid.
379   const auto* label_inst = ir_context->get_def_use_mgr()->GetDef(block_id);
380   if (!label_inst || label_inst->opcode() != SpvOpLabel) {
381     return false;
382   }
383 
384   // Check that |block| has predecessors.
385   const auto& predecessors = ir_context->cfg()->preds(block_id);
386   if (predecessors.empty()) {
387     return false;
388   }
389 
390   // The block must contain an instruction to propagate.
391   const auto* inst_to_propagate =
392       GetInstructionToPropagate(ir_context, block_id);
393   if (!inst_to_propagate) {
394     return false;
395   }
396 
397   // We should be able to insert |inst_to_propagate| into every predecessor of
398   // |block|.
399   return std::all_of(predecessors.begin(), predecessors.end(),
400                      [ir_context, inst_to_propagate](uint32_t predecessor_id) {
401                        return fuzzerutil::GetLastInsertBeforeInstruction(
402                                   ir_context, predecessor_id,
403                                   inst_to_propagate->opcode()) != nullptr;
404                      });
405 }
406 
GetFreshIds() const407 std::unordered_set<uint32_t> TransformationPropagateInstructionUp::GetFreshIds()
408     const {
409   std::unordered_set<uint32_t> result;
410   for (auto& pair : message_.predecessor_id_to_fresh_id()) {
411     result.insert(pair.second());
412   }
413   return result;
414 }
415 
416 }  // namespace fuzz
417 }  // namespace spvtools
418