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