GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 85.3% 122 / 0 / 143
Functions: 90.9% 10 / 0 / 11
Branches: 47.0% 119 / 0 / 253

src/typechecker/UnionManager.cpp
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 #include "UnionManager.h"
4
5 #include <SourceFile.h>
6 #include <ast/ASTNodes.h>
7 #include <exception/SemanticError.h>
8 #include <model/GenericType.h>
9 #include <model/Union.h>
10 #include <symboltablebuilder/Scope.h>
11 #include <typechecker/TypeMatcher.h>
12 #include <util/CodeLoc.h>
13 #include <util/Concurrency.h>
14 #include <util/CustomHashFunctions.h>
15
16 namespace spice::compiler {
17
18 // Static member initialization
19 std::unordered_map<uint64_t, Union *> UnionManager::lookupCache = {};
20 size_t UnionManager::lookupCacheHits = 0;
21 size_t UnionManager::lookupCacheMisses = 0;
22
23 60 Union *UnionManager::insert(Scope *insertScope, Union &spiceUnion, std::vector<Union *> *nodeUnionList) {
24 // Open a new manifestation list. Which gets filled by the substantiated manifestations of the union
25
3/6
✓ Branch 2 → 3 taken 60 times.
✗ Branch 2 → 20 not taken.
✓ Branch 3 → 4 taken 60 times.
✗ Branch 3 → 17 not taken.
✓ Branch 4 → 5 taken 60 times.
✗ Branch 4 → 15 not taken.
60 const std::string unionId = spiceUnion.name + ":" + spiceUnion.declNode->codeLoc.toPrettyLineAndColumn();
26
1/2
✓ Branch 8 → 9 taken 60 times.
✗ Branch 8 → 21 not taken.
60 insertScope->unions.emplace(unionId, UnionManifestationList());
27
28 // Save substantiation in declaration node
29
1/2
✓ Branch 10 → 11 taken 60 times.
✗ Branch 10 → 24 not taken.
60 Union *substantiation = insertSubstantiation(insertScope, spiceUnion, spiceUnion.declNode);
30
1/2
✓ Branch 11 → 12 taken 60 times.
✗ Branch 11 → 24 not taken.
60 nodeUnionList->push_back(substantiation);
31
32 60 return substantiation;
33 60 }
34
35 66 Union *UnionManager::insertSubstantiation(Scope *insertScope, Union &newManifestation, const ASTNode *declNode) {
36
1/2
✓ Branch 2 → 3 taken 66 times.
✗ Branch 2 → 42 not taken.
66 const std::string signature = newManifestation.getSignature();
37
38 #ifndef NDEBUG
39 // Make sure that the manifestation does not exist already
40
5/8
✓ Branch 3 → 4 taken 66 times.
✗ Branch 3 → 31 not taken.
✓ Branch 4 → 5 taken 66 times.
✗ Branch 4 → 31 not taken.
✓ Branch 5 → 6 taken 66 times.
✗ Branch 5 → 31 not taken.
✓ Branch 13 → 7 taken 66 times.
✓ Branch 13 → 14 taken 66 times.
132 for (const auto &val : insertScope->unions | std::views::values)
41
2/4
✓ Branch 8 → 9 taken 66 times.
✗ Branch 8 → 31 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 66 times.
66 assert(!val.contains(signature));
42 #endif
43
44 // Retrieve the matching manifestation list of the scope
45
3/6
✓ Branch 14 → 15 taken 66 times.
✗ Branch 14 → 37 not taken.
✓ Branch 15 → 16 taken 66 times.
✗ Branch 15 → 34 not taken.
✓ Branch 16 → 17 taken 66 times.
✗ Branch 16 → 32 not taken.
66 const std::string unionId = newManifestation.name + ":" + declNode->codeLoc.toPrettyLineAndColumn();
46
2/4
✓ Branch 19 → 20 taken 66 times.
✗ Branch 19 → 38 not taken.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 66 times.
66 assert(insertScope->unions.contains(unionId));
47
1/2
✓ Branch 22 → 23 taken 66 times.
✗ Branch 22 → 38 not taken.
66 UnionManifestationList &manifestationList = insertScope->unions.at(unionId);
48
49 // Add substantiated union
50 66 newManifestation.manifestationIndex = manifestationList.size();
51
1/2
✓ Branch 24 → 25 taken 66 times.
✗ Branch 24 → 38 not taken.
66 manifestationList.emplace(signature, newManifestation);
52
1/2
✓ Branch 25 → 26 taken 66 times.
✗ Branch 25 → 38 not taken.
132 return &manifestationList.at(signature);
53 66 }
54
55 /**
56 * Check if there is a union in this scope, fulfilling all given requirements and if found, return it.
57 * If more than one union matches the requirement, an error gets thrown
58 *
59 * @param matchScope Scope to match against
60 * @param qt Union name requirement
61 * @param reqTemplateTypes Template types to substantiate generic types
62 * @param node Instantiation AST node for printing error messages
63 * @return Matched union or nullptr
64 */
65 216 Union *UnionManager::match(Scope *matchScope, const std::string &qt, const QualTypeList &reqTemplateTypes, const ASTNode *node) {
66 // The IR generator lowers union types through QualType::getUnion, so this may run on multiple threads at once. The
67 // lock is recursive, because matching a union recurses into matching its field types.
68
1/2
✓ Branch 2 → 3 taken 216 times.
✗ Branch 2 → 194 not taken.
216 const ConditionalLock lock(symbolRegistryMutex);
69
70 // Do cache lookup
71 216 const uint64_t cacheKey = getCacheKey(matchScope, qt, reqTemplateTypes);
72
3/4
✓ Branch 4 → 5 taken 216 times.
✗ Branch 4 → 156 not taken.
✓ Branch 7 → 8 taken 158 times.
✓ Branch 7 → 10 taken 58 times.
216 if (const auto it = lookupCache.find(cacheKey); it != lookupCache.end()) {
73 158 lookupCacheHits++;
74 158 return it->second;
75 }
76 58 lookupCacheMisses++;
77
78 // Loop over union registry to find unions, that match the requirements of the instantiation
79 58 std::vector<Union *> matches;
80
2/2
✓ Branch 135 → 12 taken 54 times.
✓ Branch 135 → 136 taken 58 times.
112 for (auto &[unionId, manifestations] : matchScope->unions) {
81
2/2
✓ Branch 132 → 17 taken 62 times.
✓ Branch 132 → 133 taken 54 times.
116 for (const auto &[mangledName, presetUnion] : manifestations) {
82 // Skip generic and newly inserted substantiations to prevent double matching of a union
83
6/8
✓ Branch 20 → 21 taken 62 times.
✗ Branch 20 → 178 not taken.
✓ Branch 21 → 22 taken 54 times.
✓ Branch 21 → 23 taken 8 times.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 54 times.
✓ Branch 25 → 26 taken 8 times.
✓ Branch 25 → 27 taken 54 times.
62 if (presetUnion.isGenericSubstantiation() || presetUnion.isNewlyInserted)
84 56 continue;
85
86 // Copy the union to be able to substantiate types
87
1/2
✓ Branch 27 → 28 taken 54 times.
✗ Branch 27 → 178 not taken.
54 Union candidate = presetUnion;
88
89 // Check name requirement
90
1/2
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 54 times.
54 if (!matchName(candidate, qt))
91 break; // Leave the whole manifestation list, because all manifestations in this list have the same name
92
93 // Prepare mapping table from generic type name to concrete type
94 54 TypeMapping &typeMapping = candidate.typeMapping;
95 54 typeMapping.clear();
96
1/2
✓ Branch 33 → 34 taken 54 times.
✗ Branch 33 → 176 not taken.
54 typeMapping.reserve(candidate.templateTypes.size());
97
98 // Check template types requirement
99
2/4
✓ Branch 34 → 35 taken 54 times.
✗ Branch 34 → 176 not taken.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 54 times.
54 if (!matchTemplateTypes(candidate, reqTemplateTypes, typeMapping, node))
100 continue; // Leave this manifestation and continue with the next one
101
102 // Map field types from generic to concrete
103
1/2
✓ Branch 37 → 38 taken 54 times.
✗ Branch 37 → 176 not taken.
54 substantiateFieldTypes(candidate, typeMapping, node);
104
105 // We found a match! -> Set the actual candidate and its entry to used
106 54 candidate.used = true;
107 54 candidate.entry->used = true;
108
109 // Check if it needs to be substantiated
110
2/2
✓ Branch 39 → 40 taken 48 times.
✓ Branch 39 → 52 taken 6 times.
54 if (presetUnion.templateTypes.empty()) {
111
5/10
✓ Branch 40 → 41 taken 48 times.
✗ Branch 40 → 157 not taken.
✓ Branch 41 → 42 taken 48 times.
✗ Branch 41 → 46 not taken.
✓ Branch 42 → 43 taken 48 times.
✗ Branch 42 → 157 not taken.
✓ Branch 43 → 44 taken 48 times.
✗ Branch 43 → 157 not taken.
✓ Branch 44 → 45 taken 48 times.
✗ Branch 44 → 46 not taken.
48 assert(matchScope->unions.contains(unionId) && matchScope->unions.at(unionId).contains(mangledName));
112
2/4
✓ Branch 47 → 48 taken 48 times.
✗ Branch 47 → 157 not taken.
✓ Branch 48 → 49 taken 48 times.
✗ Branch 48 → 157 not taken.
48 Union *match = &matchScope->unions.at(unionId).at(mangledName);
113 48 match->used = true;
114
1/2
✓ Branch 49 → 50 taken 48 times.
✗ Branch 49 → 157 not taken.
48 matches.push_back(match);
115 48 continue; // Match was successful -> match the next union
116 48 }
117
118 // Check if we already have this manifestation and can simply re-use it
119
3/6
✓ Branch 52 → 53 taken 6 times.
✗ Branch 52 → 160 not taken.
✓ Branch 53 → 54 taken 6 times.
✗ Branch 53 → 158 not taken.
✗ Branch 57 → 58 not taken.
✓ Branch 57 → 62 taken 6 times.
6 if (const auto it = manifestations.find(candidate.getSignature()); it != manifestations.end()) {
120 it->second.used = true;
121 matches.push_back(&it->second);
122 break; // Leave the whole manifestation list to not double-match the manifestation
123 }
124
125 // Insert the substantiated version if required
126
1/2
✓ Branch 62 → 64 taken 6 times.
✗ Branch 62 → 176 not taken.
6 Union *substantiatedUnion = insertSubstantiation(matchScope, candidate, presetUnion.declNode);
127
2/4
✓ Branch 64 → 65 taken 6 times.
✗ Branch 64 → 176 not taken.
✓ Branch 65 → 66 taken 6 times.
✗ Branch 65 → 176 not taken.
6 substantiatedUnion->genericPreset = &matchScope->unions.at(unionId).at(mangledName);
128
2/4
✓ Branch 66 → 67 taken 6 times.
✗ Branch 66 → 176 not taken.
✓ Branch 67 → 68 taken 6 times.
✗ Branch 67 → 176 not taken.
6 substantiatedUnion->declNode->getUnionManifestations()->push_back(substantiatedUnion);
129 6 substantiatedUnion->isNewlyInserted = true; // To not iterate over it in the same matching
130
131 // Copy union entry
132
1/2
✓ Branch 68 → 69 taken 6 times.
✗ Branch 68 → 176 not taken.
6 const std::string newSignature = substantiatedUnion->getSignature();
133
1/2
✓ Branch 69 → 70 taken 6 times.
✗ Branch 69 → 174 not taken.
6 matchScope->lookupStrict(substantiatedUnion->name)->used = true;
134
1/2
✓ Branch 72 → 73 taken 6 times.
✗ Branch 72 → 174 not taken.
6 substantiatedUnion->entry = matchScope->symbolTable.copySymbol(substantiatedUnion->name, newSignature);
135
1/2
✗ Branch 73 → 74 not taken.
✓ Branch 73 → 75 taken 6 times.
6 assert(substantiatedUnion->entry != nullptr);
136
137 // Copy union scope
138
1/2
✓ Branch 75 → 76 taken 6 times.
✗ Branch 75 → 174 not taken.
6 const std::string &oldScopeName = presetUnion.getScopeName();
139
1/2
✓ Branch 76 → 77 taken 6 times.
✗ Branch 76 → 172 not taken.
6 const std::string &newScopeName = substantiatedUnion->getScopeName();
140
1/2
✓ Branch 77 → 78 taken 6 times.
✗ Branch 77 → 170 not taken.
6 substantiatedUnion->scope = matchScope->copyChildScope(oldScopeName, newScopeName);
141
1/2
✗ Branch 78 → 79 not taken.
✓ Branch 78 → 80 taken 6 times.
6 assert(substantiatedUnion->scope != nullptr);
142 6 substantiatedUnion->scope->isGenericScope = false;
143
144 // Attach the template types to the new union entry
145
1/2
✓ Branch 80 → 81 taken 6 times.
✗ Branch 80 → 166 not taken.
6 QualType entryType = substantiatedUnion->entry->getQualType()
146
2/4
✓ Branch 81 → 82 taken 6 times.
✗ Branch 81 → 165 not taken.
✓ Branch 82 → 83 taken 6 times.
✗ Branch 82 → 163 not taken.
12 .getWithTemplateTypes(substantiatedUnion->getTemplateTypes())
147
1/2
✓ Branch 83 → 84 taken 6 times.
✗ Branch 83 → 163 not taken.
6 .getWithBodyScope(substantiatedUnion->scope);
148
1/2
✓ Branch 85 → 86 taken 6 times.
✗ Branch 85 → 170 not taken.
6 substantiatedUnion->entry->updateType(entryType, true);
149
150 // Replace symbol types of field entries with concrete types
151
1/2
✗ Branch 86 → 87 not taken.
✓ Branch 86 → 88 taken 6 times.
6 assert(substantiatedUnion->scope != nullptr);
152 6 const size_t fieldCount = substantiatedUnion->fieldTypes.size();
153
1/2
✓ Branch 89 → 90 taken 6 times.
✗ Branch 89 → 170 not taken.
6 const size_t explicitFieldsStartIdx = substantiatedUnion->scope->getFieldCount() - fieldCount;
154
2/2
✓ Branch 118 → 91 taken 12 times.
✓ Branch 118 → 119 taken 6 times.
18 for (size_t i = 0; i < fieldCount; i++) {
155 // Replace field type with concrete template type
156
1/2
✓ Branch 91 → 92 taken 12 times.
✗ Branch 91 → 94 not taken.
12 SymbolTableEntry *fieldEntry = substantiatedUnion->scope->lookupField(explicitFieldsStartIdx + i);
157
3/6
✓ Branch 97 → 98 taken 12 times.
✗ Branch 97 → 101 not taken.
✓ Branch 98 → 99 taken 12 times.
✗ Branch 98 → 169 not taken.
✓ Branch 99 → 100 taken 12 times.
✗ Branch 99 → 101 not taken.
12 assert(fieldEntry != nullptr && fieldEntry->isField());
158
1/2
✓ Branch 102 → 103 taken 12 times.
✗ Branch 102 → 169 not taken.
12 QualType &fieldType = substantiatedUnion->fieldTypes.at(i);
159
1/2
✓ Branch 103 → 104 taken 12 times.
✗ Branch 103 → 169 not taken.
12 QualType baseType = fieldType.getBase();
160
161 // Set the body scope of fields that are of type <candidate-union>*
162
3/6
✓ Branch 104 → 105 taken 12 times.
✗ Branch 104 → 169 not taken.
✓ Branch 105 → 106 taken 12 times.
✗ Branch 105 → 169 not taken.
✗ Branch 106 → 107 not taken.
✓ Branch 106 → 110 taken 12 times.
12 if (baseType.matches(substantiatedUnion->entry->getQualType(), false, true, true))
163 fieldType = fieldType.replaceBaseType(baseType.getWithBodyScope(substantiatedUnion->scope));
164
165
1/2
✓ Branch 110 → 111 taken 12 times.
✗ Branch 110 → 169 not taken.
12 fieldEntry->updateType(fieldType, /*overwriteExistingType=*/true);
166
167 // Instantiate structs/unions nested by value
168
2/4
✓ Branch 111 → 112 taken 12 times.
✗ Branch 111 → 169 not taken.
✗ Branch 112 → 113 not taken.
✓ Branch 112 → 114 taken 12 times.
12 if (baseType.is(TY_STRUCT))
169 (void)baseType.getStruct(node);
170
2/4
✓ Branch 114 → 115 taken 12 times.
✗ Branch 114 → 169 not taken.
✗ Branch 115 → 116 not taken.
✓ Branch 115 → 117 taken 12 times.
12 else if (baseType.is(TY_UNION))
171 (void)baseType.getUnion(node);
172 }
173
174 // Add to matched unions
175
1/2
✓ Branch 119 → 120 taken 6 times.
✗ Branch 119 → 170 not taken.
6 matches.push_back(substantiatedUnion);
176
2/3
✓ Branch 125 → 126 taken 6 times.
✓ Branch 125 → 128 taken 48 times.
✗ Branch 125 → 129 not taken.
54 }
177 }
178
179 // If no matches were found, return a nullptr
180
2/2
✓ Branch 137 → 138 taken 4 times.
✓ Branch 137 → 139 taken 54 times.
58 if (matches.empty())
181 4 return nullptr;
182
183 // Check if more than one union matches the requirements
184
1/2
✗ Branch 140 → 141 not taken.
✓ Branch 140 → 149 taken 54 times.
54 if (matches.size() > 1)
185 throw SemanticError(node, UNION_AMBIGUITY, "Multiple unions match the requested signature");
186 54 Union *matchedUnion = matches.front();
187 54 matchedUnion->isNewlyInserted = false;
188
189 // Insert into cache
190
1/2
✓ Branch 150 → 151 taken 54 times.
✗ Branch 150 → 190 not taken.
54 lookupCache[cacheKey] = matchedUnion;
191
192 54 return matchedUnion;
193 216 }
194
195 /**
196 * Checks if the matching candidate fulfills the name requirement
197 *
198 * @param candidate Matching candidate union
199 * @param reqName Requested union name
200 * @return Fulfilled or not
201 */
202 54 bool UnionManager::matchName(const Union &candidate, const std::string &reqName) { return candidate.name == reqName; }
203
204 /**
205 * Checks if the matching candidate fulfills the template types requirement
206 *
207 * @param candidate Matching candidate union
208 * @param reqTemplateTypes Requested union template types
209 * @param typeMapping Generic type mapping
210 * @param node Instantiation AST node for printing error messages
211 * @return Fulfilled or not
212 */
213 54 bool UnionManager::matchTemplateTypes(Union &candidate, const QualTypeList &reqTemplateTypes, TypeMapping &typeMapping,
214 const ASTNode *node) {
215 // Check if the number of types match
216 54 const size_t typeCount = reqTemplateTypes.size();
217
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 54 times.
54 if (typeCount != candidate.templateTypes.size())
218 return false;
219
220 // Give the type matcher a way to retrieve instances of GenericType by their name
221 114 TypeMatcher::ResolverFct genericTypeResolver = [&](const std::string &genericTypeName) {
222 6 return getGenericTypeOfCandidateByName(candidate, genericTypeName);
223 54 };
224
225 // Loop over all template types
226
2/2
✓ Branch 17 → 8 taken 6 times.
✓ Branch 17 → 18 taken 54 times.
60 for (size_t i = 0; i < typeCount; i++) {
227
1/2
✓ Branch 8 → 9 taken 6 times.
✗ Branch 8 → 22 not taken.
6 const QualType &reqType = reqTemplateTypes.at(i);
228
1/2
✓ Branch 9 → 10 taken 6 times.
✗ Branch 9 → 22 not taken.
6 QualType &candidateType = candidate.templateTypes.at(i);
229
230 // Check if the requested template type matches the candidate template type. The type mapping may be extended
231
2/4
✓ Branch 10 → 11 taken 6 times.
✗ Branch 10 → 22 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 6 times.
6 if (!TypeMatcher::matchRequestedToCandidateType(candidateType, reqType, typeMapping, genericTypeResolver, false))
232 return false;
233
234 // Substantiate the candidate param type, based on the type mapping
235
2/4
✓ Branch 13 → 14 taken 6 times.
✗ Branch 13 → 22 not taken.
✓ Branch 14 → 15 taken 6 times.
✗ Branch 14 → 16 not taken.
6 if (candidateType.hasAnyGenericParts())
236
1/2
✓ Branch 15 → 16 taken 6 times.
✗ Branch 15 → 22 not taken.
6 TypeMatcher::substantiateTypeWithTypeMapping(candidateType, typeMapping, node);
237 }
238
239 54 return true;
240 54 }
241
242 /**
243 * Come up with the concrete field types, by applying the type mapping onto the generic field types
244 *
245 * @param candidate Candidate union
246 * @param typeMapping Generic type mapping
247 * @param node Instantiation AST node for printing error messages
248 */
249 54 void UnionManager::substantiateFieldTypes(Union &candidate, const TypeMapping &typeMapping, const ASTNode *node) {
250 // Loop over all explicit field types and substantiate the generic ones
251
2/2
✓ Branch 18 → 4 taken 288 times.
✓ Branch 18 → 19 taken 54 times.
396 for (QualType &fieldType : candidate.fieldTypes)
252
3/4
✓ Branch 6 → 7 taken 288 times.
✗ Branch 6 → 20 not taken.
✓ Branch 7 → 8 taken 6 times.
✓ Branch 7 → 9 taken 282 times.
288 if (fieldType.hasAnyGenericParts())
253
1/2
✓ Branch 8 → 9 taken 6 times.
✗ Branch 8 → 20 not taken.
6 TypeMatcher::substantiateTypeWithTypeMapping(fieldType, typeMapping, node);
254 54 }
255
256 /**
257 * Searches the candidate template types for a generic type object with a certain name and return it
258 *
259 * @param candidate Matching candidate union
260 * @param templateTypeName Template type name
261 * @return Generic type object
262 */
263 6 const GenericType *UnionManager::getGenericTypeOfCandidateByName(const Union &candidate, const std::string &templateTypeName) {
264
1/2
✓ Branch 22 → 4 taken 6 times.
✗ Branch 22 → 23 not taken.
12 for (const GenericType &templateType : candidate.templateTypes) {
265
2/4
✓ Branch 6 → 7 taken 6 times.
✗ Branch 6 → 25 not taken.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 6 times.
6 if (!templateType.is(TY_GENERIC))
266 continue;
267
2/4
✓ Branch 9 → 10 taken 6 times.
✗ Branch 9 → 25 not taken.
✓ Branch 11 → 12 taken 6 times.
✗ Branch 11 → 13 not taken.
6 if (templateType.getSubType() == templateTypeName)
268 6 return &templateType;
269 }
270 return nullptr;
271 }
272
273 /**
274 * Calculate the cache key for the union lookup cache
275 *
276 * @param scope Scope to match against
277 * @param name Union name requirement
278 * @param templateTypes Template types to substantiate generic types
279 * @return Cache key
280 */
281 216 uint64_t UnionManager::getCacheKey(const Scope *scope, const std::string &name, const QualTypeList &templateTypes) {
282 216 uint64_t hash = 0;
283 216 hashCombine64(hash, hashPointer(scope));
284 216 hashCombine64(hash, std::hash<std::string>{}(name));
285 216 hashCombine64(hash, hashVector(templateTypes));
286 216 return hash;
287 }
288
289 /**
290 * Clear the lookup cache
291 */
292 1292 void UnionManager::cleanup() {
293 1292 lookupCache.clear();
294 1292 lookupCacheHits = 0;
295 1292 lookupCacheMisses = 0;
296 1292 }
297
298 /**
299 * Dump usage statistics for the lookup cache
300 */
301 std::string UnionManager::dumpLookupCacheStatistics() {
302 std::stringstream stats;
303 stats << "UnionManager lookup cache statistics:" << std::endl;
304 stats << " lookup cache entries: " << lookupCache.size() << std::endl;
305 stats << " lookup cache hits: " << lookupCacheHits << std::endl;
306 stats << " lookup cache misses: " << lookupCacheMisses << std::endl;
307 return stats.str();
308 }
309
310 } // namespace spice::compiler
311