| /*------------------------------------------------------------------------- |
| * 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 <sstream> |
| #include <fstream> |
| #include <stdexcept> |
| #include <limits> |
| |
| 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<deUint8> 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<deUint32> 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<deUint32> words (numWords, 0u); |
| |
| deMemcpy(&words[0], combinedStr.c_str(), strLen); |
| |
| return words; |
| } |
| |
| const deUint32* findBinaryIndex (BinaryIndexAccess* index, const ProgramIdentifier& id) |
| { |
| const vector<deUint32> 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<SparseIndexNode*> 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<deUint32>::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<BinaryIndexNode>* 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<BinaryIndexNode>* dst, size_t numEntries, const ProgramIdentifierIndex* entries) |
| { |
| de::UniquePtr<SparseIndexNode> sparseIndex (new SparseIndexNode()); |
| |
| for (size_t ndx = 0; ndx < numEntries; ndx++) |
| { |
| const std::vector<deUint32> 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<ProgramBinary> 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<BinaryIndexNode> 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<tcu::Resource>(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<tcu::Resource> progRes (m_archive.getResource(fullPath.c_str())); |
| const int progSize = progRes->getSize(); |
| vector<deUint8> 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 |