/*------------------------------------------------------------------------- * Vulkan CTS Framework * -------------------- * * Copyright (c) 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Program binary registry. *//*--------------------------------------------------------------------*/ #include "vkBinaryRegistry.hpp" #include "tcuResource.hpp" #include "tcuFormatUtil.hpp" #include "deFilePath.hpp" #include "deStringUtil.hpp" #include "deDirectoryIterator.hpp" #include "deString.h" #include "deInt32.h" #include "deFile.h" #include "deMemory.h" #include #include #include #include namespace vk { namespace BinaryRegistryDetail { using std::string; using std::vector; namespace { string getProgramFileName (deUint32 index) { return de::toString(tcu::toHex(index)) + ".spv"; } string getProgramPath (const std::string& dirName, deUint32 index) { return de::FilePath::join(dirName, getProgramFileName(index)).getPath(); } bool isHexChr (char c) { return de::inRange(c, '0', '9') || de::inRange(c, 'a', 'f') || de::inRange(c, 'A', 'F'); } bool isProgramFileName (const std::string& name) { // 0x + 00000000 + .spv if (name.length() != (2 + 8 + 4)) return false; if (name[0] != '0' || name[1] != 'x' || name[10] != '.' || name[11] != 's' || name[12] != 'p' || name[13] != 'v') return false; for (size_t ndx = 2; ndx < 10; ++ndx) { if (!isHexChr(name[ndx])) return false; } return true; } deUint32 getProgramIndexFromName (const std::string& name) { DE_ASSERT(isProgramFileName(name)); deUint32 index = ~0u; std::stringstream str; str << std::hex << name.substr(2,10); str >> index; DE_ASSERT(getProgramFileName(index) == name); return index; } string getIndexPath (const std::string& dirName) { return de::FilePath::join(dirName, "index.bin").getPath(); } void writeBinary (const ProgramBinary& binary, const std::string& dstPath) { const de::FilePath filePath(dstPath); if (!de::FilePath(filePath.getDirName()).exists()) de::createDirectoryAndParents(filePath.getDirName().c_str()); { std::ofstream out (dstPath.c_str(), std::ios_base::binary); if (!out.is_open() || !out.good()) throw tcu::Exception("Failed to open " + dstPath); out.write((const char*)binary.getBinary(), binary.getSize()); out.close(); } } void writeBinary (const std::string& dstDir, deUint32 index, const ProgramBinary& binary) { writeBinary(binary, getProgramPath(dstDir, index)); } ProgramBinary* readBinary (const std::string& srcPath) { std::ifstream in (srcPath.c_str(), std::ios::binary | std::ios::ate); const size_t size = (size_t)in.tellg(); if (!in.is_open() || !in.good()) throw tcu::Exception("Failed to open " + srcPath); if (size == 0) throw tcu::Exception("Malformed binary, size = 0"); in.seekg(0, std::ios::beg); { std::vector bytes (size); in.read((char*)&bytes[0], size); DE_ASSERT(bytes[0] != 0); return new ProgramBinary(vk::PROGRAM_FORMAT_SPIRV, bytes.size(), &bytes[0]); } } deUint32 binaryHash (const ProgramBinary* binary) { return deMemoryHash(binary->getBinary(), binary->getSize()); } deBool binaryEqual (const ProgramBinary* a, const ProgramBinary* b) { if (a->getSize() == b->getSize()) return deMemoryEqual(a->getBinary(), b->getBinary(), a->getSize()); else return DE_FALSE; } std::vector getSearchPath (const ProgramIdentifier& id) { const std::string combinedStr = id.testCasePath + '#' + id.programName; const size_t strLen = combinedStr.size(); const size_t numWords = strLen/4 + 1; // Must always end up with at least one 0 byte vector words (numWords, 0u); deMemcpy(&words[0], combinedStr.c_str(), strLen); return words; } const deUint32* findBinaryIndex (BinaryIndexAccess* index, const ProgramIdentifier& id) { const vector words = getSearchPath(id); size_t nodeNdx = 0; size_t wordNdx = 0; for (;;) { const BinaryIndexNode& curNode = (*index)[nodeNdx]; if (curNode.word == words[wordNdx]) { if (wordNdx+1 < words.size()) { TCU_CHECK_INTERNAL((size_t)curNode.index < index->size()); nodeNdx = curNode.index; wordNdx += 1; } else if (wordNdx+1 == words.size()) return &curNode.index; else return DE_NULL; } else if (curNode.word != 0) { nodeNdx += 1; // Index should always be null-terminated TCU_CHECK_INTERNAL(nodeNdx < index->size()); } else return DE_NULL; } return DE_NULL; } //! Sparse index node used for final binary index construction struct SparseIndexNode { deUint32 word; deUint32 index; std::vector children; SparseIndexNode (deUint32 word_, deUint32 index_) : word (word_) , index (index_) {} SparseIndexNode (void) : word (0) , index (0) {} ~SparseIndexNode (void) { for (size_t ndx = 0; ndx < children.size(); ndx++) delete children[ndx]; } }; #if defined(DE_DEBUG) bool isNullByteTerminated (deUint32 word) { deUint8 bytes[4]; deMemcpy(bytes, &word, sizeof(word)); return bytes[3] == 0; } #endif void addToSparseIndex (SparseIndexNode* group, const deUint32* words, size_t numWords, deUint32 index) { const deUint32 curWord = words[0]; SparseIndexNode* child = DE_NULL; for (size_t childNdx = 0; childNdx < group->children.size(); childNdx++) { if (group->children[childNdx]->word == curWord) { child = group->children[childNdx]; break; } } DE_ASSERT(numWords > 1 || !child); if (!child) { group->children.reserve(group->children.size()+1); group->children.push_back(new SparseIndexNode(curWord, numWords == 1 ? index : 0)); child = group->children.back(); } if (numWords > 1) addToSparseIndex(child, words+1, numWords-1, index); else DE_ASSERT(isNullByteTerminated(curWord)); } // Prepares sparse index for finalization. Ensures that child with word = 0 is moved // to the end, or one is added if there is no such child already. void normalizeSparseIndex (SparseIndexNode* group) { int zeroChildPos = -1; for (size_t childNdx = 0; childNdx < group->children.size(); childNdx++) { normalizeSparseIndex(group->children[childNdx]); if (group->children[childNdx]->word == 0) { DE_ASSERT(zeroChildPos < 0); zeroChildPos = (int)childNdx; } } if (zeroChildPos >= 0) { // Move child with word = 0 to last while (zeroChildPos != (int)group->children.size()-1) { std::swap(group->children[zeroChildPos], group->children[zeroChildPos+1]); zeroChildPos += 1; } } else if (!group->children.empty()) { group->children.reserve(group->children.size()+1); group->children.push_back(new SparseIndexNode(0, 0)); } } deUint32 getIndexSize (const SparseIndexNode* group) { size_t numNodes = group->children.size(); for (size_t childNdx = 0; childNdx < group->children.size(); childNdx++) numNodes += getIndexSize(group->children[childNdx]); DE_ASSERT(numNodes <= std::numeric_limits::max()); return (deUint32)numNodes; } deUint32 addAndCountNodes (BinaryIndexNode* index, deUint32 baseOffset, const SparseIndexNode* group) { const deUint32 numLocalNodes = (deUint32)group->children.size(); deUint32 curOffset = numLocalNodes; // Must be normalized prior to construction of final index DE_ASSERT(group->children.empty() || group->children.back()->word == 0); for (size_t childNdx = 0; childNdx < numLocalNodes; childNdx++) { const SparseIndexNode* child = group->children[childNdx]; const deUint32 subtreeSize = addAndCountNodes(index+curOffset, baseOffset+curOffset, child); index[childNdx].word = child->word; if (subtreeSize == 0) index[childNdx].index = child->index; else { DE_ASSERT(child->index == 0); index[childNdx].index = baseOffset+curOffset; } curOffset += subtreeSize; } return curOffset; } void buildFinalIndex (std::vector* dst, const SparseIndexNode* root) { const deUint32 indexSize = getIndexSize(root); if (indexSize > 0) { dst->resize(indexSize); addAndCountNodes(&(*dst)[0], 0, root); } else { // Generate empty index dst->resize(1); (*dst)[0].word = 0u; (*dst)[0].index = 0u; } } void buildBinaryIndex (std::vector* dst, size_t numEntries, const ProgramIdentifierIndex* entries) { de::UniquePtr sparseIndex (new SparseIndexNode()); for (size_t ndx = 0; ndx < numEntries; ndx++) { const std::vector searchPath = getSearchPath(entries[ndx].id); addToSparseIndex(sparseIndex.get(), &searchPath[0], searchPath.size(), entries[ndx].index); } normalizeSparseIndex(sparseIndex.get()); buildFinalIndex(dst, sparseIndex.get()); } } // anonymous // BinaryIndexHash DE_IMPLEMENT_POOL_HASH(BinaryIndexHashImpl, const ProgramBinary*, deUint32, binaryHash, binaryEqual); BinaryIndexHash::BinaryIndexHash (void) : m_hash(BinaryIndexHashImpl_create(m_memPool.getRawPool())) { if (!m_hash) throw std::bad_alloc(); } BinaryIndexHash::~BinaryIndexHash (void) { } deUint32* BinaryIndexHash::find (const ProgramBinary* binary) const { return BinaryIndexHashImpl_find(m_hash, binary); } void BinaryIndexHash::insert (const ProgramBinary* binary, deUint32 index) { if (!BinaryIndexHashImpl_insert(m_hash, binary, index)) throw std::bad_alloc(); } // BinaryRegistryWriter BinaryRegistryWriter::BinaryRegistryWriter (const std::string& dstPath) : m_dstPath(dstPath) { if (de::FilePath(dstPath).exists()) initFromPath(dstPath); } BinaryRegistryWriter::~BinaryRegistryWriter (void) { for (BinaryVector::const_iterator binaryIter = m_binaries.begin(); binaryIter != m_binaries.end(); ++binaryIter) delete binaryIter->binary; } void BinaryRegistryWriter::initFromPath (const std::string& srcPath) { DE_ASSERT(m_binaries.empty()); for (de::DirectoryIterator iter(srcPath); iter.hasItem(); iter.next()) { const de::FilePath path = iter.getItem(); const std::string baseName = path.getBaseName(); if (isProgramFileName(baseName)) { const deUint32 index = getProgramIndexFromName(baseName); const de::UniquePtr binary (readBinary(path.getPath())); addBinary(index, *binary); // \note referenceCount is left to 0 and will only be incremented // if binary is reused (added via addProgram()). } } } void BinaryRegistryWriter::addProgram (const ProgramIdentifier& id, const ProgramBinary& binary) { const deUint32* const indexPtr = findBinary(binary); deUint32 index = indexPtr ? *indexPtr : ~0u; if (!indexPtr) { index = getNextSlot(); addBinary(index, binary); } m_binaries[index].referenceCount += 1; m_binaryIndices.push_back(ProgramIdentifierIndex(id, index)); } deUint32* BinaryRegistryWriter::findBinary (const ProgramBinary& binary) const { return m_binaryHash.find(&binary); } deUint32 BinaryRegistryWriter::getNextSlot (void) const { const deUint32 index = (deUint32)m_binaries.size(); if ((size_t)index != m_binaries.size()) throw std::bad_alloc(); // Overflow return index; } void BinaryRegistryWriter::addBinary (deUint32 index, const ProgramBinary& binary) { DE_ASSERT(binary.getFormat() == vk::PROGRAM_FORMAT_SPIRV); DE_ASSERT(findBinary(binary) == DE_NULL); ProgramBinary* const binaryClone = new ProgramBinary(binary); try { if (m_binaries.size() < (size_t)index+1) m_binaries.resize(index+1); DE_ASSERT(!m_binaries[index].binary); DE_ASSERT(m_binaries[index].referenceCount == 0); m_binaries[index].binary = binaryClone; // \note referenceCount is not incremented here } catch (...) { delete binaryClone; throw; } m_binaryHash.insert(binaryClone, index); } void BinaryRegistryWriter::write (void) const { writeToPath(m_dstPath); } void BinaryRegistryWriter::writeToPath (const std::string& dstPath) const { if (!de::FilePath(dstPath).exists()) de::createDirectoryAndParents(dstPath.c_str()); DE_ASSERT(m_binaries.size() <= 0xffffffffu); for (size_t binaryNdx = 0; binaryNdx < m_binaries.size(); ++binaryNdx) { const BinarySlot& slot = m_binaries[binaryNdx]; if (slot.referenceCount > 0) { DE_ASSERT(slot.binary); writeBinary(dstPath, (deUint32)binaryNdx, *slot.binary); } else { // Delete stale binary if such exists const std::string progPath = getProgramPath(dstPath, (deUint32)binaryNdx); if (de::FilePath(progPath).exists()) deDeleteFile(progPath.c_str()); } } // Write index { const de::FilePath indexPath = getIndexPath(dstPath); std::vector index; buildBinaryIndex(&index, m_binaryIndices.size(), !m_binaryIndices.empty() ? &m_binaryIndices[0] : DE_NULL); // Even in empty index there is always terminating node for the root group DE_ASSERT(!index.empty()); if (!de::FilePath(indexPath.getDirName()).exists()) de::createDirectoryAndParents(indexPath.getDirName().c_str()); { std::ofstream indexOut(indexPath.getPath(), std::ios_base::binary); if (!indexOut.is_open() || !indexOut.good()) throw tcu::InternalError(string("Failed to open program binary index file ") + indexPath.getPath()); indexOut.write((const char*)&index[0], index.size()*sizeof(BinaryIndexNode)); } } } // BinaryRegistryReader BinaryRegistryReader::BinaryRegistryReader (const tcu::Archive& archive, const std::string& srcPath) : m_archive (archive) , m_srcPath (srcPath) { } BinaryRegistryReader::~BinaryRegistryReader (void) { } ProgramBinary* BinaryRegistryReader::loadProgram (const ProgramIdentifier& id) const { if (!m_binaryIndex) { try { m_binaryIndex = BinaryIndexPtr(new BinaryIndexAccess(de::MovePtr(m_archive.getResource(getIndexPath(m_srcPath).c_str())))); } catch (const tcu::ResourceError& e) { throw ProgramNotFoundException(id, string("Failed to open binary index (") + e.what() + ")"); } } { const deUint32* indexPos = findBinaryIndex(m_binaryIndex.get(), id); if (indexPos) { const string fullPath = getProgramPath(m_srcPath, *indexPos); try { de::UniquePtr progRes (m_archive.getResource(fullPath.c_str())); const int progSize = progRes->getSize(); vector bytes (progSize); TCU_CHECK_INTERNAL(!bytes.empty()); progRes->read(&bytes[0], progSize); return new ProgramBinary(vk::PROGRAM_FORMAT_SPIRV, bytes.size(), &bytes[0]); } catch (const tcu::ResourceError& e) { throw ProgramNotFoundException(id, e.what()); } } else throw ProgramNotFoundException(id, "Program not found in index"); } } } // BinaryRegistryDetail } // vk