/src/spirv-tools/source/val/validate_cfg.cpp
Line | Count | Source |
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 <cassert> |
16 | | #include <functional> |
17 | | #include <iostream> |
18 | | #include <iterator> |
19 | | #include <map> |
20 | | #include <string> |
21 | | #include <tuple> |
22 | | #include <unordered_map> |
23 | | #include <unordered_set> |
24 | | #include <utility> |
25 | | #include <vector> |
26 | | |
27 | | #include "source/cfa.h" |
28 | | #include "source/opcode.h" |
29 | | #include "source/spirv_constant.h" |
30 | | #include "source/spirv_validator_options.h" |
31 | | #include "source/val/basic_block.h" |
32 | | #include "source/val/construct.h" |
33 | | #include "source/val/function.h" |
34 | | #include "source/val/validate.h" |
35 | | #include "source/val/validation_state.h" |
36 | | |
37 | | namespace spvtools { |
38 | | namespace val { |
39 | | namespace { |
40 | | |
41 | 74.5k | spv_result_t ValidatePhi(ValidationState_t& _, const Instruction* inst) { |
42 | 74.5k | auto block = inst->block(); |
43 | 74.5k | size_t num_in_ops = inst->words().size() - 3; |
44 | 74.5k | if (num_in_ops % 2 != 0) { |
45 | 3 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
46 | 3 | << "OpPhi does not have an equal number of incoming values and " |
47 | 3 | "basic blocks."; |
48 | 3 | } |
49 | | |
50 | 74.5k | if (_.IsVoidType(inst->type_id())) { |
51 | 4 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
52 | 4 | << "OpPhi must not have void result type"; |
53 | 4 | } |
54 | 74.5k | if (_.IsPointerType(inst->type_id()) && |
55 | 3 | _.addressing_model() == spv::AddressingModel::Logical) { |
56 | 2 | if (!_.features().variable_pointers) { |
57 | 2 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
58 | 2 | << "Using pointers with OpPhi requires capability " |
59 | 2 | << "VariablePointers or VariablePointersStorageBuffer"; |
60 | 2 | } |
61 | 2 | } |
62 | | |
63 | 74.5k | const Instruction* type_inst = _.FindDef(inst->type_id()); |
64 | 74.5k | assert(type_inst); |
65 | 74.5k | const spv::Op type_opcode = type_inst->opcode(); |
66 | | |
67 | 74.5k | if (!_.options()->before_hlsl_legalization && |
68 | 74.5k | !_.HasCapability(spv::Capability::BindlessTextureNV)) { |
69 | 74.5k | if (type_opcode == spv::Op::OpTypeSampledImage || |
70 | 74.5k | (_.HasCapability(spv::Capability::Shader) && |
71 | 74.4k | (type_opcode == spv::Op::OpTypeImage || |
72 | 74.4k | type_opcode == spv::Op::OpTypeSampler))) { |
73 | 5 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
74 | 5 | << "Result type cannot be Op" << spvOpcodeString(type_opcode); |
75 | 5 | } |
76 | 74.5k | } |
77 | | |
78 | | // Create a uniqued vector of predecessor ids for comparison against |
79 | | // incoming values. OpBranchConditional %cond %label %label produces two |
80 | | // predecessors in the CFG. |
81 | 74.5k | std::vector<uint32_t> pred_ids; |
82 | 74.5k | std::transform(block->predecessors()->begin(), block->predecessors()->end(), |
83 | 74.5k | std::back_inserter(pred_ids), |
84 | 158k | [](const BasicBlock* b) { return b->id(); }); |
85 | 74.5k | std::sort(pred_ids.begin(), pred_ids.end()); |
86 | 74.5k | pred_ids.erase(std::unique(pred_ids.begin(), pred_ids.end()), pred_ids.end()); |
87 | | |
88 | 74.5k | size_t num_edges = num_in_ops / 2; |
89 | 74.5k | if (num_edges != pred_ids.size()) { |
90 | 27 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
91 | 27 | << "OpPhi's number of incoming blocks (" << num_edges |
92 | 27 | << ") does not match block's predecessor count (" |
93 | 27 | << block->predecessors()->size() << ")."; |
94 | 27 | } |
95 | | |
96 | 74.5k | std::unordered_set<uint32_t> observed_predecessors; |
97 | | |
98 | 388k | for (size_t i = 3; i < inst->words().size(); ++i) { |
99 | 314k | auto inc_id = inst->word(i); |
100 | 314k | if (i % 2 == 1) { |
101 | | // Incoming value type must match the phi result type. |
102 | 157k | auto inc_type_id = _.GetTypeId(inc_id); |
103 | 157k | if (inst->type_id() != inc_type_id) { |
104 | 45 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
105 | 45 | << "OpPhi's result type <id> " << _.getIdName(inst->type_id()) |
106 | 45 | << " does not match incoming value <id> " << _.getIdName(inc_id) |
107 | 45 | << " type <id> " << _.getIdName(inc_type_id) << "."; |
108 | 45 | } |
109 | 157k | } else { |
110 | 157k | if (_.GetIdOpcode(inc_id) != spv::Op::OpLabel) { |
111 | 30 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
112 | 30 | << "OpPhi's incoming basic block <id> " << _.getIdName(inc_id) |
113 | 30 | << " is not an OpLabel."; |
114 | 30 | } |
115 | | |
116 | | // Incoming basic block must be an immediate predecessor of the phi's |
117 | | // block. |
118 | 156k | if (!std::binary_search(pred_ids.begin(), pred_ids.end(), inc_id)) { |
119 | 29 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
120 | 29 | << "OpPhi's incoming basic block <id> " << _.getIdName(inc_id) |
121 | 29 | << " is not a predecessor of <id> " << _.getIdName(block->id()) |
122 | 29 | << "."; |
123 | 29 | } |
124 | | |
125 | | // We must not have already seen this predecessor as one of the phi's |
126 | | // operands. |
127 | 156k | if (observed_predecessors.count(inc_id) != 0) { |
128 | 25 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
129 | 25 | << "OpPhi references incoming basic block <id> " |
130 | 25 | << _.getIdName(inc_id) << " multiple times."; |
131 | 25 | } |
132 | | |
133 | | // Note the fact that we have now observed this predecessor. |
134 | 156k | observed_predecessors.insert(inc_id); |
135 | 156k | } |
136 | 314k | } |
137 | | |
138 | 74.4k | return SPV_SUCCESS; |
139 | 74.5k | } |
140 | | |
141 | 228k | spv_result_t ValidateBranch(ValidationState_t& _, const Instruction* inst) { |
142 | | // target operands must be OpLabel |
143 | 228k | const auto id = inst->GetOperandAs<uint32_t>(0); |
144 | 228k | const auto target = _.FindDef(id); |
145 | 228k | if (!target || spv::Op::OpLabel != target->opcode()) { |
146 | 24 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
147 | 24 | << "'Target Label' operands for OpBranch must be the ID " |
148 | 24 | "of an OpLabel instruction"; |
149 | 24 | } |
150 | | |
151 | 228k | return SPV_SUCCESS; |
152 | 228k | } |
153 | | |
154 | | spv_result_t ValidateBranchConditional(ValidationState_t& _, |
155 | 103k | const Instruction* inst) { |
156 | | // num_operands is either 3 or 5 --- if 5, the last two need to be literal |
157 | | // integers |
158 | 103k | const auto num_operands = inst->operands().size(); |
159 | 103k | if (num_operands != 3 && num_operands != 5) { |
160 | 3 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
161 | 3 | << "OpBranchConditional requires either 3 or 5 parameters"; |
162 | 3 | } |
163 | | |
164 | | // grab the condition operand and check that it is a bool |
165 | 103k | const auto cond_id = inst->GetOperandAs<uint32_t>(0); |
166 | 103k | const auto cond_op = _.FindDef(cond_id); |
167 | 103k | if (!cond_op || !cond_op->type_id() || |
168 | 103k | !_.IsBoolScalarType(cond_op->type_id())) { |
169 | 11 | return _.diag(SPV_ERROR_INVALID_ID, inst) << "Condition operand for " |
170 | 11 | "OpBranchConditional must be " |
171 | 11 | "of boolean type"; |
172 | 11 | } |
173 | | |
174 | | // target operands must be OpLabel |
175 | | // note that we don't need to check that the target labels are in the same |
176 | | // function, |
177 | | // PerformCfgChecks already checks for that |
178 | 103k | const auto true_id = inst->GetOperandAs<uint32_t>(1); |
179 | 103k | const auto true_target = _.FindDef(true_id); |
180 | 103k | if (!true_target || spv::Op::OpLabel != true_target->opcode()) { |
181 | 11 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
182 | 11 | << "The 'True Label' operand for OpBranchConditional must be the " |
183 | 11 | "ID of an OpLabel instruction"; |
184 | 11 | } |
185 | | |
186 | 103k | const auto false_id = inst->GetOperandAs<uint32_t>(2); |
187 | 103k | const auto false_target = _.FindDef(false_id); |
188 | 103k | if (!false_target || spv::Op::OpLabel != false_target->opcode()) { |
189 | 12 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
190 | 12 | << "The 'False Label' operand for OpBranchConditional must be the " |
191 | 12 | "ID of an OpLabel instruction"; |
192 | 12 | } |
193 | | |
194 | | // A similar requirement for SPV_KHR_maximal_reconvergence is deferred until |
195 | | // entry point call trees have been reconrded. |
196 | 103k | if (_.version() >= SPV_SPIRV_VERSION_WORD(1, 6) && true_id == false_id) { |
197 | 0 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
198 | 0 | << "In SPIR-V 1.6 or later, True Label and False Label must be " |
199 | 0 | "different labels"; |
200 | 0 | } |
201 | | |
202 | 103k | return SPV_SUCCESS; |
203 | 103k | } |
204 | | |
205 | 14.1k | spv_result_t ValidateSwitch(ValidationState_t& _, const Instruction* inst) { |
206 | 14.1k | const auto num_operands = inst->operands().size(); |
207 | | // At least two operands (selector, default), any more than that are |
208 | | // literal/target. |
209 | | |
210 | 14.1k | const auto sel_type_id = _.GetOperandTypeId(inst, 0); |
211 | 14.1k | if (!_.IsIntScalarType(sel_type_id)) { |
212 | 8 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
213 | 8 | << "Selector type must be OpTypeInt"; |
214 | 8 | } |
215 | | |
216 | 14.1k | const auto default_label = _.FindDef(inst->GetOperandAs<uint32_t>(1)); |
217 | 14.1k | if (default_label->opcode() != spv::Op::OpLabel) { |
218 | 5 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
219 | 5 | << "Default must be an OpLabel instruction"; |
220 | 5 | } |
221 | | |
222 | | // target operands must be OpLabel |
223 | 46.8k | for (size_t i = 2; i < num_operands; i += 2) { |
224 | | // literal, id |
225 | 32.7k | const auto id = inst->GetOperandAs<uint32_t>(i + 1); |
226 | 32.7k | const auto target = _.FindDef(id); |
227 | 32.7k | if (!target || spv::Op::OpLabel != target->opcode()) { |
228 | 8 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
229 | 8 | << "'Target Label' operands for OpSwitch must be IDs of an " |
230 | 8 | "OpLabel instruction"; |
231 | 8 | } |
232 | 32.7k | } |
233 | | |
234 | 14.1k | return SPV_SUCCESS; |
235 | 14.1k | } |
236 | | |
237 | | spv_result_t ValidateReturnValue(ValidationState_t& _, |
238 | 17.0k | const Instruction* inst) { |
239 | 17.0k | const auto value_id = inst->GetOperandAs<uint32_t>(0); |
240 | 17.0k | const auto value = _.FindDef(value_id); |
241 | 17.0k | if (!value || !value->type_id()) { |
242 | 4 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
243 | 4 | << "OpReturnValue Value <id> " << _.getIdName(value_id) |
244 | 4 | << " does not represent a value."; |
245 | 4 | } |
246 | 17.0k | auto value_type = _.FindDef(value->type_id()); |
247 | 17.0k | if (!value_type || spv::Op::OpTypeVoid == value_type->opcode()) { |
248 | 3 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
249 | 3 | << "OpReturnValue value's type <id> " |
250 | 3 | << _.getIdName(value->type_id()) << " is missing or void."; |
251 | 3 | } |
252 | | |
253 | 17.0k | if (_.addressing_model() == spv::AddressingModel::Logical && |
254 | 17.0k | (spv::Op::OpTypePointer == value_type->opcode() || |
255 | 17.0k | spv::Op::OpTypeUntypedPointerKHR == value_type->opcode()) && |
256 | 6 | !_.features().variable_pointers && !_.options()->relax_logical_pointer) { |
257 | 6 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
258 | 6 | << "OpReturnValue value's type <id> " |
259 | 6 | << _.getIdName(value->type_id()) |
260 | 6 | << " is a pointer, which is invalid in the Logical addressing " |
261 | 6 | "model."; |
262 | 6 | } |
263 | | |
264 | 17.0k | const auto function = inst->function(); |
265 | 17.0k | const auto return_type = _.FindDef(function->GetResultTypeId()); |
266 | 17.0k | if (!return_type || return_type->id() != value_type->id()) { |
267 | 20 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
268 | 20 | << "OpReturnValue Value <id> " << _.getIdName(value_id) |
269 | 20 | << "s type does not match OpFunction's return type."; |
270 | 20 | } |
271 | | |
272 | 17.0k | return SPV_SUCCESS; |
273 | 17.0k | } |
274 | | |
275 | | uint32_t operator>>(const spv::LoopControlShift& lhs, |
276 | 431k | const spv::LoopControlShift& rhs) { |
277 | 431k | return uint32_t(lhs) >> uint32_t(rhs); |
278 | 431k | } |
279 | | |
280 | 42.0k | spv_result_t ValidateLoopMerge(ValidationState_t& _, const Instruction* inst) { |
281 | 42.0k | const auto merge_id = inst->GetOperandAs<uint32_t>(0); |
282 | 42.0k | const auto merge = _.FindDef(merge_id); |
283 | 42.0k | if (!merge || merge->opcode() != spv::Op::OpLabel) { |
284 | 7 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
285 | 7 | << "Merge Block " << _.getIdName(merge_id) << " must be an OpLabel"; |
286 | 7 | } |
287 | 41.9k | if (merge_id == inst->block()->id()) { |
288 | 3 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
289 | 3 | << "Merge Block may not be the block containing the OpLoopMerge\n"; |
290 | 3 | } |
291 | | |
292 | 41.9k | const auto continue_id = inst->GetOperandAs<uint32_t>(1); |
293 | 41.9k | const auto continue_target = _.FindDef(continue_id); |
294 | 41.9k | if (!continue_target || continue_target->opcode() != spv::Op::OpLabel) { |
295 | 10 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
296 | 10 | << "Continue Target " << _.getIdName(continue_id) |
297 | 10 | << " must be an OpLabel"; |
298 | 10 | } |
299 | | |
300 | 41.9k | if (merge_id == continue_id) { |
301 | 5 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
302 | 5 | << "Merge Block and Continue Target must be different ids"; |
303 | 5 | } |
304 | | |
305 | 41.9k | const auto loop_control = inst->GetOperandAs<spv::LoopControlShift>(2); |
306 | 41.9k | if ((loop_control >> spv::LoopControlShift::Unroll) & 0x1 && |
307 | 9.61k | (loop_control >> spv::LoopControlShift::DontUnroll) & 0x1) { |
308 | 3 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
309 | 3 | << "Unroll and DontUnroll loop controls must not both be specified"; |
310 | 3 | } |
311 | 41.9k | if ((loop_control >> spv::LoopControlShift::DontUnroll) & 0x1 && |
312 | 831 | (loop_control >> spv::LoopControlShift::PeelCount) & 0x1) { |
313 | 0 | return _.diag(SPV_ERROR_INVALID_DATA, inst) << "PeelCount and DontUnroll " |
314 | 0 | "loop controls must not " |
315 | 0 | "both be specified"; |
316 | 0 | } |
317 | 41.9k | if ((loop_control >> spv::LoopControlShift::DontUnroll) & 0x1 && |
318 | 831 | (loop_control >> spv::LoopControlShift::PartialCount) & 0x1) { |
319 | 0 | return _.diag(SPV_ERROR_INVALID_DATA, inst) << "PartialCount and " |
320 | 0 | "DontUnroll loop controls " |
321 | 0 | "must not both be specified"; |
322 | 0 | } |
323 | | |
324 | 41.9k | uint32_t operand = 3; |
325 | 41.9k | if ((loop_control >> spv::LoopControlShift::DependencyLength) & 0x1) { |
326 | 0 | ++operand; |
327 | 0 | } |
328 | 41.9k | if ((loop_control >> spv::LoopControlShift::MinIterations) & 0x1) { |
329 | 0 | ++operand; |
330 | 0 | } |
331 | 41.9k | if ((loop_control >> spv::LoopControlShift::MaxIterations) & 0x1) { |
332 | 0 | ++operand; |
333 | 0 | } |
334 | 41.9k | if ((loop_control >> spv::LoopControlShift::IterationMultiple) & 0x1) { |
335 | 0 | if (inst->operands().size() < operand || |
336 | 0 | inst->GetOperandAs<uint32_t>(operand) == 0) { |
337 | 0 | return _.diag(SPV_ERROR_INVALID_DATA, inst) << "IterationMultiple loop " |
338 | 0 | "control operand must be " |
339 | 0 | "greater than zero"; |
340 | 0 | } |
341 | 0 | ++operand; |
342 | 0 | } |
343 | 41.9k | if ((loop_control >> spv::LoopControlShift::PeelCount) & 0x1) { |
344 | 0 | ++operand; |
345 | 0 | } |
346 | 41.9k | if ((loop_control >> spv::LoopControlShift::PartialCount) & 0x1) { |
347 | 0 | ++operand; |
348 | 0 | } |
349 | 41.9k | if ((loop_control >> spv::LoopControlShift::MultipleWaitQueuesQCOM) & 0x1) { |
350 | 0 | ++operand; |
351 | 0 | } |
352 | | |
353 | | // That the right number of operands is present is checked by the parser. The |
354 | | // above code tracks operands for expanded validation checking in the future. |
355 | | |
356 | 41.9k | return SPV_SUCCESS; |
357 | 41.9k | } |
358 | | |
359 | 29 | spv_result_t ValidateLifetime(ValidationState_t& _, const Instruction* inst) { |
360 | 29 | const uint32_t pointer_id = _.GetOperandTypeId(inst, 0); |
361 | 29 | const Instruction* pointer_inst = _.FindDef(pointer_id); |
362 | 29 | if (pointer_inst->opcode() != spv::Op::OpTypePointer) { |
363 | 0 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
364 | 0 | << "Op" << spvOpcodeString(inst->opcode()) |
365 | 0 | << " pointer operand type must be a OpTypePointer."; |
366 | 29 | } else if (pointer_inst->GetOperandAs<spv::StorageClass>(1) != |
367 | 29 | spv::StorageClass::Function) { |
368 | 1 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
369 | 1 | << "Op" << spvOpcodeString(inst->opcode()) |
370 | 1 | << " pointer operand must be in the Function storage class."; |
371 | 1 | } |
372 | | |
373 | 28 | const uint32_t size = inst->GetOperandAs<uint32_t>(1); |
374 | 28 | if (size != 0) { |
375 | 9 | if (!_.HasCapability(spv::Capability::Addresses)) { |
376 | 9 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
377 | 9 | << "Op" << spvOpcodeString(inst->opcode()) |
378 | 9 | << " size is non-zero, but the Addresses Capability is not " |
379 | 9 | "declared."; |
380 | 9 | } |
381 | | // TODO - "Size must be 0 if Pointer is a pointer to a non-void type" |
382 | 9 | } |
383 | | |
384 | 19 | return SPV_SUCCESS; |
385 | 28 | } |
386 | | |
387 | | } // namespace |
388 | | |
389 | 0 | void printDominatorList(const BasicBlock& b) { |
390 | 0 | std::cout << b.id() << " is dominated by: "; |
391 | 0 | const BasicBlock* bb = &b; |
392 | 0 | while (bb->immediate_dominator() != bb) { |
393 | 0 | bb = bb->immediate_dominator(); |
394 | 0 | std::cout << bb->id() << " "; |
395 | 0 | } |
396 | 0 | } |
397 | | |
398 | | #define CFG_ASSERT(ASSERT_FUNC, TARGET) \ |
399 | 750k | if (spv_result_t rcode = ASSERT_FUNC(_, TARGET)) return rcode |
400 | | |
401 | 583k | spv_result_t FirstBlockAssert(ValidationState_t& _, uint32_t target) { |
402 | 583k | if (_.current_function().IsFirstBlock(target)) { |
403 | 27 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(_.current_function().id())) |
404 | 27 | << "First block " << _.getIdName(target) << " of function " |
405 | 27 | << _.getIdName(_.current_function().id()) << " is targeted by block " |
406 | 27 | << _.getIdName(_.current_function().current_block()->id()); |
407 | 27 | } |
408 | 583k | return SPV_SUCCESS; |
409 | 583k | } |
410 | | |
411 | 167k | spv_result_t MergeBlockAssert(ValidationState_t& _, uint32_t merge_block) { |
412 | 167k | if (_.current_function().IsBlockType(merge_block, kBlockTypeMerge)) { |
413 | 78 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(_.current_function().id())) |
414 | 78 | << "Block " << _.getIdName(merge_block) |
415 | 78 | << " is already a merge block for another header"; |
416 | 78 | } |
417 | 167k | return SPV_SUCCESS; |
418 | 167k | } |
419 | | |
420 | | /// Update the continue construct's exit blocks once the backedge blocks are |
421 | | /// identified in the CFG. |
422 | | void UpdateContinueConstructExitBlocks( |
423 | | Function& function, |
424 | 37.4k | const std::vector<std::pair<uint32_t, uint32_t>>& back_edges) { |
425 | 37.4k | auto& constructs = function.constructs(); |
426 | | // TODO(umar): Think of a faster way to do this |
427 | 52.7k | for (auto& edge : back_edges) { |
428 | 52.7k | uint32_t back_edge_block_id; |
429 | 52.7k | uint32_t loop_header_block_id; |
430 | 52.7k | std::tie(back_edge_block_id, loop_header_block_id) = edge; |
431 | 1.27M | auto is_this_header = [=](Construct& c) { |
432 | 1.27M | return c.type() == ConstructType::kLoop && |
433 | 377k | c.entry_block()->id() == loop_header_block_id; |
434 | 1.27M | }; |
435 | | |
436 | 1.27M | for (auto construct : constructs) { |
437 | 1.27M | if (is_this_header(construct)) { |
438 | 44.5k | Construct* continue_construct = |
439 | 44.5k | construct.corresponding_constructs().back(); |
440 | 44.5k | assert(continue_construct->type() == ConstructType::kContinue); |
441 | | |
442 | 44.5k | BasicBlock* back_edge_block; |
443 | 44.5k | std::tie(back_edge_block, std::ignore) = |
444 | 44.5k | function.GetBlock(back_edge_block_id); |
445 | 44.5k | continue_construct->set_exit(back_edge_block); |
446 | 44.5k | } |
447 | 1.27M | } |
448 | 52.7k | } |
449 | 37.4k | } |
450 | | |
451 | | std::tuple<std::string, std::string, std::string> ConstructNames( |
452 | 129k | ConstructType type) { |
453 | 129k | std::string construct_name, header_name, exit_name; |
454 | | |
455 | 129k | switch (type) { |
456 | 61.2k | case ConstructType::kSelection: |
457 | 61.2k | construct_name = "selection"; |
458 | 61.2k | header_name = "selection header"; |
459 | 61.2k | exit_name = "merge block"; |
460 | 61.2k | break; |
461 | 34.0k | case ConstructType::kLoop: |
462 | 34.0k | construct_name = "loop"; |
463 | 34.0k | header_name = "loop header"; |
464 | 34.0k | exit_name = "merge block"; |
465 | 34.0k | break; |
466 | 33.8k | case ConstructType::kContinue: |
467 | 33.8k | construct_name = "continue"; |
468 | 33.8k | header_name = "continue target"; |
469 | 33.8k | exit_name = "back-edge block"; |
470 | 33.8k | break; |
471 | 0 | case ConstructType::kCase: |
472 | 0 | construct_name = "case"; |
473 | 0 | header_name = "case entry block"; |
474 | 0 | exit_name = "case exit block"; |
475 | 0 | break; |
476 | 0 | default: |
477 | 0 | assert(1 == 0 && "Not defined type"); |
478 | 129k | } |
479 | | |
480 | 129k | return std::make_tuple(construct_name, header_name, exit_name); |
481 | 129k | } |
482 | | |
483 | | /// Constructs an error message for construct validation errors |
484 | | std::string ConstructErrorString(const Construct& construct, |
485 | | const std::string& header_string, |
486 | | const std::string& exit_string, |
487 | 149 | const std::string& dominate_text) { |
488 | 149 | std::string construct_name, header_name, exit_name; |
489 | 149 | std::tie(construct_name, header_name, exit_name) = |
490 | 149 | ConstructNames(construct.type()); |
491 | | |
492 | | // TODO(umar): Add header block for continue constructs to error message |
493 | 149 | return "The " + construct_name + " construct with the " + header_name + " " + |
494 | 149 | header_string + " " + dominate_text + " the " + exit_name + " " + |
495 | 149 | exit_string; |
496 | 149 | } |
497 | | |
498 | | // Finds the fall through case construct of |target_block| and records it in |
499 | | // |case_fall_through|. Returns SPV_ERROR_INVALID_CFG if the case construct |
500 | | // headed by |target_block| branches to multiple case constructs. |
501 | | spv_result_t FindCaseFallThrough( |
502 | | ValidationState_t& _, BasicBlock* target_block, uint32_t* case_fall_through, |
503 | | const Construct& switch_construct, |
504 | 17.1k | const std::unordered_set<uint32_t>& case_targets) { |
505 | 17.1k | const auto* merge = switch_construct.exit_block(); |
506 | 17.1k | std::vector<BasicBlock*> stack; |
507 | 17.1k | stack.push_back(target_block); |
508 | 17.1k | std::unordered_set<const BasicBlock*> visited; |
509 | 17.1k | bool target_reachable = target_block->structurally_reachable(); |
510 | 152k | while (!stack.empty()) { |
511 | 135k | auto block = stack.back(); |
512 | 135k | stack.pop_back(); |
513 | | |
514 | 135k | if (block == merge) continue; |
515 | | |
516 | 114k | if (!visited.insert(block).second) continue; |
517 | | |
518 | 87.9k | if (target_reachable && block->structurally_reachable() && |
519 | 87.9k | target_block->structurally_dominates(*block)) { |
520 | | // Still in the case construct. |
521 | 118k | for (auto successor : *block->successors()) { |
522 | 118k | stack.push_back(successor); |
523 | 118k | } |
524 | 86.6k | } else { |
525 | | // Exiting the case construct to non-merge block. |
526 | 1.26k | if (!case_targets.count(block->id())) { |
527 | | // We have already filtered out the following: |
528 | | // * The switch's merge |
529 | | // * Other case targets |
530 | | // * Blocks in the same case construct |
531 | | // |
532 | | // So the only remaining valid branches are the structured exits from |
533 | | // the overall selection construct of the switch. |
534 | 620 | if (switch_construct.IsStructuredExit(_, block)) { |
535 | 599 | continue; |
536 | 599 | } |
537 | | |
538 | 21 | return _.diag(SPV_ERROR_INVALID_CFG, target_block->label()) |
539 | 21 | << "Case construct that targets " |
540 | 21 | << _.getIdName(target_block->id()) |
541 | 21 | << " has invalid branch to block " << _.getIdName(block->id()) |
542 | 21 | << " (not another case construct, corresponding merge, outer " |
543 | 21 | "loop merge or outer loop continue)"; |
544 | 620 | } |
545 | | |
546 | 643 | if (*case_fall_through == 0u) { |
547 | 631 | if (target_block != block) { |
548 | 631 | *case_fall_through = block->id(); |
549 | 631 | } |
550 | 631 | } else if (*case_fall_through != block->id()) { |
551 | | // Case construct has at most one branch to another case construct. |
552 | 12 | return _.diag(SPV_ERROR_INVALID_CFG, target_block->label()) |
553 | 12 | << "Case construct that targets " |
554 | 12 | << _.getIdName(target_block->id()) |
555 | 12 | << " has branches to multiple other case construct targets " |
556 | 12 | << _.getIdName(*case_fall_through) << " and " |
557 | 12 | << _.getIdName(block->id()); |
558 | 12 | } |
559 | 643 | } |
560 | 87.9k | } |
561 | | |
562 | 17.1k | return SPV_SUCCESS; |
563 | 17.1k | } |
564 | | |
565 | | spv_result_t StructuredSwitchChecks(ValidationState_t& _, Function* function, |
566 | 9.93k | const Construct& switch_construct) { |
567 | 9.93k | const auto* header = switch_construct.entry_block(); |
568 | 9.93k | const auto* merge = switch_construct.exit_block(); |
569 | 9.93k | const auto* switch_inst = header->terminator(); |
570 | 9.93k | std::unordered_set<uint32_t> case_targets; |
571 | 38.5k | for (uint32_t i = 1; i < switch_inst->operands().size(); i += 2) { |
572 | 28.6k | uint32_t target = switch_inst->GetOperandAs<uint32_t>(i); |
573 | 28.6k | if (target != merge->id()) case_targets.insert(target); |
574 | 28.6k | } |
575 | | // Tracks how many times each case construct is targeted by another case |
576 | | // construct. |
577 | 9.93k | std::map<uint32_t, uint32_t> num_fall_through_targeted; |
578 | 9.93k | uint32_t default_case_fall_through = 0u; |
579 | 9.93k | uint32_t default_target = switch_inst->GetOperandAs<uint32_t>(1u); |
580 | 9.93k | bool default_appears_multiple_times = false; |
581 | 23.4k | for (uint32_t i = 3; i < switch_inst->operands().size(); i += 2) { |
582 | 14.2k | if (default_target == switch_inst->GetOperandAs<uint32_t>(i)) { |
583 | 790 | default_appears_multiple_times = true; |
584 | 790 | break; |
585 | 790 | } |
586 | 14.2k | } |
587 | | |
588 | 9.93k | std::unordered_map<uint32_t, uint32_t> seen_to_fall_through; |
589 | 37.1k | for (uint32_t i = 1; i < switch_inst->operands().size(); i += 2) { |
590 | 27.4k | uint32_t target = switch_inst->GetOperandAs<uint32_t>(i); |
591 | 27.4k | if (target == merge->id()) continue; |
592 | | |
593 | 24.7k | uint32_t case_fall_through = 0u; |
594 | 24.7k | auto seen_iter = seen_to_fall_through.find(target); |
595 | 24.7k | if (seen_iter == seen_to_fall_through.end()) { |
596 | 17.2k | const auto target_block = function->GetBlock(target).first; |
597 | | // OpSwitch must dominate all its case constructs. |
598 | 17.2k | if (header->structurally_reachable() && |
599 | 17.2k | target_block->structurally_reachable() && |
600 | 17.2k | !header->structurally_dominates(*target_block)) { |
601 | 82 | return _.diag(SPV_ERROR_INVALID_CFG, header->label()) |
602 | 82 | << "Switch header " << _.getIdName(header->id()) |
603 | 82 | << " does not structurally dominate its case construct " |
604 | 82 | << _.getIdName(target); |
605 | 82 | } |
606 | | |
607 | 17.1k | if (auto error = FindCaseFallThrough(_, target_block, &case_fall_through, |
608 | 17.1k | switch_construct, case_targets)) { |
609 | 33 | return error; |
610 | 33 | } |
611 | | |
612 | | // Track how many time the fall through case has been targeted. |
613 | 17.1k | if (case_fall_through != 0u) { |
614 | 616 | auto where = num_fall_through_targeted.lower_bound(case_fall_through); |
615 | 616 | if (where == num_fall_through_targeted.end() || |
616 | 550 | where->first != case_fall_through) { |
617 | 550 | num_fall_through_targeted.insert( |
618 | 550 | where, std::make_pair(case_fall_through, 1)); |
619 | 550 | } else { |
620 | 66 | where->second++; |
621 | 66 | } |
622 | 616 | } |
623 | 17.1k | seen_to_fall_through.insert(std::make_pair(target, case_fall_through)); |
624 | 17.1k | } else { |
625 | 7.49k | case_fall_through = seen_iter->second; |
626 | 7.49k | } |
627 | | |
628 | 24.6k | if (case_fall_through == default_target && |
629 | 693 | !default_appears_multiple_times) { |
630 | 326 | case_fall_through = default_case_fall_through; |
631 | 326 | } |
632 | 24.6k | if (case_fall_through != 0u) { |
633 | 1.56k | bool is_default = i == 1; |
634 | 1.56k | if (is_default) { |
635 | 190 | default_case_fall_through = case_fall_through; |
636 | 1.37k | } else { |
637 | | // Allow code like: |
638 | | // case x: |
639 | | // case y: |
640 | | // ... |
641 | | // case z: |
642 | | // |
643 | | // Where x and y target the same block and fall through to z. |
644 | 1.37k | uint32_t j = i; |
645 | 4.12k | while ((j + 2 < switch_inst->operands().size()) && |
646 | 4.08k | target == switch_inst->GetOperandAs<uint32_t>(j + 2)) { |
647 | 2.74k | j += 2; |
648 | 2.74k | } |
649 | | // If Target T1 branches to Target T2, or if Target T1 branches to the |
650 | | // Default target and the Default target branches to Target T2, then T1 |
651 | | // must immediately precede T2 in the list of OpSwitch Target operands. |
652 | 1.37k | if ((switch_inst->operands().size() < j + 2) || |
653 | 1.33k | (case_fall_through != switch_inst->GetOperandAs<uint32_t>(j + 2))) { |
654 | 118 | return _.diag(SPV_ERROR_INVALID_CFG, switch_inst) |
655 | 118 | << "Case construct that targets " << _.getIdName(target) |
656 | 118 | << " has branches to the case construct that targets " |
657 | 118 | << _.getIdName(case_fall_through) |
658 | 118 | << ", but does not immediately precede it in the " |
659 | 118 | "OpSwitch's target list"; |
660 | 118 | } |
661 | 1.37k | } |
662 | 1.56k | } |
663 | 24.6k | } |
664 | | |
665 | | // Each case construct must be branched to by at most one other case |
666 | | // construct. |
667 | 9.70k | for (const auto& pair : num_fall_through_targeted) { |
668 | 306 | if (pair.second > 1) { |
669 | 17 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(pair.first)) |
670 | 17 | << "Multiple case constructs have branches to the case construct " |
671 | 17 | "that targets " |
672 | 17 | << _.getIdName(pair.first); |
673 | 17 | } |
674 | 306 | } |
675 | | |
676 | 9.68k | return SPV_SUCCESS; |
677 | 9.70k | } |
678 | | |
679 | | // Validates that all CFG divergences (i.e. conditional branch or switch) are |
680 | | // structured correctly. Either divergence is preceded by a merge instruction |
681 | | // or the divergence introduces at most one unseen label. |
682 | | spv_result_t ValidateStructuredSelections( |
683 | 36.1k | ValidationState_t& _, const std::vector<const BasicBlock*>& postorder) { |
684 | 36.1k | std::unordered_set<uint32_t> seen; |
685 | 390k | for (auto iter = postorder.rbegin(); iter != postorder.rend(); ++iter) { |
686 | 354k | const auto* block = *iter; |
687 | 354k | const auto* terminator = block->terminator(); |
688 | 354k | if (!terminator) continue; |
689 | 318k | const auto index = terminator - &_.ordered_instructions()[0]; |
690 | 318k | auto* merge = &_.ordered_instructions()[index - 1]; |
691 | | // Marks merges and continues as seen. |
692 | 318k | if (merge->opcode() == spv::Op::OpSelectionMerge) { |
693 | 58.6k | seen.insert(merge->GetOperandAs<uint32_t>(0)); |
694 | 260k | } else if (merge->opcode() == spv::Op::OpLoopMerge) { |
695 | 32.7k | seen.insert(merge->GetOperandAs<uint32_t>(0)); |
696 | 32.7k | seen.insert(merge->GetOperandAs<uint32_t>(1)); |
697 | 227k | } else { |
698 | | // Only track the pointer if it is a merge instruction. |
699 | 227k | merge = nullptr; |
700 | 227k | } |
701 | | |
702 | | // Skip unreachable blocks. |
703 | 318k | if (!block->structurally_reachable()) continue; |
704 | | |
705 | 318k | if (terminator->opcode() == spv::Op::OpBranchConditional) { |
706 | 80.8k | const auto true_label = terminator->GetOperandAs<uint32_t>(1); |
707 | 80.8k | const auto false_label = terminator->GetOperandAs<uint32_t>(2); |
708 | | // Mark the upcoming blocks as seen now, but only error out if this block |
709 | | // was missing a merge instruction and both labels hadn't been seen |
710 | | // previously. |
711 | 80.8k | const bool true_label_unseen = seen.insert(true_label).second; |
712 | 80.8k | const bool false_label_unseen = seen.insert(false_label).second; |
713 | 80.8k | if ((!merge || merge->opcode() == spv::Op::OpLoopMerge) && |
714 | 31.2k | true_label_unseen && false_label_unseen) { |
715 | 24 | return _.diag(SPV_ERROR_INVALID_CFG, terminator) |
716 | 24 | << "Selection must be structured"; |
717 | 24 | } |
718 | 237k | } else if (terminator->opcode() == spv::Op::OpSwitch) { |
719 | 9.09k | if (!merge) { |
720 | 43 | return _.diag(SPV_ERROR_INVALID_CFG, terminator) |
721 | 43 | << "OpSwitch must be preceded by an OpSelectionMerge " |
722 | 43 | "instruction"; |
723 | 43 | } |
724 | | // Mark the targets as seen. |
725 | 33.0k | for (uint32_t i = 1; i < terminator->operands().size(); i += 2) { |
726 | 23.9k | const auto target = terminator->GetOperandAs<uint32_t>(i); |
727 | 23.9k | seen.insert(target); |
728 | 23.9k | } |
729 | 9.04k | } |
730 | 318k | } |
731 | | |
732 | 36.0k | return SPV_SUCCESS; |
733 | 36.1k | } |
734 | | |
735 | | spv_result_t StructuredControlFlowChecks( |
736 | | ValidationState_t& _, Function* function, |
737 | | const std::vector<std::pair<uint32_t, uint32_t>>& back_edges, |
738 | 37.4k | const std::vector<const BasicBlock*>& postorder) { |
739 | | /// Check all backedges target only loop headers and have exactly one |
740 | | /// back-edge branching to it |
741 | | |
742 | | // Map a loop header to blocks with back-edges to the loop header. |
743 | 37.4k | std::map<uint32_t, std::unordered_set<uint32_t>> loop_latch_blocks; |
744 | 43.4k | for (auto back_edge : back_edges) { |
745 | 43.4k | uint32_t back_edge_block; |
746 | 43.4k | uint32_t header_block; |
747 | 43.4k | std::tie(back_edge_block, header_block) = back_edge; |
748 | 43.4k | if (!function->IsBlockType(header_block, kBlockTypeLoop)) { |
749 | 499 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(back_edge_block)) |
750 | 499 | << "Back-edges (" << _.getIdName(back_edge_block) << " -> " |
751 | 499 | << _.getIdName(header_block) |
752 | 499 | << ") can only be formed between a block and a loop header."; |
753 | 499 | } |
754 | 42.9k | loop_latch_blocks[header_block].insert(back_edge_block); |
755 | 42.9k | } |
756 | | |
757 | | // Check the loop headers have exactly one back-edge branching to it |
758 | 381k | for (BasicBlock* loop_header : function->ordered_blocks()) { |
759 | 381k | if (!loop_header->structurally_reachable()) continue; |
760 | 340k | if (!loop_header->is_type(kBlockTypeLoop)) continue; |
761 | 35.3k | auto loop_header_id = loop_header->id(); |
762 | 35.3k | auto num_latch_blocks = loop_latch_blocks[loop_header_id].size(); |
763 | 35.3k | if (num_latch_blocks != 1) { |
764 | 128 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(loop_header_id)) |
765 | 128 | << "Loop header " << _.getIdName(loop_header_id) |
766 | 128 | << " is targeted by " << num_latch_blocks |
767 | 128 | << " back-edge blocks but the standard requires exactly one"; |
768 | 128 | } |
769 | 35.3k | } |
770 | | |
771 | | // Check construct rules |
772 | 134k | for (const Construct& construct : function->constructs()) { |
773 | 134k | auto header = construct.entry_block(); |
774 | 134k | if (!header->structurally_reachable()) continue; |
775 | 129k | auto merge = construct.exit_block(); |
776 | | |
777 | 129k | if (!merge) { |
778 | 6 | std::string construct_name, header_name, exit_name; |
779 | 6 | std::tie(construct_name, header_name, exit_name) = |
780 | 6 | ConstructNames(construct.type()); |
781 | 6 | return _.diag(SPV_ERROR_INTERNAL, _.FindDef(header->id())) |
782 | 6 | << "Construct " + construct_name + " with " + header_name + " " + |
783 | 6 | _.getIdName(header->id()) + " does not have a " + |
784 | 6 | exit_name + ". This may be a bug in the validator."; |
785 | 6 | } |
786 | | |
787 | | // If the header is reachable, the merge is guaranteed to be structurally |
788 | | // reachable. |
789 | 129k | if (!header->structurally_dominates(*merge)) { |
790 | 107 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id())) |
791 | 107 | << ConstructErrorString(construct, _.getIdName(header->id()), |
792 | 107 | _.getIdName(merge->id()), |
793 | 107 | "does not structurally dominate"); |
794 | 107 | } |
795 | | |
796 | | // If it's really a merge block for a selection or loop, then it must be |
797 | | // *strictly* structrually dominated by the header. |
798 | 129k | if (construct.ExitBlockIsMergeBlock() && (header == merge)) { |
799 | 11 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id())) |
800 | 11 | << ConstructErrorString(construct, _.getIdName(header->id()), |
801 | 11 | _.getIdName(merge->id()), |
802 | 11 | "does not strictly structurally dominate"); |
803 | 11 | } |
804 | | |
805 | | // Check post-dominance for continue constructs. But dominance and |
806 | | // post-dominance only make sense when the construct is reachable. |
807 | 129k | if (construct.type() == ConstructType::kContinue) { |
808 | 33.8k | if (!merge->structurally_postdominates(*header)) { |
809 | 31 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id())) |
810 | 31 | << ConstructErrorString(construct, _.getIdName(header->id()), |
811 | 31 | _.getIdName(merge->id()), |
812 | 31 | "is not structurally post dominated by"); |
813 | 31 | } |
814 | 33.8k | } |
815 | | |
816 | 129k | Construct::ConstructBlockSet construct_blocks = construct.blocks(function); |
817 | 129k | std::string construct_name, header_name, exit_name; |
818 | 129k | std::tie(construct_name, header_name, exit_name) = |
819 | 129k | ConstructNames(construct.type()); |
820 | 581k | for (auto block : construct_blocks) { |
821 | | // Check that all exits from the construct are via structured exits. |
822 | 771k | for (auto succ : *block->successors()) { |
823 | 771k | if (!construct_blocks.count(succ) && |
824 | 221k | !construct.IsStructuredExit(_, succ)) { |
825 | 141 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id())) |
826 | 141 | << "block <ID> " << _.getIdName(block->id()) << " exits the " |
827 | 141 | << construct_name << " headed by <ID> " |
828 | 141 | << _.getIdName(header->id()) |
829 | 141 | << ", but not via a structured exit"; |
830 | 141 | } |
831 | 771k | } |
832 | 581k | if (block == header) continue; |
833 | | // Check that for all non-header blocks, all predecessors are within this |
834 | | // construct. |
835 | 556k | for (auto pred : *block->predecessors()) { |
836 | 556k | if (pred->structurally_reachable() && !construct_blocks.count(pred)) { |
837 | 108 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(pred->id())) |
838 | 108 | << "block <ID> " << pred->id() << " branches to the " |
839 | 108 | << construct_name << " construct, but not to the " |
840 | 108 | << header_name << " <ID> " << header->id(); |
841 | 108 | } |
842 | 556k | } |
843 | | |
844 | 454k | if (block->is_type(BlockType::kBlockTypeSelection) || |
845 | 354k | block->is_type(BlockType::kBlockTypeLoop)) { |
846 | 119k | size_t index = (block->terminator() - &_.ordered_instructions()[0]) - 1; |
847 | 119k | const auto& merge_inst = _.ordered_instructions()[index]; |
848 | 119k | if (merge_inst.opcode() == spv::Op::OpSelectionMerge || |
849 | 119k | merge_inst.opcode() == spv::Op::OpLoopMerge) { |
850 | 119k | uint32_t merge_id = merge_inst.GetOperandAs<uint32_t>(0); |
851 | 119k | auto merge_block = function->GetBlock(merge_id).first; |
852 | 119k | if (merge_block->structurally_reachable() && |
853 | 119k | !construct_blocks.count(merge_block)) { |
854 | 16 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id())) |
855 | 16 | << "Header block " << _.getIdName(block->id()) |
856 | 16 | << " is contained in the " << construct_name |
857 | 16 | << " construct headed by " << _.getIdName(header->id()) |
858 | 16 | << ", but its merge block " << _.getIdName(merge_id) |
859 | 16 | << " is not"; |
860 | 16 | } |
861 | 119k | } |
862 | 119k | } |
863 | 454k | } |
864 | | |
865 | 128k | if (construct.type() == ConstructType::kLoop) { |
866 | | // If the continue target differs from the loop header, then check that |
867 | | // all edges into the continue construct come from within the loop. |
868 | 33.9k | const auto index = header->terminator() - &_.ordered_instructions()[0]; |
869 | 33.9k | const auto& merge_inst = _.ordered_instructions()[index - 1]; |
870 | 33.9k | const auto continue_id = merge_inst.GetOperandAs<uint32_t>(1); |
871 | 33.9k | const auto* continue_inst = _.FindDef(continue_id); |
872 | | // OpLabel instructions aren't stored as part of the basic block for |
873 | | // legacy reaasons. Grab the next instruction and use it's block pointer |
874 | | // instead. |
875 | 33.9k | const auto next_index = |
876 | 33.9k | (continue_inst - &_.ordered_instructions()[0]) + 1; |
877 | 33.9k | const auto& next_inst = _.ordered_instructions()[next_index]; |
878 | 33.9k | const auto* continue_target = next_inst.block(); |
879 | 33.9k | if (header->id() != continue_id) { |
880 | 35.3k | for (auto pred : *continue_target->predecessors()) { |
881 | 35.3k | if (!pred->structurally_reachable()) { |
882 | 1.43k | continue; |
883 | 1.43k | } |
884 | | // Ignore back-edges from within the continue construct. |
885 | 33.8k | bool is_back_edge = false; |
886 | 270k | for (auto back_edge : back_edges) { |
887 | 270k | uint32_t back_edge_block; |
888 | 270k | uint32_t header_block; |
889 | 270k | std::tie(back_edge_block, header_block) = back_edge; |
890 | 270k | if (header_block == continue_id && back_edge_block == pred->id()) |
891 | 13.2k | is_back_edge = true; |
892 | 270k | } |
893 | 33.8k | if (!construct_blocks.count(pred) && !is_back_edge) { |
894 | 66 | return _.diag(SPV_ERROR_INVALID_CFG, pred->terminator()) |
895 | 66 | << "Block " << _.getIdName(pred->id()) |
896 | 66 | << " branches to the loop continue target " |
897 | 66 | << _.getIdName(continue_id) |
898 | 66 | << ", but is not contained in the associated loop construct " |
899 | 66 | << _.getIdName(header->id()); |
900 | 66 | } |
901 | 33.8k | } |
902 | 32.1k | } |
903 | 33.9k | } |
904 | | |
905 | | // Checks rules for case constructs. |
906 | 128k | if (construct.type() == ConstructType::kSelection && |
907 | 61.0k | header->terminator()->opcode() == spv::Op::OpSwitch) { |
908 | 9.93k | if (auto error = StructuredSwitchChecks(_, function, construct)) { |
909 | 250 | return error; |
910 | 250 | } |
911 | 9.93k | } |
912 | 128k | } |
913 | | |
914 | 36.1k | if (auto error = ValidateStructuredSelections(_, postorder)) { |
915 | 67 | return error; |
916 | 67 | } |
917 | | |
918 | 36.0k | return SPV_SUCCESS; |
919 | 36.1k | } |
920 | | |
921 | 25.1k | spv_result_t MaximalReconvergenceChecks(ValidationState_t& _) { |
922 | | // Find all the entry points with the MaximallyReconvergencesKHR execution |
923 | | // mode. |
924 | 25.1k | std::unordered_set<uint32_t> maximal_funcs; |
925 | 25.1k | std::unordered_set<uint32_t> maximal_entry_points; |
926 | 25.1k | for (auto entry_point : _.entry_points()) { |
927 | 19.1k | const auto* exec_modes = _.GetExecutionModes(entry_point); |
928 | 19.1k | if (exec_modes && |
929 | 16.2k | exec_modes->count(spv::ExecutionMode::MaximallyReconvergesKHR)) { |
930 | 0 | maximal_entry_points.insert(entry_point); |
931 | 0 | maximal_funcs.insert(entry_point); |
932 | 0 | } |
933 | 19.1k | } |
934 | | |
935 | 25.1k | if (maximal_entry_points.empty()) { |
936 | 25.1k | return SPV_SUCCESS; |
937 | 25.1k | } |
938 | | |
939 | | // Find all the functions reachable from a maximal reconvergence entry point. |
940 | 0 | for (const auto& func : _.functions()) { |
941 | 0 | const auto& entry_points = _.EntryPointReferences(func.id()); |
942 | 0 | for (auto id : entry_points) { |
943 | 0 | if (maximal_entry_points.count(id)) { |
944 | 0 | maximal_funcs.insert(func.id()); |
945 | 0 | break; |
946 | 0 | } |
947 | 0 | } |
948 | 0 | } |
949 | | |
950 | | // Check for conditional branches with the same true and false targets. |
951 | 0 | for (const auto& inst : _.ordered_instructions()) { |
952 | 0 | if (inst.opcode() == spv::Op::OpBranchConditional) { |
953 | 0 | const auto true_id = inst.GetOperandAs<uint32_t>(1); |
954 | 0 | const auto false_id = inst.GetOperandAs<uint32_t>(2); |
955 | 0 | if (true_id == false_id && maximal_funcs.count(inst.function()->id())) { |
956 | 0 | return _.diag(SPV_ERROR_INVALID_ID, &inst) |
957 | 0 | << "In entry points using the MaximallyReconvergesKHR execution " |
958 | 0 | "mode, True Label and False Label must be different labels"; |
959 | 0 | } |
960 | 0 | } |
961 | 0 | } |
962 | | |
963 | | // Check for invalid multiple predecessors. Only loop headers, continue |
964 | | // targets, merge targets or switch targets or defaults may have multiple |
965 | | // unique predecessors. |
966 | 0 | for (const auto& func : _.functions()) { |
967 | 0 | if (!maximal_funcs.count(func.id())) continue; |
968 | | |
969 | 0 | for (const auto* block : func.ordered_blocks()) { |
970 | 0 | std::unordered_set<uint32_t> unique_preds; |
971 | 0 | const auto* preds = block->predecessors(); |
972 | 0 | if (!preds) continue; |
973 | | |
974 | 0 | for (const auto* pred : *preds) { |
975 | 0 | unique_preds.insert(pred->id()); |
976 | 0 | } |
977 | 0 | if (unique_preds.size() < 2) continue; |
978 | | |
979 | 0 | const auto* terminator = block->terminator(); |
980 | 0 | const auto index = terminator - &_.ordered_instructions()[0]; |
981 | 0 | const auto* pre_terminator = &_.ordered_instructions()[index - 1]; |
982 | 0 | if (pre_terminator->opcode() == spv::Op::OpLoopMerge) continue; |
983 | | |
984 | 0 | const auto* label = _.FindDef(block->id()); |
985 | 0 | bool ok = false; |
986 | 0 | for (const auto& pair : label->uses()) { |
987 | 0 | const auto* use_inst = pair.first; |
988 | 0 | switch (use_inst->opcode()) { |
989 | 0 | case spv::Op::OpSelectionMerge: |
990 | 0 | case spv::Op::OpLoopMerge: |
991 | 0 | case spv::Op::OpSwitch: |
992 | 0 | ok = true; |
993 | 0 | break; |
994 | 0 | default: |
995 | 0 | break; |
996 | 0 | } |
997 | 0 | } |
998 | 0 | if (!ok) { |
999 | 0 | return _.diag(SPV_ERROR_INVALID_CFG, label) |
1000 | 0 | << "In entry points using the MaximallyReconvergesKHR " |
1001 | 0 | "execution mode, this basic block must not have multiple " |
1002 | 0 | "unique predecessors"; |
1003 | 0 | } |
1004 | 0 | } |
1005 | 0 | } |
1006 | | |
1007 | 0 | return SPV_SUCCESS; |
1008 | 0 | } |
1009 | | |
1010 | 26.6k | spv_result_t PerformCfgChecks(ValidationState_t& _) { |
1011 | 37.6k | for (auto& function : _.functions()) { |
1012 | | // Check all referenced blocks are defined within a function |
1013 | 37.6k | if (function.undefined_block_count() != 0) { |
1014 | 46 | std::string undef_blocks("{"); |
1015 | 46 | bool first = true; |
1016 | 153 | for (auto undefined_block : function.undefined_blocks()) { |
1017 | 153 | undef_blocks += _.getIdName(undefined_block); |
1018 | 153 | if (!first) { |
1019 | 107 | undef_blocks += " "; |
1020 | 107 | } |
1021 | 153 | first = false; |
1022 | 153 | } |
1023 | 46 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(function.id())) |
1024 | 46 | << "Block(s) " << undef_blocks << "}" |
1025 | 46 | << " are referenced but not defined in function " |
1026 | 46 | << _.getIdName(function.id()); |
1027 | 46 | } |
1028 | | |
1029 | | // Set each block's immediate dominator. |
1030 | | // |
1031 | | // We want to analyze all the blocks in the function, even in degenerate |
1032 | | // control flow cases including unreachable blocks. So use the augmented |
1033 | | // CFG to ensure we cover all the blocks. |
1034 | 37.6k | std::vector<const BasicBlock*> postorder; |
1035 | 2.22M | auto ignore_block = [](const BasicBlock*) {}; |
1036 | 4.33M | auto no_terminal_blocks = [](const BasicBlock*) { return false; }; |
1037 | 37.6k | if (!function.ordered_blocks().empty()) { |
1038 | | /// calculate dominators |
1039 | 36.9k | CFA<BasicBlock>::DepthFirstTraversal( |
1040 | 36.9k | function.first_block(), function.AugmentedCFGSuccessorsFunction(), |
1041 | 378k | ignore_block, [&](const BasicBlock* b) { postorder.push_back(b); }, |
1042 | 36.9k | no_terminal_blocks); |
1043 | 36.9k | auto edges = CFA<BasicBlock>::CalculateDominators( |
1044 | 36.9k | postorder, function.AugmentedCFGPredecessorsFunction()); |
1045 | 378k | for (auto edge : edges) { |
1046 | 378k | if (edge.first != edge.second) |
1047 | 341k | edge.first->SetImmediateDominator(edge.second); |
1048 | 378k | } |
1049 | 36.9k | } |
1050 | | |
1051 | 37.6k | auto& blocks = function.ordered_blocks(); |
1052 | 37.6k | if (!blocks.empty()) { |
1053 | | // Check if the order of blocks in the binary appear before the blocks |
1054 | | // they dominate |
1055 | 409k | for (auto block = begin(blocks) + 1; block != end(blocks); ++block) { |
1056 | 372k | if (auto idom = (*block)->immediate_dominator()) { |
1057 | 304k | if (idom != function.pseudo_entry_block() && |
1058 | 304k | block == std::find(begin(blocks), block, idom)) { |
1059 | 83 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(idom->id())) |
1060 | 83 | << "Block " << _.getIdName((*block)->id()) |
1061 | 83 | << " appears in the binary before its dominator " |
1062 | 83 | << _.getIdName(idom->id()); |
1063 | 83 | } |
1064 | 304k | } |
1065 | 372k | } |
1066 | | // If we have structured control flow, check that no block has a control |
1067 | | // flow nesting depth larger than the limit. |
1068 | 36.8k | if (_.HasCapability(spv::Capability::Shader)) { |
1069 | 36.7k | const int control_flow_nesting_depth_limit = |
1070 | 36.7k | _.options()->universal_limits_.max_control_flow_nesting_depth; |
1071 | 445k | for (auto block = begin(blocks); block != end(blocks); ++block) { |
1072 | 408k | if (function.GetBlockDepth(*block) > |
1073 | 408k | control_flow_nesting_depth_limit) { |
1074 | 0 | return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef((*block)->id())) |
1075 | 0 | << "Maximum Control Flow nesting depth exceeded."; |
1076 | 0 | } |
1077 | 408k | } |
1078 | 36.7k | } |
1079 | 36.8k | } |
1080 | | |
1081 | | /// Structured control flow checks are only required for shader capabilities |
1082 | 37.5k | if (_.HasCapability(spv::Capability::Shader)) { |
1083 | | // Calculate structural dominance. |
1084 | 37.4k | postorder.clear(); |
1085 | 37.4k | std::vector<const BasicBlock*> postdom_postorder; |
1086 | 37.4k | std::vector<std::pair<uint32_t, uint32_t>> back_edges; |
1087 | 37.4k | if (!function.ordered_blocks().empty()) { |
1088 | | /// calculate dominators |
1089 | 36.7k | CFA<BasicBlock>::DepthFirstTraversal( |
1090 | 36.7k | function.first_block(), |
1091 | 36.7k | function.AugmentedStructuralCFGSuccessorsFunction(), ignore_block, |
1092 | 399k | [&](const BasicBlock* b) { postorder.push_back(b); }, |
1093 | 36.7k | no_terminal_blocks); |
1094 | 36.7k | auto edges = CFA<BasicBlock>::CalculateDominators( |
1095 | 36.7k | postorder, function.AugmentedStructuralCFGPredecessorsFunction()); |
1096 | 399k | for (auto edge : edges) { |
1097 | 399k | if (edge.first != edge.second) |
1098 | 362k | edge.first->SetImmediateStructuralDominator(edge.second); |
1099 | 399k | } |
1100 | | |
1101 | | /// calculate post dominators |
1102 | 36.7k | CFA<BasicBlock>::DepthFirstTraversal( |
1103 | 36.7k | function.pseudo_exit_block(), |
1104 | 36.7k | function.AugmentedStructuralCFGPredecessorsFunction(), ignore_block, |
1105 | 481k | [&](const BasicBlock* b) { postdom_postorder.push_back(b); }, |
1106 | 36.7k | no_terminal_blocks); |
1107 | 36.7k | auto postdom_edges = CFA<BasicBlock>::CalculateDominators( |
1108 | 36.7k | postdom_postorder, |
1109 | 36.7k | function.AugmentedStructuralCFGSuccessorsFunction()); |
1110 | 481k | for (auto edge : postdom_edges) { |
1111 | 481k | edge.first->SetImmediateStructuralPostDominator(edge.second); |
1112 | 481k | } |
1113 | | /// calculate back edges. |
1114 | 36.7k | CFA<BasicBlock>::DepthFirstTraversal( |
1115 | 36.7k | function.pseudo_entry_block(), |
1116 | 36.7k | function.AugmentedStructuralCFGSuccessorsFunction(), ignore_block, |
1117 | 36.7k | ignore_block, |
1118 | 45.3k | [&](const BasicBlock* from, const BasicBlock* to) { |
1119 | | // A back edge must be a real edge. Since the augmented successors |
1120 | | // contain structural edges, filter those from consideration. |
1121 | 78.4k | for (const auto* succ : *(from->successors())) { |
1122 | 78.4k | if (succ == to) back_edges.emplace_back(from->id(), to->id()); |
1123 | 78.4k | } |
1124 | 45.3k | }, |
1125 | 36.7k | no_terminal_blocks); |
1126 | 36.7k | } |
1127 | 37.4k | UpdateContinueConstructExitBlocks(function, back_edges); |
1128 | | |
1129 | 37.4k | if (auto error = |
1130 | 37.4k | StructuredControlFlowChecks(_, &function, back_edges, postorder)) |
1131 | 1.43k | return error; |
1132 | 37.4k | } |
1133 | 37.5k | } |
1134 | | |
1135 | 25.1k | if (auto error = MaximalReconvergenceChecks(_)) { |
1136 | 0 | return error; |
1137 | 0 | } |
1138 | | |
1139 | 25.1k | return SPV_SUCCESS; |
1140 | 25.1k | } |
1141 | | |
1142 | 15.4M | spv_result_t CfgPass(ValidationState_t& _, const Instruction* inst) { |
1143 | 15.4M | spv::Op opcode = inst->opcode(); |
1144 | 15.4M | switch (opcode) { |
1145 | 491k | case spv::Op::OpLabel: |
1146 | 491k | if (auto error = _.current_function().RegisterBlock(inst->id())) |
1147 | 0 | return error; |
1148 | | |
1149 | | // TODO(github:1661) This should be done in the |
1150 | | // ValidationState::RegisterInstruction method but because of the order of |
1151 | | // passes the OpLabel ends up not being part of the basic block it starts. |
1152 | 491k | _.current_function().current_block()->set_label(inst); |
1153 | 491k | break; |
1154 | 80.2k | case spv::Op::OpLoopMerge: { |
1155 | 80.2k | uint32_t merge_block = inst->GetOperandAs<uint32_t>(0); |
1156 | 80.2k | uint32_t continue_block = inst->GetOperandAs<uint32_t>(1); |
1157 | 80.2k | CFG_ASSERT(MergeBlockAssert, merge_block); |
1158 | | |
1159 | 80.1k | if (auto error = _.current_function().RegisterLoopMerge(merge_block, |
1160 | 80.1k | continue_block)) |
1161 | 0 | return error; |
1162 | 80.1k | } break; |
1163 | 87.0k | case spv::Op::OpSelectionMerge: { |
1164 | 87.0k | uint32_t merge_block = inst->GetOperandAs<uint32_t>(0); |
1165 | 87.0k | CFG_ASSERT(MergeBlockAssert, merge_block); |
1166 | | |
1167 | 87.0k | if (auto error = _.current_function().RegisterSelectionMerge(merge_block)) |
1168 | 0 | return error; |
1169 | 87.0k | } break; |
1170 | 255k | case spv::Op::OpBranch: { |
1171 | 255k | uint32_t target = inst->GetOperandAs<uint32_t>(0); |
1172 | 255k | CFG_ASSERT(FirstBlockAssert, target); |
1173 | | |
1174 | 255k | _.current_function().RegisterBlockEnd({target}); |
1175 | 255k | } break; |
1176 | 114k | case spv::Op::OpBranchConditional: { |
1177 | 114k | uint32_t tlabel = inst->GetOperandAs<uint32_t>(1); |
1178 | 114k | uint32_t flabel = inst->GetOperandAs<uint32_t>(2); |
1179 | 114k | CFG_ASSERT(FirstBlockAssert, tlabel); |
1180 | 114k | CFG_ASSERT(FirstBlockAssert, flabel); |
1181 | | |
1182 | 114k | _.current_function().RegisterBlockEnd({tlabel, flabel}); |
1183 | 114k | } break; |
1184 | | |
1185 | 16.7k | case spv::Op::OpSwitch: { |
1186 | 16.7k | std::vector<uint32_t> cases; |
1187 | 116k | for (size_t i = 1; i < inst->operands().size(); i += 2) { |
1188 | 100k | uint32_t target = inst->GetOperandAs<uint32_t>(i); |
1189 | 100k | CFG_ASSERT(FirstBlockAssert, target); |
1190 | 100k | cases.push_back(target); |
1191 | 100k | } |
1192 | 16.7k | _.current_function().RegisterBlockEnd({cases}); |
1193 | 16.7k | } break; |
1194 | 39.5k | case spv::Op::OpReturn: { |
1195 | 39.5k | const uint32_t return_type = _.current_function().GetResultTypeId(); |
1196 | 39.5k | const Instruction* return_type_inst = _.FindDef(return_type); |
1197 | 39.5k | assert(return_type_inst); |
1198 | 39.5k | if (return_type_inst->opcode() != spv::Op::OpTypeVoid) |
1199 | 46 | return _.diag(SPV_ERROR_INVALID_CFG, inst) |
1200 | 46 | << "OpReturn can only be called from a function with void " |
1201 | 46 | << "return type."; |
1202 | 39.4k | _.current_function().RegisterBlockEnd(std::vector<uint32_t>()); |
1203 | 39.4k | break; |
1204 | 39.5k | } |
1205 | 15.3k | case spv::Op::OpKill: |
1206 | 33.6k | case spv::Op::OpReturnValue: |
1207 | 64.0k | case spv::Op::OpUnreachable: |
1208 | 64.0k | case spv::Op::OpTerminateInvocation: |
1209 | 64.0k | case spv::Op::OpIgnoreIntersectionKHR: |
1210 | 64.0k | case spv::Op::OpTerminateRayKHR: |
1211 | 64.0k | case spv::Op::OpEmitMeshTasksEXT: |
1212 | 64.0k | case spv::Op::OpAbortKHR: |
1213 | 64.0k | _.current_function().RegisterBlockEnd(std::vector<uint32_t>()); |
1214 | | // Ops with dedicated passes check for the Execution Model there |
1215 | 64.0k | if (opcode == spv::Op::OpKill) { |
1216 | 15.3k | _.current_function().RegisterExecutionModelLimitation( |
1217 | 15.3k | spv::ExecutionModel::Fragment, |
1218 | 15.3k | "OpKill requires Fragment execution model"); |
1219 | 15.3k | } |
1220 | 64.0k | if (opcode == spv::Op::OpTerminateInvocation) { |
1221 | 38 | _.current_function().RegisterExecutionModelLimitation( |
1222 | 38 | spv::ExecutionModel::Fragment, |
1223 | 38 | "OpTerminateInvocation requires Fragment execution model"); |
1224 | 38 | } |
1225 | 64.0k | if (opcode == spv::Op::OpIgnoreIntersectionKHR) { |
1226 | 3 | _.current_function().RegisterExecutionModelLimitation( |
1227 | 3 | spv::ExecutionModel::AnyHitKHR, |
1228 | 3 | "OpIgnoreIntersectionKHR requires AnyHitKHR execution model"); |
1229 | 3 | } |
1230 | 64.0k | if (opcode == spv::Op::OpTerminateRayKHR) { |
1231 | 3 | _.current_function().RegisterExecutionModelLimitation( |
1232 | 3 | spv::ExecutionModel::AnyHitKHR, |
1233 | 3 | "OpTerminateRayKHR requires AnyHitKHR execution model"); |
1234 | 3 | } |
1235 | | |
1236 | 64.0k | break; |
1237 | 14.3M | default: |
1238 | 14.3M | break; |
1239 | 15.4M | } |
1240 | 15.4M | return SPV_SUCCESS; |
1241 | 15.4M | } |
1242 | | |
1243 | 37.9k | void ReachabilityPass(ValidationState_t& _) { |
1244 | 45.2k | for (auto& f : _.functions()) { |
1245 | 45.2k | std::vector<BasicBlock*> stack; |
1246 | 45.2k | auto entry = f.first_block(); |
1247 | | // Skip function declarations. |
1248 | 45.2k | if (entry) stack.push_back(entry); |
1249 | | |
1250 | 548k | while (!stack.empty()) { |
1251 | 502k | auto block = stack.back(); |
1252 | 502k | stack.pop_back(); |
1253 | | |
1254 | 502k | if (block->reachable()) continue; |
1255 | | |
1256 | 384k | block->set_reachable(true); |
1257 | 459k | for (auto succ : *block->successors()) { |
1258 | 459k | stack.push_back(succ); |
1259 | 459k | } |
1260 | 384k | } |
1261 | 45.2k | } |
1262 | | |
1263 | | // Repeat for structural reachability. |
1264 | 45.2k | for (auto& f : _.functions()) { |
1265 | 45.2k | std::vector<BasicBlock*> stack; |
1266 | 45.2k | auto entry = f.first_block(); |
1267 | | // Skip function declarations. |
1268 | 45.2k | if (entry) stack.push_back(entry); |
1269 | | |
1270 | 727k | while (!stack.empty()) { |
1271 | 682k | auto block = stack.back(); |
1272 | 682k | stack.pop_back(); |
1273 | | |
1274 | 682k | if (block->structurally_reachable()) continue; |
1275 | | |
1276 | 410k | block->set_structurally_reachable(true); |
1277 | 638k | for (auto succ : *block->structural_successors()) { |
1278 | 638k | stack.push_back(succ); |
1279 | 638k | } |
1280 | 410k | } |
1281 | 45.2k | } |
1282 | 37.9k | } |
1283 | | |
1284 | 14.9M | spv_result_t ControlFlowPass(ValidationState_t& _, const Instruction* inst) { |
1285 | 14.9M | switch (inst->opcode()) { |
1286 | 74.5k | case spv::Op::OpPhi: |
1287 | 74.5k | if (auto error = ValidatePhi(_, inst)) return error; |
1288 | 74.4k | break; |
1289 | 228k | case spv::Op::OpBranch: |
1290 | 228k | if (auto error = ValidateBranch(_, inst)) return error; |
1291 | 228k | break; |
1292 | 228k | case spv::Op::OpBranchConditional: |
1293 | 103k | if (auto error = ValidateBranchConditional(_, inst)) return error; |
1294 | 103k | break; |
1295 | 103k | case spv::Op::OpReturnValue: |
1296 | 17.0k | if (auto error = ValidateReturnValue(_, inst)) return error; |
1297 | 17.0k | break; |
1298 | 17.0k | case spv::Op::OpSwitch: |
1299 | 14.1k | if (auto error = ValidateSwitch(_, inst)) return error; |
1300 | 14.1k | break; |
1301 | 42.0k | case spv::Op::OpLoopMerge: |
1302 | 42.0k | if (auto error = ValidateLoopMerge(_, inst)) return error; |
1303 | 41.9k | break; |
1304 | 41.9k | case spv::Op::OpLifetimeStart: |
1305 | 29 | case spv::Op::OpLifetimeStop: |
1306 | 29 | if (auto error = ValidateLifetime(_, inst)) return error; |
1307 | 19 | break; |
1308 | 14.4M | default: |
1309 | 14.4M | break; |
1310 | 14.9M | } |
1311 | | |
1312 | 14.9M | return SPV_SUCCESS; |
1313 | 14.9M | } |
1314 | | |
1315 | | } // namespace val |
1316 | | } // namespace spvtools |