1 /*------------------------------------------------------------------------- 2 * Vulkan CTS Framework 3 * -------------------- 4 * 5 * Copyright (c) 2015 Google Inc. 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 * 19 *//*! 20 * \file 21 * \brief Program binary registry. 22 *//*--------------------------------------------------------------------*/ 23 24 #include "vkBinaryRegistry.hpp" 25 #include "tcuResource.hpp" 26 #include "tcuFormatUtil.hpp" 27 #include "deFilePath.hpp" 28 #include "deStringUtil.hpp" 29 #include "deDirectoryIterator.hpp" 30 #include "deString.h" 31 #include "deInt32.h" 32 #include "deFile.h" 33 #include "deMemory.h" 34 35 #include <sstream> 36 #include <fstream> 37 #include <stdexcept> 38 #include <limits> 39 40 namespace vk 41 { 42 namespace BinaryRegistryDetail 43 { 44 45 using std::string; 46 using std::vector; 47 48 namespace 49 { 50 51 string getProgramFileName (deUint32 index) 52 { 53 return de::toString(tcu::toHex(index)) + ".spv"; 54 } 55 56 string getProgramPath (const std::string& dirName, deUint32 index) 57 { 58 return de::FilePath::join(dirName, getProgramFileName(index)).getPath(); 59 } 60 61 bool isHexChr (char c) 62 { 63 return de::inRange(c, '0', '9') || de::inRange(c, 'a', 'f') || de::inRange(c, 'A', 'F'); 64 } 65 66 bool isProgramFileName (const std::string& name) 67 { 68 // 0x + 00000000 + .spv 69 if (name.length() != (2 + 8 + 4)) 70 return false; 71 72 if (name[0] != '0' || 73 name[1] != 'x' || 74 name[10] != '.' || 75 name[11] != 's' || 76 name[12] != 'p' || 77 name[13] != 'v') 78 return false; 79 80 for (size_t ndx = 2; ndx < 10; ++ndx) 81 { 82 if (!isHexChr(name[ndx])) 83 return false; 84 } 85 86 return true; 87 } 88 89 deUint32 getProgramIndexFromName (const std::string& name) 90 { 91 DE_ASSERT(isProgramFileName(name)); 92 93 deUint32 index = ~0u; 94 std::stringstream str; 95 96 str << std::hex << name.substr(2,10); 97 str >> index; 98 99 DE_ASSERT(getProgramFileName(index) == name); 100 101 return index; 102 } 103 104 string getIndexPath (const std::string& dirName) 105 { 106 return de::FilePath::join(dirName, "index.bin").getPath(); 107 } 108 109 void writeBinary (const ProgramBinary& binary, const std::string& dstPath) 110 { 111 const de::FilePath filePath(dstPath); 112 113 if (!de::FilePath(filePath.getDirName()).exists()) 114 de::createDirectoryAndParents(filePath.getDirName().c_str()); 115 116 { 117 std::ofstream out (dstPath.c_str(), std::ios_base::binary); 118 119 if (!out.is_open() || !out.good()) 120 throw tcu::Exception("Failed to open " + dstPath); 121 122 out.write((const char*)binary.getBinary(), binary.getSize()); 123 out.close(); 124 } 125 } 126 127 void writeBinary (const std::string& dstDir, deUint32 index, const ProgramBinary& binary) 128 { 129 writeBinary(binary, getProgramPath(dstDir, index)); 130 } 131 132 ProgramBinary* readBinary (const std::string& srcPath) 133 { 134 std::ifstream in (srcPath.c_str(), std::ios::binary | std::ios::ate); 135 const size_t size = (size_t)in.tellg(); 136 137 if (!in.is_open() || !in.good()) 138 throw tcu::Exception("Failed to open " + srcPath); 139 140 if (size == 0) 141 throw tcu::Exception("Malformed binary, size = 0"); 142 143 in.seekg(0, std::ios::beg); 144 145 { 146 std::vector<deUint8> bytes (size); 147 148 in.read((char*)&bytes[0], size); 149 DE_ASSERT(bytes[0] != 0); 150 151 return new ProgramBinary(vk::PROGRAM_FORMAT_SPIRV, bytes.size(), &bytes[0]); 152 } 153 } 154 155 deUint32 binaryHash (const ProgramBinary* binary) 156 { 157 return deMemoryHash(binary->getBinary(), binary->getSize()); 158 } 159 160 deBool binaryEqual (const ProgramBinary* a, const ProgramBinary* b) 161 { 162 if (a->getSize() == b->getSize()) 163 return deMemoryEqual(a->getBinary(), b->getBinary(), a->getSize()); 164 else 165 return DE_FALSE; 166 } 167 168 std::vector<deUint32> getSearchPath (const ProgramIdentifier& id) 169 { 170 const std::string combinedStr = id.testCasePath + '#' + id.programName; 171 const size_t strLen = combinedStr.size(); 172 const size_t numWords = strLen/4 + 1; // Must always end up with at least one 0 byte 173 vector<deUint32> words (numWords, 0u); 174 175 deMemcpy(&words[0], combinedStr.c_str(), strLen); 176 177 return words; 178 } 179 180 const deUint32* findBinaryIndex (BinaryIndexAccess* index, const ProgramIdentifier& id) 181 { 182 const vector<deUint32> words = getSearchPath(id); 183 size_t nodeNdx = 0; 184 size_t wordNdx = 0; 185 186 for (;;) 187 { 188 const BinaryIndexNode& curNode = (*index)[nodeNdx]; 189 190 if (curNode.word == words[wordNdx]) 191 { 192 if (wordNdx+1 < words.size()) 193 { 194 TCU_CHECK_INTERNAL((size_t)curNode.index < index->size()); 195 196 nodeNdx = curNode.index; 197 wordNdx += 1; 198 } 199 else if (wordNdx+1 == words.size()) 200 return &curNode.index; 201 else 202 return DE_NULL; 203 } 204 else if (curNode.word != 0) 205 { 206 nodeNdx += 1; 207 208 // Index should always be null-terminated 209 TCU_CHECK_INTERNAL(nodeNdx < index->size()); 210 } 211 else 212 return DE_NULL; 213 } 214 215 return DE_NULL; 216 } 217 218 //! Sparse index node used for final binary index construction 219 struct SparseIndexNode 220 { 221 deUint32 word; 222 deUint32 index; 223 std::vector<SparseIndexNode*> children; 224 225 SparseIndexNode (deUint32 word_, deUint32 index_) 226 : word (word_) 227 , index (index_) 228 {} 229 230 SparseIndexNode (void) 231 : word (0) 232 , index (0) 233 {} 234 235 ~SparseIndexNode (void) 236 { 237 for (size_t ndx = 0; ndx < children.size(); ndx++) 238 delete children[ndx]; 239 } 240 }; 241 242 #if defined(DE_DEBUG) 243 bool isNullByteTerminated (deUint32 word) 244 { 245 deUint8 bytes[4]; 246 deMemcpy(bytes, &word, sizeof(word)); 247 return bytes[3] == 0; 248 } 249 #endif 250 251 void addToSparseIndex (SparseIndexNode* group, const deUint32* words, size_t numWords, deUint32 index) 252 { 253 const deUint32 curWord = words[0]; 254 SparseIndexNode* child = DE_NULL; 255 256 for (size_t childNdx = 0; childNdx < group->children.size(); childNdx++) 257 { 258 if (group->children[childNdx]->word == curWord) 259 { 260 child = group->children[childNdx]; 261 break; 262 } 263 } 264 265 DE_ASSERT(numWords > 1 || !child); 266 267 if (!child) 268 { 269 group->children.reserve(group->children.size()+1); 270 group->children.push_back(new SparseIndexNode(curWord, numWords == 1 ? index : 0)); 271 272 child = group->children.back(); 273 } 274 275 if (numWords > 1) 276 addToSparseIndex(child, words+1, numWords-1, index); 277 else 278 DE_ASSERT(isNullByteTerminated(curWord)); 279 } 280 281 // Prepares sparse index for finalization. Ensures that child with word = 0 is moved 282 // to the end, or one is added if there is no such child already. 283 void normalizeSparseIndex (SparseIndexNode* group) 284 { 285 int zeroChildPos = -1; 286 287 for (size_t childNdx = 0; childNdx < group->children.size(); childNdx++) 288 { 289 normalizeSparseIndex(group->children[childNdx]); 290 291 if (group->children[childNdx]->word == 0) 292 { 293 DE_ASSERT(zeroChildPos < 0); 294 zeroChildPos = (int)childNdx; 295 } 296 } 297 298 if (zeroChildPos >= 0) 299 { 300 // Move child with word = 0 to last 301 while (zeroChildPos != (int)group->children.size()-1) 302 { 303 std::swap(group->children[zeroChildPos], group->children[zeroChildPos+1]); 304 zeroChildPos += 1; 305 } 306 } 307 else if (!group->children.empty()) 308 { 309 group->children.reserve(group->children.size()+1); 310 group->children.push_back(new SparseIndexNode(0, 0)); 311 } 312 } 313 314 deUint32 getIndexSize (const SparseIndexNode* group) 315 { 316 size_t numNodes = group->children.size(); 317 318 for (size_t childNdx = 0; childNdx < group->children.size(); childNdx++) 319 numNodes += getIndexSize(group->children[childNdx]); 320 321 DE_ASSERT(numNodes <= std::numeric_limits<deUint32>::max()); 322 323 return (deUint32)numNodes; 324 } 325 326 deUint32 addAndCountNodes (BinaryIndexNode* index, deUint32 baseOffset, const SparseIndexNode* group) 327 { 328 const deUint32 numLocalNodes = (deUint32)group->children.size(); 329 deUint32 curOffset = numLocalNodes; 330 331 // Must be normalized prior to construction of final index 332 DE_ASSERT(group->children.empty() || group->children.back()->word == 0); 333 334 for (size_t childNdx = 0; childNdx < numLocalNodes; childNdx++) 335 { 336 const SparseIndexNode* child = group->children[childNdx]; 337 const deUint32 subtreeSize = addAndCountNodes(index+curOffset, baseOffset+curOffset, child); 338 339 index[childNdx].word = child->word; 340 341 if (subtreeSize == 0) 342 index[childNdx].index = child->index; 343 else 344 { 345 DE_ASSERT(child->index == 0); 346 index[childNdx].index = baseOffset+curOffset; 347 } 348 349 curOffset += subtreeSize; 350 } 351 352 return curOffset; 353 } 354 355 void buildFinalIndex (std::vector<BinaryIndexNode>* dst, const SparseIndexNode* root) 356 { 357 const deUint32 indexSize = getIndexSize(root); 358 359 if (indexSize > 0) 360 { 361 dst->resize(indexSize); 362 addAndCountNodes(&(*dst)[0], 0, root); 363 } 364 else 365 { 366 // Generate empty index 367 dst->resize(1); 368 (*dst)[0].word = 0u; 369 (*dst)[0].index = 0u; 370 } 371 } 372 373 void buildBinaryIndex (std::vector<BinaryIndexNode>* dst, size_t numEntries, const ProgramIdentifierIndex* entries) 374 { 375 de::UniquePtr<SparseIndexNode> sparseIndex (new SparseIndexNode()); 376 377 for (size_t ndx = 0; ndx < numEntries; ndx++) 378 { 379 const std::vector<deUint32> searchPath = getSearchPath(entries[ndx].id); 380 addToSparseIndex(sparseIndex.get(), &searchPath[0], searchPath.size(), entries[ndx].index); 381 } 382 383 normalizeSparseIndex(sparseIndex.get()); 384 buildFinalIndex(dst, sparseIndex.get()); 385 } 386 387 } // anonymous 388 389 // BinaryIndexHash 390 391 DE_IMPLEMENT_POOL_HASH(BinaryIndexHashImpl, const ProgramBinary*, deUint32, binaryHash, binaryEqual); 392 393 BinaryIndexHash::BinaryIndexHash (void) 394 : m_hash(BinaryIndexHashImpl_create(m_memPool.getRawPool())) 395 { 396 if (!m_hash) 397 throw std::bad_alloc(); 398 } 399 400 BinaryIndexHash::~BinaryIndexHash (void) 401 { 402 } 403 404 deUint32* BinaryIndexHash::find (const ProgramBinary* binary) const 405 { 406 return BinaryIndexHashImpl_find(m_hash, binary); 407 } 408 409 void BinaryIndexHash::insert (const ProgramBinary* binary, deUint32 index) 410 { 411 if (!BinaryIndexHashImpl_insert(m_hash, binary, index)) 412 throw std::bad_alloc(); 413 } 414 415 // BinaryRegistryWriter 416 417 BinaryRegistryWriter::BinaryRegistryWriter (const std::string& dstPath) 418 : m_dstPath(dstPath) 419 { 420 if (de::FilePath(dstPath).exists()) 421 initFromPath(dstPath); 422 } 423 424 BinaryRegistryWriter::~BinaryRegistryWriter (void) 425 { 426 for (BinaryVector::const_iterator binaryIter = m_binaries.begin(); 427 binaryIter != m_binaries.end(); 428 ++binaryIter) 429 delete binaryIter->binary; 430 } 431 432 void BinaryRegistryWriter::initFromPath (const std::string& srcPath) 433 { 434 DE_ASSERT(m_binaries.empty()); 435 436 for (de::DirectoryIterator iter(srcPath); iter.hasItem(); iter.next()) 437 { 438 const de::FilePath path = iter.getItem(); 439 const std::string baseName = path.getBaseName(); 440 441 if (isProgramFileName(baseName)) 442 { 443 const deUint32 index = getProgramIndexFromName(baseName); 444 const de::UniquePtr<ProgramBinary> binary (readBinary(path.getPath())); 445 446 addBinary(index, *binary); 447 // \note referenceCount is left to 0 and will only be incremented 448 // if binary is reused (added via addProgram()). 449 } 450 } 451 } 452 453 void BinaryRegistryWriter::addProgram (const ProgramIdentifier& id, const ProgramBinary& binary) 454 { 455 const deUint32* const indexPtr = findBinary(binary); 456 deUint32 index = indexPtr ? *indexPtr : ~0u; 457 458 if (!indexPtr) 459 { 460 index = getNextSlot(); 461 addBinary(index, binary); 462 } 463 464 m_binaries[index].referenceCount += 1; 465 m_binaryIndices.push_back(ProgramIdentifierIndex(id, index)); 466 } 467 468 deUint32* BinaryRegistryWriter::findBinary (const ProgramBinary& binary) const 469 { 470 return m_binaryHash.find(&binary); 471 } 472 473 deUint32 BinaryRegistryWriter::getNextSlot (void) const 474 { 475 const deUint32 index = (deUint32)m_binaries.size(); 476 477 if ((size_t)index != m_binaries.size()) 478 throw std::bad_alloc(); // Overflow 479 480 return index; 481 } 482 483 void BinaryRegistryWriter::addBinary (deUint32 index, const ProgramBinary& binary) 484 { 485 DE_ASSERT(binary.getFormat() == vk::PROGRAM_FORMAT_SPIRV); 486 DE_ASSERT(findBinary(binary) == DE_NULL); 487 488 ProgramBinary* const binaryClone = new ProgramBinary(binary); 489 490 try 491 { 492 if (m_binaries.size() < (size_t)index+1) 493 m_binaries.resize(index+1); 494 495 DE_ASSERT(!m_binaries[index].binary); 496 DE_ASSERT(m_binaries[index].referenceCount == 0); 497 498 m_binaries[index].binary = binaryClone; 499 // \note referenceCount is not incremented here 500 } 501 catch (...) 502 { 503 delete binaryClone; 504 throw; 505 } 506 507 m_binaryHash.insert(binaryClone, index); 508 } 509 510 void BinaryRegistryWriter::write (void) const 511 { 512 writeToPath(m_dstPath); 513 } 514 515 void BinaryRegistryWriter::writeToPath (const std::string& dstPath) const 516 { 517 if (!de::FilePath(dstPath).exists()) 518 de::createDirectoryAndParents(dstPath.c_str()); 519 520 DE_ASSERT(m_binaries.size() <= 0xffffffffu); 521 for (size_t binaryNdx = 0; binaryNdx < m_binaries.size(); ++binaryNdx) 522 { 523 const BinarySlot& slot = m_binaries[binaryNdx]; 524 525 if (slot.referenceCount > 0) 526 { 527 DE_ASSERT(slot.binary); 528 writeBinary(dstPath, (deUint32)binaryNdx, *slot.binary); 529 } 530 else 531 { 532 // Delete stale binary if such exists 533 const std::string progPath = getProgramPath(dstPath, (deUint32)binaryNdx); 534 535 if (de::FilePath(progPath).exists()) 536 deDeleteFile(progPath.c_str()); 537 } 538 539 } 540 541 // Write index 542 { 543 const de::FilePath indexPath = getIndexPath(dstPath); 544 std::vector<BinaryIndexNode> index; 545 546 buildBinaryIndex(&index, m_binaryIndices.size(), !m_binaryIndices.empty() ? &m_binaryIndices[0] : DE_NULL); 547 548 // Even in empty index there is always terminating node for the root group 549 DE_ASSERT(!index.empty()); 550 551 if (!de::FilePath(indexPath.getDirName()).exists()) 552 de::createDirectoryAndParents(indexPath.getDirName().c_str()); 553 554 { 555 std::ofstream indexOut(indexPath.getPath(), std::ios_base::binary); 556 557 if (!indexOut.is_open() || !indexOut.good()) 558 throw tcu::InternalError(string("Failed to open program binary index file ") + indexPath.getPath()); 559 560 indexOut.write((const char*)&index[0], index.size()*sizeof(BinaryIndexNode)); 561 } 562 } 563 } 564 565 // BinaryRegistryReader 566 567 BinaryRegistryReader::BinaryRegistryReader (const tcu::Archive& archive, const std::string& srcPath) 568 : m_archive (archive) 569 , m_srcPath (srcPath) 570 { 571 } 572 573 BinaryRegistryReader::~BinaryRegistryReader (void) 574 { 575 } 576 577 ProgramBinary* BinaryRegistryReader::loadProgram (const ProgramIdentifier& id) const 578 { 579 if (!m_binaryIndex) 580 { 581 try 582 { 583 m_binaryIndex = BinaryIndexPtr(new BinaryIndexAccess(de::MovePtr<tcu::Resource>(m_archive.getResource(getIndexPath(m_srcPath).c_str())))); 584 } 585 catch (const tcu::ResourceError& e) 586 { 587 throw ProgramNotFoundException(id, string("Failed to open binary index (") + e.what() + ")"); 588 } 589 } 590 591 { 592 const deUint32* indexPos = findBinaryIndex(m_binaryIndex.get(), id); 593 594 if (indexPos) 595 { 596 const string fullPath = getProgramPath(m_srcPath, *indexPos); 597 598 try 599 { 600 de::UniquePtr<tcu::Resource> progRes (m_archive.getResource(fullPath.c_str())); 601 const int progSize = progRes->getSize(); 602 vector<deUint8> bytes (progSize); 603 604 TCU_CHECK_INTERNAL(!bytes.empty()); 605 606 progRes->read(&bytes[0], progSize); 607 608 return new ProgramBinary(vk::PROGRAM_FORMAT_SPIRV, bytes.size(), &bytes[0]); 609 } 610 catch (const tcu::ResourceError& e) 611 { 612 throw ProgramNotFoundException(id, e.what()); 613 } 614 } 615 else 616 throw ProgramNotFoundException(id, "Program not found in index"); 617 } 618 } 619 620 } // BinaryRegistryDetail 621 } // vk 622