/src/shaderc/third_party/spirv-tools/source/val/function.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2015-2016 The Khronos Group Inc. |
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/val/function.h" |
16 | | |
17 | | #include <algorithm> |
18 | | #include <cassert> |
19 | | #include <sstream> |
20 | | #include <unordered_map> |
21 | | #include <utility> |
22 | | |
23 | | #include "source/cfa.h" |
24 | | #include "source/val/basic_block.h" |
25 | | #include "source/val/construct.h" |
26 | | #include "source/val/validate.h" |
27 | | |
28 | | namespace spvtools { |
29 | | namespace val { |
30 | | |
31 | | // Universal Limit of ResultID + 1 |
32 | | static const uint32_t kInvalidId = 0x400000; |
33 | | |
34 | | Function::Function(uint32_t function_id, uint32_t result_type_id, |
35 | | spv::FunctionControlMask function_control, |
36 | | uint32_t function_type_id) |
37 | 2.20k | : id_(function_id), |
38 | 2.20k | function_type_id_(function_type_id), |
39 | 2.20k | result_type_id_(result_type_id), |
40 | 2.20k | function_control_(function_control), |
41 | 2.20k | declaration_type_(FunctionDecl::kFunctionDeclUnknown), |
42 | 2.20k | end_has_been_registered_(false), |
43 | 2.20k | blocks_(), |
44 | 2.20k | current_block_(nullptr), |
45 | 2.20k | pseudo_entry_block_(0), |
46 | 2.20k | pseudo_exit_block_(kInvalidId), |
47 | 2.20k | cfg_constructs_(), |
48 | 2.20k | variable_ids_(), |
49 | 2.20k | parameter_ids_() {} |
50 | | |
51 | 14.4k | bool Function::IsFirstBlock(uint32_t block_id) const { |
52 | 14.4k | return !ordered_blocks_.empty() && *first_block() == block_id; |
53 | 14.4k | } |
54 | | |
55 | | spv_result_t Function::RegisterFunctionParameter(uint32_t parameter_id, |
56 | 1.36k | uint32_t type_id) { |
57 | 1.36k | assert(current_block_ == nullptr && |
58 | 1.36k | "RegisterFunctionParameter can only be called when parsing the binary " |
59 | 1.36k | "outside of a block"); |
60 | | // TODO(umar): Validate function parameter type order and count |
61 | | // TODO(umar): Use these variables to validate parameter type |
62 | 1.36k | (void)parameter_id; |
63 | 1.36k | (void)type_id; |
64 | 1.36k | return SPV_SUCCESS; |
65 | 1.36k | } |
66 | | |
67 | | spv_result_t Function::RegisterLoopMerge(uint32_t merge_id, |
68 | 970 | uint32_t continue_id) { |
69 | 970 | RegisterBlock(merge_id, false); |
70 | 970 | RegisterBlock(continue_id, false); |
71 | 970 | BasicBlock& merge_block = blocks_.at(merge_id); |
72 | 970 | BasicBlock& continue_target_block = blocks_.at(continue_id); |
73 | 970 | assert(current_block_ && |
74 | 970 | "RegisterLoopMerge must be called when called within a block"); |
75 | 970 | current_block_->RegisterStructuralSuccessor(&merge_block); |
76 | 970 | current_block_->RegisterStructuralSuccessor(&continue_target_block); |
77 | | |
78 | 970 | current_block_->set_type(kBlockTypeLoop); |
79 | 970 | merge_block.set_type(kBlockTypeMerge); |
80 | 970 | continue_target_block.set_type(kBlockTypeContinue); |
81 | 970 | Construct& loop_construct = |
82 | 970 | AddConstruct({ConstructType::kLoop, current_block_, &merge_block}); |
83 | 970 | Construct& continue_construct = |
84 | 970 | AddConstruct({ConstructType::kContinue, &continue_target_block}); |
85 | | |
86 | 970 | continue_construct.set_corresponding_constructs({&loop_construct}); |
87 | 970 | loop_construct.set_corresponding_constructs({&continue_construct}); |
88 | 970 | merge_block_header_[&merge_block] = current_block_; |
89 | 970 | if (continue_target_headers_.find(&continue_target_block) == |
90 | 970 | continue_target_headers_.end()) { |
91 | 970 | continue_target_headers_[&continue_target_block] = {current_block_}; |
92 | 970 | } else { |
93 | 0 | continue_target_headers_[&continue_target_block].push_back(current_block_); |
94 | 0 | } |
95 | | |
96 | 970 | return SPV_SUCCESS; |
97 | 970 | } |
98 | | |
99 | 2.46k | spv_result_t Function::RegisterSelectionMerge(uint32_t merge_id) { |
100 | 2.46k | RegisterBlock(merge_id, false); |
101 | 2.46k | BasicBlock& merge_block = blocks_.at(merge_id); |
102 | 2.46k | current_block_->set_type(kBlockTypeSelection); |
103 | 2.46k | merge_block.set_type(kBlockTypeMerge); |
104 | 2.46k | merge_block_header_[&merge_block] = current_block_; |
105 | 2.46k | current_block_->RegisterStructuralSuccessor(&merge_block); |
106 | | |
107 | 2.46k | AddConstruct({ConstructType::kSelection, current_block(), &merge_block}); |
108 | | |
109 | 2.46k | return SPV_SUCCESS; |
110 | 2.46k | } |
111 | | |
112 | 2.20k | spv_result_t Function::RegisterSetFunctionDeclType(FunctionDecl type) { |
113 | 2.20k | assert(declaration_type_ == FunctionDecl::kFunctionDeclUnknown); |
114 | 2.20k | declaration_type_ = type; |
115 | 2.20k | return SPV_SUCCESS; |
116 | 2.20k | } |
117 | | |
118 | 17.0k | spv_result_t Function::RegisterBlock(uint32_t block_id, bool is_definition) { |
119 | 17.0k | assert( |
120 | 17.0k | declaration_type_ == FunctionDecl::kFunctionDeclDefinition && |
121 | 17.0k | "RegisterBlocks can only be called after declaration_type_ is defined"); |
122 | | |
123 | 17.0k | std::unordered_map<uint32_t, BasicBlock>::iterator inserted_block; |
124 | 17.0k | bool success = false; |
125 | 17.0k | tie(inserted_block, success) = |
126 | 17.0k | blocks_.insert({block_id, BasicBlock(block_id)}); |
127 | 17.0k | if (is_definition) { // new block definition |
128 | 12.6k | assert(current_block_ == nullptr && |
129 | 12.6k | "Register Block can only be called when parsing a binary outside of " |
130 | 12.6k | "a BasicBlock"); |
131 | | |
132 | 12.6k | undefined_blocks_.erase(block_id); |
133 | 12.6k | current_block_ = &inserted_block->second; |
134 | 12.6k | ordered_blocks_.push_back(current_block_); |
135 | 12.6k | } else if (success) { // Block doesn't exist but this is not a definition |
136 | 4.40k | undefined_blocks_.insert(block_id); |
137 | 4.40k | } |
138 | | |
139 | 17.0k | return SPV_SUCCESS; |
140 | 17.0k | } |
141 | | |
142 | 12.6k | void Function::RegisterBlockEnd(std::vector<uint32_t> next_list) { |
143 | 12.6k | assert( |
144 | 12.6k | current_block_ && |
145 | 12.6k | "RegisterBlockEnd can only be called when parsing a binary in a block"); |
146 | 12.6k | std::vector<BasicBlock*> next_blocks; |
147 | 12.6k | next_blocks.reserve(next_list.size()); |
148 | | |
149 | 12.6k | std::unordered_map<uint32_t, BasicBlock>::iterator inserted_block; |
150 | 12.6k | bool success; |
151 | 14.4k | for (uint32_t successor_id : next_list) { |
152 | 14.4k | tie(inserted_block, success) = |
153 | 14.4k | blocks_.insert({successor_id, BasicBlock(successor_id)}); |
154 | 14.4k | if (success) { |
155 | 6.06k | undefined_blocks_.insert(successor_id); |
156 | 6.06k | } |
157 | 14.4k | next_blocks.push_back(&inserted_block->second); |
158 | 14.4k | } |
159 | | |
160 | 12.6k | if (current_block_->is_type(kBlockTypeLoop)) { |
161 | | // For each loop header, record the set of its successors, and include |
162 | | // its continue target if the continue target is not the loop header |
163 | | // itself. |
164 | 958 | std::vector<BasicBlock*>& next_blocks_plus_continue_target = |
165 | 958 | loop_header_successors_plus_continue_target_map_[current_block_]; |
166 | 958 | next_blocks_plus_continue_target = next_blocks; |
167 | 958 | auto continue_target = |
168 | 958 | FindConstructForEntryBlock(current_block_, ConstructType::kLoop) |
169 | 958 | .corresponding_constructs() |
170 | 958 | .back() |
171 | 958 | ->entry_block(); |
172 | 958 | if (continue_target != current_block_) { |
173 | 958 | next_blocks_plus_continue_target.push_back(continue_target); |
174 | 958 | } |
175 | 958 | } |
176 | | |
177 | 12.6k | current_block_->RegisterSuccessors(next_blocks); |
178 | 12.6k | current_block_ = nullptr; |
179 | 12.6k | return; |
180 | 12.6k | } |
181 | | |
182 | 2.19k | void Function::RegisterFunctionEnd() { |
183 | 2.19k | if (!end_has_been_registered_) { |
184 | 2.19k | end_has_been_registered_ = true; |
185 | | |
186 | 2.19k | ComputeAugmentedCFG(); |
187 | 2.19k | } |
188 | 2.19k | } |
189 | | |
190 | 5.69k | size_t Function::block_count() const { return blocks_.size(); } |
191 | | |
192 | 2.13k | size_t Function::undefined_block_count() const { |
193 | 2.13k | return undefined_blocks_.size(); |
194 | 2.13k | } |
195 | | |
196 | 0 | const std::vector<BasicBlock*>& Function::ordered_blocks() const { |
197 | 0 | return ordered_blocks_; |
198 | 0 | } |
199 | 8.52k | std::vector<BasicBlock*>& Function::ordered_blocks() { return ordered_blocks_; } |
200 | | |
201 | 274k | const BasicBlock* Function::current_block() const { return current_block_; } |
202 | 165k | BasicBlock* Function::current_block() { return current_block_; } |
203 | | |
204 | 0 | const std::list<Construct>& Function::constructs() const { |
205 | 0 | return cfg_constructs_; |
206 | 0 | } |
207 | 4.26k | std::list<Construct>& Function::constructs() { return cfg_constructs_; } |
208 | | |
209 | 14.4k | const BasicBlock* Function::first_block() const { |
210 | 14.4k | if (ordered_blocks_.empty()) return nullptr; |
211 | 14.4k | return ordered_blocks_[0]; |
212 | 14.4k | } |
213 | 8.65k | BasicBlock* Function::first_block() { |
214 | 8.65k | if (ordered_blocks_.empty()) return nullptr; |
215 | 8.65k | return ordered_blocks_[0]; |
216 | 8.65k | } |
217 | | |
218 | 4.37k | bool Function::IsBlockType(uint32_t merge_block_id, BlockType type) const { |
219 | 4.37k | bool ret = false; |
220 | 4.37k | const BasicBlock* block; |
221 | 4.37k | std::tie(block, std::ignore) = GetBlock(merge_block_id); |
222 | 4.37k | if (block) { |
223 | 946 | ret = block->is_type(type); |
224 | 946 | } |
225 | 4.37k | return ret; |
226 | 4.37k | } |
227 | | |
228 | 9.04k | std::pair<const BasicBlock*, bool> Function::GetBlock(uint32_t block_id) const { |
229 | 9.04k | const auto b = blocks_.find(block_id); |
230 | 9.04k | if (b != end(blocks_)) { |
231 | 5.61k | const BasicBlock* block = &(b->second); |
232 | 5.61k | bool defined = |
233 | 5.61k | undefined_blocks_.find(block->id()) == std::end(undefined_blocks_); |
234 | 5.61k | return std::make_pair(block, defined); |
235 | 5.61k | } else { |
236 | 3.43k | return std::make_pair(nullptr, false); |
237 | 3.43k | } |
238 | 9.04k | } |
239 | | |
240 | 3.91k | std::pair<BasicBlock*, bool> Function::GetBlock(uint32_t block_id) { |
241 | 3.91k | const BasicBlock* out; |
242 | 3.91k | bool defined; |
243 | 3.91k | std::tie(out, defined) = |
244 | 3.91k | const_cast<const Function*>(this)->GetBlock(block_id); |
245 | 3.91k | return std::make_pair(const_cast<BasicBlock*>(out), defined); |
246 | 3.91k | } |
247 | | |
248 | 2.13k | Function::GetBlocksFunction Function::AugmentedCFGSuccessorsFunction() const { |
249 | 45.9k | return [this](const BasicBlock* block) { |
250 | 45.9k | auto where = augmented_successors_map_.find(block); |
251 | 45.9k | return where == augmented_successors_map_.end() ? block->successors() |
252 | 45.9k | : &(*where).second; |
253 | 45.9k | }; |
254 | 2.13k | } |
255 | | |
256 | 2.13k | Function::GetBlocksFunction Function::AugmentedCFGPredecessorsFunction() const { |
257 | 25.0k | return [this](const BasicBlock* block) { |
258 | 25.0k | auto where = augmented_predecessors_map_.find(block); |
259 | 25.0k | return where == augmented_predecessors_map_.end() ? block->predecessors() |
260 | 25.0k | : &(*where).second; |
261 | 25.0k | }; |
262 | 2.13k | } |
263 | | |
264 | | Function::GetBlocksFunction Function::AugmentedStructuralCFGSuccessorsFunction() |
265 | 6.39k | const { |
266 | 136k | return [this](const BasicBlock* block) { |
267 | 136k | auto where = augmented_successors_map_.find(block); |
268 | 136k | return where == augmented_successors_map_.end() |
269 | 136k | ? block->structural_successors() |
270 | 136k | : &(*where).second; |
271 | 136k | }; |
272 | 6.39k | } |
273 | | |
274 | | Function::GetBlocksFunction |
275 | 4.26k | Function::AugmentedStructuralCFGPredecessorsFunction() const { |
276 | 81.6k | return [this](const BasicBlock* block) { |
277 | 81.6k | auto where = augmented_predecessors_map_.find(block); |
278 | 81.6k | return where == augmented_predecessors_map_.end() |
279 | 81.6k | ? block->structural_predecessors() |
280 | 81.6k | : &(*where).second; |
281 | 81.6k | }; |
282 | 4.26k | } |
283 | | |
284 | 2.19k | void Function::ComputeAugmentedCFG() { |
285 | | // Compute the successors of the pseudo-entry block, and |
286 | | // the predecessors of the pseudo exit block. |
287 | 58.7k | auto succ_func = [](const BasicBlock* b) { |
288 | 58.7k | return b->structural_successors(); |
289 | 58.7k | }; |
290 | 58.9k | auto pred_func = [](const BasicBlock* b) { |
291 | 58.9k | return b->structural_predecessors(); |
292 | 58.9k | }; |
293 | 2.19k | CFA<BasicBlock>::ComputeAugmentedCFG( |
294 | 2.19k | ordered_blocks_, &pseudo_entry_block_, &pseudo_exit_block_, |
295 | 2.19k | &augmented_successors_map_, &augmented_predecessors_map_, succ_func, |
296 | 2.19k | pred_func); |
297 | 2.19k | } |
298 | | |
299 | 4.40k | Construct& Function::AddConstruct(const Construct& new_construct) { |
300 | 4.40k | cfg_constructs_.push_back(new_construct); |
301 | 4.40k | auto& result = cfg_constructs_.back(); |
302 | 4.40k | entry_block_to_construct_[std::make_pair(new_construct.entry_block(), |
303 | 4.40k | new_construct.type())] = &result; |
304 | 4.40k | return result; |
305 | 4.40k | } |
306 | | |
307 | | Construct& Function::FindConstructForEntryBlock(const BasicBlock* entry_block, |
308 | 958 | ConstructType type) { |
309 | 958 | auto where = |
310 | 958 | entry_block_to_construct_.find(std::make_pair(entry_block, type)); |
311 | 958 | assert(where != entry_block_to_construct_.end()); |
312 | 958 | auto construct_ptr = (*where).second; |
313 | 958 | assert(construct_ptr); |
314 | 958 | return *construct_ptr; |
315 | 958 | } |
316 | | |
317 | 22.8k | int Function::GetBlockDepth(BasicBlock* bb) { |
318 | | // Guard against nullptr. |
319 | 22.8k | if (!bb) { |
320 | 0 | return 0; |
321 | 0 | } |
322 | | // Only calculate the depth if it's not already calculated. |
323 | | // This function uses memoization to avoid duplicate CFG depth calculations. |
324 | 22.8k | if (block_depth_.find(bb) != block_depth_.end()) { |
325 | 10.3k | return block_depth_[bb]; |
326 | 10.3k | } |
327 | | // Avoid recursion. Something is wrong if the same block is encountered |
328 | | // multiple times. |
329 | 12.5k | block_depth_[bb] = 0; |
330 | | |
331 | 12.5k | BasicBlock* bb_dom = bb->immediate_dominator(); |
332 | 12.5k | if (!bb_dom || bb == bb_dom) { |
333 | | // This block has no dominator, so it's at depth 0. |
334 | 2.13k | block_depth_[bb] = 0; |
335 | 10.3k | } else if (bb->is_type(kBlockTypeContinue)) { |
336 | | // This rule must precede the rule for merge blocks in order to set up |
337 | | // depths correctly. If a block is both a merge and continue then the merge |
338 | | // is nested within the continue's loop (or the graph is incorrect). |
339 | | // The depth of the continue block entry point is 1 + loop header depth. |
340 | 946 | Construct* continue_construct = |
341 | 946 | entry_block_to_construct_[std::make_pair(bb, ConstructType::kContinue)]; |
342 | 946 | assert(continue_construct); |
343 | | // Continue construct has only 1 corresponding construct (loop header). |
344 | 946 | Construct* loop_construct = |
345 | 946 | continue_construct->corresponding_constructs()[0]; |
346 | 946 | assert(loop_construct); |
347 | 946 | BasicBlock* loop_header = loop_construct->entry_block(); |
348 | | // The continue target may be the loop itself (while 1). |
349 | | // In such cases, the depth of the continue block is: 1 + depth of the |
350 | | // loop's dominator block. |
351 | 946 | if (loop_header == bb) { |
352 | 0 | block_depth_[bb] = 1 + GetBlockDepth(bb_dom); |
353 | 946 | } else { |
354 | 946 | block_depth_[bb] = 1 + GetBlockDepth(loop_header); |
355 | 946 | } |
356 | 9.42k | } else if (bb->is_type(kBlockTypeMerge)) { |
357 | | // If this is a merge block, its depth is equal to the block before |
358 | | // branching. |
359 | 3.40k | BasicBlock* header = merge_block_header_[bb]; |
360 | 3.40k | assert(header); |
361 | 3.40k | block_depth_[bb] = GetBlockDepth(header); |
362 | 6.01k | } else if (bb_dom->is_type(kBlockTypeSelection) || |
363 | 6.01k | bb_dom->is_type(kBlockTypeLoop)) { |
364 | | // The dominator of the given block is a header block. So, the nesting |
365 | | // depth of this block is: 1 + nesting depth of the header. |
366 | 4.18k | block_depth_[bb] = 1 + GetBlockDepth(bb_dom); |
367 | 4.18k | } else { |
368 | 1.83k | block_depth_[bb] = GetBlockDepth(bb_dom); |
369 | 1.83k | } |
370 | 12.5k | return block_depth_[bb]; |
371 | 22.8k | } |
372 | | |
373 | | void Function::RegisterExecutionModelLimitation(spv::ExecutionModel model, |
374 | 0 | const std::string& message) { |
375 | 0 | execution_model_limitations_.push_back( |
376 | 0 | [model, message](spv::ExecutionModel in_model, std::string* out_message) { |
377 | 0 | if (model != in_model) { |
378 | 0 | if (out_message) { |
379 | 0 | *out_message = message; |
380 | 0 | } |
381 | 0 | return false; |
382 | 0 | } |
383 | 0 | return true; |
384 | 0 | }); |
385 | 0 | } |
386 | | |
387 | | bool Function::IsCompatibleWithExecutionModel(spv::ExecutionModel model, |
388 | 2.13k | std::string* reason) const { |
389 | 2.13k | bool return_value = true; |
390 | 2.13k | std::stringstream ss_reason; |
391 | | |
392 | 5.62k | for (const auto& is_compatible : execution_model_limitations_) { |
393 | 5.62k | std::string message; |
394 | 5.62k | if (!is_compatible(model, &message)) { |
395 | 0 | if (!reason) return false; |
396 | 0 | return_value = false; |
397 | 0 | if (!message.empty()) { |
398 | 0 | ss_reason << message << "\n"; |
399 | 0 | } |
400 | 0 | } |
401 | 5.62k | } |
402 | | |
403 | 2.13k | if (!return_value && reason) { |
404 | 0 | *reason = ss_reason.str(); |
405 | 0 | } |
406 | | |
407 | 2.13k | return return_value; |
408 | 2.13k | } |
409 | | |
410 | | bool Function::CheckLimitations(const ValidationState_t& _, |
411 | | const Function* entry_point, |
412 | 2.13k | std::string* reason) const { |
413 | 2.13k | bool return_value = true; |
414 | 2.13k | std::stringstream ss_reason; |
415 | | |
416 | 2.13k | for (const auto& is_compatible : limitations_) { |
417 | 0 | std::string message; |
418 | 0 | if (!is_compatible(_, entry_point, &message)) { |
419 | 0 | if (!reason) return false; |
420 | 0 | return_value = false; |
421 | 0 | if (!message.empty()) { |
422 | 0 | ss_reason << message << "\n"; |
423 | 0 | } |
424 | 0 | } |
425 | 0 | } |
426 | | |
427 | 2.13k | if (!return_value && reason) { |
428 | 0 | *reason = ss_reason.str(); |
429 | 0 | } |
430 | | |
431 | 2.13k | return return_value; |
432 | 2.13k | } |
433 | | |
434 | | } // namespace val |
435 | | } // namespace spvtools |