• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2020 André Perez Maselco
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_inline_function.h"
16 
17 #include "source/fuzz/fuzzer_util.h"
18 #include "source/fuzz/instruction_descriptor.h"
19 
20 namespace spvtools {
21 namespace fuzz {
22 
TransformationInlineFunction(protobufs::TransformationInlineFunction message)23 TransformationInlineFunction::TransformationInlineFunction(
24     protobufs::TransformationInlineFunction message)
25     : message_(std::move(message)) {}
26 
TransformationInlineFunction(uint32_t function_call_id,const std::map<uint32_t,uint32_t> & result_id_map)27 TransformationInlineFunction::TransformationInlineFunction(
28     uint32_t function_call_id,
29     const std::map<uint32_t, uint32_t>& result_id_map) {
30   message_.set_function_call_id(function_call_id);
31   *message_.mutable_result_id_map() =
32       fuzzerutil::MapToRepeatedUInt32Pair(result_id_map);
33 }
34 
IsApplicable(opt::IRContext * ir_context,const TransformationContext & transformation_context) const35 bool TransformationInlineFunction::IsApplicable(
36     opt::IRContext* ir_context,
37     const TransformationContext& transformation_context) const {
38   // The values in the |message_.result_id_map| must be all fresh and all
39   // distinct.
40   const auto result_id_map =
41       fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
42   std::set<uint32_t> ids_used_by_this_transformation;
43   for (auto& pair : result_id_map) {
44     if (!CheckIdIsFreshAndNotUsedByThisTransformation(
45             pair.second, ir_context, &ids_used_by_this_transformation)) {
46       return false;
47     }
48   }
49 
50   // |function_call_instruction| must be suitable for inlining.
51   auto* function_call_instruction =
52       ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
53   if (!IsSuitableForInlining(ir_context, function_call_instruction)) {
54     return false;
55   }
56 
57   // |function_call_instruction| must be the penultimate instruction in its
58   // block and its block termination instruction must be an OpBranch. This
59   // avoids the case where the penultimate instruction is an OpLoopMerge, which
60   // would make the back-edge block not branch to the loop header.
61   auto* function_call_instruction_block =
62       ir_context->get_instr_block(function_call_instruction);
63   if (function_call_instruction !=
64           &*--function_call_instruction_block->tail() ||
65       function_call_instruction_block->terminator()->opcode() != SpvOpBranch) {
66     return false;
67   }
68 
69   auto* called_function = fuzzerutil::FindFunction(
70       ir_context, function_call_instruction->GetSingleWordInOperand(0));
71   for (auto& block : *called_function) {
72     // Since the entry block label will not be inlined, only the remaining
73     // labels must have a corresponding value in the map.
74     if (&block != &*called_function->entry() &&
75         !result_id_map.count(block.id()) &&
76         !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
77       return false;
78     }
79 
80     // |result_id_map| must have an entry for every result id in the called
81     // function.
82     for (auto& instruction : block) {
83       // If |instruction| has result id, then it must have a mapped id in
84       // |result_id_map|.
85       if (instruction.HasResultId() &&
86           !result_id_map.count(instruction.result_id()) &&
87           !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
88         return false;
89       }
90     }
91   }
92 
93   // |result_id_map| must not contain an entry for any parameter of the function
94   // that is being inlined.
95   bool found_entry_for_parameter = false;
96   called_function->ForEachParam(
97       [&result_id_map, &found_entry_for_parameter](opt::Instruction* param) {
98         if (result_id_map.count(param->result_id())) {
99           found_entry_for_parameter = true;
100         }
101       });
102   return !found_entry_for_parameter;
103 }
104 
Apply(opt::IRContext * ir_context,TransformationContext * transformation_context) const105 void TransformationInlineFunction::Apply(
106     opt::IRContext* ir_context,
107     TransformationContext* transformation_context) const {
108   auto* function_call_instruction =
109       ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
110   auto* caller_function =
111       ir_context->get_instr_block(function_call_instruction)->GetParent();
112   auto* called_function = fuzzerutil::FindFunction(
113       ir_context, function_call_instruction->GetSingleWordInOperand(0));
114   std::map<uint32_t, uint32_t> result_id_map =
115       fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
116 
117   // If there are gaps in the result id map, fill them using overflow ids.
118   for (auto& block : *called_function) {
119     if (&block != &*called_function->entry() &&
120         !result_id_map.count(block.id())) {
121       result_id_map.insert(
122           {block.id(),
123            transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
124     }
125     for (auto& instruction : block) {
126       // If |instruction| has result id, then it must have a mapped id in
127       // |result_id_map|.
128       if (instruction.HasResultId() &&
129           !result_id_map.count(instruction.result_id())) {
130         result_id_map.insert({instruction.result_id(),
131                               transformation_context->GetOverflowIdSource()
132                                   ->GetNextOverflowId()});
133       }
134     }
135   }
136 
137   auto* successor_block = ir_context->cfg()->block(
138       ir_context->get_instr_block(function_call_instruction)
139           ->terminator()
140           ->GetSingleWordInOperand(0));
141 
142   // Inline the |called_function| entry block.
143   for (auto& entry_block_instruction : *called_function->entry()) {
144     opt::Instruction* inlined_instruction;
145 
146     if (entry_block_instruction.opcode() == SpvOpVariable) {
147       // All OpVariable instructions in a function must be in the first block
148       // in the function.
149       inlined_instruction = caller_function->begin()->begin()->InsertBefore(
150           std::unique_ptr<opt::Instruction>(
151               entry_block_instruction.Clone(ir_context)));
152     } else {
153       inlined_instruction = function_call_instruction->InsertBefore(
154           std::unique_ptr<opt::Instruction>(
155               entry_block_instruction.Clone(ir_context)));
156     }
157 
158     AdaptInlinedInstruction(result_id_map, ir_context, inlined_instruction);
159   }
160 
161   // If the function call's successor block contains OpPhi instructions that
162   // refer to the block containing the call then these will need to be rewritten
163   // to instead refer to the block associated with "returning" from the inlined
164   // function, as this block will be the predecessor of what used to be the
165   // function call's successor block.  We look out for this block.
166   uint32_t new_return_block_id = 0;
167 
168   // Inline the |called_function| non-entry blocks.
169   for (auto& block : *called_function) {
170     if (&block == &*called_function->entry()) {
171       continue;
172     }
173 
174     // Check whether this is the function's return block.  Take note if it is,
175     // so that OpPhi instructions in the successor of the original function call
176     // block can be re-written.
177     if (block.terminator()->IsReturn()) {
178       assert(new_return_block_id == 0 &&
179              "There should be only one return block.");
180       new_return_block_id = result_id_map.at(block.id());
181     }
182 
183     auto* cloned_block = block.Clone(ir_context);
184     cloned_block = caller_function->InsertBasicBlockBefore(
185         std::unique_ptr<opt::BasicBlock>(cloned_block), successor_block);
186     cloned_block->GetLabel()->SetResultId(result_id_map.at(cloned_block->id()));
187     fuzzerutil::UpdateModuleIdBound(ir_context, cloned_block->id());
188 
189     for (auto& inlined_instruction : *cloned_block) {
190       AdaptInlinedInstruction(result_id_map, ir_context, &inlined_instruction);
191     }
192   }
193 
194   opt::BasicBlock* block_containing_function_call =
195       ir_context->get_instr_block(function_call_instruction);
196 
197   assert(((new_return_block_id == 0) ==
198           called_function->entry()->terminator()->IsReturn()) &&
199          "We should have found a return block unless the function being "
200          "inlined returns in its first block.");
201   if (new_return_block_id != 0) {
202     // Rewrite any OpPhi instructions in the successor block so that they refer
203     // to the new return block instead of the block that originally contained
204     // the function call.
205     ir_context->get_def_use_mgr()->ForEachUse(
206         block_containing_function_call->id(),
207         [ir_context, new_return_block_id, successor_block](
208             opt::Instruction* use_instruction, uint32_t operand_index) {
209           if (use_instruction->opcode() == SpvOpPhi &&
210               ir_context->get_instr_block(use_instruction) == successor_block) {
211             use_instruction->SetOperand(operand_index, {new_return_block_id});
212           }
213         });
214   }
215 
216   // Removes the function call instruction and its block termination instruction
217   // from |caller_function|.
218   ir_context->KillInst(block_containing_function_call->terminator());
219   ir_context->KillInst(function_call_instruction);
220 
221   // Since the SPIR-V module has changed, no analyses must be validated.
222   ir_context->InvalidateAnalysesExceptFor(
223       opt::IRContext::Analysis::kAnalysisNone);
224 }
225 
ToMessage() const226 protobufs::Transformation TransformationInlineFunction::ToMessage() const {
227   protobufs::Transformation result;
228   *result.mutable_inline_function() = message_;
229   return result;
230 }
231 
IsSuitableForInlining(opt::IRContext * ir_context,opt::Instruction * function_call_instruction)232 bool TransformationInlineFunction::IsSuitableForInlining(
233     opt::IRContext* ir_context, opt::Instruction* function_call_instruction) {
234   // |function_call_instruction| must be defined and must be an OpFunctionCall
235   // instruction.
236   if (!function_call_instruction ||
237       function_call_instruction->opcode() != SpvOpFunctionCall) {
238     return false;
239   }
240 
241   // If |function_call_instruction| return type is void, then
242   // |function_call_instruction| must not have uses.
243   if (ir_context->get_type_mgr()
244           ->GetType(function_call_instruction->type_id())
245           ->AsVoid() &&
246       ir_context->get_def_use_mgr()->NumUses(function_call_instruction) != 0) {
247     return false;
248   }
249 
250   // |called_function| must not have an early return.
251   auto called_function = fuzzerutil::FindFunction(
252       ir_context, function_call_instruction->GetSingleWordInOperand(0));
253   if (called_function->HasEarlyReturn()) {
254     return false;
255   }
256 
257   // |called_function| must not use OpKill or OpUnreachable.
258   if (fuzzerutil::FunctionContainsOpKillOrUnreachable(*called_function)) {
259     return false;
260   }
261 
262   return true;
263 }
264 
AdaptInlinedInstruction(const std::map<uint32_t,uint32_t> & result_id_map,opt::IRContext * ir_context,opt::Instruction * instruction_to_be_inlined) const265 void TransformationInlineFunction::AdaptInlinedInstruction(
266     const std::map<uint32_t, uint32_t>& result_id_map,
267     opt::IRContext* ir_context,
268     opt::Instruction* instruction_to_be_inlined) const {
269   auto* function_call_instruction =
270       ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
271   auto* called_function = fuzzerutil::FindFunction(
272       ir_context, function_call_instruction->GetSingleWordInOperand(0));
273 
274   const auto* function_call_block =
275       ir_context->get_instr_block(function_call_instruction);
276   assert(function_call_block && "OpFunctionCall must belong to some block");
277 
278   // Replaces the operand ids with their mapped result ids.
279   instruction_to_be_inlined->ForEachInId(
280       [called_function, function_call_instruction, &result_id_map,
281        function_call_block](uint32_t* id) {
282         // We are not inlining the entry block of the |called_function|.
283         //
284         // We must check this condition first since we can't use the fresh id
285         // from |result_id_map| even if it has one. This is because that fresh
286         // id will never be added to the module since entry blocks are not
287         // inlined.
288         if (*id == called_function->entry()->id()) {
289           *id = function_call_block->id();
290           return;
291         }
292 
293         // If |id| is mapped, then set it to its mapped value.
294         if (result_id_map.count(*id)) {
295           *id = result_id_map.at(*id);
296           return;
297         }
298 
299         uint32_t parameter_index = 0;
300         called_function->ForEachParam(
301             [id, function_call_instruction,
302              &parameter_index](opt::Instruction* parameter_instruction) {
303               // If the id is a function parameter, then set it to the
304               // parameter value passed in the function call instruction.
305               if (*id == parameter_instruction->result_id()) {
306                 // We do + 1 because the first in-operand for OpFunctionCall is
307                 // the function id that is being called.
308                 *id = function_call_instruction->GetSingleWordInOperand(
309                     parameter_index + 1);
310               }
311               parameter_index++;
312             });
313       });
314 
315   // If |instruction_to_be_inlined| has result id, then set it to its mapped
316   // value.
317   if (instruction_to_be_inlined->HasResultId()) {
318     assert(result_id_map.count(instruction_to_be_inlined->result_id()) &&
319            "Result id must be mapped to a fresh id.");
320     instruction_to_be_inlined->SetResultId(
321         result_id_map.at(instruction_to_be_inlined->result_id()));
322     fuzzerutil::UpdateModuleIdBound(ir_context,
323                                     instruction_to_be_inlined->result_id());
324   }
325 
326   // The return instruction will be changed into an OpBranch to the basic
327   // block that follows the block containing the function call.
328   if (spvOpcodeIsReturn(instruction_to_be_inlined->opcode())) {
329     uint32_t successor_block_id =
330         ir_context->get_instr_block(function_call_instruction)
331             ->terminator()
332             ->GetSingleWordInOperand(0);
333     switch (instruction_to_be_inlined->opcode()) {
334       case SpvOpReturn:
335         instruction_to_be_inlined->AddOperand(
336             {SPV_OPERAND_TYPE_ID, {successor_block_id}});
337         break;
338       case SpvOpReturnValue: {
339         instruction_to_be_inlined->InsertBefore(MakeUnique<opt::Instruction>(
340             ir_context, SpvOpCopyObject, function_call_instruction->type_id(),
341             function_call_instruction->result_id(),
342             opt::Instruction::OperandList(
343                 {{SPV_OPERAND_TYPE_ID,
344                   {instruction_to_be_inlined->GetSingleWordOperand(0)}}})));
345         instruction_to_be_inlined->SetInOperand(0, {successor_block_id});
346         break;
347       }
348       default:
349         break;
350     }
351     instruction_to_be_inlined->SetOpcode(SpvOpBranch);
352   }
353 }
354 
GetFreshIds() const355 std::unordered_set<uint32_t> TransformationInlineFunction::GetFreshIds() const {
356   std::unordered_set<uint32_t> result;
357   for (auto& pair : message_.result_id_map()) {
358     result.insert(pair.second());
359   }
360   return result;
361 }
362 
363 }  // namespace fuzz
364 }  // namespace spvtools
365