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