1 | // |
2 | // Copyright (C) 2015 LunarG, Inc. |
3 | // |
4 | // All rights reserved. |
5 | // |
6 | // Redistribution and use in source and binary forms, with or without |
7 | // modification, are permitted provided that the following conditions |
8 | // are met: |
9 | // |
10 | // Redistributions of source code must retain the above copyright |
11 | // notice, this list of conditions and the following disclaimer. |
12 | // |
13 | // Redistributions in binary form must reproduce the above |
14 | // copyright notice, this list of conditions and the following |
15 | // disclaimer in the documentation and/or other materials provided |
16 | // with the distribution. |
17 | // |
18 | // Neither the name of 3Dlabs Inc. Ltd. nor the names of its |
19 | // contributors may be used to endorse or promote products derived |
20 | // from this software without specific prior written permission. |
21 | // |
22 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
23 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
24 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
25 | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
26 | // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
27 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
28 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
29 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
30 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
31 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
32 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
33 | // POSSIBILITY OF SUCH DAMAGE. |
34 | // |
35 | |
36 | #include "SPVRemapper.h" |
37 | #include "doc.h" |
38 | |
39 | #if !defined (use_cpp11) |
40 | // ... not supported before C++11 |
41 | #else // defined (use_cpp11) |
42 | |
43 | #include <algorithm> |
44 | #include <cassert> |
45 | #include "../glslang/Include/Common.h" |
46 | |
47 | namespace spv { |
48 | |
49 | // By default, just abort on error. Can be overridden via RegisterErrorHandler |
50 | spirvbin_t::errorfn_t spirvbin_t::errorHandler = [](const std::string&) { exit(status: 5); }; |
51 | // By default, eat log messages. Can be overridden via RegisterLogHandler |
52 | spirvbin_t::logfn_t spirvbin_t::logHandler = [](const std::string&) { }; |
53 | |
54 | // This can be overridden to provide other message behavior if needed |
55 | void spirvbin_t::msg(int minVerbosity, int indent, const std::string& txt) const |
56 | { |
57 | if (verbose >= minVerbosity) |
58 | logHandler(std::string(indent, ' ') + txt); |
59 | } |
60 | |
61 | // hash opcode, with special handling for OpExtInst |
62 | std::uint32_t spirvbin_t::asOpCodeHash(unsigned word) |
63 | { |
64 | const spv::Op opCode = asOpCode(word); |
65 | |
66 | std::uint32_t offset = 0; |
67 | |
68 | switch (opCode) { |
69 | case spv::OpExtInst: |
70 | offset += asId(word: word + 4); break; |
71 | default: |
72 | break; |
73 | } |
74 | |
75 | return opCode * 19 + offset; // 19 = small prime |
76 | } |
77 | |
78 | spirvbin_t::range_t spirvbin_t::literalRange(spv::Op opCode) const |
79 | { |
80 | static const int maxCount = 1<<30; |
81 | |
82 | switch (opCode) { |
83 | case spv::OpTypeFloat: // fall through... |
84 | case spv::OpTypePointer: return range_t(2, 3); |
85 | case spv::OpTypeInt: return range_t(2, 4); |
86 | // TODO: case spv::OpTypeImage: |
87 | // TODO: case spv::OpTypeSampledImage: |
88 | case spv::OpTypeSampler: return range_t(3, 8); |
89 | case spv::OpTypeVector: // fall through |
90 | case spv::OpTypeMatrix: // ... |
91 | case spv::OpTypePipe: return range_t(3, 4); |
92 | case spv::OpConstant: return range_t(3, maxCount); |
93 | default: return range_t(0, 0); |
94 | } |
95 | } |
96 | |
97 | spirvbin_t::range_t spirvbin_t::typeRange(spv::Op opCode) const |
98 | { |
99 | static const int maxCount = 1<<30; |
100 | |
101 | if (isConstOp(opCode)) |
102 | return range_t(1, 2); |
103 | |
104 | switch (opCode) { |
105 | case spv::OpTypeVector: // fall through |
106 | case spv::OpTypeMatrix: // ... |
107 | case spv::OpTypeSampler: // ... |
108 | case spv::OpTypeArray: // ... |
109 | case spv::OpTypeRuntimeArray: // ... |
110 | case spv::OpTypePipe: return range_t(2, 3); |
111 | case spv::OpTypeStruct: // fall through |
112 | case spv::OpTypeFunction: return range_t(2, maxCount); |
113 | case spv::OpTypePointer: return range_t(3, 4); |
114 | default: return range_t(0, 0); |
115 | } |
116 | } |
117 | |
118 | spirvbin_t::range_t spirvbin_t::constRange(spv::Op opCode) const |
119 | { |
120 | static const int maxCount = 1<<30; |
121 | |
122 | switch (opCode) { |
123 | case spv::OpTypeArray: // fall through... |
124 | case spv::OpTypeRuntimeArray: return range_t(3, 4); |
125 | case spv::OpConstantComposite: return range_t(3, maxCount); |
126 | default: return range_t(0, 0); |
127 | } |
128 | } |
129 | |
130 | // Return the size of a type in 32-bit words. This currently only |
131 | // handles ints and floats, and is only invoked by queries which must be |
132 | // integer types. If ever needed, it can be generalized. |
133 | unsigned spirvbin_t::typeSizeInWords(spv::Id id) const |
134 | { |
135 | const unsigned typeStart = idPos(id); |
136 | const spv::Op opCode = asOpCode(word: typeStart); |
137 | |
138 | if (errorLatch) |
139 | return 0; |
140 | |
141 | switch (opCode) { |
142 | case spv::OpTypeInt: // fall through... |
143 | case spv::OpTypeFloat: return (spv[typeStart+2]+31)/32; |
144 | default: |
145 | return 0; |
146 | } |
147 | } |
148 | |
149 | // Looks up the type of a given const or variable ID, and |
150 | // returns its size in 32-bit words. |
151 | unsigned spirvbin_t::idTypeSizeInWords(spv::Id id) const |
152 | { |
153 | const auto tid_it = idTypeSizeMap.find(x: id); |
154 | if (tid_it == idTypeSizeMap.end()) { |
155 | error(txt: "type size for ID not found" ); |
156 | return 0; |
157 | } |
158 | |
159 | return tid_it->second; |
160 | } |
161 | |
162 | // Is this an opcode we should remove when using --strip? |
163 | bool spirvbin_t::isStripOp(spv::Op opCode, unsigned start) const |
164 | { |
165 | switch (opCode) { |
166 | case spv::OpSource: |
167 | case spv::OpSourceExtension: |
168 | case spv::OpName: |
169 | case spv::OpMemberName: |
170 | case spv::OpLine : |
171 | { |
172 | const std::string name = literalString(word: start + 2); |
173 | |
174 | std::vector<std::string>::const_iterator it; |
175 | for (it = stripWhiteList.begin(); it < stripWhiteList.end(); it++) |
176 | { |
177 | if (name.find(str: *it) != std::string::npos) { |
178 | return false; |
179 | } |
180 | } |
181 | |
182 | return true; |
183 | } |
184 | default : |
185 | return false; |
186 | } |
187 | } |
188 | |
189 | // Return true if this opcode is flow control |
190 | bool spirvbin_t::isFlowCtrl(spv::Op opCode) const |
191 | { |
192 | switch (opCode) { |
193 | case spv::OpBranchConditional: |
194 | case spv::OpBranch: |
195 | case spv::OpSwitch: |
196 | case spv::OpLoopMerge: |
197 | case spv::OpSelectionMerge: |
198 | case spv::OpLabel: |
199 | case spv::OpFunction: |
200 | case spv::OpFunctionEnd: return true; |
201 | default: return false; |
202 | } |
203 | } |
204 | |
205 | // Return true if this opcode defines a type |
206 | bool spirvbin_t::isTypeOp(spv::Op opCode) const |
207 | { |
208 | switch (opCode) { |
209 | case spv::OpTypeVoid: |
210 | case spv::OpTypeBool: |
211 | case spv::OpTypeInt: |
212 | case spv::OpTypeFloat: |
213 | case spv::OpTypeVector: |
214 | case spv::OpTypeMatrix: |
215 | case spv::OpTypeImage: |
216 | case spv::OpTypeSampler: |
217 | case spv::OpTypeArray: |
218 | case spv::OpTypeRuntimeArray: |
219 | case spv::OpTypeStruct: |
220 | case spv::OpTypeOpaque: |
221 | case spv::OpTypePointer: |
222 | case spv::OpTypeFunction: |
223 | case spv::OpTypeEvent: |
224 | case spv::OpTypeDeviceEvent: |
225 | case spv::OpTypeReserveId: |
226 | case spv::OpTypeQueue: |
227 | case spv::OpTypeSampledImage: |
228 | case spv::OpTypePipe: return true; |
229 | default: return false; |
230 | } |
231 | } |
232 | |
233 | // Return true if this opcode defines a constant |
234 | bool spirvbin_t::isConstOp(spv::Op opCode) const |
235 | { |
236 | switch (opCode) { |
237 | case spv::OpConstantSampler: |
238 | error(txt: "unimplemented constant type" ); |
239 | return true; |
240 | |
241 | case spv::OpConstantNull: |
242 | case spv::OpConstantTrue: |
243 | case spv::OpConstantFalse: |
244 | case spv::OpConstantComposite: |
245 | case spv::OpConstant: |
246 | return true; |
247 | |
248 | default: |
249 | return false; |
250 | } |
251 | } |
252 | |
253 | const auto inst_fn_nop = [](spv::Op, unsigned) { return false; }; |
254 | const auto op_fn_nop = [](spv::Id&) { }; |
255 | |
256 | // g++ doesn't like these defined in the class proper in an anonymous namespace. |
257 | // Dunno why. Also MSVC doesn't like the constexpr keyword. Also dunno why. |
258 | // Defining them externally seems to please both compilers, so, here they are. |
259 | const spv::Id spirvbin_t::unmapped = spv::Id(-10000); |
260 | const spv::Id spirvbin_t::unused = spv::Id(-10001); |
261 | const int spirvbin_t:: = 5; |
262 | |
263 | spv::Id spirvbin_t::nextUnusedId(spv::Id id) |
264 | { |
265 | while (isNewIdMapped(newId: id)) // search for an unused ID |
266 | ++id; |
267 | |
268 | return id; |
269 | } |
270 | |
271 | spv::Id spirvbin_t::localId(spv::Id id, spv::Id newId) |
272 | { |
273 | //assert(id != spv::NoResult && newId != spv::NoResult); |
274 | |
275 | if (id > bound()) { |
276 | error(txt: std::string("ID out of range: " ) + std::to_string(val: id)); |
277 | return spirvbin_t::unused; |
278 | } |
279 | |
280 | if (id >= idMapL.size()) |
281 | idMapL.resize(new_size: id+1, x: unused); |
282 | |
283 | if (newId != unmapped && newId != unused) { |
284 | if (isOldIdUnused(oldId: id)) { |
285 | error(txt: std::string("ID unused in module: " ) + std::to_string(val: id)); |
286 | return spirvbin_t::unused; |
287 | } |
288 | |
289 | if (!isOldIdUnmapped(oldId: id)) { |
290 | error(txt: std::string("ID already mapped: " ) + std::to_string(val: id) + " -> " |
291 | + std::to_string(val: localId(id))); |
292 | |
293 | return spirvbin_t::unused; |
294 | } |
295 | |
296 | if (isNewIdMapped(newId)) { |
297 | error(txt: std::string("ID already used in module: " ) + std::to_string(val: newId)); |
298 | return spirvbin_t::unused; |
299 | } |
300 | |
301 | msg(minVerbosity: 4, indent: 4, txt: std::string("map: " ) + std::to_string(val: id) + " -> " + std::to_string(val: newId)); |
302 | setMapped(newId); |
303 | largestNewId = std::max(a: largestNewId, b: newId); |
304 | } |
305 | |
306 | return idMapL[id] = newId; |
307 | } |
308 | |
309 | // Parse a literal string from the SPIR binary and return it as an std::string |
310 | // Due to C++11 RValue references, this doesn't copy the result string. |
311 | std::string spirvbin_t::literalString(unsigned word) const |
312 | { |
313 | std::string literal; |
314 | const spirword_t * pos = spv.data() + word; |
315 | |
316 | literal.reserve(res_arg: 16); |
317 | |
318 | do { |
319 | spirword_t word = *pos; |
320 | for (int i = 0; i < 4; i++) { |
321 | char c = word & 0xff; |
322 | if (c == '\0') |
323 | return literal; |
324 | literal += c; |
325 | word >>= 8; |
326 | } |
327 | pos++; |
328 | } while (true); |
329 | } |
330 | |
331 | void spirvbin_t::applyMap() |
332 | { |
333 | msg(minVerbosity: 3, indent: 2, txt: std::string("Applying map: " )); |
334 | |
335 | // Map local IDs through the ID map |
336 | process(inst_fn_nop, // ignore instructions |
337 | [this](spv::Id& id) { |
338 | id = localId(id); |
339 | |
340 | if (errorLatch) |
341 | return; |
342 | |
343 | assert(id != unused && id != unmapped); |
344 | } |
345 | ); |
346 | } |
347 | |
348 | // Find free IDs for anything we haven't mapped |
349 | void spirvbin_t::mapRemainder() |
350 | { |
351 | msg(minVerbosity: 3, indent: 2, txt: std::string("Remapping remainder: " )); |
352 | |
353 | spv::Id unusedId = 1; // can't use 0: that's NoResult |
354 | spirword_t maxBound = 0; |
355 | |
356 | for (spv::Id id = 0; id < idMapL.size(); ++id) { |
357 | if (isOldIdUnused(oldId: id)) |
358 | continue; |
359 | |
360 | // Find a new mapping for any used but unmapped IDs |
361 | if (isOldIdUnmapped(oldId: id)) { |
362 | localId(id, newId: unusedId = nextUnusedId(id: unusedId)); |
363 | if (errorLatch) |
364 | return; |
365 | } |
366 | |
367 | if (isOldIdUnmapped(oldId: id)) { |
368 | error(txt: std::string("old ID not mapped: " ) + std::to_string(val: id)); |
369 | return; |
370 | } |
371 | |
372 | // Track max bound |
373 | maxBound = std::max(a: maxBound, b: localId(id) + 1); |
374 | |
375 | if (errorLatch) |
376 | return; |
377 | } |
378 | |
379 | bound(b: maxBound); // reset header ID bound to as big as it now needs to be |
380 | } |
381 | |
382 | // Mark debug instructions for stripping |
383 | void spirvbin_t::stripDebug() |
384 | { |
385 | // Strip instructions in the stripOp set: debug info. |
386 | process( |
387 | [&](spv::Op opCode, unsigned start) { |
388 | // remember opcodes we want to strip later |
389 | if (isStripOp(opCode, start)) |
390 | stripInst(start); |
391 | return true; |
392 | }, |
393 | op_fn_nop); |
394 | } |
395 | |
396 | // Mark instructions that refer to now-removed IDs for stripping |
397 | void spirvbin_t::stripDeadRefs() |
398 | { |
399 | process( |
400 | [&](spv::Op opCode, unsigned start) { |
401 | // strip opcodes pointing to removed data |
402 | switch (opCode) { |
403 | case spv::OpName: |
404 | case spv::OpMemberName: |
405 | case spv::OpDecorate: |
406 | case spv::OpMemberDecorate: |
407 | if (idPosR.find(x: asId(word: start+1)) == idPosR.end()) |
408 | stripInst(start); |
409 | break; |
410 | default: |
411 | break; // leave it alone |
412 | } |
413 | |
414 | return true; |
415 | }, |
416 | op_fn_nop); |
417 | |
418 | strip(); |
419 | } |
420 | |
421 | // Update local maps of ID, type, etc positions |
422 | void spirvbin_t::buildLocalMaps() |
423 | { |
424 | msg(minVerbosity: 2, indent: 2, txt: std::string("build local maps: " )); |
425 | |
426 | mapped.clear(); |
427 | idMapL.clear(); |
428 | // preserve nameMap, so we don't clear that. |
429 | fnPos.clear(); |
430 | fnCalls.clear(); |
431 | typeConstPos.clear(); |
432 | idPosR.clear(); |
433 | entryPoint = spv::NoResult; |
434 | largestNewId = 0; |
435 | |
436 | idMapL.resize(new_size: bound(), x: unused); |
437 | |
438 | int fnStart = 0; |
439 | spv::Id fnRes = spv::NoResult; |
440 | |
441 | // build local Id and name maps |
442 | process( |
443 | [&](spv::Op opCode, unsigned start) { |
444 | unsigned word = start+1; |
445 | spv::Id typeId = spv::NoResult; |
446 | |
447 | if (spv::InstructionDesc[opCode].hasType()) |
448 | typeId = asId(word: word++); |
449 | |
450 | // If there's a result ID, remember the size of its type |
451 | if (spv::InstructionDesc[opCode].hasResult()) { |
452 | const spv::Id resultId = asId(word: word++); |
453 | idPosR[resultId] = start; |
454 | |
455 | if (typeId != spv::NoResult) { |
456 | const unsigned idTypeSize = typeSizeInWords(id: typeId); |
457 | |
458 | if (errorLatch) |
459 | return false; |
460 | |
461 | if (idTypeSize != 0) |
462 | idTypeSizeMap[resultId] = idTypeSize; |
463 | } |
464 | } |
465 | |
466 | if (opCode == spv::Op::OpName) { |
467 | const spv::Id target = asId(word: start+1); |
468 | const std::string name = literalString(word: start+2); |
469 | nameMap[name] = target; |
470 | |
471 | } else if (opCode == spv::Op::OpFunctionCall) { |
472 | ++fnCalls[asId(word: start + 3)]; |
473 | } else if (opCode == spv::Op::OpEntryPoint) { |
474 | entryPoint = asId(word: start + 2); |
475 | } else if (opCode == spv::Op::OpFunction) { |
476 | if (fnStart != 0) { |
477 | error(txt: "nested function found" ); |
478 | return false; |
479 | } |
480 | |
481 | fnStart = start; |
482 | fnRes = asId(word: start + 2); |
483 | } else if (opCode == spv::Op::OpFunctionEnd) { |
484 | assert(fnRes != spv::NoResult); |
485 | if (fnStart == 0) { |
486 | error(txt: "function end without function start" ); |
487 | return false; |
488 | } |
489 | |
490 | fnPos[fnRes] = range_t(fnStart, start + asWordCount(word: start)); |
491 | fnStart = 0; |
492 | } else if (isConstOp(opCode)) { |
493 | if (errorLatch) |
494 | return false; |
495 | |
496 | assert(asId(start + 2) != spv::NoResult); |
497 | typeConstPos.insert(x: start); |
498 | } else if (isTypeOp(opCode)) { |
499 | assert(asId(start + 1) != spv::NoResult); |
500 | typeConstPos.insert(x: start); |
501 | } |
502 | |
503 | return false; |
504 | }, |
505 | |
506 | [this](spv::Id& id) { localId(id, newId: unmapped); } |
507 | ); |
508 | } |
509 | |
510 | // Validate the SPIR header |
511 | void spirvbin_t::validate() const |
512 | { |
513 | msg(minVerbosity: 2, indent: 2, txt: std::string("validating: " )); |
514 | |
515 | if (spv.size() < header_size) { |
516 | error(txt: "file too short: " ); |
517 | return; |
518 | } |
519 | |
520 | if (magic() != spv::MagicNumber) { |
521 | error(txt: "bad magic number" ); |
522 | return; |
523 | } |
524 | |
525 | // field 1 = version |
526 | // field 2 = generator magic |
527 | // field 3 = result <id> bound |
528 | |
529 | if (schemaNum() != 0) { |
530 | error(txt: "bad schema, must be 0" ); |
531 | return; |
532 | } |
533 | } |
534 | |
535 | int spirvbin_t::processInstruction(unsigned word, instfn_t instFn, idfn_t idFn) |
536 | { |
537 | const auto instructionStart = word; |
538 | const unsigned wordCount = asWordCount(word: instructionStart); |
539 | const int nextInst = word++ + wordCount; |
540 | spv::Op opCode = asOpCode(word: instructionStart); |
541 | |
542 | if (nextInst > int(spv.size())) { |
543 | error(txt: "spir instruction terminated too early" ); |
544 | return -1; |
545 | } |
546 | |
547 | // Base for computing number of operands; will be updated as more is learned |
548 | unsigned numOperands = wordCount - 1; |
549 | |
550 | if (instFn(opCode, instructionStart)) |
551 | return nextInst; |
552 | |
553 | // Read type and result ID from instruction desc table |
554 | if (spv::InstructionDesc[opCode].hasType()) { |
555 | idFn(asId(word: word++)); |
556 | --numOperands; |
557 | } |
558 | |
559 | if (spv::InstructionDesc[opCode].hasResult()) { |
560 | idFn(asId(word: word++)); |
561 | --numOperands; |
562 | } |
563 | |
564 | // Extended instructions: currently, assume everything is an ID. |
565 | // TODO: add whatever data we need for exceptions to that |
566 | if (opCode == spv::OpExtInst) { |
567 | |
568 | idFn(asId(word)); // Instruction set is an ID that also needs to be mapped |
569 | |
570 | word += 2; // instruction set, and instruction from set |
571 | numOperands -= 2; |
572 | |
573 | for (unsigned op=0; op < numOperands; ++op) |
574 | idFn(asId(word: word++)); // ID |
575 | |
576 | return nextInst; |
577 | } |
578 | |
579 | // Circular buffer so we can look back at previous unmapped values during the mapping pass. |
580 | static const unsigned idBufferSize = 4; |
581 | spv::Id idBuffer[idBufferSize]; |
582 | unsigned idBufferPos = 0; |
583 | |
584 | // Store IDs from instruction in our map |
585 | for (int op = 0; numOperands > 0; ++op, --numOperands) { |
586 | // SpecConstantOp is special: it includes the operands of another opcode which is |
587 | // given as a literal in the 3rd word. We will switch over to pretending that the |
588 | // opcode being processed is the literal opcode value of the SpecConstantOp. See the |
589 | // SPIRV spec for details. This way we will handle IDs and literals as appropriate for |
590 | // the embedded op. |
591 | if (opCode == spv::OpSpecConstantOp) { |
592 | if (op == 0) { |
593 | opCode = asOpCode(word: word++); // this is the opcode embedded in the SpecConstantOp. |
594 | --numOperands; |
595 | } |
596 | } |
597 | |
598 | switch (spv::InstructionDesc[opCode].operands.getClass(op)) { |
599 | case spv::OperandId: |
600 | case spv::OperandScope: |
601 | case spv::OperandMemorySemantics: |
602 | idBuffer[idBufferPos] = asId(word); |
603 | idBufferPos = (idBufferPos + 1) % idBufferSize; |
604 | idFn(asId(word: word++)); |
605 | break; |
606 | |
607 | case spv::OperandVariableIds: |
608 | for (unsigned i = 0; i < numOperands; ++i) |
609 | idFn(asId(word: word++)); |
610 | return nextInst; |
611 | |
612 | case spv::OperandVariableLiterals: |
613 | // for clarity |
614 | // if (opCode == spv::OpDecorate && asDecoration(word - 1) == spv::DecorationBuiltIn) { |
615 | // ++word; |
616 | // --numOperands; |
617 | // } |
618 | // word += numOperands; |
619 | return nextInst; |
620 | |
621 | case spv::OperandVariableLiteralId: { |
622 | if (opCode == OpSwitch) { |
623 | // word-2 is the position of the selector ID. OpSwitch Literals match its type. |
624 | // In case the IDs are currently being remapped, we get the word[-2] ID from |
625 | // the circular idBuffer. |
626 | const unsigned literalSizePos = (idBufferPos+idBufferSize-2) % idBufferSize; |
627 | const unsigned literalSize = idTypeSizeInWords(id: idBuffer[literalSizePos]); |
628 | const unsigned numLiteralIdPairs = (nextInst-word) / (1+literalSize); |
629 | |
630 | if (errorLatch) |
631 | return -1; |
632 | |
633 | for (unsigned arg=0; arg<numLiteralIdPairs; ++arg) { |
634 | word += literalSize; // literal |
635 | idFn(asId(word: word++)); // label |
636 | } |
637 | } else { |
638 | assert(0); // currentely, only OpSwitch uses OperandVariableLiteralId |
639 | } |
640 | |
641 | return nextInst; |
642 | } |
643 | |
644 | case spv::OperandLiteralString: { |
645 | const int stringWordCount = literalStringWords(str: literalString(word)); |
646 | word += stringWordCount; |
647 | numOperands -= (stringWordCount-1); // -1 because for() header post-decrements |
648 | break; |
649 | } |
650 | |
651 | case spv::OperandVariableLiteralStrings: |
652 | return nextInst; |
653 | |
654 | // Execution mode might have extra literal operands. Skip them. |
655 | case spv::OperandExecutionMode: |
656 | return nextInst; |
657 | |
658 | // Single word operands we simply ignore, as they hold no IDs |
659 | case spv::OperandLiteralNumber: |
660 | case spv::OperandSource: |
661 | case spv::OperandExecutionModel: |
662 | case spv::OperandAddressing: |
663 | case spv::OperandMemory: |
664 | case spv::OperandStorage: |
665 | case spv::OperandDimensionality: |
666 | case spv::OperandSamplerAddressingMode: |
667 | case spv::OperandSamplerFilterMode: |
668 | case spv::OperandSamplerImageFormat: |
669 | case spv::OperandImageChannelOrder: |
670 | case spv::OperandImageChannelDataType: |
671 | case spv::OperandImageOperands: |
672 | case spv::OperandFPFastMath: |
673 | case spv::OperandFPRoundingMode: |
674 | case spv::OperandLinkageType: |
675 | case spv::OperandAccessQualifier: |
676 | case spv::OperandFuncParamAttr: |
677 | case spv::OperandDecoration: |
678 | case spv::OperandBuiltIn: |
679 | case spv::OperandSelect: |
680 | case spv::OperandLoop: |
681 | case spv::OperandFunction: |
682 | case spv::OperandMemoryAccess: |
683 | case spv::OperandGroupOperation: |
684 | case spv::OperandKernelEnqueueFlags: |
685 | case spv::OperandKernelProfilingInfo: |
686 | case spv::OperandCapability: |
687 | ++word; |
688 | break; |
689 | |
690 | default: |
691 | assert(0 && "Unhandled Operand Class" ); |
692 | break; |
693 | } |
694 | } |
695 | |
696 | return nextInst; |
697 | } |
698 | |
699 | // Make a pass over all the instructions and process them given appropriate functions |
700 | spirvbin_t& spirvbin_t::process(instfn_t instFn, idfn_t idFn, unsigned begin, unsigned end) |
701 | { |
702 | // For efficiency, reserve name map space. It can grow if needed. |
703 | nameMap.reserve(n: 32); |
704 | |
705 | // If begin or end == 0, use defaults |
706 | begin = (begin == 0 ? header_size : begin); |
707 | end = (end == 0 ? unsigned(spv.size()) : end); |
708 | |
709 | // basic parsing and InstructionDesc table borrowed from SpvDisassemble.cpp... |
710 | unsigned nextInst = unsigned(spv.size()); |
711 | |
712 | for (unsigned word = begin; word < end; word = nextInst) { |
713 | nextInst = processInstruction(word, instFn, idFn); |
714 | |
715 | if (errorLatch) |
716 | return *this; |
717 | } |
718 | |
719 | return *this; |
720 | } |
721 | |
722 | // Apply global name mapping to a single module |
723 | void spirvbin_t::mapNames() |
724 | { |
725 | static const std::uint32_t softTypeIdLimit = 3011; // small prime. TODO: get from options |
726 | static const std::uint32_t firstMappedID = 3019; // offset into ID space |
727 | |
728 | for (const auto& name : nameMap) { |
729 | std::uint32_t hashval = 1911; |
730 | for (const char c : name.first) |
731 | hashval = hashval * 1009 + c; |
732 | |
733 | if (isOldIdUnmapped(oldId: name.second)) { |
734 | localId(id: name.second, newId: nextUnusedId(id: hashval % softTypeIdLimit + firstMappedID)); |
735 | if (errorLatch) |
736 | return; |
737 | } |
738 | } |
739 | } |
740 | |
741 | // Map fn contents to IDs of similar functions in other modules |
742 | void spirvbin_t::mapFnBodies() |
743 | { |
744 | static const std::uint32_t softTypeIdLimit = 19071; // small prime. TODO: get from options |
745 | static const std::uint32_t firstMappedID = 6203; // offset into ID space |
746 | |
747 | // Initial approach: go through some high priority opcodes first and assign them |
748 | // hash values. |
749 | |
750 | spv::Id fnId = spv::NoResult; |
751 | std::vector<unsigned> instPos; |
752 | instPos.reserve(n: unsigned(spv.size()) / 16); // initial estimate; can grow if needed. |
753 | |
754 | // Build local table of instruction start positions |
755 | process( |
756 | instFn: [&](spv::Op, unsigned start) { instPos.push_back(x: start); return true; }, |
757 | idFn: op_fn_nop); |
758 | |
759 | if (errorLatch) |
760 | return; |
761 | |
762 | // Window size for context-sensitive canonicalization values |
763 | // Empirical best size from a single data set. TODO: Would be a good tunable. |
764 | // We essentially perform a little convolution around each instruction, |
765 | // to capture the flavor of nearby code, to hopefully match to similar |
766 | // code in other modules. |
767 | static const unsigned windowSize = 2; |
768 | |
769 | for (unsigned entry = 0; entry < unsigned(instPos.size()); ++entry) { |
770 | const unsigned start = instPos[entry]; |
771 | const spv::Op opCode = asOpCode(word: start); |
772 | |
773 | if (opCode == spv::OpFunction) |
774 | fnId = asId(word: start + 2); |
775 | |
776 | if (opCode == spv::OpFunctionEnd) |
777 | fnId = spv::NoResult; |
778 | |
779 | if (fnId != spv::NoResult) { // if inside a function |
780 | if (spv::InstructionDesc[opCode].hasResult()) { |
781 | const unsigned word = start + (spv::InstructionDesc[opCode].hasType() ? 2 : 1); |
782 | const spv::Id resId = asId(word); |
783 | std::uint32_t hashval = fnId * 17; // small prime |
784 | |
785 | for (unsigned i = entry-1; i >= entry-windowSize; --i) { |
786 | if (asOpCode(word: instPos[i]) == spv::OpFunction) |
787 | break; |
788 | hashval = hashval * 30103 + asOpCodeHash(word: instPos[i]); // 30103 = semiarbitrary prime |
789 | } |
790 | |
791 | for (unsigned i = entry; i <= entry + windowSize; ++i) { |
792 | if (asOpCode(word: instPos[i]) == spv::OpFunctionEnd) |
793 | break; |
794 | hashval = hashval * 30103 + asOpCodeHash(word: instPos[i]); // 30103 = semiarbitrary prime |
795 | } |
796 | |
797 | if (isOldIdUnmapped(oldId: resId)) { |
798 | localId(id: resId, newId: nextUnusedId(id: hashval % softTypeIdLimit + firstMappedID)); |
799 | if (errorLatch) |
800 | return; |
801 | } |
802 | |
803 | } |
804 | } |
805 | } |
806 | |
807 | spv::Op thisOpCode(spv::OpNop); |
808 | std::unordered_map<int, int> opCounter; |
809 | int idCounter(0); |
810 | fnId = spv::NoResult; |
811 | |
812 | process( |
813 | instFn: [&](spv::Op opCode, unsigned start) { |
814 | switch (opCode) { |
815 | case spv::OpFunction: |
816 | // Reset counters at each function |
817 | idCounter = 0; |
818 | opCounter.clear(); |
819 | fnId = asId(word: start + 2); |
820 | break; |
821 | |
822 | case spv::OpImageSampleImplicitLod: |
823 | case spv::OpImageSampleExplicitLod: |
824 | case spv::OpImageSampleDrefImplicitLod: |
825 | case spv::OpImageSampleDrefExplicitLod: |
826 | case spv::OpImageSampleProjImplicitLod: |
827 | case spv::OpImageSampleProjExplicitLod: |
828 | case spv::OpImageSampleProjDrefImplicitLod: |
829 | case spv::OpImageSampleProjDrefExplicitLod: |
830 | case spv::OpDot: |
831 | case spv::OpCompositeExtract: |
832 | case spv::OpCompositeInsert: |
833 | case spv::OpVectorShuffle: |
834 | case spv::OpLabel: |
835 | case spv::OpVariable: |
836 | |
837 | case spv::OpAccessChain: |
838 | case spv::OpLoad: |
839 | case spv::OpStore: |
840 | case spv::OpCompositeConstruct: |
841 | case spv::OpFunctionCall: |
842 | ++opCounter[opCode]; |
843 | idCounter = 0; |
844 | thisOpCode = opCode; |
845 | break; |
846 | default: |
847 | thisOpCode = spv::OpNop; |
848 | } |
849 | |
850 | return false; |
851 | }, |
852 | |
853 | idFn: [&](spv::Id& id) { |
854 | if (thisOpCode != spv::OpNop) { |
855 | ++idCounter; |
856 | const std::uint32_t hashval = |
857 | // Explicitly cast operands to unsigned int to avoid integer |
858 | // promotion to signed int followed by integer overflow, |
859 | // which would result in undefined behavior. |
860 | static_cast<unsigned int>(opCounter[thisOpCode]) |
861 | * thisOpCode |
862 | * 50047 |
863 | + idCounter |
864 | + static_cast<unsigned int>(fnId) * 117; |
865 | |
866 | if (isOldIdUnmapped(oldId: id)) |
867 | localId(id, newId: nextUnusedId(id: hashval % softTypeIdLimit + firstMappedID)); |
868 | } |
869 | }); |
870 | } |
871 | |
872 | // EXPERIMENTAL: forward IO and uniform load/stores into operands |
873 | // This produces invalid Schema-0 SPIRV |
874 | void spirvbin_t::forwardLoadStores() |
875 | { |
876 | idset_t fnLocalVars; // set of function local vars |
877 | idmap_t idMap; // Map of load result IDs to what they load |
878 | |
879 | // EXPERIMENTAL: Forward input and access chain loads into consumptions |
880 | process( |
881 | instFn: [&](spv::Op opCode, unsigned start) { |
882 | // Add inputs and uniforms to the map |
883 | if ((opCode == spv::OpVariable && asWordCount(word: start) == 4) && |
884 | (spv[start+3] == spv::StorageClassUniform || |
885 | spv[start+3] == spv::StorageClassUniformConstant || |
886 | spv[start+3] == spv::StorageClassInput)) |
887 | fnLocalVars.insert(x: asId(word: start+2)); |
888 | |
889 | if (opCode == spv::OpAccessChain && fnLocalVars.count(x: asId(word: start+3)) > 0) |
890 | fnLocalVars.insert(x: asId(word: start+2)); |
891 | |
892 | if (opCode == spv::OpLoad && fnLocalVars.count(x: asId(word: start+3)) > 0) { |
893 | idMap[asId(word: start+2)] = asId(word: start+3); |
894 | stripInst(start); |
895 | } |
896 | |
897 | return false; |
898 | }, |
899 | |
900 | idFn: [&](spv::Id& id) { if (idMap.find(x: id) != idMap.end()) id = idMap[id]; } |
901 | ); |
902 | |
903 | if (errorLatch) |
904 | return; |
905 | |
906 | // EXPERIMENTAL: Implicit output stores |
907 | fnLocalVars.clear(); |
908 | idMap.clear(); |
909 | |
910 | process( |
911 | instFn: [&](spv::Op opCode, unsigned start) { |
912 | // Add inputs and uniforms to the map |
913 | if ((opCode == spv::OpVariable && asWordCount(word: start) == 4) && |
914 | (spv[start+3] == spv::StorageClassOutput)) |
915 | fnLocalVars.insert(x: asId(word: start+2)); |
916 | |
917 | if (opCode == spv::OpStore && fnLocalVars.count(x: asId(word: start+1)) > 0) { |
918 | idMap[asId(word: start+2)] = asId(word: start+1); |
919 | stripInst(start); |
920 | } |
921 | |
922 | return false; |
923 | }, |
924 | idFn: op_fn_nop); |
925 | |
926 | if (errorLatch) |
927 | return; |
928 | |
929 | process( |
930 | instFn: inst_fn_nop, |
931 | idFn: [&](spv::Id& id) { if (idMap.find(x: id) != idMap.end()) id = idMap[id]; } |
932 | ); |
933 | |
934 | if (errorLatch) |
935 | return; |
936 | |
937 | strip(); // strip out data we decided to eliminate |
938 | } |
939 | |
940 | // optimize loads and stores |
941 | void spirvbin_t::optLoadStore() |
942 | { |
943 | idset_t fnLocalVars; // candidates for removal (only locals) |
944 | idmap_t idMap; // Map of load result IDs to what they load |
945 | blockmap_t blockMap; // Map of IDs to blocks they first appear in |
946 | int blockNum = 0; // block count, to avoid crossing flow control |
947 | |
948 | // Find all the function local pointers stored at most once, and not via access chains |
949 | process( |
950 | instFn: [&](spv::Op opCode, unsigned start) { |
951 | const int wordCount = asWordCount(word: start); |
952 | |
953 | // Count blocks, so we can avoid crossing flow control |
954 | if (isFlowCtrl(opCode)) |
955 | ++blockNum; |
956 | |
957 | // Add local variables to the map |
958 | if ((opCode == spv::OpVariable && spv[start+3] == spv::StorageClassFunction && asWordCount(word: start) == 4)) { |
959 | fnLocalVars.insert(x: asId(word: start+2)); |
960 | return true; |
961 | } |
962 | |
963 | // Ignore process vars referenced via access chain |
964 | if ((opCode == spv::OpAccessChain || opCode == spv::OpInBoundsAccessChain) && fnLocalVars.count(x: asId(word: start+3)) > 0) { |
965 | fnLocalVars.erase(x: asId(word: start+3)); |
966 | idMap.erase(x: asId(word: start+3)); |
967 | return true; |
968 | } |
969 | |
970 | if (opCode == spv::OpLoad && fnLocalVars.count(x: asId(word: start+3)) > 0) { |
971 | const spv::Id varId = asId(word: start+3); |
972 | |
973 | // Avoid loads before stores |
974 | if (idMap.find(x: varId) == idMap.end()) { |
975 | fnLocalVars.erase(x: varId); |
976 | idMap.erase(x: varId); |
977 | } |
978 | |
979 | // don't do for volatile references |
980 | if (wordCount > 4 && (spv[start+4] & spv::MemoryAccessVolatileMask)) { |
981 | fnLocalVars.erase(x: varId); |
982 | idMap.erase(x: varId); |
983 | } |
984 | |
985 | // Handle flow control |
986 | if (blockMap.find(x: varId) == blockMap.end()) { |
987 | blockMap[varId] = blockNum; // track block we found it in. |
988 | } else if (blockMap[varId] != blockNum) { |
989 | fnLocalVars.erase(x: varId); // Ignore if crosses flow control |
990 | idMap.erase(x: varId); |
991 | } |
992 | |
993 | return true; |
994 | } |
995 | |
996 | if (opCode == spv::OpStore && fnLocalVars.count(x: asId(word: start+1)) > 0) { |
997 | const spv::Id varId = asId(word: start+1); |
998 | |
999 | if (idMap.find(x: varId) == idMap.end()) { |
1000 | idMap[varId] = asId(word: start+2); |
1001 | } else { |
1002 | // Remove if it has more than one store to the same pointer |
1003 | fnLocalVars.erase(x: varId); |
1004 | idMap.erase(x: varId); |
1005 | } |
1006 | |
1007 | // don't do for volatile references |
1008 | if (wordCount > 3 && (spv[start+3] & spv::MemoryAccessVolatileMask)) { |
1009 | fnLocalVars.erase(x: asId(word: start+3)); |
1010 | idMap.erase(x: asId(word: start+3)); |
1011 | } |
1012 | |
1013 | // Handle flow control |
1014 | if (blockMap.find(x: varId) == blockMap.end()) { |
1015 | blockMap[varId] = blockNum; // track block we found it in. |
1016 | } else if (blockMap[varId] != blockNum) { |
1017 | fnLocalVars.erase(x: varId); // Ignore if crosses flow control |
1018 | idMap.erase(x: varId); |
1019 | } |
1020 | |
1021 | return true; |
1022 | } |
1023 | |
1024 | return false; |
1025 | }, |
1026 | |
1027 | // If local var id used anywhere else, don't eliminate |
1028 | idFn: [&](spv::Id& id) { |
1029 | if (fnLocalVars.count(x: id) > 0) { |
1030 | fnLocalVars.erase(x: id); |
1031 | idMap.erase(x: id); |
1032 | } |
1033 | } |
1034 | ); |
1035 | |
1036 | if (errorLatch) |
1037 | return; |
1038 | |
1039 | process( |
1040 | instFn: [&](spv::Op opCode, unsigned start) { |
1041 | if (opCode == spv::OpLoad && fnLocalVars.count(x: asId(word: start+3)) > 0) |
1042 | idMap[asId(word: start+2)] = idMap[asId(word: start+3)]; |
1043 | return false; |
1044 | }, |
1045 | idFn: op_fn_nop); |
1046 | |
1047 | if (errorLatch) |
1048 | return; |
1049 | |
1050 | // Chase replacements to their origins, in case there is a chain such as: |
1051 | // 2 = store 1 |
1052 | // 3 = load 2 |
1053 | // 4 = store 3 |
1054 | // 5 = load 4 |
1055 | // We want to replace uses of 5 with 1. |
1056 | for (const auto& idPair : idMap) { |
1057 | spv::Id id = idPair.first; |
1058 | while (idMap.find(x: id) != idMap.end()) // Chase to end of chain |
1059 | id = idMap[id]; |
1060 | |
1061 | idMap[idPair.first] = id; // replace with final result |
1062 | } |
1063 | |
1064 | // Remove the load/store/variables for the ones we've discovered |
1065 | process( |
1066 | instFn: [&](spv::Op opCode, unsigned start) { |
1067 | if ((opCode == spv::OpLoad && fnLocalVars.count(x: asId(word: start+3)) > 0) || |
1068 | (opCode == spv::OpStore && fnLocalVars.count(x: asId(word: start+1)) > 0) || |
1069 | (opCode == spv::OpVariable && fnLocalVars.count(x: asId(word: start+2)) > 0)) { |
1070 | |
1071 | stripInst(start); |
1072 | return true; |
1073 | } |
1074 | |
1075 | return false; |
1076 | }, |
1077 | |
1078 | idFn: [&](spv::Id& id) { |
1079 | if (idMap.find(x: id) != idMap.end()) id = idMap[id]; |
1080 | } |
1081 | ); |
1082 | |
1083 | if (errorLatch) |
1084 | return; |
1085 | |
1086 | strip(); // strip out data we decided to eliminate |
1087 | } |
1088 | |
1089 | // remove bodies of uncalled functions |
1090 | void spirvbin_t::dceFuncs() |
1091 | { |
1092 | msg(minVerbosity: 3, indent: 2, txt: std::string("Removing Dead Functions: " )); |
1093 | |
1094 | // TODO: There are more efficient ways to do this. |
1095 | bool changed = true; |
1096 | |
1097 | while (changed) { |
1098 | changed = false; |
1099 | |
1100 | for (auto fn = fnPos.begin(); fn != fnPos.end(); ) { |
1101 | if (fn->first == entryPoint) { // don't DCE away the entry point! |
1102 | ++fn; |
1103 | continue; |
1104 | } |
1105 | |
1106 | const auto call_it = fnCalls.find(x: fn->first); |
1107 | |
1108 | if (call_it == fnCalls.end() || call_it->second == 0) { |
1109 | changed = true; |
1110 | stripRange.push_back(x: fn->second); |
1111 | |
1112 | // decrease counts of called functions |
1113 | process( |
1114 | instFn: [&](spv::Op opCode, unsigned start) { |
1115 | if (opCode == spv::Op::OpFunctionCall) { |
1116 | const auto call_it = fnCalls.find(x: asId(word: start + 3)); |
1117 | if (call_it != fnCalls.end()) { |
1118 | if (--call_it->second <= 0) |
1119 | fnCalls.erase(position: call_it); |
1120 | } |
1121 | } |
1122 | |
1123 | return true; |
1124 | }, |
1125 | idFn: op_fn_nop, |
1126 | begin: fn->second.first, |
1127 | end: fn->second.second); |
1128 | |
1129 | if (errorLatch) |
1130 | return; |
1131 | |
1132 | fn = fnPos.erase(position: fn); |
1133 | } else ++fn; |
1134 | } |
1135 | } |
1136 | } |
1137 | |
1138 | // remove unused function variables + decorations |
1139 | void spirvbin_t::dceVars() |
1140 | { |
1141 | msg(minVerbosity: 3, indent: 2, txt: std::string("DCE Vars: " )); |
1142 | |
1143 | std::unordered_map<spv::Id, int> varUseCount; |
1144 | |
1145 | // Count function variable use |
1146 | process( |
1147 | instFn: [&](spv::Op opCode, unsigned start) { |
1148 | if (opCode == spv::OpVariable) { |
1149 | ++varUseCount[asId(word: start+2)]; |
1150 | return true; |
1151 | } else if (opCode == spv::OpEntryPoint) { |
1152 | const int wordCount = asWordCount(word: start); |
1153 | for (int i = 4; i < wordCount; i++) { |
1154 | ++varUseCount[asId(word: start+i)]; |
1155 | } |
1156 | return true; |
1157 | } else |
1158 | return false; |
1159 | }, |
1160 | |
1161 | idFn: [&](spv::Id& id) { if (varUseCount[id]) ++varUseCount[id]; } |
1162 | ); |
1163 | |
1164 | if (errorLatch) |
1165 | return; |
1166 | |
1167 | // Remove single-use function variables + associated decorations and names |
1168 | process( |
1169 | instFn: [&](spv::Op opCode, unsigned start) { |
1170 | spv::Id id = spv::NoResult; |
1171 | if (opCode == spv::OpVariable) |
1172 | id = asId(word: start+2); |
1173 | if (opCode == spv::OpDecorate || opCode == spv::OpName) |
1174 | id = asId(word: start+1); |
1175 | |
1176 | if (id != spv::NoResult && varUseCount[id] == 1) |
1177 | stripInst(start); |
1178 | |
1179 | return true; |
1180 | }, |
1181 | idFn: op_fn_nop); |
1182 | } |
1183 | |
1184 | // remove unused types |
1185 | void spirvbin_t::dceTypes() |
1186 | { |
1187 | std::vector<bool> isType(bound(), false); |
1188 | |
1189 | // for speed, make O(1) way to get to type query (map is log(n)) |
1190 | for (const auto typeStart : typeConstPos) |
1191 | isType[asTypeConstId(word: typeStart)] = true; |
1192 | |
1193 | std::unordered_map<spv::Id, int> typeUseCount; |
1194 | |
1195 | // This is not the most efficient algorithm, but this is an offline tool, and |
1196 | // it's easy to write this way. Can be improved opportunistically if needed. |
1197 | bool changed = true; |
1198 | while (changed) { |
1199 | changed = false; |
1200 | strip(); |
1201 | typeUseCount.clear(); |
1202 | |
1203 | // Count total type usage |
1204 | process(instFn: inst_fn_nop, |
1205 | idFn: [&](spv::Id& id) { if (isType[id]) ++typeUseCount[id]; } |
1206 | ); |
1207 | |
1208 | if (errorLatch) |
1209 | return; |
1210 | |
1211 | // Remove single reference types |
1212 | for (const auto typeStart : typeConstPos) { |
1213 | const spv::Id typeId = asTypeConstId(word: typeStart); |
1214 | if (typeUseCount[typeId] == 1) { |
1215 | changed = true; |
1216 | --typeUseCount[typeId]; |
1217 | stripInst(start: typeStart); |
1218 | } |
1219 | } |
1220 | |
1221 | if (errorLatch) |
1222 | return; |
1223 | } |
1224 | } |
1225 | |
1226 | #ifdef NOTDEF |
1227 | bool spirvbin_t::matchType(const spirvbin_t::globaltypes_t& globalTypes, spv::Id lt, spv::Id gt) const |
1228 | { |
1229 | // Find the local type id "lt" and global type id "gt" |
1230 | const auto lt_it = typeConstPosR.find(lt); |
1231 | if (lt_it == typeConstPosR.end()) |
1232 | return false; |
1233 | |
1234 | const auto typeStart = lt_it->second; |
1235 | |
1236 | // Search for entry in global table |
1237 | const auto gtype = globalTypes.find(gt); |
1238 | if (gtype == globalTypes.end()) |
1239 | return false; |
1240 | |
1241 | const auto& gdata = gtype->second; |
1242 | |
1243 | // local wordcount and opcode |
1244 | const int wordCount = asWordCount(typeStart); |
1245 | const spv::Op opCode = asOpCode(typeStart); |
1246 | |
1247 | // no type match if opcodes don't match, or operand count doesn't match |
1248 | if (opCode != opOpCode(gdata[0]) || wordCount != opWordCount(gdata[0])) |
1249 | return false; |
1250 | |
1251 | const unsigned numOperands = wordCount - 2; // all types have a result |
1252 | |
1253 | const auto cmpIdRange = [&](range_t range) { |
1254 | for (int x=range.first; x<std::min(range.second, wordCount); ++x) |
1255 | if (!matchType(globalTypes, asId(typeStart+x), gdata[x])) |
1256 | return false; |
1257 | return true; |
1258 | }; |
1259 | |
1260 | const auto cmpConst = [&]() { return cmpIdRange(constRange(opCode)); }; |
1261 | const auto cmpSubType = [&]() { return cmpIdRange(typeRange(opCode)); }; |
1262 | |
1263 | // Compare literals in range [start,end) |
1264 | const auto cmpLiteral = [&]() { |
1265 | const auto range = literalRange(opCode); |
1266 | return std::equal(spir.begin() + typeStart + range.first, |
1267 | spir.begin() + typeStart + std::min(range.second, wordCount), |
1268 | gdata.begin() + range.first); |
1269 | }; |
1270 | |
1271 | assert(isTypeOp(opCode) || isConstOp(opCode)); |
1272 | |
1273 | switch (opCode) { |
1274 | case spv::OpTypeOpaque: // TODO: disable until we compare the literal strings. |
1275 | case spv::OpTypeQueue: return false; |
1276 | case spv::OpTypeEvent: // fall through... |
1277 | case spv::OpTypeDeviceEvent: // ... |
1278 | case spv::OpTypeReserveId: return false; |
1279 | // for samplers, we don't handle the optional parameters yet |
1280 | case spv::OpTypeSampler: return cmpLiteral() && cmpConst() && cmpSubType() && wordCount == 8; |
1281 | default: return cmpLiteral() && cmpConst() && cmpSubType(); |
1282 | } |
1283 | } |
1284 | |
1285 | // Look for an equivalent type in the globalTypes map |
1286 | spv::Id spirvbin_t::findType(const spirvbin_t::globaltypes_t& globalTypes, spv::Id lt) const |
1287 | { |
1288 | // Try a recursive type match on each in turn, and return a match if we find one |
1289 | for (const auto& gt : globalTypes) |
1290 | if (matchType(globalTypes, lt, gt.first)) |
1291 | return gt.first; |
1292 | |
1293 | return spv::NoType; |
1294 | } |
1295 | #endif // NOTDEF |
1296 | |
1297 | // Return start position in SPV of given Id. error if not found. |
1298 | unsigned spirvbin_t::idPos(spv::Id id) const |
1299 | { |
1300 | const auto tid_it = idPosR.find(x: id); |
1301 | if (tid_it == idPosR.end()) { |
1302 | error(txt: "ID not found" ); |
1303 | return 0; |
1304 | } |
1305 | |
1306 | return tid_it->second; |
1307 | } |
1308 | |
1309 | // Hash types to canonical values. This can return ID collisions (it's a bit |
1310 | // inevitable): it's up to the caller to handle that gracefully. |
1311 | std::uint32_t spirvbin_t::hashType(unsigned typeStart) const |
1312 | { |
1313 | const unsigned wordCount = asWordCount(word: typeStart); |
1314 | const spv::Op opCode = asOpCode(word: typeStart); |
1315 | |
1316 | switch (opCode) { |
1317 | case spv::OpTypeVoid: return 0; |
1318 | case spv::OpTypeBool: return 1; |
1319 | case spv::OpTypeInt: return 3 + (spv[typeStart+3]); |
1320 | case spv::OpTypeFloat: return 5; |
1321 | case spv::OpTypeVector: |
1322 | return 6 + hashType(typeStart: idPos(id: spv[typeStart+2])) * (spv[typeStart+3] - 1); |
1323 | case spv::OpTypeMatrix: |
1324 | return 30 + hashType(typeStart: idPos(id: spv[typeStart+2])) * (spv[typeStart+3] - 1); |
1325 | case spv::OpTypeImage: |
1326 | return 120 + hashType(typeStart: idPos(id: spv[typeStart+2])) + |
1327 | spv[typeStart+3] + // dimensionality |
1328 | spv[typeStart+4] * 8 * 16 + // depth |
1329 | spv[typeStart+5] * 4 * 16 + // arrayed |
1330 | spv[typeStart+6] * 2 * 16 + // multisampled |
1331 | spv[typeStart+7] * 1 * 16; // format |
1332 | case spv::OpTypeSampler: |
1333 | return 500; |
1334 | case spv::OpTypeSampledImage: |
1335 | return 502; |
1336 | case spv::OpTypeArray: |
1337 | return 501 + hashType(typeStart: idPos(id: spv[typeStart+2])) * spv[typeStart+3]; |
1338 | case spv::OpTypeRuntimeArray: |
1339 | return 5000 + hashType(typeStart: idPos(id: spv[typeStart+2])); |
1340 | case spv::OpTypeStruct: |
1341 | { |
1342 | std::uint32_t hash = 10000; |
1343 | for (unsigned w=2; w < wordCount; ++w) |
1344 | hash += w * hashType(typeStart: idPos(id: spv[typeStart+w])); |
1345 | return hash; |
1346 | } |
1347 | |
1348 | case spv::OpTypeOpaque: return 6000 + spv[typeStart+2]; |
1349 | case spv::OpTypePointer: return 100000 + hashType(typeStart: idPos(id: spv[typeStart+3])); |
1350 | case spv::OpTypeFunction: |
1351 | { |
1352 | std::uint32_t hash = 200000; |
1353 | for (unsigned w=2; w < wordCount; ++w) |
1354 | hash += w * hashType(typeStart: idPos(id: spv[typeStart+w])); |
1355 | return hash; |
1356 | } |
1357 | |
1358 | case spv::OpTypeEvent: return 300000; |
1359 | case spv::OpTypeDeviceEvent: return 300001; |
1360 | case spv::OpTypeReserveId: return 300002; |
1361 | case spv::OpTypeQueue: return 300003; |
1362 | case spv::OpTypePipe: return 300004; |
1363 | case spv::OpConstantTrue: return 300007; |
1364 | case spv::OpConstantFalse: return 300008; |
1365 | case spv::OpConstantComposite: |
1366 | { |
1367 | std::uint32_t hash = 300011 + hashType(typeStart: idPos(id: spv[typeStart+1])); |
1368 | for (unsigned w=3; w < wordCount; ++w) |
1369 | hash += w * hashType(typeStart: idPos(id: spv[typeStart+w])); |
1370 | return hash; |
1371 | } |
1372 | case spv::OpConstant: |
1373 | { |
1374 | std::uint32_t hash = 400011 + hashType(typeStart: idPos(id: spv[typeStart+1])); |
1375 | for (unsigned w=3; w < wordCount; ++w) |
1376 | hash += w * spv[typeStart+w]; |
1377 | return hash; |
1378 | } |
1379 | case spv::OpConstantNull: |
1380 | { |
1381 | std::uint32_t hash = 500009 + hashType(typeStart: idPos(id: spv[typeStart+1])); |
1382 | return hash; |
1383 | } |
1384 | case spv::OpConstantSampler: |
1385 | { |
1386 | std::uint32_t hash = 600011 + hashType(typeStart: idPos(id: spv[typeStart+1])); |
1387 | for (unsigned w=3; w < wordCount; ++w) |
1388 | hash += w * spv[typeStart+w]; |
1389 | return hash; |
1390 | } |
1391 | |
1392 | default: |
1393 | error(txt: "unknown type opcode" ); |
1394 | return 0; |
1395 | } |
1396 | } |
1397 | |
1398 | void spirvbin_t::mapTypeConst() |
1399 | { |
1400 | globaltypes_t globalTypeMap; |
1401 | |
1402 | msg(minVerbosity: 3, indent: 2, txt: std::string("Remapping Consts & Types: " )); |
1403 | |
1404 | static const std::uint32_t softTypeIdLimit = 3011; // small prime. TODO: get from options |
1405 | static const std::uint32_t firstMappedID = 8; // offset into ID space |
1406 | |
1407 | for (auto& typeStart : typeConstPos) { |
1408 | const spv::Id resId = asTypeConstId(word: typeStart); |
1409 | const std::uint32_t hashval = hashType(typeStart); |
1410 | |
1411 | if (errorLatch) |
1412 | return; |
1413 | |
1414 | if (isOldIdUnmapped(oldId: resId)) { |
1415 | localId(id: resId, newId: nextUnusedId(id: hashval % softTypeIdLimit + firstMappedID)); |
1416 | if (errorLatch) |
1417 | return; |
1418 | } |
1419 | } |
1420 | } |
1421 | |
1422 | // Strip a single binary by removing ranges given in stripRange |
1423 | void spirvbin_t::strip() |
1424 | { |
1425 | if (stripRange.empty()) // nothing to do |
1426 | return; |
1427 | |
1428 | // Sort strip ranges in order of traversal |
1429 | std::sort(first: stripRange.begin(), last: stripRange.end()); |
1430 | |
1431 | // Allocate a new binary big enough to hold old binary |
1432 | // We'll step this iterator through the strip ranges as we go through the binary |
1433 | auto strip_it = stripRange.begin(); |
1434 | |
1435 | int strippedPos = 0; |
1436 | for (unsigned word = 0; word < unsigned(spv.size()); ++word) { |
1437 | while (strip_it != stripRange.end() && word >= strip_it->second) |
1438 | ++strip_it; |
1439 | |
1440 | if (strip_it == stripRange.end() || word < strip_it->first || word >= strip_it->second) |
1441 | spv[strippedPos++] = spv[word]; |
1442 | } |
1443 | |
1444 | spv.resize(new_size: strippedPos); |
1445 | stripRange.clear(); |
1446 | |
1447 | buildLocalMaps(); |
1448 | } |
1449 | |
1450 | // Strip a single binary by removing ranges given in stripRange |
1451 | void spirvbin_t::remap(std::uint32_t opts) |
1452 | { |
1453 | options = opts; |
1454 | |
1455 | // Set up opcode tables from SpvDoc |
1456 | spv::Parameterize(); |
1457 | |
1458 | validate(); // validate header |
1459 | buildLocalMaps(); // build ID maps |
1460 | |
1461 | msg(minVerbosity: 3, indent: 4, txt: std::string("ID bound: " ) + std::to_string(val: bound())); |
1462 | |
1463 | if (options & STRIP) stripDebug(); |
1464 | if (errorLatch) return; |
1465 | |
1466 | strip(); // strip out data we decided to eliminate |
1467 | if (errorLatch) return; |
1468 | |
1469 | if (options & OPT_LOADSTORE) optLoadStore(); |
1470 | if (errorLatch) return; |
1471 | |
1472 | if (options & OPT_FWD_LS) forwardLoadStores(); |
1473 | if (errorLatch) return; |
1474 | |
1475 | if (options & DCE_FUNCS) dceFuncs(); |
1476 | if (errorLatch) return; |
1477 | |
1478 | if (options & DCE_VARS) dceVars(); |
1479 | if (errorLatch) return; |
1480 | |
1481 | if (options & DCE_TYPES) dceTypes(); |
1482 | if (errorLatch) return; |
1483 | |
1484 | strip(); // strip out data we decided to eliminate |
1485 | if (errorLatch) return; |
1486 | |
1487 | stripDeadRefs(); // remove references to things we DCEed |
1488 | if (errorLatch) return; |
1489 | |
1490 | // after the last strip, we must clean any debug info referring to now-deleted data |
1491 | |
1492 | if (options & MAP_TYPES) mapTypeConst(); |
1493 | if (errorLatch) return; |
1494 | |
1495 | if (options & MAP_NAMES) mapNames(); |
1496 | if (errorLatch) return; |
1497 | |
1498 | if (options & MAP_FUNCS) mapFnBodies(); |
1499 | if (errorLatch) return; |
1500 | |
1501 | if (options & MAP_ALL) { |
1502 | mapRemainder(); // map any unmapped IDs |
1503 | if (errorLatch) return; |
1504 | |
1505 | applyMap(); // Now remap each shader to the new IDs we've come up with |
1506 | if (errorLatch) return; |
1507 | } |
1508 | } |
1509 | |
1510 | // remap from a memory image |
1511 | void spirvbin_t::remap(std::vector<std::uint32_t>& in_spv, const std::vector<std::string>& whiteListStrings, |
1512 | std::uint32_t opts) |
1513 | { |
1514 | stripWhiteList = whiteListStrings; |
1515 | spv.swap(x&: in_spv); |
1516 | remap(opts); |
1517 | spv.swap(x&: in_spv); |
1518 | } |
1519 | |
1520 | // remap from a memory image - legacy interface without white list |
1521 | void spirvbin_t::remap(std::vector<std::uint32_t>& in_spv, std::uint32_t opts) |
1522 | { |
1523 | stripWhiteList.clear(); |
1524 | spv.swap(x&: in_spv); |
1525 | remap(opts); |
1526 | spv.swap(x&: in_spv); |
1527 | } |
1528 | |
1529 | } // namespace SPV |
1530 | |
1531 | #endif // defined (use_cpp11) |
1532 | |
1533 | |