typechecker/struct-manager.spice
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Copyright (c) 2021-2026 ChilliBits. All rights reserved. | ||
| 2 | |||
| 3 | // Std imports | ||
| 4 | import "std/data/map"; | ||
| 5 | import "std/data/unordered-map"; | ||
| 6 | import "std/data/vector"; | ||
| 7 | import "std/data/pair"; | ||
| 8 | import "std/math/hash"; | ||
| 9 | |||
| 10 | // Own imports | ||
| 11 | import "@/exception/error-manager"; | ||
| 12 | import "@/exception/semantic-error"; | ||
| 13 | import "@/ast/ast-nodes"; | ||
| 14 | import "@/model/struct"; | ||
| 15 | import "@/model/interface"; | ||
| 16 | import "@/model/generic-type"; | ||
| 17 | import "@/symboltablebuilder/qual-type"; | ||
| 18 | import "@/symboltablebuilder/super-type"; | ||
| 19 | import "@/symboltablebuilder/scope"; | ||
| 20 | import "@/symboltablebuilder/symbol-table-entry"; | ||
| 21 | import "@/source-file"; | ||
| 22 | import "@/typechecker/type-checker"; | ||
| 23 | import "@/typechecker/type-matcher"; | ||
| 24 | import "@/util/custom-hash-functions"; | ||
| 25 | import "@/util/lookup-cache"; | ||
| 26 | import "@/util/concurrency"; | ||
| 27 | |||
| 28 | // Type aliases | ||
| 29 | public type StructManifestationList alias UnorderedMap</*mangledName=*/String, Struct>; | ||
| 30 | public type StructRegistry alias Map</*structId=*/String, /*manifestationList=*/StructManifestationList>; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Insert a struct declaration into the given scope | ||
| 34 | * | ||
| 35 | * @param insertScope Scope to insert the struct into | ||
| 36 | * @param spiceStruct Struct to insert | ||
| 37 | * @param nodeStructList List to collect the inserted manifestation into (owned by the declaration node) | ||
| 38 | * @return Inserted struct manifestation | ||
| 39 | */ | ||
| 40 | 3218 | #[ignoreUnusedReturnValue] | |
| 41 | 3218 | public f<Struct*> insertStruct(Scope* insertScope, Struct& spiceStruct, Vector<Struct*>* nodeStructList) { | |
| 42 | // Open a new manifestation list. Which gets filled by the substantiated manifestations of the struct | ||
| 43 | 3218 | const String structId = spiceStruct.name + ":" + spiceStruct.declNode.codeLoc.toString(); | |
| 44 | // Keep an existing manifestation list | ||
| 45 | 3218 | if !insertScope.structs.contains(structId) { | |
| 46 | 3218 | insertScope.structs.upsert(structId, StructManifestationList()); | |
| 47 | 3218 | } | |
| 48 | |||
| 49 | // Save substantiation in declaration node | ||
| 50 | 3218 | Struct* substantiation = insertSubstantiation(insertScope, spiceStruct, spiceStruct.declNode); | |
| 51 | 3218 | nodeStructList.pushBack(substantiation); | |
| 52 | |||
| 53 | 6436 | return substantiation; | |
| 54 | 3218 | } | |
| 55 | |||
| 56 | 5358 | f<Struct*> insertSubstantiation(Scope* insertScope, Struct& newManifestation, const ASTNode* declNode) { | |
| 57 | 5358 | const String signature = newManifestation.structBase.getSignature(); | |
| 58 | |||
| 59 | // Retrieve the matching manifestation list of the scope | ||
| 60 | 5358 | const String structId = newManifestation.name + ":" + declNode.codeLoc.toString(); | |
| 61 | 5358 | assert insertScope.structs.contains(structId); | |
| 62 | 5358 | StructManifestationList& manifestationList = insertScope.structs.get(structId); | |
| 63 | |||
| 64 | // Add substantiated struct | ||
| 65 | 5358 | newManifestation.manifestationIndex = manifestationList.getSize(); | |
| 66 | 5358 | manifestationList.upsert(signature, newManifestation); | |
| 67 | 10716 | return &manifestationList.get(signature); | |
| 68 | 5358 | } | |
| 69 | |||
| 70 | // Active struct lookup cache. It is owned by the global resource manager. | ||
| 71 | LookupCache<Struct>* ACTIVE_STRUCT_LOOKUP_CACHE; | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Make the given lookup cache the active one. The struct lookup operates on it. | ||
| 75 | * | ||
| 76 | * @param cache Lookup cache | ||
| 77 | */ | ||
| 78 | 1055 | public p setActiveStructLookupCache(LookupCache<Struct>* cache) { | |
| 79 | 1055 | ACTIVE_STRUCT_LOOKUP_CACHE = cache; | |
| 80 | 1055 | } | |
| 81 | |||
| 82 | /** | ||
| 83 | * Get the active struct lookup cache | ||
| 84 | * | ||
| 85 | * @return Active lookup cache | ||
| 86 | */ | ||
| 87 | 361066 | f<LookupCache<Struct>*> getActiveStructLookupCache() { | |
| 88 | 361066 | assert ACTIVE_STRUCT_LOOKUP_CACHE != nil<LookupCache<Struct>*>; | |
| 89 | 361066 | return ACTIVE_STRUCT_LOOKUP_CACHE; | |
| 90 | } | ||
| 91 | |||
| 92 | /** | ||
| 93 | * Check if there is a struct in this scope, fulfilling all given requirements and if found, return it. | ||
| 94 | * If more than one struct matches the requirement, this is reported as an ambiguity error. | ||
| 95 | * | ||
| 96 | * @param matchScope Scope to match against | ||
| 97 | * @param qt Struct name requirement | ||
| 98 | * @param reqTemplateTypes Template types to substantiate generic types | ||
| 99 | * @param node Instantiation AST node for printing error messages | ||
| 100 | * @return Matched struct or nil | ||
| 101 | */ | ||
| 102 | 360306 | public f<Struct*> matchStruct(Scope* matchScope, const String& qt, const QualTypeList& reqTemplateTypes, const ASTNode* node) { | |
| 103 | // The IR generator reaches this from the worker threads of the parallel back end | ||
| 104 | 360306 | ConditionalLock _lock = ConditionalLock(getSymbolRegistryMutex()); | |
| 105 | |||
| 106 | // Do cache lookup | ||
| 107 | 360306 | LookupCache<Struct>* cache = getActiveStructLookupCache(); | |
| 108 | 360306 | const Hash cacheKey = getStructCacheKey(matchScope, qt, reqTemplateTypes); | |
| 109 | 360306 | if cache.lookupCache.contains(cacheKey) { | |
| 110 | 354571 | cache.lookupCacheHits++; | |
| 111 | 709142 | return cache.lookupCache.get(cacheKey); | |
| 112 | } | ||
| 113 | 5735 | cache.lookupCacheMisses++; | |
| 114 | |||
| 115 | // Loop over struct registry to find structs, that match the requirements of the instantiation | ||
| 116 | 5735 | Vector<Struct*> matches; | |
| 117 | 15735 | foreach Pair<const String&, StructManifestationList&> defEntry : matchScope.structs { | |
| 118 | 10000 | const String& structId = defEntry.getFirst(); | |
| 119 | 15064 | foreach Pair<const String&, Struct&> manEntry : defEntry.getSecond() { | |
| 120 | 12780 | const String& mangledName = manEntry.getFirst(); | |
| 121 | 12780 | const Struct& presetStruct = manEntry.getSecond(); | |
| 122 | // Skip generic and newly inserted substantiations to prevent double matching of a struct | ||
| 123 | 12780 | if presetStruct.structBase.isGenericSubstantiation() || presetStruct.isNewlyInserted { | |
| 124 | 2780 | continue; | |
| 125 | } | ||
| 126 | |||
| 127 | // Copy the struct to be able to substantiate types | ||
| 128 | 10000 | Struct candidate = presetStruct; | |
| 129 | |||
| 130 | // Check name requirement | ||
| 131 | 10000 | if !matchName(candidate, qt) { | |
| 132 | 4451 | break; // Leave the whole manifestation list, because all manifestations in this list have the same name | |
| 133 | } | ||
| 134 | |||
| 135 | // Prepare mapping table from generic type name to concrete type | ||
| 136 | 5549 | candidate.typeMapping.clear(); | |
| 137 | |||
| 138 | // Check template types requirement | ||
| 139 | 5549 | if !matchTemplateTypes(candidate, reqTemplateTypes, candidate.typeMapping, node) { | |
| 140 | 2 | continue; // Leave this manifestation and continue with the next one | |
| 141 | } | ||
| 142 | |||
| 143 | // Map field types from generic to concrete | ||
| 144 | 5547 | substantiateFieldTypes(candidate, candidate.typeMapping, node); | |
| 145 | |||
| 146 | // We found a match! -> Set the actual candidate and its entry to used | ||
| 147 | 5547 | candidate.used = true; | |
| 148 | 5547 | candidate.entry.used = true; | |
| 149 | |||
| 150 | // Check if it needs to be substantiated | ||
| 151 | 5547 | if presetStruct.templateTypes.isEmpty() { | |
| 152 | 2282 | assert matchScope.structs.contains(structId); | |
| 153 | 2282 | StructManifestationList& structManifestations = matchScope.structs.get(structId); | |
| 154 | 2282 | assert structManifestations.contains(mangledName); | |
| 155 | 2282 | Struct* structMatch = &structManifestations.get(mangledName); | |
| 156 | 2282 | structMatch.used = true; | |
| 157 | 2282 | matches.pushBack(structMatch); | |
| 158 | 2282 | continue; // Match was successful -> match the next struct | |
| 159 | } | ||
| 160 | |||
| 161 | // Check if we already have this manifestation and can simply re-use it | ||
| 162 | 3265 | const String candidateSignature = candidate.structBase.getSignature(); | |
| 163 | 3265 | StructManifestationList& defManifestations = defEntry.getSecond(); | |
| 164 | 3265 | if defManifestations.contains(candidateSignature) { | |
| 165 | 1125 | Struct& existingManifestation = defManifestations.get(candidateSignature); | |
| 166 | 1125 | existingManifestation.used = true; | |
| 167 | 1125 | matches.pushBack(&existingManifestation); | |
| 168 | 1125 | break; // Leave the whole manifestation list to not double-match the manifestation | |
| 169 | } | ||
| 170 | |||
| 171 | // Insert the substantiated version if required | ||
| 172 | 2140 | Struct* substantiatedStruct = insertSubstantiation(matchScope, candidate, presetStruct.declNode); | |
| 173 | 2140 | StructManifestationList& presetManifestations = matchScope.structs.get(structId); | |
| 174 | 2140 | Struct& presetManifestation = presetManifestations.get(mangledName); | |
| 175 | 2140 | substantiatedStruct.genericPreset = &presetManifestation.structBase; | |
| 176 | 2140 | const ASTStructDefNode* structDefNode = downcast<ASTStructDefNode>(substantiatedStruct.structBase.declNode); | |
| 177 | 2140 | structDefNode.structManifestations.pushBack(substantiatedStruct); | |
| 178 | 2140 | substantiatedStruct.isNewlyInserted = true; // To not iterate over it in the same matching | |
| 179 | // The generic preset was already decided on, but this manifestation carries concrete field types and therefore | ||
| 180 | // needs its own decision (taken further below, once its fields and interfaces are substantiated) | ||
| 181 | 2140 | substantiatedStruct.structBase.implicitDefaultMembersDecided = false; | |
| 182 | |||
| 183 | // Copy struct entry | ||
| 184 | 2140 | const String newSignature = substantiatedStruct.structBase.getSignature(); | |
| 185 | 2140 | matchScope.lookupStrict(substantiatedStruct.name).used = true; | |
| 186 | 2140 | substantiatedStruct.entry = matchScope.symbolTable.copySymbol(substantiatedStruct.name, newSignature); | |
| 187 | 2140 | assert substantiatedStruct.entry != nil<SymbolTableEntry*>; | |
| 188 | |||
| 189 | // Copy struct scope | ||
| 190 | 2140 | const String oldScopeName = presetStruct.getScopeName(); | |
| 191 | 2140 | const String newScopeName = substantiatedStruct.getScopeName(); | |
| 192 | 2140 | matchScope.copyChildScope(oldScopeName, newScopeName); | |
| 193 | 2140 | substantiatedStruct.scope = matchScope.getChildScope(newScopeName); | |
| 194 | 2140 | assert substantiatedStruct.scope != nil<Scope*>; | |
| 195 | 2140 | substantiatedStruct.scope.isGenericScope = false; | |
| 196 | |||
| 197 | // Attach the template types to the new struct entry | ||
| 198 | 2140 | const QualType& oldEntryType = substantiatedStruct.entry.getQualType(); | |
| 199 | 2140 | const QualTypeList concreteTemplateTypes = substantiatedStruct.structBase.getTemplateTypes(); | |
| 200 | 2140 | const QualType entryTypeWithTemplates = oldEntryType.getWithTemplateTypes(concreteTemplateTypes); | |
| 201 | 2140 | const QualType entryType = entryTypeWithTemplates.getWithBodyScope(substantiatedStruct.scope); | |
| 202 | 2140 | substantiatedStruct.entry.updateType(entryType, true); | |
| 203 | |||
| 204 | // Replace symbol types of field entries with concrete types | ||
| 205 | 2140 | assert substantiatedStruct.scope != nil<Scope*>; | |
| 206 | 2140 | const unsigned long fieldCount = substantiatedStruct.fieldTypes.getSize(); | |
| 207 | 2140 | const unsigned long explicitFieldsStartIdx = substantiatedStruct.scope.getFieldCount() - fieldCount; | |
| 208 | 2140 | unsigned long i = 0ul; | |
| 209 | 7234 | while i < fieldCount { | |
| 210 | // Replace field type with concrete template type | ||
| 211 | 5094 | SymbolTableEntry* fieldEntry = substantiatedStruct.scope.lookupField(cast<unsigned int>(explicitFieldsStartIdx + i)); | |
| 212 | 5094 | assert fieldEntry != nil<SymbolTableEntry*> && fieldEntry.isField(); | |
| 213 | 5094 | QualType& fieldType = substantiatedStruct.fieldTypes.get(i); | |
| 214 | 5094 | QualType baseType = fieldType.getBase(); | |
| 215 | |||
| 216 | // Set the body scope of fields that are of type <candidate-struct>* | ||
| 217 | 5094 | if baseType.matches(substantiatedStruct.entry.getQualType(), false, true, true) { | |
| 218 | 104 | fieldType = fieldType.replaceBaseType(baseType.getWithBodyScope(substantiatedStruct.scope)); | |
| 219 | 104 | } | |
| 220 | |||
| 221 | 5094 | fieldEntry.updateType(fieldType, /*overwriteExistingType=*/true); | |
| 222 | |||
| 223 | // Instantiate structs | ||
| 224 | 5094 | if baseType.is(SuperType::TY_STRUCT) { | |
| 225 | 1999 | const Struct* _ = baseType.getStruct(node); | |
| 226 | 1999 | } | |
| 227 | |||
| 228 | 5094 | i++; | |
| 229 | 5094 | } | |
| 230 | |||
| 231 | // Instantiate implemented interfaces if required | ||
| 232 | 2842 | foreach QualType& interfaceType : substantiatedStruct.interfaceTypes { | |
| 233 | // Skip non-generic interfaces | ||
| 234 | 702 | if !interfaceType.hasAnyGenericParts() { | |
| 235 | 11 | continue; | |
| 236 | } | ||
| 237 | |||
| 238 | // Build template types | ||
| 239 | 691 | QualTypeList templateTypes = interfaceType.getTemplateTypes(); | |
| 240 | 691 | substantiateTypesWithTypeMapping(templateTypes, candidate.typeMapping, node); | |
| 241 | |||
| 242 | // Instantiate interface | ||
| 243 | 691 | Interface* spiceInterface = interfaceType.getInterface(node, templateTypes); | |
| 244 | 691 | if spiceInterface != nil<Interface*> { | |
| 245 | 691 | interfaceType = spiceInterface.entry.getQualType(); | |
| 246 | 691 | } | |
| 247 | 691 | } | |
| 248 | |||
| 249 | // Decide which compiler-generated default members this brand-new manifestation needs. This has to happen right | ||
| 250 | // here, because the decision depends on the concrete field types and every reactive lookup that asks the struct | ||
| 251 | // for its dtor/ctor from now on has to see the answer (see createImplicitDefaultMembers). The nested | ||
| 252 | // field manifestations created above are decided on first, by their own recursive matchStruct() call, so a field's | ||
| 253 | // dtor is already in place when the outer struct is asked whether it needs one. | ||
| 254 | // A manifestation that is created while its own file is still being prepared is left alone: the generic preset | ||
| 255 | // has not been decided on yet at that point, so this manifestation could neither inherit its default members | ||
| 256 | // through the copied scope nor come to the same conclusion on its own. Those manifestations are covered by the | ||
| 257 | // one-shot sweep at the end of the file's prepare run (TypeChecker.visitEntry), which sees all of them. | ||
| 258 | 2140 | SourceFile* structSourceFile = substantiatedStruct.structBase.scope.sourceFile; | |
| 259 | 2140 | if STRUCT_DEFAULT_MEMBERS_DECIDABLE && structSourceFile.previousStage >= CompileStageType::TYPE_CHECKER_PRE { | |
| 260 | 2116 | createImplicitDefaultMembers(*substantiatedStruct, node, /*withMoveCtor=*/false); | |
| 261 | 2116 | } | |
| 262 | |||
| 263 | // Add to matched structs | ||
| 264 | 2140 | matches.pushBack(substantiatedStruct); | |
| 265 | // Leave the manifestation list: the remaining manifestations are substantiations, which are skipped anyway, and | ||
| 266 | // iterating the hash table further after inserting into it could visit the generic preset a second time | ||
| 267 | 2140 | break; | |
| 268 | 10000 | } | |
| 269 | 10000 | } | |
| 270 | |||
| 271 | // If no matches were found, return a nil | ||
| 272 | 5735 | if matches.isEmpty() { | |
| 273 | 188 | return nil<Struct*>; | |
| 274 | } | ||
| 275 | |||
| 276 | // Check if more than one struct matches the requirements | ||
| 277 | 5547 | if matches.getSize() > 1l { | |
| 278 | ✗ | throwSemanticError(node, SemanticErrorType::STRUCT_AMBIGUITY, String("Multiple structs match the requested signature")); | |
| 279 | // The compilation is aborted after this stage. Until then, deterministically pick the first candidate | ||
| 280 | ✗ | } | |
| 281 | 5547 | Struct* matchedStruct = matches.front(); | |
| 282 | 5547 | matchedStruct.isNewlyInserted = false; | |
| 283 | |||
| 284 | // Insert into cache | ||
| 285 | 5547 | cache.lookupCache.upsert(cacheKey, matchedStruct); | |
| 286 | |||
| 287 | 11094 | return matchedStruct; | |
| 288 | 360306 | } | |
| 289 | |||
| 290 | /** | ||
| 291 | * Checks if the matching candidate fulfills the name requirement | ||
| 292 | */ | ||
| 293 | 10000 | f<bool> matchName(const Struct& candidate, const String& reqName) { | |
| 294 | 10000 | return candidate.name == reqName; | |
| 295 | } | ||
| 296 | |||
| 297 | /** | ||
| 298 | * Checks if the matching candidate fulfills the template types requirement | ||
| 299 | */ | ||
| 300 | 5549 | f<bool> matchTemplateTypes(Struct& candidate, const QualTypeList& reqTemplateTypes, TypeMapping& typeMapping, const ASTNode* node) { | |
| 301 | // Check if the number of types match | ||
| 302 | 5549 | if reqTemplateTypes.getSize() != candidate.templateTypes.getSize() { | |
| 303 | 2 | return false; | |
| 304 | } | ||
| 305 | |||
| 306 | |||
| 307 | // Loop over all template types | ||
| 308 | 10121 | foreach unsigned long i, GenericType& candidateTemplateType : candidate.templateTypes { | |
| 309 | 4574 | QualType& candidateType = candidateTemplateType.qualType; | |
| 310 | 4574 | const QualType& reqType = reqTemplateTypes.get(i); | |
| 311 | |||
| 312 | // Check if the requested template type matches the candidate template type. The type mapping may be extended | ||
| 313 | 4574 | if !matchRequestedToCandidateType(candidateType, reqType, typeMapping, candidate.templateTypes, true, false) { | |
| 314 | ✗ | return false; | |
| 315 | } | ||
| 316 | |||
| 317 | // Substantiate the candidate param type, based on the type mapping | ||
| 318 | 4574 | if candidateType.hasAnyGenericParts() { | |
| 319 | 4574 | substantiateTypeWithTypeMapping(candidateType, typeMapping, node); | |
| 320 | 4574 | } | |
| 321 | 4574 | } | |
| 322 | |||
| 323 | 5547 | return true; | |
| 324 | } | ||
| 325 | |||
| 326 | /** | ||
| 327 | * Come up with the concrete field types, by applying the type mapping onto the generic field types | ||
| 328 | */ | ||
| 329 | 5547 | p substantiateFieldTypes(Struct& candidate, const TypeMapping& typeMapping, const ASTNode* node) { | |
| 330 | // Loop over all implicit fields and substantiate the generic ones | ||
| 331 | 5547 | const unsigned long implicitFieldCount = candidate.scope.getFieldCount() - candidate.fieldTypes.getSize(); | |
| 332 | 5547 | unsigned long i = 0ul; | |
| 333 | 6705 | while i < implicitFieldCount { | |
| 334 | 1158 | SymbolTableEntry* fieldEntry = candidate.scope.lookupField(cast<unsigned int>(i)); | |
| 335 | 1158 | QualType fieldType = fieldEntry.getQualType(); | |
| 336 | 1158 | if fieldType.hasAnyGenericParts() { | |
| 337 | 745 | substantiateTypeWithTypeMapping(fieldType, typeMapping, node); | |
| 338 | 745 | fieldEntry.updateType(fieldType, true); | |
| 339 | 745 | } | |
| 340 | 1158 | i++; | |
| 341 | 1158 | } | |
| 342 | |||
| 343 | // Loop over all explicit field types and substantiate the generic ones | ||
| 344 | 18976 | foreach QualType& fieldType : candidate.fieldTypes { | |
| 345 | 13429 | if fieldType.hasAnyGenericParts() { | |
| 346 | 4846 | substantiateTypeWithTypeMapping(fieldType, typeMapping, node); | |
| 347 | 4846 | } | |
| 348 | 13429 | } | |
| 349 | 5547 | } | |
| 350 | |||
| 351 | |||
| 352 | /** | ||
| 353 | * Calculate the cache key for the struct lookup cache | ||
| 354 | * | ||
| 355 | * @param scope Scope to match against | ||
| 356 | * @param name Struct name requirement | ||
| 357 | * @param templateTypes Template types to substantiate generic types | ||
| 358 | * @return Cache key | ||
| 359 | */ | ||
| 360 | 360306 | f<Hash> getStructCacheKey(const Scope* scope, const String& name, const QualTypeList& templateTypes) { | |
| 361 | 360306 | Hash hash = 0ul; | |
| 362 | 360306 | hashCombine64(hash, hashPointer(scope)); | |
| 363 | 360306 | hashCombine64(hash, hash(name)); | |
| 364 | 360306 | hashCombine64(hash, hashQualTypeList(templateTypes)); | |
| 365 | 360306 | return hash; | |
| 366 | } | ||
| 367 | |||
| 368 | /** | ||
| 369 | * Dump usage statistics for the lookup cache | ||
| 370 | */ | ||
| 371 | 760 | public f<String> dumpStructLookupCacheStatistics() { | |
| 372 | 760 | LookupCache<Struct>* cache = getActiveStructLookupCache(); | |
| 373 | 760 | return cache.dumpLookupCacheStatistics("StructManager"); | |
| 374 | } | ||
| 375 |