|
19 | 19 | #include <memory>
|
20 | 20 | #include <string>
|
21 | 21 | #include <vector>
|
| 22 | +#include <unordered_set> |
| 23 | +#include <unordered_map> |
22 | 24 |
|
23 | 25 | namespace nativescript {
|
24 | 26 |
|
| 27 | +// Forward declaration |
| 28 | +class StructTypeConv; |
| 29 | + |
| 30 | +// Thread-local storage for tracking structs currently being processed to detect cycles |
| 31 | +thread_local std::unordered_set<MDSectionOffset> processingStructs; |
| 32 | +thread_local std::unordered_set<std::string> processingEncodingStructs; |
| 33 | + |
| 34 | +// Cache for forward-declared struct types that need deferred resolution |
| 35 | +thread_local std::unordered_map<MDSectionOffset, ffi_type*> forwardDeclaredStructs; |
| 36 | +thread_local std::unordered_map<std::string, ffi_type*> forwardDeclaredEncodingStructs; |
| 37 | + |
| 38 | +// Cache for StructTypeConv instances to avoid recreating them and handle recursion |
| 39 | +thread_local std::unordered_map<MDSectionOffset, std::shared_ptr<StructTypeConv>> structTypeCache; |
| 40 | + |
| 41 | +// Cache for encoding-based structs to handle recursion |
| 42 | +thread_local std::unordered_map<std::string, std::shared_ptr<StructTypeConv>> encodingStructCache; |
| 43 | + |
25 | 44 | ffi_type* typeFromStruct(napi_env env, const char** encoding) {
|
| 45 | + // Extract struct name for cycle detection |
| 46 | + std::string structname; |
| 47 | + const char* nameStart = *encoding + 1; // skip '{' |
| 48 | + const char* c = nameStart; |
| 49 | + while (*c != '=') { |
| 50 | + structname += *c; |
| 51 | + c++; |
| 52 | + } |
| 53 | + |
| 54 | + // Check if we're already processing this struct (cycle detection) |
| 55 | + if (processingEncodingStructs.find(structname) != processingEncodingStructs.end()) { |
| 56 | + // Create a forward declaration placeholder |
| 57 | + ffi_type* forwardType = new ffi_type; |
| 58 | + forwardType->type = FFI_TYPE_STRUCT; |
| 59 | + forwardType->size = 0; |
| 60 | + forwardType->alignment = 0; |
| 61 | + forwardType->elements = nullptr; |
| 62 | + |
| 63 | + // Cache this forward declaration for later resolution |
| 64 | + forwardDeclaredEncodingStructs[structname] = forwardType; |
| 65 | + |
| 66 | + // Skip the struct encoding |
| 67 | + (*encoding)++; // skip '{' |
| 68 | + while (**encoding != '}') { |
| 69 | + (*encoding)++; |
| 70 | + } |
| 71 | + (*encoding)++; // skip '}' |
| 72 | + |
| 73 | + return forwardType; |
| 74 | + } |
| 75 | + |
| 76 | + // Check if we already have a forward declaration for this struct |
| 77 | + auto forwardIt = forwardDeclaredEncodingStructs.find(structname); |
| 78 | + if (forwardIt != forwardDeclaredEncodingStructs.end()) { |
| 79 | + // Skip the struct encoding |
| 80 | + (*encoding)++; // skip '{' |
| 81 | + while (**encoding != '}') { |
| 82 | + (*encoding)++; |
| 83 | + } |
| 84 | + (*encoding)++; // skip '}' |
| 85 | + |
| 86 | + return forwardIt->second; |
| 87 | + } |
| 88 | + |
| 89 | + // Mark this struct as being processed |
| 90 | + processingEncodingStructs.insert(structname); |
| 91 | + |
26 | 92 | ffi_type* type = new ffi_type;
|
27 | 93 | type->type = FFI_TYPE_STRUCT;
|
28 | 94 | type->size = 0;
|
|
53 | 119 | // null-terminate the array
|
54 | 120 | type->elements[elements.size()] = nullptr;
|
55 | 121 |
|
| 122 | + // If this was a forward declaration, update it with the real layout |
| 123 | + if (forwardIt != forwardDeclaredEncodingStructs.end()) { |
| 124 | + ffi_type* forwardType = forwardIt->second; |
| 125 | + forwardType->type = type->type; |
| 126 | + forwardType->size = type->size; |
| 127 | + forwardType->alignment = type->alignment; |
| 128 | + forwardType->elements = type->elements; |
| 129 | + |
| 130 | + // Clean up the temporary type and use the forward declaration |
| 131 | + delete type; |
| 132 | + type = forwardType; |
| 133 | + forwardDeclaredEncodingStructs.erase(forwardIt); |
| 134 | + } |
| 135 | + |
| 136 | + // Remove from processing set |
| 137 | + processingEncodingStructs.erase(structname); |
| 138 | + |
56 | 139 | return type;
|
57 | 140 | }
|
58 | 141 |
|
59 | 142 | ffi_type* typeFromStruct(napi_env env, MDMetadataReader* reader, MDSectionOffset structOffset,
|
60 | 143 | bool isUnion) {
|
| 144 | + // Check if we're already processing this struct (cycle detection) |
| 145 | + if (processingStructs.find(structOffset) != processingStructs.end()) { |
| 146 | + // Create a forward declaration placeholder |
| 147 | + ffi_type* forwardType = new ffi_type; |
| 148 | + forwardType->type = FFI_TYPE_STRUCT; |
| 149 | + forwardType->size = 0; |
| 150 | + forwardType->alignment = 0; |
| 151 | + forwardType->elements = nullptr; |
| 152 | + |
| 153 | + // Cache this forward declaration for later resolution |
| 154 | + forwardDeclaredStructs[structOffset] = forwardType; |
| 155 | + return forwardType; |
| 156 | + } |
| 157 | + |
| 158 | + // Check if we already have a forward declaration for this struct |
| 159 | + auto forwardIt = forwardDeclaredStructs.find(structOffset); |
| 160 | + if (forwardIt != forwardDeclaredStructs.end()) { |
| 161 | + return forwardIt->second; |
| 162 | + } |
| 163 | + |
| 164 | + // Mark this struct as being processed |
| 165 | + processingStructs.insert(structOffset); |
| 166 | + |
61 | 167 | ffi_type* type = new ffi_type;
|
62 | 168 | type->type = FFI_TYPE_STRUCT;
|
63 | 169 | type->size = 0;
|
|
67 | 173 | MDSectionOffset nameOffset = reader->getOffset(structOffset);
|
68 | 174 | auto name = reader->resolveString(nameOffset);
|
69 | 175 | bool next = true;
|
70 |
| - structOffset += sizeof(MDSectionOffset); // skip name |
71 |
| - structOffset += sizeof(uint16_t); // skip size |
| 176 | + MDSectionOffset currentOffset = structOffset + sizeof(MDSectionOffset); // skip name |
| 177 | + currentOffset += sizeof(uint16_t); // skip size |
72 | 178 |
|
73 | 179 | std::vector<ffi_type*> elements;
|
74 | 180 |
|
75 | 181 | while (next) {
|
76 |
| - nameOffset = reader->getOffset(structOffset); |
| 182 | + nameOffset = reader->getOffset(currentOffset); |
77 | 183 | next = nameOffset & mdSectionOffsetNext;
|
78 | 184 | nameOffset &= ~mdSectionOffsetNext;
|
79 | 185 | if (nameOffset == MD_SECTION_OFFSET_NULL) {
|
80 | 186 | break;
|
81 | 187 | }
|
82 |
| - structOffset += sizeof(MDSectionOffset); // skip name |
83 |
| - if (!isUnion) structOffset += sizeof(uint16_t); // skip offset |
84 |
| - ffi_type* elementType = TypeConv::Make(env, reader, &structOffset, 1)->type; |
| 188 | + currentOffset += sizeof(MDSectionOffset); // skip name |
| 189 | + if (!isUnion) currentOffset += sizeof(uint16_t); // skip offset |
| 190 | + ffi_type* elementType = TypeConv::Make(env, reader, ¤tOffset, 1)->type; |
85 | 191 | elements.push_back(elementType);
|
86 | 192 | }
|
87 | 193 |
|
|
92 | 198 | // null-terminate the array
|
93 | 199 | type->elements[elements.size()] = nullptr;
|
94 | 200 |
|
| 201 | + // If this was a forward declaration, update it with the real layout |
| 202 | + if (forwardIt != forwardDeclaredStructs.end()) { |
| 203 | + ffi_type* forwardType = forwardIt->second; |
| 204 | + forwardType->type = type->type; |
| 205 | + forwardType->size = type->size; |
| 206 | + forwardType->alignment = type->alignment; |
| 207 | + forwardType->elements = type->elements; |
| 208 | + |
| 209 | + // Clean up the temporary type and use the forward declaration |
| 210 | + delete type; |
| 211 | + type = forwardType; |
| 212 | + forwardDeclaredStructs.erase(forwardIt); |
| 213 | + } |
| 214 | + |
| 215 | + // Remove from processing set |
| 216 | + processingStructs.erase(structOffset); |
| 217 | + |
95 | 218 | return type;
|
96 | 219 | }
|
97 | 220 |
|
@@ -1511,12 +1634,24 @@ void toNative(napi_env env, napi_value value, void* result, bool* shouldFree,
|
1511 | 1634 | structname += *c;
|
1512 | 1635 | c++;
|
1513 | 1636 | }
|
| 1637 | + |
| 1638 | + // Check if we already have a cached StructTypeConv for this encoding-based struct |
| 1639 | + auto cacheIt = encodingStructCache.find(structname); |
| 1640 | + if (cacheIt != encodingStructCache.end()) { |
| 1641 | + return cacheIt->second; |
| 1642 | + } |
| 1643 | + |
1514 | 1644 | auto bridgeState = ObjCBridgeState::InstanceData(env);
|
1515 | 1645 | // NSLog(@"struct: %s, %d", structname.c_str(),
|
1516 | 1646 | // bridgeState->structOffsets[structname]);
|
1517 | 1647 | auto structOffset = bridgeState->structOffsets[structname];
|
1518 | 1648 | auto type = typeFromStruct(env, encoding);
|
1519 |
| - return std::make_shared<StructTypeConv>(StructTypeConv(structOffset, type)); |
| 1649 | + auto structTypeConv = std::make_shared<StructTypeConv>(StructTypeConv(structOffset, type)); |
| 1650 | + |
| 1651 | + // Cache the StructTypeConv |
| 1652 | + encodingStructCache[structname] = structTypeConv; |
| 1653 | + |
| 1654 | + return structTypeConv; |
1520 | 1655 | }
|
1521 | 1656 | case 'b': {
|
1522 | 1657 | (*encoding)++;
|
@@ -1687,9 +1822,27 @@ void toNative(napi_env env, napi_value value, void* result, bool* shouldFree,
|
1687 | 1822 | }
|
1688 | 1823 | structOffset += isUnion ? reader->unionsOffset : reader->structsOffset;
|
1689 | 1824 | auto structName = reader->getString(structOffset);
|
1690 |
| - auto type = |
1691 |
| - opaquePointers == 2 ? nullptr : typeFromStruct(env, reader, structOffset, isUnion); |
1692 |
| - return std::make_shared<StructTypeConv>(structOffset, type); |
| 1825 | + |
| 1826 | + // Check if we already have a cached StructTypeConv for this struct |
| 1827 | + auto cacheIt = structTypeCache.find(structOffset); |
| 1828 | + if (cacheIt != structTypeCache.end()) { |
| 1829 | + return cacheIt->second; |
| 1830 | + } |
| 1831 | + |
| 1832 | + // Check if we're currently processing this struct (recursion detection) |
| 1833 | + bool isRecursive = processingStructs.find(structOffset) != processingStructs.end(); |
| 1834 | + |
| 1835 | + ffi_type* type = nullptr; |
| 1836 | + if (opaquePointers != 2 && !isRecursive) { |
| 1837 | + type = typeFromStruct(env, reader, structOffset, isUnion); |
| 1838 | + } |
| 1839 | + |
| 1840 | + auto structTypeConv = std::make_shared<StructTypeConv>(structOffset, type); |
| 1841 | + |
| 1842 | + // Cache the StructTypeConv to handle recursion and avoid duplicates |
| 1843 | + structTypeCache[structOffset] = structTypeConv; |
| 1844 | + |
| 1845 | + return structTypeConv; |
1693 | 1846 | }
|
1694 | 1847 |
|
1695 | 1848 | case mdTypePointer: {
|
@@ -1738,4 +1891,14 @@ void toNative(napi_env env, napi_value value, void* result, bool* shouldFree,
|
1738 | 1891 | }
|
1739 | 1892 | }
|
1740 | 1893 |
|
| 1894 | +// Cleanup function to clear thread-local caches |
| 1895 | +void clearStructTypeCaches() { |
| 1896 | + processingStructs.clear(); |
| 1897 | + processingEncodingStructs.clear(); |
| 1898 | + forwardDeclaredStructs.clear(); |
| 1899 | + forwardDeclaredEncodingStructs.clear(); |
| 1900 | + structTypeCache.clear(); |
| 1901 | + encodingStructCache.clear(); |
| 1902 | +} |
| 1903 | + |
1741 | 1904 | } // namespace nativescript
|
0 commit comments