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