xref: /aosp_15_r20/external/deqp/external/vulkancts/framework/vulkan/vkBinaryRegistry.cpp (revision 35238bce31c2a825756842865a792f8cf7f89930)
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