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 ¶meter_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