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 "deString.h"
30 #include "deInt32.h"
31 
32 #include <sstream>
33 #include <fstream>
34 #include <stdexcept>
35 #include <limits>
36 
37 namespace vk
38 {
39 namespace BinaryRegistryDetail
40 {
41 
42 using std::string;
43 using std::vector;
44 
45 namespace
46 {
47 
getProgramPath(const std::string & dirName,deUint32 index)48 string getProgramPath (const std::string& dirName, deUint32 index)
49 {
50 	return de::FilePath::join(dirName, de::toString(tcu::toHex(index)) + ".spv").getPath();
51 }
52 
getIndexPath(const std::string & dirName)53 string getIndexPath (const std::string& dirName)
54 {
55 	return de::FilePath::join(dirName, "index.bin").getPath();
56 }
57 
writeBinary(const std::string & dstDir,deUint32 index,const ProgramBinary & binary)58 void writeBinary (const std::string& dstDir, deUint32 index, const ProgramBinary& binary)
59 {
60 	const de::FilePath	fullPath	= getProgramPath(dstDir, index);
61 
62 	if (!de::FilePath(fullPath.getDirName()).exists())
63 		de::createDirectoryAndParents(fullPath.getDirName().c_str());
64 
65 	{
66 		std::ofstream	out		(fullPath.getPath(), std::ios_base::binary);
67 
68 		if (!out.is_open() || !out.good())
69 			throw tcu::Exception("Failed to open " + string(fullPath.getPath()));
70 
71 		out.write((const char*)binary.getBinary(), binary.getSize());
72 		out.close();
73 	}
74 }
75 
binaryHash(const ProgramBinary * binary)76 deUint32 binaryHash (const ProgramBinary* binary)
77 {
78 	return deMemoryHash(binary->getBinary(), binary->getSize());
79 }
80 
binaryEqual(const ProgramBinary * a,const ProgramBinary * b)81 deBool binaryEqual (const ProgramBinary* a, const ProgramBinary* b)
82 {
83 	if (a->getSize() == b->getSize())
84 		return deMemoryEqual(a->getBinary(), b->getBinary(), a->getSize());
85 	else
86 		return DE_FALSE;
87 }
88 
getSearchPath(const ProgramIdentifier & id)89 std::vector<deUint32> getSearchPath (const ProgramIdentifier& id)
90 {
91 	const std::string	combinedStr		= id.testCasePath + '#' + id.programName;
92 	const size_t		strLen			= combinedStr.size();
93 	const size_t		numWords		= strLen/4 + 1;		// Must always end up with at least one 0 byte
94 	vector<deUint32>	words			(numWords, 0u);
95 
96 	deMemcpy(&words[0], combinedStr.c_str(), strLen);
97 
98 	return words;
99 }
100 
findBinaryIndex(BinaryIndexAccess * index,const ProgramIdentifier & id)101 const deUint32* findBinaryIndex (BinaryIndexAccess* index, const ProgramIdentifier& id)
102 {
103 	const vector<deUint32>	words	= getSearchPath(id);
104 	size_t					nodeNdx	= 0;
105 	size_t					wordNdx	= 0;
106 
107 	for (;;)
108 	{
109 		const BinaryIndexNode&	curNode	= (*index)[nodeNdx];
110 
111 		if (curNode.word == words[wordNdx])
112 		{
113 			if (wordNdx+1 < words.size())
114 			{
115 				TCU_CHECK_INTERNAL((size_t)curNode.index < index->size());
116 
117 				nodeNdx  = curNode.index;
118 				wordNdx	+= 1;
119 			}
120 			else if (wordNdx+1 == words.size())
121 				return &curNode.index;
122 			else
123 				return DE_NULL;
124 		}
125 		else if (curNode.word != 0)
126 		{
127 			nodeNdx += 1;
128 
129 			// Index should always be null-terminated
130 			TCU_CHECK_INTERNAL(nodeNdx < index->size());
131 		}
132 		else
133 			return DE_NULL;
134 	}
135 
136 	return DE_NULL;
137 }
138 
139 //! Sparse index node used for final binary index construction
140 struct SparseIndexNode
141 {
142 	deUint32						word;
143 	deUint32						index;
144 	std::vector<SparseIndexNode*>	children;
145 
SparseIndexNodevk::BinaryRegistryDetail::__anonf8028af30111::SparseIndexNode146 	SparseIndexNode (deUint32 word_, deUint32 index_)
147 		: word	(word_)
148 		, index	(index_)
149 	{}
150 
SparseIndexNodevk::BinaryRegistryDetail::__anonf8028af30111::SparseIndexNode151 	SparseIndexNode (void)
152 		: word	(0)
153 		, index	(0)
154 	{}
155 
~SparseIndexNodevk::BinaryRegistryDetail::__anonf8028af30111::SparseIndexNode156 	~SparseIndexNode (void)
157 	{
158 		for (size_t ndx = 0; ndx < children.size(); ndx++)
159 			delete children[ndx];
160 	}
161 };
162 
163 #if defined(DE_DEBUG)
isNullByteTerminated(deUint32 word)164 bool isNullByteTerminated (deUint32 word)
165 {
166 	deUint8 bytes[4];
167 	deMemcpy(bytes, &word, sizeof(word));
168 	return bytes[3] == 0;
169 }
170 #endif
171 
addToSparseIndex(SparseIndexNode * group,const deUint32 * words,size_t numWords,deUint32 index)172 void addToSparseIndex (SparseIndexNode* group, const deUint32* words, size_t numWords, deUint32 index)
173 {
174 	const deUint32		curWord	= words[0];
175 	SparseIndexNode*	child	= DE_NULL;
176 
177 	for (size_t childNdx = 0; childNdx < group->children.size(); childNdx++)
178 	{
179 		if (group->children[childNdx]->word == curWord)
180 		{
181 			child = group->children[childNdx];
182 			break;
183 		}
184 	}
185 
186 	DE_ASSERT(numWords > 1 || !child);
187 
188 	if (!child)
189 	{
190 		group->children.reserve(group->children.size()+1);
191 		group->children.push_back(new SparseIndexNode(curWord, numWords == 1 ? index : 0));
192 
193 		child = group->children.back();
194 	}
195 
196 	if (numWords > 1)
197 		addToSparseIndex(child, words+1, numWords-1, index);
198 	else
199 		DE_ASSERT(isNullByteTerminated(curWord));
200 }
201 
202 // Prepares sparse index for finalization. Ensures that child with word = 0 is moved
203 // to the end, or one is added if there is no such child already.
normalizeSparseIndex(SparseIndexNode * group)204 void normalizeSparseIndex (SparseIndexNode* group)
205 {
206 	int		zeroChildPos	= -1;
207 
208 	for (size_t childNdx = 0; childNdx < group->children.size(); childNdx++)
209 	{
210 		normalizeSparseIndex(group->children[childNdx]);
211 
212 		if (group->children[childNdx]->word == 0)
213 		{
214 			DE_ASSERT(zeroChildPos < 0);
215 			zeroChildPos = (int)childNdx;
216 		}
217 	}
218 
219 	if (zeroChildPos >= 0)
220 	{
221 		// Move child with word = 0 to last
222 		while (zeroChildPos != (int)group->children.size()-1)
223 		{
224 			std::swap(group->children[zeroChildPos], group->children[zeroChildPos+1]);
225 			zeroChildPos += 1;
226 		}
227 	}
228 	else if (!group->children.empty())
229 	{
230 		group->children.reserve(group->children.size()+1);
231 		group->children.push_back(new SparseIndexNode(0, 0));
232 	}
233 }
234 
getIndexSize(const SparseIndexNode * group)235 deUint32 getIndexSize (const SparseIndexNode* group)
236 {
237 	size_t	numNodes	= group->children.size();
238 
239 	for (size_t childNdx = 0; childNdx < group->children.size(); childNdx++)
240 		numNodes += getIndexSize(group->children[childNdx]);
241 
242 	DE_ASSERT(numNodes <= std::numeric_limits<deUint32>::max());
243 
244 	return (deUint32)numNodes;
245 }
246 
addAndCountNodes(BinaryIndexNode * index,deUint32 baseOffset,const SparseIndexNode * group)247 deUint32 addAndCountNodes (BinaryIndexNode* index, deUint32 baseOffset, const SparseIndexNode* group)
248 {
249 	const deUint32	numLocalNodes	= (deUint32)group->children.size();
250 	deUint32		curOffset		= numLocalNodes;
251 
252 	// Must be normalized prior to construction of final index
253 	DE_ASSERT(group->children.empty() || group->children.back()->word == 0);
254 
255 	for (size_t childNdx = 0; childNdx < numLocalNodes; childNdx++)
256 	{
257 		const SparseIndexNode*	child		= group->children[childNdx];
258 		const deUint32			subtreeSize	= addAndCountNodes(index+curOffset, baseOffset+curOffset, child);
259 
260 		index[childNdx].word = child->word;
261 
262 		if (subtreeSize == 0)
263 			index[childNdx].index = child->index;
264 		else
265 		{
266 			DE_ASSERT(child->index == 0);
267 			index[childNdx].index = baseOffset+curOffset;
268 		}
269 
270 		curOffset += subtreeSize;
271 	}
272 
273 	return curOffset;
274 }
275 
buildFinalIndex(std::vector<BinaryIndexNode> * dst,const SparseIndexNode * root)276 void buildFinalIndex (std::vector<BinaryIndexNode>* dst, const SparseIndexNode* root)
277 {
278 	const deUint32	indexSize	= getIndexSize(root);
279 
280 	if (indexSize > 0)
281 	{
282 		dst->resize(indexSize);
283 		addAndCountNodes(&(*dst)[0], 0, root);
284 	}
285 	else
286 	{
287 		// Generate empty index
288 		dst->resize(1);
289 		(*dst)[0].word	= 0u;
290 		(*dst)[0].index	= 0u;
291 	}
292 }
293 
294 } // anonymous
295 
296 // BinaryRegistryWriter
297 
298 DE_IMPLEMENT_POOL_HASH(BinaryHash, const ProgramBinary*, deUint32, binaryHash, binaryEqual);
299 
BinaryRegistryWriter(const std::string & dstPath)300 BinaryRegistryWriter::BinaryRegistryWriter (const std::string& dstPath)
301 	: m_dstPath			(dstPath)
302 	, m_binaryIndexMap	(DE_NULL)
303 {
304 	m_binaryIndexMap = BinaryHash_create(m_memPool.getRawPool());
305 
306 	if (!m_binaryIndexMap)
307 		throw std::bad_alloc();
308 }
309 
~BinaryRegistryWriter(void)310 BinaryRegistryWriter::~BinaryRegistryWriter (void)
311 {
312 	for (BinaryVector::const_iterator binaryIter = m_compactedBinaries.begin();
313 		 binaryIter != m_compactedBinaries.end();
314 		 ++binaryIter)
315 		delete *binaryIter;
316 }
317 
storeProgram(const ProgramIdentifier & id,const ProgramBinary & binary)318 void BinaryRegistryWriter::storeProgram (const ProgramIdentifier& id, const ProgramBinary& binary)
319 {
320 	const deUint32* const	indexPtr	= BinaryHash_find(m_binaryIndexMap, &binary);
321 	deUint32				index		= indexPtr ? *indexPtr : ~0u;
322 
323 	DE_ASSERT(binary.getFormat() == vk::PROGRAM_FORMAT_SPIRV);
324 
325 	if (!indexPtr)
326 	{
327 		ProgramBinary* const	binaryClone		= new ProgramBinary(binary);
328 
329 		try
330 		{
331 			index = (deUint32)m_compactedBinaries.size();
332 			m_compactedBinaries.push_back(binaryClone);
333 		}
334 		catch (...)
335 		{
336 			delete binaryClone;
337 			throw;
338 		}
339 
340 		writeBinary(m_dstPath, index, binary);
341 
342 		if (!BinaryHash_insert(m_binaryIndexMap, binaryClone, index))
343 			throw std::bad_alloc();
344 	}
345 
346 	DE_ASSERT((size_t)index < m_compactedBinaries.size());
347 
348 	m_binaryIndices.push_back(BinaryIndex(id, index));
349 }
350 
writeIndex(void) const351 void BinaryRegistryWriter::writeIndex (void) const
352 {
353 	const de::FilePath				indexPath	= getIndexPath(m_dstPath);
354 	std::vector<BinaryIndexNode>	index;
355 
356 	{
357 		de::UniquePtr<SparseIndexNode>	sparseIndex	(new SparseIndexNode());
358 
359 		for (size_t progNdx = 0; progNdx < m_binaryIndices.size(); progNdx++)
360 		{
361 			const std::vector<deUint32>	searchPath	= getSearchPath(m_binaryIndices[progNdx].id);
362 			addToSparseIndex(sparseIndex.get(), &searchPath[0], searchPath.size(), m_binaryIndices[progNdx].index);
363 		}
364 
365 		normalizeSparseIndex(sparseIndex.get());
366 		buildFinalIndex(&index, sparseIndex.get());
367 	}
368 
369 	// Even in empty index there is always terminating node for the root group
370 	DE_ASSERT(!index.empty());
371 
372 	if (!de::FilePath(indexPath.getDirName()).exists())
373 		de::createDirectoryAndParents(indexPath.getDirName().c_str());
374 
375 	{
376 		std::ofstream indexOut(indexPath.getPath(), std::ios_base::binary);
377 
378 		if (!indexOut.is_open() || !indexOut.good())
379 			throw tcu::InternalError(string("Failed to open program binary index file ") + indexPath.getPath());
380 
381 		indexOut.write((const char*)&index[0], index.size()*sizeof(BinaryIndexNode));
382 	}
383 }
384 
385 // BinaryRegistryReader
386 
BinaryRegistryReader(const tcu::Archive & archive,const std::string & srcPath)387 BinaryRegistryReader::BinaryRegistryReader (const tcu::Archive& archive, const std::string& srcPath)
388 	: m_archive	(archive)
389 	, m_srcPath	(srcPath)
390 {
391 }
392 
~BinaryRegistryReader(void)393 BinaryRegistryReader::~BinaryRegistryReader (void)
394 {
395 }
396 
loadProgram(const ProgramIdentifier & id) const397 ProgramBinary* BinaryRegistryReader::loadProgram (const ProgramIdentifier& id) const
398 {
399 	if (!m_binaryIndex)
400 	{
401 		try
402 		{
403 			m_binaryIndex = BinaryIndexPtr(new BinaryIndexAccess(de::MovePtr<tcu::Resource>(m_archive.getResource(getIndexPath(m_srcPath).c_str()))));
404 		}
405 		catch (const tcu::ResourceError& e)
406 		{
407 			throw ProgramNotFoundException(id, string("Failed to open binary index (") + e.what() + ")");
408 		}
409 	}
410 
411 	{
412 		const deUint32*	indexPos	= findBinaryIndex(m_binaryIndex.get(), id);
413 
414 		if (indexPos)
415 		{
416 			const string	fullPath	= getProgramPath(m_srcPath, *indexPos);
417 
418 			try
419 			{
420 				de::UniquePtr<tcu::Resource>	progRes		(m_archive.getResource(fullPath.c_str()));
421 				const int						progSize	= progRes->getSize();
422 				vector<deUint8>					bytes		(progSize);
423 
424 				TCU_CHECK_INTERNAL(!bytes.empty());
425 
426 				progRes->read(&bytes[0], progSize);
427 
428 				return new ProgramBinary(vk::PROGRAM_FORMAT_SPIRV, bytes.size(), &bytes[0]);
429 			}
430 			catch (const tcu::ResourceError& e)
431 			{
432 				throw ProgramNotFoundException(id, e.what());
433 			}
434 		}
435 		else
436 			throw ProgramNotFoundException(id, "Program not found in index");
437 	}
438 }
439 
440 } // BinaryRegistryDetail
441 } // vk
442