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(uint32_t index)51 string getProgramFileName(uint32_t index)
52 {
53 return de::toString(tcu::toHex(index)) + ".spv";
54 }
55
getProgramPath(const std::string & dirName,uint32_t index)56 string getProgramPath(const std::string &dirName, uint32_t 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' || name[1] != 'x' || name[10] != '.' || name[11] != 's' || name[12] != 'p' || name[13] != 'v')
73 return false;
74
75 for (size_t ndx = 2; ndx < 10; ++ndx)
76 {
77 if (!isHexChr(name[ndx]))
78 return false;
79 }
80
81 return true;
82 }
83
getProgramIndexFromName(const std::string & name)84 uint32_t getProgramIndexFromName(const std::string &name)
85 {
86 DE_ASSERT(isProgramFileName(name));
87
88 uint32_t index = ~0u;
89 std::stringstream str;
90
91 str << std::hex << name.substr(2, 10);
92 str >> index;
93
94 DE_ASSERT(getProgramFileName(index) == name);
95
96 return index;
97 }
98
getIndexPath(const std::string & dirName)99 string getIndexPath(const std::string &dirName)
100 {
101 return de::FilePath::join(dirName, "index.bin").getPath();
102 }
103
writeBinary(const ProgramBinary & binary,const std::string & dstPath)104 void writeBinary(const ProgramBinary &binary, const std::string &dstPath)
105 {
106 const de::FilePath filePath(dstPath);
107
108 if (!de::FilePath(filePath.getDirName()).exists())
109 de::createDirectoryAndParents(filePath.getDirName().c_str());
110
111 {
112 std::ofstream out(dstPath.c_str(), std::ios_base::binary);
113
114 if (!out.is_open() || !out.good())
115 throw tcu::Exception("Failed to open " + dstPath);
116
117 out.write((const char *)binary.getBinary(), binary.getSize());
118 out.close();
119 }
120 }
121
writeBinary(const std::string & dstDir,uint32_t index,const ProgramBinary & binary)122 void writeBinary(const std::string &dstDir, uint32_t index, const ProgramBinary &binary)
123 {
124 writeBinary(binary, getProgramPath(dstDir, index));
125 }
126
readBinary(const std::string & srcPath)127 ProgramBinary *readBinary(const std::string &srcPath)
128 {
129 std::ifstream in(srcPath.c_str(), std::ios::binary | std::ios::ate);
130 const size_t size = (size_t)in.tellg();
131
132 if (!in.is_open() || !in.good())
133 throw tcu::Exception("Failed to open " + srcPath);
134
135 if (size == 0)
136 throw tcu::Exception("Malformed binary, size = 0");
137
138 in.seekg(0, std::ios::beg);
139
140 {
141 std::vector<uint8_t> bytes(size);
142
143 in.read((char *)&bytes[0], size);
144 DE_ASSERT(bytes[0] != 0);
145
146 return new ProgramBinary(vk::PROGRAM_FORMAT_SPIRV, bytes.size(), &bytes[0]);
147 }
148 }
149
binaryHash(const ProgramBinary * binary)150 uint32_t binaryHash(const ProgramBinary *binary)
151 {
152 return deMemoryHash(binary->getBinary(), binary->getSize());
153 }
154
binaryEqual(const ProgramBinary * a,const ProgramBinary * b)155 bool binaryEqual(const ProgramBinary *a, const ProgramBinary *b)
156 {
157 if (a->getSize() == b->getSize())
158 return deMemoryEqual(a->getBinary(), b->getBinary(), a->getSize());
159 else
160 return false;
161 }
162
getSearchPath(const ProgramIdentifier & id)163 std::vector<uint32_t> getSearchPath(const ProgramIdentifier &id)
164 {
165 const std::string combinedStr = id.testCasePath + '#' + id.programName;
166 const size_t strLen = combinedStr.size();
167 const size_t numWords = strLen / 4 + 1; // Must always end up with at least one 0 byte
168 vector<uint32_t> words(numWords, 0u);
169
170 deMemcpy(&words[0], combinedStr.c_str(), strLen);
171
172 return words;
173 }
174
findBinaryIndex(BinaryIndexAccess * index,const ProgramIdentifier & id)175 const uint32_t *findBinaryIndex(BinaryIndexAccess *index, const ProgramIdentifier &id)
176 {
177 const vector<uint32_t> words = getSearchPath(id);
178 size_t nodeNdx = 0;
179 size_t wordNdx = 0;
180
181 for (;;)
182 {
183 const BinaryIndexNode &curNode = (*index)[nodeNdx];
184
185 if (curNode.word == words[wordNdx])
186 {
187 if (wordNdx + 1 < words.size())
188 {
189 TCU_CHECK_INTERNAL((size_t)curNode.index < index->size());
190
191 nodeNdx = curNode.index;
192 wordNdx += 1;
193 }
194 else if (wordNdx + 1 == words.size())
195 return &curNode.index;
196 else
197 return DE_NULL;
198 }
199 else if (curNode.word != 0)
200 {
201 nodeNdx += 1;
202
203 // Index should always be null-terminated
204 TCU_CHECK_INTERNAL(nodeNdx < index->size());
205 }
206 else
207 return DE_NULL;
208 }
209
210 return DE_NULL;
211 }
212
213 //! Sparse index node used for final binary index construction
214 struct SparseIndexNode
215 {
216 uint32_t word;
217 uint32_t index;
218 std::vector<SparseIndexNode *> children;
219
SparseIndexNodevk::BinaryRegistryDetail::__anon985b26a90111::SparseIndexNode220 SparseIndexNode(uint32_t word_, uint32_t index_) : word(word_), index(index_)
221 {
222 }
223
SparseIndexNodevk::BinaryRegistryDetail::__anon985b26a90111::SparseIndexNode224 SparseIndexNode(void) : word(0), index(0)
225 {
226 }
227
~SparseIndexNodevk::BinaryRegistryDetail::__anon985b26a90111::SparseIndexNode228 ~SparseIndexNode(void)
229 {
230 for (size_t ndx = 0; ndx < children.size(); ndx++)
231 delete children[ndx];
232 }
233 };
234
235 #if defined(DE_DEBUG)
isNullByteTerminated(uint32_t word)236 bool isNullByteTerminated(uint32_t word)
237 {
238 uint8_t bytes[4];
239 deMemcpy(bytes, &word, sizeof(word));
240 return bytes[3] == 0;
241 }
242 #endif
243
addToSparseIndex(SparseIndexNode * group,const uint32_t * words,size_t numWords,uint32_t index)244 void addToSparseIndex(SparseIndexNode *group, const uint32_t *words, size_t numWords, uint32_t index)
245 {
246 const uint32_t curWord = words[0];
247 SparseIndexNode *child = DE_NULL;
248
249 for (size_t childNdx = 0; childNdx < group->children.size(); childNdx++)
250 {
251 if (group->children[childNdx]->word == curWord)
252 {
253 child = group->children[childNdx];
254 break;
255 }
256 }
257
258 DE_ASSERT(numWords > 1 || !child);
259
260 if (!child)
261 {
262 group->children.reserve(group->children.size() + 1);
263 group->children.push_back(new SparseIndexNode(curWord, numWords == 1 ? index : 0));
264
265 child = group->children.back();
266 }
267
268 if (numWords > 1)
269 addToSparseIndex(child, words + 1, numWords - 1, index);
270 else
271 DE_ASSERT(isNullByteTerminated(curWord));
272 }
273
274 // Prepares sparse index for finalization. Ensures that child with word = 0 is moved
275 // to the end, or one is added if there is no such child already.
normalizeSparseIndex(SparseIndexNode * group)276 void normalizeSparseIndex(SparseIndexNode *group)
277 {
278 int zeroChildPos = -1;
279
280 for (size_t childNdx = 0; childNdx < group->children.size(); childNdx++)
281 {
282 normalizeSparseIndex(group->children[childNdx]);
283
284 if (group->children[childNdx]->word == 0)
285 {
286 DE_ASSERT(zeroChildPos < 0);
287 zeroChildPos = (int)childNdx;
288 }
289 }
290
291 if (zeroChildPos >= 0)
292 {
293 // Move child with word = 0 to last
294 while (zeroChildPos != (int)group->children.size() - 1)
295 {
296 std::swap(group->children[zeroChildPos], group->children[zeroChildPos + 1]);
297 zeroChildPos += 1;
298 }
299 }
300 else if (!group->children.empty())
301 {
302 group->children.reserve(group->children.size() + 1);
303 group->children.push_back(new SparseIndexNode(0, 0));
304 }
305 }
306
getIndexSize(const SparseIndexNode * group)307 uint32_t getIndexSize(const SparseIndexNode *group)
308 {
309 size_t numNodes = group->children.size();
310
311 for (size_t childNdx = 0; childNdx < group->children.size(); childNdx++)
312 numNodes += getIndexSize(group->children[childNdx]);
313
314 DE_ASSERT(numNodes <= std::numeric_limits<uint32_t>::max());
315
316 return (uint32_t)numNodes;
317 }
318
addAndCountNodes(BinaryIndexNode * index,uint32_t baseOffset,const SparseIndexNode * group)319 uint32_t addAndCountNodes(BinaryIndexNode *index, uint32_t baseOffset, const SparseIndexNode *group)
320 {
321 const uint32_t numLocalNodes = (uint32_t)group->children.size();
322 uint32_t curOffset = numLocalNodes;
323
324 // Must be normalized prior to construction of final index
325 DE_ASSERT(group->children.empty() || group->children.back()->word == 0);
326
327 for (size_t childNdx = 0; childNdx < numLocalNodes; childNdx++)
328 {
329 const SparseIndexNode *child = group->children[childNdx];
330 const uint32_t subtreeSize = addAndCountNodes(index + curOffset, baseOffset + curOffset, child);
331
332 index[childNdx].word = child->word;
333
334 if (subtreeSize == 0)
335 index[childNdx].index = child->index;
336 else
337 {
338 DE_ASSERT(child->index == 0);
339 index[childNdx].index = baseOffset + curOffset;
340 }
341
342 curOffset += subtreeSize;
343 }
344
345 return curOffset;
346 }
347
buildFinalIndex(std::vector<BinaryIndexNode> * dst,const SparseIndexNode * root)348 void buildFinalIndex(std::vector<BinaryIndexNode> *dst, const SparseIndexNode *root)
349 {
350 const uint32_t indexSize = getIndexSize(root);
351
352 if (indexSize > 0)
353 {
354 dst->resize(indexSize);
355 addAndCountNodes(&(*dst)[0], 0, root);
356 }
357 else
358 {
359 // Generate empty index
360 dst->resize(1);
361 (*dst)[0].word = 0u;
362 (*dst)[0].index = 0u;
363 }
364 }
365
buildBinaryIndex(std::vector<BinaryIndexNode> * dst,size_t numEntries,const ProgramIdentifierIndex * entries)366 void buildBinaryIndex(std::vector<BinaryIndexNode> *dst, size_t numEntries, const ProgramIdentifierIndex *entries)
367 {
368 de::UniquePtr<SparseIndexNode> sparseIndex(new SparseIndexNode());
369
370 for (size_t ndx = 0; ndx < numEntries; ndx++)
371 {
372 const std::vector<uint32_t> searchPath = getSearchPath(entries[ndx].id);
373 addToSparseIndex(sparseIndex.get(), &searchPath[0], searchPath.size(), entries[ndx].index);
374 }
375
376 normalizeSparseIndex(sparseIndex.get());
377 buildFinalIndex(dst, sparseIndex.get());
378 }
379
380 } // namespace
381
382 // BinaryIndexHash
383
384 DE_IMPLEMENT_POOL_HASH(BinaryIndexHashImpl, const ProgramBinary *, uint32_t, binaryHash, binaryEqual);
385
BinaryIndexHash(void)386 BinaryIndexHash::BinaryIndexHash(void) : m_hash(BinaryIndexHashImpl_create(m_memPool.getRawPool()))
387 {
388 if (!m_hash)
389 throw std::bad_alloc();
390 }
391
~BinaryIndexHash(void)392 BinaryIndexHash::~BinaryIndexHash(void)
393 {
394 }
395
find(const ProgramBinary * binary) const396 uint32_t *BinaryIndexHash::find(const ProgramBinary *binary) const
397 {
398 return BinaryIndexHashImpl_find(m_hash, binary);
399 }
400
insert(const ProgramBinary * binary,uint32_t index)401 void BinaryIndexHash::insert(const ProgramBinary *binary, uint32_t index)
402 {
403 if (!BinaryIndexHashImpl_insert(m_hash, binary, index))
404 throw std::bad_alloc();
405 }
406
407 // BinaryRegistryWriter
408
BinaryRegistryWriter(const std::string & dstPath)409 BinaryRegistryWriter::BinaryRegistryWriter(const std::string &dstPath) : m_dstPath(dstPath)
410 {
411 if (de::FilePath(dstPath).exists())
412 initFromPath(dstPath);
413 }
414
~BinaryRegistryWriter(void)415 BinaryRegistryWriter::~BinaryRegistryWriter(void)
416 {
417 for (BinaryVector::const_iterator binaryIter = m_binaries.begin(); binaryIter != m_binaries.end(); ++binaryIter)
418 delete binaryIter->binary;
419 }
420
initFromPath(const std::string & srcPath)421 void BinaryRegistryWriter::initFromPath(const std::string &srcPath)
422 {
423 DE_ASSERT(m_binaries.empty());
424
425 for (de::DirectoryIterator iter(srcPath); iter.hasItem(); iter.next())
426 {
427 const de::FilePath path = iter.getItem();
428 const std::string baseName = path.getBaseName();
429
430 if (isProgramFileName(baseName))
431 {
432 const uint32_t index = getProgramIndexFromName(baseName);
433 const de::UniquePtr<ProgramBinary> binary(readBinary(path.getPath()));
434
435 addBinary(index, *binary);
436 // \note referenceCount is left to 0 and will only be incremented
437 // if binary is reused (added via addProgram()).
438 }
439 }
440 }
441
addProgram(const ProgramIdentifier & id,const ProgramBinary & binary)442 void BinaryRegistryWriter::addProgram(const ProgramIdentifier &id, const ProgramBinary &binary)
443 {
444 const uint32_t *const indexPtr = findBinary(binary);
445 uint32_t index = indexPtr ? *indexPtr : ~0u;
446
447 if (!indexPtr)
448 {
449 index = getNextSlot();
450 addBinary(index, binary);
451 }
452
453 m_binaries[index].referenceCount += 1;
454 m_binaryIndices.push_back(ProgramIdentifierIndex(id, index));
455 }
456
findBinary(const ProgramBinary & binary) const457 uint32_t *BinaryRegistryWriter::findBinary(const ProgramBinary &binary) const
458 {
459 return m_binaryHash.find(&binary);
460 }
461
getNextSlot(void) const462 uint32_t BinaryRegistryWriter::getNextSlot(void) const
463 {
464 const uint32_t index = (uint32_t)m_binaries.size();
465
466 if ((size_t)index != m_binaries.size())
467 throw std::bad_alloc(); // Overflow
468
469 return index;
470 }
471
addBinary(uint32_t index,const ProgramBinary & binary)472 void BinaryRegistryWriter::addBinary(uint32_t index, const ProgramBinary &binary)
473 {
474 DE_ASSERT(binary.getFormat() == vk::PROGRAM_FORMAT_SPIRV);
475 DE_ASSERT(findBinary(binary) == DE_NULL);
476
477 ProgramBinary *const binaryClone = new ProgramBinary(binary);
478
479 try
480 {
481 if (m_binaries.size() < (size_t)index + 1)
482 m_binaries.resize(index + 1);
483
484 DE_ASSERT(!m_binaries[index].binary);
485 DE_ASSERT(m_binaries[index].referenceCount == 0);
486
487 m_binaries[index].binary = binaryClone;
488 // \note referenceCount is not incremented here
489 }
490 catch (...)
491 {
492 delete binaryClone;
493 throw;
494 }
495
496 m_binaryHash.insert(binaryClone, index);
497 }
498
write(void) const499 void BinaryRegistryWriter::write(void) const
500 {
501 writeToPath(m_dstPath);
502 }
503
writeToPath(const std::string & dstPath) const504 void BinaryRegistryWriter::writeToPath(const std::string &dstPath) const
505 {
506 if (!de::FilePath(dstPath).exists())
507 de::createDirectoryAndParents(dstPath.c_str());
508
509 DE_ASSERT(m_binaries.size() <= 0xffffffffu);
510 for (size_t binaryNdx = 0; binaryNdx < m_binaries.size(); ++binaryNdx)
511 {
512 const BinarySlot &slot = m_binaries[binaryNdx];
513
514 if (slot.referenceCount > 0)
515 {
516 DE_ASSERT(slot.binary);
517 writeBinary(dstPath, (uint32_t)binaryNdx, *slot.binary);
518 }
519 else
520 {
521 // Delete stale binary if such exists
522 const std::string progPath = getProgramPath(dstPath, (uint32_t)binaryNdx);
523
524 if (de::FilePath(progPath).exists())
525 deDeleteFile(progPath.c_str());
526 }
527 }
528
529 // Write index
530 {
531 const de::FilePath indexPath = getIndexPath(dstPath);
532 std::vector<BinaryIndexNode> index;
533
534 buildBinaryIndex(&index, m_binaryIndices.size(), !m_binaryIndices.empty() ? &m_binaryIndices[0] : DE_NULL);
535
536 // Even in empty index there is always terminating node for the root group
537 DE_ASSERT(!index.empty());
538
539 if (!de::FilePath(indexPath.getDirName()).exists())
540 de::createDirectoryAndParents(indexPath.getDirName().c_str());
541
542 {
543 std::ofstream indexOut(indexPath.getPath(), std::ios_base::binary);
544
545 if (!indexOut.is_open() || !indexOut.good())
546 throw tcu::InternalError(string("Failed to open program binary index file ") + indexPath.getPath());
547
548 indexOut.write((const char *)&index[0], index.size() * sizeof(BinaryIndexNode));
549 }
550 }
551 }
552
553 // BinaryRegistryReader
554
BinaryRegistryReader(const tcu::Archive & archive,const std::string & srcPath)555 BinaryRegistryReader::BinaryRegistryReader(const tcu::Archive &archive, const std::string &srcPath)
556 : m_archive(archive)
557 , m_srcPath(srcPath)
558 {
559 }
560
~BinaryRegistryReader(void)561 BinaryRegistryReader::~BinaryRegistryReader(void)
562 {
563 }
564
loadProgram(const ProgramIdentifier & id) const565 ProgramBinary *BinaryRegistryReader::loadProgram(const ProgramIdentifier &id) const
566 {
567 if (!m_binaryIndex)
568 {
569 try
570 {
571 m_binaryIndex = BinaryIndexPtr(new BinaryIndexAccess(
572 de::MovePtr<tcu::Resource>(m_archive.getResource(getIndexPath(m_srcPath).c_str()))));
573 }
574 catch (const tcu::ResourceError &e)
575 {
576 throw ProgramNotFoundException(id, string("Failed to open binary index (") + e.what() + ")");
577 }
578 }
579
580 {
581 const uint32_t *indexPos = findBinaryIndex(m_binaryIndex.get(), id);
582
583 if (indexPos)
584 {
585 const string fullPath = getProgramPath(m_srcPath, *indexPos);
586
587 try
588 {
589 de::UniquePtr<tcu::Resource> progRes(m_archive.getResource(fullPath.c_str()));
590 const int progSize = progRes->getSize();
591 vector<uint8_t> bytes(progSize);
592
593 TCU_CHECK_INTERNAL(!bytes.empty());
594
595 progRes->read(&bytes[0], progSize);
596
597 return new ProgramBinary(vk::PROGRAM_FORMAT_SPIRV, bytes.size(), &bytes[0]);
598 }
599 catch (const tcu::ResourceError &e)
600 {
601 throw ProgramNotFoundException(id, e.what());
602 }
603 }
604 else
605 throw ProgramNotFoundException(id, "Program not found in index");
606 }
607 }
608
609 } // namespace BinaryRegistryDetail
610 } // namespace vk
611