1 | // Copyright (c) 2018 Google LLC. |
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/validate.h" |
16 | |
17 | #include <algorithm> |
18 | |
19 | #include "source/opcode.h" |
20 | #include "source/spirv_target_env.h" |
21 | #include "source/val/instruction.h" |
22 | #include "source/val/validation_state.h" |
23 | |
24 | namespace spvtools { |
25 | namespace val { |
26 | namespace { |
27 | |
28 | spv_result_t ValidateEntryPoint(ValidationState_t& _, const Instruction* inst) { |
29 | const auto entry_point_id = inst->GetOperandAs<uint32_t>(1); |
30 | auto entry_point = _.FindDef(entry_point_id); |
31 | if (!entry_point || SpvOpFunction != entry_point->opcode()) { |
32 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
33 | << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_point_id) |
34 | << "' is not a function." ; |
35 | } |
36 | |
37 | // Only check the shader execution models |
38 | const SpvExecutionModel execution_model = |
39 | inst->GetOperandAs<SpvExecutionModel>(0); |
40 | if (execution_model != SpvExecutionModelKernel) { |
41 | const auto entry_point_type_id = entry_point->GetOperandAs<uint32_t>(3); |
42 | const auto entry_point_type = _.FindDef(entry_point_type_id); |
43 | if (!entry_point_type || 3 != entry_point_type->words().size()) { |
44 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
45 | << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_point_id) |
46 | << "'s function parameter count is not zero." ; |
47 | } |
48 | } |
49 | |
50 | auto return_type = _.FindDef(entry_point->type_id()); |
51 | if (!return_type || SpvOpTypeVoid != return_type->opcode()) { |
52 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
53 | << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_point_id) |
54 | << "'s function return type is not void." ; |
55 | } |
56 | |
57 | const auto* execution_modes = _.GetExecutionModes(entry_point_id); |
58 | if (_.HasCapability(SpvCapabilityShader)) { |
59 | switch (execution_model) { |
60 | case SpvExecutionModelFragment: |
61 | if (execution_modes && |
62 | execution_modes->count(SpvExecutionModeOriginUpperLeft) && |
63 | execution_modes->count(SpvExecutionModeOriginLowerLeft)) { |
64 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
65 | << "Fragment execution model entry points can only specify " |
66 | "one of OriginUpperLeft or OriginLowerLeft execution " |
67 | "modes." ; |
68 | } |
69 | if (!execution_modes || |
70 | (!execution_modes->count(SpvExecutionModeOriginUpperLeft) && |
71 | !execution_modes->count(SpvExecutionModeOriginLowerLeft))) { |
72 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
73 | << "Fragment execution model entry points require either an " |
74 | "OriginUpperLeft or OriginLowerLeft execution mode." ; |
75 | } |
76 | if (execution_modes && |
77 | 1 < std::count_if(execution_modes->begin(), execution_modes->end(), |
78 | [](const SpvExecutionMode& mode) { |
79 | switch (mode) { |
80 | case SpvExecutionModeDepthGreater: |
81 | case SpvExecutionModeDepthLess: |
82 | case SpvExecutionModeDepthUnchanged: |
83 | return true; |
84 | default: |
85 | return false; |
86 | } |
87 | })) { |
88 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
89 | << "Fragment execution model entry points can specify at most " |
90 | "one of DepthGreater, DepthLess or DepthUnchanged " |
91 | "execution modes." ; |
92 | } |
93 | if (execution_modes && |
94 | 1 < std::count_if( |
95 | execution_modes->begin(), execution_modes->end(), |
96 | [](const SpvExecutionMode& mode) { |
97 | switch (mode) { |
98 | case SpvExecutionModePixelInterlockOrderedEXT: |
99 | case SpvExecutionModePixelInterlockUnorderedEXT: |
100 | case SpvExecutionModeSampleInterlockOrderedEXT: |
101 | case SpvExecutionModeSampleInterlockUnorderedEXT: |
102 | case SpvExecutionModeShadingRateInterlockOrderedEXT: |
103 | case SpvExecutionModeShadingRateInterlockUnorderedEXT: |
104 | return true; |
105 | default: |
106 | return false; |
107 | } |
108 | })) { |
109 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
110 | << "Fragment execution model entry points can specify at most " |
111 | "one fragment shader interlock execution mode." ; |
112 | } |
113 | break; |
114 | case SpvExecutionModelTessellationControl: |
115 | case SpvExecutionModelTessellationEvaluation: |
116 | if (execution_modes && |
117 | 1 < std::count_if(execution_modes->begin(), execution_modes->end(), |
118 | [](const SpvExecutionMode& mode) { |
119 | switch (mode) { |
120 | case SpvExecutionModeSpacingEqual: |
121 | case SpvExecutionModeSpacingFractionalEven: |
122 | case SpvExecutionModeSpacingFractionalOdd: |
123 | return true; |
124 | default: |
125 | return false; |
126 | } |
127 | })) { |
128 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
129 | << "Tessellation execution model entry points can specify at " |
130 | "most one of SpacingEqual, SpacingFractionalOdd or " |
131 | "SpacingFractionalEven execution modes." ; |
132 | } |
133 | if (execution_modes && |
134 | 1 < std::count_if(execution_modes->begin(), execution_modes->end(), |
135 | [](const SpvExecutionMode& mode) { |
136 | switch (mode) { |
137 | case SpvExecutionModeTriangles: |
138 | case SpvExecutionModeQuads: |
139 | case SpvExecutionModeIsolines: |
140 | return true; |
141 | default: |
142 | return false; |
143 | } |
144 | })) { |
145 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
146 | << "Tessellation execution model entry points can specify at " |
147 | "most one of Triangles, Quads or Isolines execution modes." ; |
148 | } |
149 | if (execution_modes && |
150 | 1 < std::count_if(execution_modes->begin(), execution_modes->end(), |
151 | [](const SpvExecutionMode& mode) { |
152 | switch (mode) { |
153 | case SpvExecutionModeVertexOrderCw: |
154 | case SpvExecutionModeVertexOrderCcw: |
155 | return true; |
156 | default: |
157 | return false; |
158 | } |
159 | })) { |
160 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
161 | << "Tessellation execution model entry points can specify at " |
162 | "most one of VertexOrderCw or VertexOrderCcw execution " |
163 | "modes." ; |
164 | } |
165 | break; |
166 | case SpvExecutionModelGeometry: |
167 | if (!execution_modes || |
168 | 1 != std::count_if(execution_modes->begin(), execution_modes->end(), |
169 | [](const SpvExecutionMode& mode) { |
170 | switch (mode) { |
171 | case SpvExecutionModeInputPoints: |
172 | case SpvExecutionModeInputLines: |
173 | case SpvExecutionModeInputLinesAdjacency: |
174 | case SpvExecutionModeTriangles: |
175 | case SpvExecutionModeInputTrianglesAdjacency: |
176 | return true; |
177 | default: |
178 | return false; |
179 | } |
180 | })) { |
181 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
182 | << "Geometry execution model entry points must specify " |
183 | "exactly one of InputPoints, InputLines, " |
184 | "InputLinesAdjacency, Triangles or InputTrianglesAdjacency " |
185 | "execution modes." ; |
186 | } |
187 | if (!execution_modes || |
188 | 1 != std::count_if(execution_modes->begin(), execution_modes->end(), |
189 | [](const SpvExecutionMode& mode) { |
190 | switch (mode) { |
191 | case SpvExecutionModeOutputPoints: |
192 | case SpvExecutionModeOutputLineStrip: |
193 | case SpvExecutionModeOutputTriangleStrip: |
194 | return true; |
195 | default: |
196 | return false; |
197 | } |
198 | })) { |
199 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
200 | << "Geometry execution model entry points must specify " |
201 | "exactly one of OutputPoints, OutputLineStrip or " |
202 | "OutputTriangleStrip execution modes." ; |
203 | } |
204 | break; |
205 | default: |
206 | break; |
207 | } |
208 | } |
209 | |
210 | if (spvIsVulkanEnv(_.context()->target_env)) { |
211 | switch (execution_model) { |
212 | case SpvExecutionModelGLCompute: |
213 | if (!execution_modes || |
214 | !execution_modes->count(SpvExecutionModeLocalSize)) { |
215 | bool ok = false; |
216 | for (auto& i : _.ordered_instructions()) { |
217 | if (i.opcode() == SpvOpDecorate) { |
218 | if (i.operands().size() > 2) { |
219 | if (i.GetOperandAs<SpvDecoration>(1) == SpvDecorationBuiltIn && |
220 | i.GetOperandAs<SpvBuiltIn>(2) == SpvBuiltInWorkgroupSize) { |
221 | ok = true; |
222 | break; |
223 | } |
224 | } |
225 | } |
226 | } |
227 | if (!ok) { |
228 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
229 | << "In the Vulkan environment, GLCompute execution model " |
230 | "entry points require either the LocalSize execution " |
231 | "mode or an object decorated with WorkgroupSize must be " |
232 | "specified." ; |
233 | } |
234 | } |
235 | break; |
236 | default: |
237 | break; |
238 | } |
239 | } |
240 | |
241 | return SPV_SUCCESS; |
242 | } |
243 | |
244 | spv_result_t ValidateExecutionMode(ValidationState_t& _, |
245 | const Instruction* inst) { |
246 | const auto entry_point_id = inst->GetOperandAs<uint32_t>(0); |
247 | const auto found = std::find(_.entry_points().cbegin(), |
248 | _.entry_points().cend(), entry_point_id); |
249 | if (found == _.entry_points().cend()) { |
250 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
251 | << "OpExecutionMode Entry Point <id> '" |
252 | << _.getIdName(entry_point_id) |
253 | << "' is not the Entry Point " |
254 | "operand of an OpEntryPoint." ; |
255 | } |
256 | |
257 | const auto mode = inst->GetOperandAs<SpvExecutionMode>(1); |
258 | if (inst->opcode() == SpvOpExecutionModeId) { |
259 | size_t operand_count = inst->operands().size(); |
260 | for (size_t i = 2; i < operand_count; ++i) { |
261 | const auto operand_id = inst->GetOperandAs<uint32_t>(2); |
262 | const auto* operand_inst = _.FindDef(operand_id); |
263 | if (mode == SpvExecutionModeSubgroupsPerWorkgroupId || |
264 | mode == SpvExecutionModeLocalSizeHintId || |
265 | mode == SpvExecutionModeLocalSizeId) { |
266 | if (!spvOpcodeIsConstant(operand_inst->opcode())) { |
267 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
268 | << "For OpExecutionModeId all Extra Operand ids must be " |
269 | "constant " |
270 | "instructions." ; |
271 | } |
272 | } else { |
273 | return _.diag(SPV_ERROR_INVALID_ID, inst) |
274 | << "OpExecutionModeId is only valid when the Mode operand is an " |
275 | "execution mode that takes Extra Operands that are id " |
276 | "operands." ; |
277 | } |
278 | } |
279 | } else if (mode == SpvExecutionModeSubgroupsPerWorkgroupId || |
280 | mode == SpvExecutionModeLocalSizeHintId || |
281 | mode == SpvExecutionModeLocalSizeId) { |
282 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
283 | << "OpExecutionMode is only valid when the Mode operand is an " |
284 | "execution mode that takes no Extra Operands, or takes Extra " |
285 | "Operands that are not id operands." ; |
286 | } |
287 | |
288 | const auto* models = _.GetExecutionModels(entry_point_id); |
289 | switch (mode) { |
290 | case SpvExecutionModeInvocations: |
291 | case SpvExecutionModeInputPoints: |
292 | case SpvExecutionModeInputLines: |
293 | case SpvExecutionModeInputLinesAdjacency: |
294 | case SpvExecutionModeInputTrianglesAdjacency: |
295 | case SpvExecutionModeOutputLineStrip: |
296 | case SpvExecutionModeOutputTriangleStrip: |
297 | if (!std::all_of(models->begin(), models->end(), |
298 | [](const SpvExecutionModel& model) { |
299 | return model == SpvExecutionModelGeometry; |
300 | })) { |
301 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
302 | << "Execution mode can only be used with the Geometry execution " |
303 | "model." ; |
304 | } |
305 | break; |
306 | case SpvExecutionModeOutputPoints: |
307 | if (!std::all_of(models->begin(), models->end(), |
308 | [&_](const SpvExecutionModel& model) { |
309 | switch (model) { |
310 | case SpvExecutionModelGeometry: |
311 | return true; |
312 | case SpvExecutionModelMeshNV: |
313 | return _.HasCapability(SpvCapabilityMeshShadingNV); |
314 | default: |
315 | return false; |
316 | } |
317 | })) { |
318 | if (_.HasCapability(SpvCapabilityMeshShadingNV)) { |
319 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
320 | << "Execution mode can only be used with the Geometry or " |
321 | "MeshNV execution model." ; |
322 | } else { |
323 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
324 | << "Execution mode can only be used with the Geometry " |
325 | "execution " |
326 | "model." ; |
327 | } |
328 | } |
329 | break; |
330 | case SpvExecutionModeSpacingEqual: |
331 | case SpvExecutionModeSpacingFractionalEven: |
332 | case SpvExecutionModeSpacingFractionalOdd: |
333 | case SpvExecutionModeVertexOrderCw: |
334 | case SpvExecutionModeVertexOrderCcw: |
335 | case SpvExecutionModePointMode: |
336 | case SpvExecutionModeQuads: |
337 | case SpvExecutionModeIsolines: |
338 | if (!std::all_of( |
339 | models->begin(), models->end(), |
340 | [](const SpvExecutionModel& model) { |
341 | return (model == SpvExecutionModelTessellationControl) || |
342 | (model == SpvExecutionModelTessellationEvaluation); |
343 | })) { |
344 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
345 | << "Execution mode can only be used with a tessellation " |
346 | "execution model." ; |
347 | } |
348 | break; |
349 | case SpvExecutionModeTriangles: |
350 | if (!std::all_of(models->begin(), models->end(), |
351 | [](const SpvExecutionModel& model) { |
352 | switch (model) { |
353 | case SpvExecutionModelGeometry: |
354 | case SpvExecutionModelTessellationControl: |
355 | case SpvExecutionModelTessellationEvaluation: |
356 | return true; |
357 | default: |
358 | return false; |
359 | } |
360 | })) { |
361 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
362 | << "Execution mode can only be used with a Geometry or " |
363 | "tessellation execution model." ; |
364 | } |
365 | break; |
366 | case SpvExecutionModeOutputVertices: |
367 | if (!std::all_of(models->begin(), models->end(), |
368 | [&_](const SpvExecutionModel& model) { |
369 | switch (model) { |
370 | case SpvExecutionModelGeometry: |
371 | case SpvExecutionModelTessellationControl: |
372 | case SpvExecutionModelTessellationEvaluation: |
373 | return true; |
374 | case SpvExecutionModelMeshNV: |
375 | return _.HasCapability(SpvCapabilityMeshShadingNV); |
376 | default: |
377 | return false; |
378 | } |
379 | })) { |
380 | if (_.HasCapability(SpvCapabilityMeshShadingNV)) { |
381 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
382 | << "Execution mode can only be used with a Geometry, " |
383 | "tessellation or MeshNV execution model." ; |
384 | } else { |
385 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
386 | << "Execution mode can only be used with a Geometry or " |
387 | "tessellation execution model." ; |
388 | } |
389 | } |
390 | break; |
391 | case SpvExecutionModePixelCenterInteger: |
392 | case SpvExecutionModeOriginUpperLeft: |
393 | case SpvExecutionModeOriginLowerLeft: |
394 | case SpvExecutionModeEarlyFragmentTests: |
395 | case SpvExecutionModeDepthReplacing: |
396 | case SpvExecutionModeDepthGreater: |
397 | case SpvExecutionModeDepthLess: |
398 | case SpvExecutionModeDepthUnchanged: |
399 | case SpvExecutionModePixelInterlockOrderedEXT: |
400 | case SpvExecutionModePixelInterlockUnorderedEXT: |
401 | case SpvExecutionModeSampleInterlockOrderedEXT: |
402 | case SpvExecutionModeSampleInterlockUnorderedEXT: |
403 | case SpvExecutionModeShadingRateInterlockOrderedEXT: |
404 | case SpvExecutionModeShadingRateInterlockUnorderedEXT: |
405 | if (!std::all_of(models->begin(), models->end(), |
406 | [](const SpvExecutionModel& model) { |
407 | return model == SpvExecutionModelFragment; |
408 | })) { |
409 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
410 | << "Execution mode can only be used with the Fragment execution " |
411 | "model." ; |
412 | } |
413 | break; |
414 | case SpvExecutionModeLocalSizeHint: |
415 | case SpvExecutionModeVecTypeHint: |
416 | case SpvExecutionModeContractionOff: |
417 | case SpvExecutionModeLocalSizeHintId: |
418 | if (!std::all_of(models->begin(), models->end(), |
419 | [](const SpvExecutionModel& model) { |
420 | return model == SpvExecutionModelKernel; |
421 | })) { |
422 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
423 | << "Execution mode can only be used with the Kernel execution " |
424 | "model." ; |
425 | } |
426 | break; |
427 | case SpvExecutionModeLocalSize: |
428 | case SpvExecutionModeLocalSizeId: |
429 | if (!std::all_of(models->begin(), models->end(), |
430 | [&_](const SpvExecutionModel& model) { |
431 | switch (model) { |
432 | case SpvExecutionModelKernel: |
433 | case SpvExecutionModelGLCompute: |
434 | return true; |
435 | case SpvExecutionModelTaskNV: |
436 | case SpvExecutionModelMeshNV: |
437 | return _.HasCapability(SpvCapabilityMeshShadingNV); |
438 | default: |
439 | return false; |
440 | } |
441 | })) { |
442 | if (_.HasCapability(SpvCapabilityMeshShadingNV)) { |
443 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
444 | << "Execution mode can only be used with a Kernel, GLCompute, " |
445 | "MeshNV, or TaskNV execution model." ; |
446 | } else { |
447 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
448 | << "Execution mode can only be used with a Kernel or " |
449 | "GLCompute " |
450 | "execution model." ; |
451 | } |
452 | } |
453 | default: |
454 | break; |
455 | } |
456 | |
457 | if (spvIsVulkanEnv(_.context()->target_env)) { |
458 | if (mode == SpvExecutionModeOriginLowerLeft) { |
459 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
460 | << "In the Vulkan environment, the OriginLowerLeft execution mode " |
461 | "must not be used." ; |
462 | } |
463 | if (mode == SpvExecutionModePixelCenterInteger) { |
464 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
465 | << "In the Vulkan environment, the PixelCenterInteger execution " |
466 | "mode must not be used." ; |
467 | } |
468 | } |
469 | |
470 | if (spvIsWebGPUEnv(_.context()->target_env)) { |
471 | if (mode != SpvExecutionModeOriginUpperLeft && |
472 | mode != SpvExecutionModeDepthReplacing && |
473 | mode != SpvExecutionModeDepthGreater && |
474 | mode != SpvExecutionModeDepthLess && |
475 | mode != SpvExecutionModeDepthUnchanged && |
476 | mode != SpvExecutionModeLocalSize && |
477 | mode != SpvExecutionModeLocalSizeHint) { |
478 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
479 | << "Execution mode must be one of OriginUpperLeft, " |
480 | "DepthReplacing, DepthGreater, DepthLess, DepthUnchanged, " |
481 | "LocalSize, or LocalSizeHint for WebGPU environment." ; |
482 | } |
483 | } |
484 | |
485 | return SPV_SUCCESS; |
486 | } |
487 | |
488 | spv_result_t ValidateMemoryModel(ValidationState_t& _, |
489 | const Instruction* inst) { |
490 | // Already produced an error if multiple memory model instructions are |
491 | // present. |
492 | if (_.memory_model() != SpvMemoryModelVulkanKHR && |
493 | _.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) { |
494 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
495 | << "VulkanMemoryModelKHR capability must only be specified if " |
496 | "the VulkanKHR memory model is used." ; |
497 | } |
498 | |
499 | if (spvIsWebGPUEnv(_.context()->target_env)) { |
500 | if (_.addressing_model() != SpvAddressingModelLogical) { |
501 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
502 | << "Addressing model must be Logical for WebGPU environment." ; |
503 | } |
504 | if (_.memory_model() != SpvMemoryModelVulkanKHR) { |
505 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
506 | << "Memory model must be VulkanKHR for WebGPU environment." ; |
507 | } |
508 | } |
509 | |
510 | if (spvIsOpenCLEnv(_.context()->target_env)) { |
511 | if ((_.addressing_model() != SpvAddressingModelPhysical32) && |
512 | (_.addressing_model() != SpvAddressingModelPhysical64)) { |
513 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
514 | << "Addressing model must be Physical32 or Physical64 " |
515 | << "in the OpenCL environment." ; |
516 | } |
517 | if (_.memory_model() != SpvMemoryModelOpenCL) { |
518 | return _.diag(SPV_ERROR_INVALID_DATA, inst) |
519 | << "Memory model must be OpenCL in the OpenCL environment." ; |
520 | } |
521 | } |
522 | |
523 | return SPV_SUCCESS; |
524 | } |
525 | |
526 | } // namespace |
527 | |
528 | spv_result_t ModeSettingPass(ValidationState_t& _, const Instruction* inst) { |
529 | switch (inst->opcode()) { |
530 | case SpvOpEntryPoint: |
531 | if (auto error = ValidateEntryPoint(_, inst)) return error; |
532 | break; |
533 | case SpvOpExecutionMode: |
534 | case SpvOpExecutionModeId: |
535 | if (auto error = ValidateExecutionMode(_, inst)) return error; |
536 | break; |
537 | case SpvOpMemoryModel: |
538 | if (auto error = ValidateMemoryModel(_, inst)) return error; |
539 | break; |
540 | default: |
541 | break; |
542 | } |
543 | return SPV_SUCCESS; |
544 | } |
545 | |
546 | } // namespace val |
547 | } // namespace spvtools |
548 | |