Home | History | Annotate | Download | only in vulkan
      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