• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getProgramFileName(deUint32 index)51 string getProgramFileName (deUint32 index)
52 {
53 	return de::toString(tcu::toHex(index)) + ".spv";
54 }
55 
getProgramPath(const std::string & dirName,deUint32 index)56 string getProgramPath (const std::string& dirName, deUint32 index)
57 {
58 	return de::FilePath::join(dirName, getProgramFileName(index)).getPath();
59 }
60 
isHexChr(char c)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 
isProgramFileName(const std::string & name)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 
getProgramIndexFromName(const std::string & name)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 
getIndexPath(const std::string & dirName)104 string getIndexPath (const std::string& dirName)
105 {
106 	return de::FilePath::join(dirName, "index.bin").getPath();
107 }
108 
writeBinary(const ProgramBinary & binary,const std::string & dstPath)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 
writeBinary(const std::string & dstDir,deUint32 index,const ProgramBinary & binary)127 void writeBinary (const std::string& dstDir, deUint32 index, const ProgramBinary& binary)
128 {
129 	writeBinary(binary, getProgramPath(dstDir, index));
130 }
131 
readBinary(const std::string & srcPath)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 
binaryHash(const ProgramBinary * binary)155 deUint32 binaryHash (const ProgramBinary* binary)
156 {
157 	return deMemoryHash(binary->getBinary(), binary->getSize());
158 }
159 
binaryEqual(const ProgramBinary * a,const ProgramBinary * b)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 
getSearchPath(const ProgramIdentifier & id)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 
findBinaryIndex(BinaryIndexAccess * index,const ProgramIdentifier & id)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 
SparseIndexNodevk::BinaryRegistryDetail::__anon7b8dfcfc0111::SparseIndexNode225 	SparseIndexNode (deUint32 word_, deUint32 index_)
226 		: word	(word_)
227 		, index	(index_)
228 	{}
229 
SparseIndexNodevk::BinaryRegistryDetail::__anon7b8dfcfc0111::SparseIndexNode230 	SparseIndexNode (void)
231 		: word	(0)
232 		, index	(0)
233 	{}
234 
~SparseIndexNodevk::BinaryRegistryDetail::__anon7b8dfcfc0111::SparseIndexNode235 	~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)
isNullByteTerminated(deUint32 word)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 
addToSparseIndex(SparseIndexNode * group,const deUint32 * words,size_t numWords,deUint32 index)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.
normalizeSparseIndex(SparseIndexNode * group)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 
getIndexSize(const SparseIndexNode * group)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 
addAndCountNodes(BinaryIndexNode * index,deUint32 baseOffset,const SparseIndexNode * group)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 
buildFinalIndex(std::vector<BinaryIndexNode> * dst,const SparseIndexNode * root)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 
buildBinaryIndex(std::vector<BinaryIndexNode> * dst,size_t numEntries,const ProgramIdentifierIndex * entries)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 
BinaryIndexHash(void)393 BinaryIndexHash::BinaryIndexHash (void)
394 	: m_hash(BinaryIndexHashImpl_create(m_memPool.getRawPool()))
395 {
396 	if (!m_hash)
397 		throw std::bad_alloc();
398 }
399 
~BinaryIndexHash(void)400 BinaryIndexHash::~BinaryIndexHash (void)
401 {
402 }
403 
find(const ProgramBinary * binary) const404 deUint32* BinaryIndexHash::find (const ProgramBinary* binary) const
405 {
406 	return BinaryIndexHashImpl_find(m_hash, binary);
407 }
408 
insert(const ProgramBinary * binary,deUint32 index)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 
BinaryRegistryWriter(const std::string & dstPath)417 BinaryRegistryWriter::BinaryRegistryWriter (const std::string& dstPath)
418 	: m_dstPath(dstPath)
419 {
420 	if (de::FilePath(dstPath).exists())
421 		initFromPath(dstPath);
422 }
423 
~BinaryRegistryWriter(void)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 
initFromPath(const std::string & srcPath)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 
addProgram(const ProgramIdentifier & id,const ProgramBinary & binary)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 
findBinary(const ProgramBinary & binary) const468 deUint32* BinaryRegistryWriter::findBinary (const ProgramBinary& binary) const
469 {
470 	return m_binaryHash.find(&binary);
471 }
472 
getNextSlot(void) const473 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 
addBinary(deUint32 index,const ProgramBinary & binary)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 
write(void) const510 void BinaryRegistryWriter::write (void) const
511 {
512 	writeToPath(m_dstPath);
513 }
514 
writeToPath(const std::string & dstPath) const515 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 
BinaryRegistryReader(const tcu::Archive & archive,const std::string & srcPath)567 BinaryRegistryReader::BinaryRegistryReader (const tcu::Archive& archive, const std::string& srcPath)
568 	: m_archive	(archive)
569 	, m_srcPath	(srcPath)
570 {
571 }
572 
~BinaryRegistryReader(void)573 BinaryRegistryReader::~BinaryRegistryReader (void)
574 {
575 }
576 
loadProgram(const ProgramIdentifier & id) const577 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