model/struct.spice
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Copyright (c) 2021-2026 ChilliBits. All rights reserved. | ||
| 2 | |||
| 3 | // Std imports | ||
| 4 | import "std/data/vector"; | ||
| 5 | import "std/text/stringstream"; | ||
| 6 | |||
| 7 | // Own imports | ||
| 8 | import "@/ast/ast-nodes"; | ||
| 9 | import "@/model/struct-base"; | ||
| 10 | import "@/model/generic-type"; | ||
| 11 | import "@/symboltablebuilder/qual-type"; | ||
| 12 | import "@/symboltablebuilder/symbol-table-entry"; | ||
| 13 | import "@/symboltablebuilder/scope"; | ||
| 14 | import "@/symboltablebuilder/lifecycle"; | ||
| 15 | import "@/util/common-util"; | ||
| 16 | |||
| 17 | // Constants | ||
| 18 | const string STRUCT_SCOPE_PREFIX = "struct:"; | ||
| 19 | |||
| 20 | 33679 | public type Struct struct { | |
| 21 | public compose StructBase structBase | ||
| 22 | public QualTypeList fieldTypes | ||
| 23 | public QualTypeList interfaceTypes | ||
| 24 | } | ||
| 25 | |||
| 26 | 3218 | public p Struct.ctor(const String& name, SymbolTableEntry* entry, Scope* scope, const QualTypeList& fieldTypes, const Vector<GenericType>& templateTypes, const QualTypeList& interfaceTypes, ASTNode* declNode) { | |
| 27 | 3218 | this.structBase = StructBase(name, entry, scope, templateTypes, declNode); | |
| 28 | 3218 | this.fieldTypes = fieldTypes; | |
| 29 | 3218 | this.interfaceTypes = interfaceTypes; | |
| 30 | 3218 | } | |
| 31 | |||
| 32 | /** | ||
| 33 | * Retrieve the name of the scope, where members and methods are placed. This is used to navigate to the scope of the struct | ||
| 34 | * from the parent scope. | ||
| 35 | * | ||
| 36 | * @return Name of the struct scope | ||
| 37 | */ | ||
| 38 | 4280 | public f<String> Struct.getScopeName() { | |
| 39 | 4280 | const String &appendix = this.structBase.isGenericSubstantiation() ? this.structBase.getSignature() : this.name; | |
| 40 | 8560 | return String(STRUCT_SCOPE_PREFIX) + appendix; | |
| 41 | 4280 | } | |
| 42 | |||
| 43 | /** | ||
| 44 | * Retrieve the name of the scope, where members and methods are placed, from just a name and concrete | ||
| 45 | * template types — usable without a live Struct instance (e.g. from a plain AST node, before any Struct | ||
| 46 | * model object exists, such as during symbol-table building). Mirrors StructBase.getSignature's 2-arg | ||
| 47 | * overload, which is a pure function of its params and does not read 'this'. | ||
| 48 | * | ||
| 49 | * @param name Struct name | ||
| 50 | * @param concreteTemplateTypes Concrete template types | ||
| 51 | * @return Name of the struct scope | ||
| 52 | */ | ||
| 53 | 83244 | public f<String> getStructScopeName(const String& name, const QualTypeList& concreteTemplateTypes) { | |
| 54 | 83244 | StringStream templateTyStr; | |
| 55 | 83244 | if !concreteTemplateTypes.isEmpty() { | |
| 56 | 17162 | templateTyStr << "<"; | |
| 57 | 37851 | foreach unsigned long i, const QualType& templateType : concreteTemplateTypes { | |
| 58 | 20689 | if i > 0ul { templateTyStr << ","; } | |
| 59 | 20689 | templateType.getName(templateTyStr, false, true); | |
| 60 | 20689 | } | |
| 61 | 17162 | templateTyStr << ">"; | |
| 62 | 17162 | } | |
| 63 | 166488 | return String(STRUCT_SCOPE_PREFIX) + getLastFragment(name, SCOPE_ACCESS_TOKEN) + templateTyStr.str(); | |
| 64 | 83244 | } | |
| 65 | |||
| 66 | /** | ||
| 67 | * Checks at least one field is a reference. | ||
| 68 | * This is used to prohibit constant instantiations. | ||
| 69 | * | ||
| 70 | * @return Has reference as field type or not | ||
| 71 | */ | ||
| 72 | 1662 | public f<bool> Struct.hasReferenceFields() { | |
| 73 | 5634 | foreach const QualType& fieldType : this.fieldTypes { | |
| 74 | 3981 | if fieldType.isRef() { | |
| 75 | 9 | return true; | |
| 76 | } | ||
| 77 | 3972 | } | |
| 78 | 1653 | return false; | |
| 79 | } | ||
| 80 | |||
| 81 | /** | ||
| 82 | * Checks if the LLVM struct type of this struct carries a synthesized vtable pointer as its first element, which | ||
| 83 | * shifts all fields by one element. Structs that implement interfaces receive their vtable pointer through their | ||
| 84 | * leading implicit interface field instead, so only structs that request a vtable without implementing any | ||
| 85 | * interface carry the extra element. | ||
| 86 | * | ||
| 87 | * @return Carries a synthesized vtable pointer or not | ||
| 88 | */ | ||
| 89 | 203456 | public f<bool> Struct.hasSynthesizedVTablePtr() { | |
| 90 | 203456 | assert this.declNode.kind == ASTNodeKind::STRUCT_DEF; | |
| 91 | 203456 | const ASTStructDefNode* structDeclNode = downcast<ASTStructDefNode>(this.declNode); | |
| 92 | 203456 | return !structDeclNode.hasInterfaces && structDeclNode.emitVTable; | |
| 93 | } | ||
| 94 | |||
| 95 | /** | ||
| 96 | * Map the order index of a field to the index of the corresponding element in the LLVM struct type. Those differ by one | ||
| 97 | * if the struct carries a synthesized vtable pointer as its first element. | ||
| 98 | * | ||
| 99 | * @param orderIndex Order index of the field | ||
| 100 | * @return Index of the field in the LLVM struct type | ||
| 101 | */ | ||
| 102 | 192282 | public f<unsigned long> Struct.getFieldElementIndex(unsigned long orderIndex) { | |
| 103 | 192282 | return this.hasSynthesizedVTablePtr() ? orderIndex + 1l : orderIndex; | |
| 104 | } | ||
| 105 | |||
| 106 | /** | ||
| 107 | * Check if a field exists in this struct or in one of its composed fields and return it if possible. | ||
| 108 | * | ||
| 109 | * @param name Name of the desired field | ||
| 110 | * @param indexPath How to index the found field using LLVM struct element indices (e.g. for GEP) | ||
| 111 | * @return Desired field / nullptr if the field was not found | ||
| 112 | */ | ||
| 113 | 172279 | public f<SymbolTableEntry*> Struct.lookupInComposedFields(const String& name, Vector<unsigned long>& indexPath) { | |
| 114 | // Check if we have a field with this name in the current struct | ||
| 115 | 172279 | SymbolTableEntry* strictResult = this.scope.lookupStrict(name); | |
| 116 | 172279 | if strictResult != nil<SymbolTableEntry*> { | |
| 117 | 172207 | indexPath.pushBack(this.getFieldElementIndex(strictResult.orderIndex)); | |
| 118 | 172207 | return strictResult; | |
| 119 | } | ||
| 120 | |||
| 121 | // If it was not found in the current struct, loop through all composed fields | ||
| 122 | 72 | const unsigned long fieldCount = this.scope.getFieldCount(); | |
| 123 | 116 | for unsigned long i = 0l; i < fieldCount; i++ { | |
| 124 | 94 | const SymbolTableEntry* fieldEntry = this.scope.lookupField(cast<unsigned int>(i)); | |
| 125 | 94 | const QualType fieldType = fieldEntry.getQualType(); | |
| 126 | |||
| 127 | // Skip all fields that are not composition fields | ||
| 128 | 94 | if !fieldType.isComposition() { | |
| 129 | 25 | continue; | |
| 130 | } | ||
| 131 | |||
| 132 | // Add the current field's element index to the index path | ||
| 133 | 69 | indexPath.pushBack(this.getFieldElementIndex(fieldEntry.orderIndex)); | |
| 134 | |||
| 135 | // Search in the composed struct | ||
| 136 | 69 | Struct* composedStruct = fieldType.getStruct(fieldEntry.declNode); | |
| 137 | 69 | assert composedStruct != nil<Struct*>; | |
| 138 | 69 | SymbolTableEntry* composedResult = composedStruct.lookupInComposedFields(name, indexPath); | |
| 139 | 69 | if composedResult != nil<SymbolTableEntry*> { | |
| 140 | 50 | return composedResult; | |
| 141 | } | ||
| 142 | |||
| 143 | // Remove the current field's element index from the index path | ||
| 144 | 19 | indexPath.popBack(); | |
| 145 | 19 | } | |
| 146 | |||
| 147 | // Field was not found in the current struct, return nullptr | ||
| 148 | 22 | return nil<SymbolTableEntry*>; | |
| 149 | } | ||
| 150 | |||
| 151 | /** | ||
| 152 | * Check that all fields are in a certain lifecycle state. | ||
| 153 | * | ||
| 154 | * @param state Lifecycle state to check for | ||
| 155 | * @return nullptr if all fields are in this state, otherwise the first mismatched field symbol | ||
| 156 | */ | ||
| 157 | 1640 | public f<SymbolTableEntry*> Struct.areAllFieldsInState(const LifecycleState state) { | |
| 158 | 5417 | for unsigned long i = 0l; i < this.fieldTypes.getSize(); i++ { | |
| 159 | 3777 | SymbolTableEntry* fieldSymbol = this.scope.lookupField(cast<unsigned int>(i)); | |
| 160 | 3777 | assert fieldSymbol != nil<SymbolTableEntry*>; | |
| 161 | 3777 | const Lifecycle& lifecycle = fieldSymbol.getLifecycle(); | |
| 162 | 3777 | if !fieldSymbol.isImplicitField && lifecycle.getCurrentState() != state { | |
| 163 | ✗ | return fieldSymbol; | |
| 164 | } | ||
| 165 | 3777 | } | |
| 166 | 1640 | return nil<SymbolTableEntry*>; | |
| 167 | } | ||
| 168 | |||
| 169 | /** | ||
| 170 | * Check that all fields are initialized. | ||
| 171 | * | ||
| 172 | * @return nullptr if all fields are initialized, otherwise the first uninitialized field symbol | ||
| 173 | */ | ||
| 174 | 1640 | public f<SymbolTableEntry*> Struct.areAllFieldsInitialized() { | |
| 175 | 1640 | return this.areAllFieldsInState(LifecycleState::INITIALIZED); | |
| 176 | } | ||
| 177 | |||
| 178 | /** | ||
| 179 | * Reset the initialization state of all fields to DECLARED. | ||
| 180 | */ | ||
| 181 | 8078 | public p Struct.resetFieldSymbolsToDeclared() { | |
| 182 | // Reset every explicit field | ||
| 183 | 8078 | const unsigned long fieldCount = this.scope.getFieldCount(); | |
| 184 | 30403 | for unsigned long i = 0l; i < fieldCount; i++ { | |
| 185 | 22325 | SymbolTableEntry* fieldSymbol = this.scope.lookupField(cast<unsigned int>(i)); | |
| 186 | 22325 | if !fieldSymbol.isImplicitField { | |
| 187 | 20661 | fieldSymbol.updateState(LifecycleState::DECLARED); | |
| 188 | 20661 | } | |
| 189 | 22325 | } | |
| 190 | 8078 | } | |
| 191 |