/src/spirv-tools/source/val/validate_id.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 <unordered_set> |
16 | | #include <vector> |
17 | | |
18 | | #include "source/instruction.h" |
19 | | #include "source/opcode.h" |
20 | | #include "source/operand.h" |
21 | | #include "source/val/function.h" |
22 | | #include "source/val/validate.h" |
23 | | #include "source/val/validation_state.h" |
24 | | #include "spirv-tools/libspirv.h" |
25 | | |
26 | | namespace spvtools { |
27 | | namespace val { |
28 | | |
29 | 14.7M | spv_result_t UpdateIdUse(ValidationState_t& _, const Instruction* inst) { |
30 | 22.2M | for (auto& operand : inst->operands()) { |
31 | 22.2M | const spv_operand_type_t& type = operand.type; |
32 | 22.2M | const uint32_t operand_id = inst->word(operand.offset); |
33 | 22.2M | if (spvIsIdType(type) && type != SPV_OPERAND_TYPE_RESULT_ID) { |
34 | 17.3M | if (auto def = _.FindDef(operand_id)) |
35 | 17.3M | def->RegisterUse(inst, operand.offset); |
36 | 17.3M | } |
37 | 22.2M | } |
38 | | |
39 | 14.7M | return SPV_SUCCESS; |
40 | 14.7M | } |
41 | | |
42 | | /// This function checks all ID definitions dominate their use in the CFG. |
43 | | /// |
44 | | /// This function will iterate over all ID definitions that are defined in the |
45 | | /// functions of a module and make sure that the definitions appear in a |
46 | | /// block that dominates their use. |
47 | | /// |
48 | | /// NOTE: This function does NOT check module scoped functions which are |
49 | | /// checked during the initial binary parse in the IdPass below |
50 | 26.2k | spv_result_t CheckIdDefinitionDominateUse(ValidationState_t& _) { |
51 | 26.2k | std::vector<const Instruction*> phi_instructions; |
52 | 26.2k | std::unordered_set<uint32_t> phi_ids; |
53 | 13.9M | for (const auto& inst : _.ordered_instructions()) { |
54 | 13.9M | if (inst.id() == 0) continue; |
55 | 2.19M | if (const Function* func = inst.function()) { |
56 | 1.59M | if (const BasicBlock* block = inst.block()) { |
57 | | // If the Id is defined within a block then make sure all references to |
58 | | // that Id appear in a blocks that are dominated by the defining block |
59 | 1.84M | for (auto& use_index_pair : inst.uses()) { |
60 | 1.84M | const Instruction* use = use_index_pair.first; |
61 | 1.84M | if (const BasicBlock* use_block = use->block()) { |
62 | 1.72M | if (use_block->reachable() == false) continue; |
63 | 1.64M | if (use->opcode() == spv::Op::OpPhi) { |
64 | 87.5k | if (phi_ids.insert(use->id()).second) { |
65 | 57.3k | phi_instructions.push_back(use); |
66 | 57.3k | } |
67 | 1.55M | } else if (!block->dominates(*use->block())) { |
68 | 72 | return _.diag(SPV_ERROR_INVALID_ID, use_block->label()) |
69 | 72 | << "ID " << _.getIdName(inst.id()) << " defined in block " |
70 | 72 | << _.getIdName(block->id()) |
71 | 72 | << " does not dominate its use in block " |
72 | 72 | << _.getIdName(use_block->id()); |
73 | 72 | } |
74 | 1.64M | } |
75 | 1.84M | } |
76 | 1.20M | } else { |
77 | | // If the Ids defined within a function but not in a block(i.e. function |
78 | | // parameters, block ids), then make sure all references to that Id |
79 | | // appear within the same function |
80 | 738k | for (auto use : inst.uses()) { |
81 | 738k | const Instruction* user = use.first; |
82 | 738k | if (user->function() && user->function() != func) { |
83 | 15 | return _.diag(SPV_ERROR_INVALID_ID, _.FindDef(func->id())) |
84 | 15 | << "ID " << _.getIdName(inst.id()) << " used in function " |
85 | 15 | << _.getIdName(user->function()->id()) |
86 | 15 | << " is used outside of it's defining function " |
87 | 15 | << _.getIdName(func->id()); |
88 | 15 | } |
89 | 738k | } |
90 | 394k | } |
91 | 1.59M | } |
92 | | // NOTE: Ids defined outside of functions must appear before they are used |
93 | | // This check is being performed in the IdPass function |
94 | 2.19M | } |
95 | | |
96 | | // Check all OpPhi parent blocks are dominated by the variable's defining |
97 | | // blocks |
98 | 57.1k | for (const Instruction* phi : phi_instructions) { |
99 | 57.1k | if (phi->block()->reachable() == false) continue; |
100 | 175k | for (size_t i = 3; i < phi->operands().size(); i += 2) { |
101 | 118k | const Instruction* variable = _.FindDef(phi->word(i)); |
102 | 118k | const BasicBlock* parent = |
103 | 118k | phi->function()->GetBlock(phi->word(i + 1)).first; |
104 | 118k | if (variable->block() && parent->reachable() && |
105 | 84.9k | !variable->block()->dominates(*parent)) { |
106 | 31 | return _.diag(SPV_ERROR_INVALID_ID, phi) |
107 | 31 | << "In OpPhi instruction " << _.getIdName(phi->id()) << ", ID " |
108 | 31 | << _.getIdName(variable->id()) |
109 | 31 | << " definition does not dominate its parent " |
110 | 31 | << _.getIdName(parent->id()); |
111 | 31 | } |
112 | 118k | } |
113 | 57.1k | } |
114 | | |
115 | 26.0k | return SPV_SUCCESS; |
116 | 26.1k | } |
117 | | |
118 | 334k | bool InstructionCanHaveTypeOperand(const Instruction* inst) { |
119 | 334k | static std::unordered_set<spv::Op> instruction_allow_set{ |
120 | 334k | spv::Op::OpSizeOf, |
121 | 334k | spv::Op::OpCooperativeMatrixLengthNV, |
122 | 334k | spv::Op::OpCooperativeMatrixLengthKHR, |
123 | 334k | spv::Op::OpUntypedArrayLengthKHR, |
124 | 334k | spv::Op::OpFunction, |
125 | 334k | spv::Op::OpAsmINTEL, |
126 | 334k | spv::Op::OpConstantSizeOfEXT, |
127 | 334k | spv::Op::OpBufferPointerEXT, |
128 | 334k | spv::Op::OpUntypedImageTexelPointerEXT, |
129 | 334k | spv::Op::OpAbortKHR, |
130 | 334k | }; |
131 | 334k | const auto opcode = inst->opcode(); |
132 | 334k | bool type_instruction = spvOpcodeGeneratesType(opcode); |
133 | 334k | bool debug_instruction = spvOpcodeIsDebug(opcode) || inst->IsDebugInfo(); |
134 | 334k | bool coop_matrix_spec_constant_op_length = |
135 | 334k | (opcode == spv::Op::OpSpecConstantOp) && |
136 | 21 | (spv::Op(inst->word(3)) == spv::Op::OpCooperativeMatrixLengthNV || |
137 | 6 | spv::Op(inst->word(3)) == spv::Op::OpCooperativeMatrixLengthKHR); |
138 | 334k | return type_instruction || debug_instruction || inst->IsNonSemantic() || |
139 | 55.1k | spvOpcodeIsDecoration(opcode) || instruction_allow_set.count(opcode) || |
140 | 141 | spvOpcodeGeneratesUntypedPointer(opcode) || |
141 | 110 | coop_matrix_spec_constant_op_length; |
142 | 334k | } |
143 | | |
144 | 11.2M | bool InstructionRequiresTypeOperand(const Instruction* inst) { |
145 | 11.2M | static std::unordered_set<spv::Op> instruction_deny_set{ |
146 | 11.2M | spv::Op::OpExtInst, |
147 | 11.2M | spv::Op::OpExtInstWithForwardRefsKHR, |
148 | 11.2M | spv::Op::OpExtInstImport, |
149 | 11.2M | spv::Op::OpSelectionMerge, |
150 | 11.2M | spv::Op::OpLoopMerge, |
151 | 11.2M | spv::Op::OpFunction, |
152 | 11.2M | spv::Op::OpSizeOf, |
153 | 11.2M | spv::Op::OpCooperativeMatrixLengthNV, |
154 | 11.2M | spv::Op::OpCooperativeMatrixLengthKHR, |
155 | 11.2M | spv::Op::OpPhi, |
156 | 11.2M | spv::Op::OpUntypedArrayLengthKHR, |
157 | 11.2M | spv::Op::OpAsmINTEL, |
158 | 11.2M | spv::Op::OpAliasScopeDeclINTEL, |
159 | 11.2M | spv::Op::OpAliasScopeListDeclINTEL, |
160 | 11.2M | spv::Op::OpAbortKHR, |
161 | 11.2M | }; |
162 | 11.2M | const auto opcode = inst->opcode(); |
163 | 11.2M | bool debug_instruction = spvOpcodeIsDebug(opcode) || inst->IsDebugInfo(); |
164 | 11.2M | bool coop_matrix_spec_constant_op_length = |
165 | 11.2M | opcode == spv::Op::OpSpecConstantOp && |
166 | 3 | (spv::Op(inst->word(3)) == spv::Op::OpCooperativeMatrixLengthNV || |
167 | 3 | spv::Op(inst->word(3)) == spv::Op::OpCooperativeMatrixLengthKHR); |
168 | | |
169 | 11.2M | return !debug_instruction && !inst->IsNonSemantic() && |
170 | 11.2M | !spvOpcodeIsDecoration(opcode) && !spvOpcodeIsBranch(opcode) && |
171 | 219k | !instruction_deny_set.count(opcode) && |
172 | 535 | !spvOpcodeGeneratesUntypedPointer(opcode) && |
173 | 518 | !coop_matrix_spec_constant_op_length; |
174 | 11.2M | } |
175 | | |
176 | | // Performs SSA validation on the IDs of an instruction. The |
177 | | // can_have_forward_declared_ids functor should return true if the |
178 | | // instruction operand's ID can be forward referenced. |
179 | 15.3M | spv_result_t IdPass(ValidationState_t& _, Instruction* inst) { |
180 | 15.3M | auto can_have_forward_declared_ids = |
181 | 15.3M | spvIsExtendedInstruction(inst->opcode()) && |
182 | 64.3k | spvExtInstIsDebugInfo(inst->ext_inst_type()) |
183 | 15.3M | ? spvDbgInfoExtOperandCanBeForwardDeclaredFunction( |
184 | 99 | inst->opcode(), inst->ext_inst_type(), inst->word(4)) |
185 | 15.3M | : spvOperandCanBeForwardDeclaredFunction(inst->opcode()); |
186 | | |
187 | | // Keep track of a result id defined by this instruction. 0 means it |
188 | | // does not define an id. |
189 | 15.3M | uint32_t result_id = 0; |
190 | 15.3M | bool has_forward_declared_ids = false; |
191 | | |
192 | 41.1M | for (unsigned i = 0; i < inst->operands().size(); i++) { |
193 | 25.7M | const spv_parsed_operand_t& operand = inst->operand(i); |
194 | 25.7M | const spv_operand_type_t& type = operand.type; |
195 | | // We only care about Id operands, which are a single word. |
196 | 25.7M | const uint32_t operand_word = inst->word(operand.offset); |
197 | | |
198 | 25.7M | auto ret = SPV_ERROR_INTERNAL; |
199 | 25.7M | switch (type) { |
200 | 2.85M | case SPV_OPERAND_TYPE_RESULT_ID: |
201 | | // NOTE: Multiple Id definitions are being checked by the binary parser. |
202 | | // |
203 | | // Defer undefined-forward-reference removal until after we've analyzed |
204 | | // the remaining operands to this instruction. Deferral only matters |
205 | | // for OpPhi since it's the only case where it defines its own forward |
206 | | // reference. Other instructions that can have forward references |
207 | | // either don't define a value or the forward reference is to a function |
208 | | // Id (and hence defined outside of a function body). |
209 | 2.85M | result_id = operand_word; |
210 | | // NOTE: The result Id is added (in RegisterInstruction) *after* all of |
211 | | // the other Ids have been checked to avoid premature use in the same |
212 | | // instruction. |
213 | 2.85M | ret = SPV_SUCCESS; |
214 | 2.85M | break; |
215 | 18.5M | case SPV_OPERAND_TYPE_ID: |
216 | 18.5M | case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: |
217 | 18.5M | case SPV_OPERAND_TYPE_SCOPE_ID: |
218 | 18.5M | if (const auto def = _.FindDef(operand_word)) { |
219 | 14.7M | if (spvOpcodeGeneratesType(def->opcode()) && |
220 | 334k | !InstructionCanHaveTypeOperand(inst)) { |
221 | 95 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
222 | 95 | << "Operand " << _.getIdName(operand_word) |
223 | 95 | << " cannot be a type"; |
224 | 14.7M | } else if (def->type_id() == 0 && |
225 | 11.5M | !spvOpcodeGeneratesType(def->opcode()) && |
226 | 11.2M | InstructionRequiresTypeOperand(inst) && |
227 | 483 | InstructionRequiresTypeOperand(def)) { |
228 | 35 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
229 | 35 | << "Operand " << _.getIdName(operand_word) |
230 | 35 | << " requires a type"; |
231 | 14.7M | } else if (def->IsNonSemantic() && !inst->IsNonSemantic()) { |
232 | 10 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
233 | 10 | << "Operand " << _.getIdName(operand_word) |
234 | 10 | << " in semantic instruction cannot be a non-semantic " |
235 | 10 | "instruction"; |
236 | 14.7M | } else { |
237 | 14.7M | ret = SPV_SUCCESS; |
238 | 14.7M | } |
239 | 14.7M | } else if (can_have_forward_declared_ids(i)) { |
240 | 3.79M | has_forward_declared_ids = true; |
241 | 3.79M | if (spvOpcodeGeneratesType(inst->opcode()) && |
242 | 1.05k | !_.IsForwardPointer(operand_word)) { |
243 | 671 | ret = _.diag(SPV_ERROR_INVALID_ID, inst) |
244 | 671 | << "Operand " << _.getIdName(operand_word) |
245 | 671 | << " requires a previous definition"; |
246 | 3.79M | } else { |
247 | 3.79M | ret = _.ForwardDeclareId(operand_word); |
248 | 3.79M | } |
249 | 3.79M | } else { |
250 | 559 | ret = _.diag(SPV_ERROR_INVALID_ID, inst) |
251 | 559 | << "ID " << _.getIdName(operand_word) |
252 | 559 | << " has not been defined"; |
253 | 559 | } |
254 | 18.5M | break; |
255 | 18.5M | case SPV_OPERAND_TYPE_TYPE_ID: |
256 | 1.94M | if (_.IsDefinedId(operand_word)) { |
257 | 1.94M | auto* def = _.FindDef(operand_word); |
258 | 1.94M | if (!spvOpcodeGeneratesType(def->opcode())) { |
259 | 12 | ret = _.diag(SPV_ERROR_INVALID_ID, inst) |
260 | 12 | << "ID " << _.getIdName(operand_word) << " is not a type id"; |
261 | 1.94M | } else { |
262 | 1.94M | ret = SPV_SUCCESS; |
263 | 1.94M | } |
264 | 1.94M | } else { |
265 | 941 | ret = _.diag(SPV_ERROR_INVALID_ID, inst) |
266 | 941 | << "ID " << _.getIdName(operand_word) |
267 | 941 | << " has not been defined"; |
268 | 941 | } |
269 | 1.94M | break; |
270 | 64.1k | case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: |
271 | | // Ideally, this check would live in validate_extensions.cpp. But since |
272 | | // forward references are only allowed on non-semantic instructions, and |
273 | | // ID validation is done first, we would fail with a "ID had not been |
274 | | // defined" error before we could give a more helpful message. For this |
275 | | // reason, this test is done here, so we can be more helpful to the |
276 | | // user. |
277 | 64.1k | if (inst->opcode() == spv::Op::OpExtInstWithForwardRefsKHR && |
278 | 6 | !inst->IsNonSemantic()) |
279 | 4 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
280 | 4 | << "OpExtInstWithForwardRefsKHR is only allowed with " |
281 | 4 | "non-semantic instructions."; |
282 | 64.1k | ret = SPV_SUCCESS; |
283 | 64.1k | break; |
284 | 2.36M | default: |
285 | 2.36M | ret = SPV_SUCCESS; |
286 | 2.36M | break; |
287 | 25.7M | } |
288 | 25.7M | if (SPV_SUCCESS != ret) return ret; |
289 | 25.7M | } |
290 | 15.3M | const bool must_have_forward_declared_ids = |
291 | 15.3M | inst->opcode() == spv::Op::OpExtInstWithForwardRefsKHR; |
292 | 15.3M | if (must_have_forward_declared_ids && !has_forward_declared_ids) { |
293 | 1 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
294 | 1 | << "Opcode OpExtInstWithForwardRefsKHR must have at least one " |
295 | 1 | "forward " |
296 | 1 | "declared ID."; |
297 | 1 | } |
298 | | |
299 | 15.3M | if (result_id) _.RemoveIfForwardDeclared(result_id); |
300 | | |
301 | 15.3M | return SPV_SUCCESS; |
302 | 15.3M | } |
303 | | |
304 | | } // namespace val |
305 | | } // namespace spvtools |