diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index 0615fef9..77808fc1 100644 --- a/NativeScript/runtime/ArgConverter.mm +++ b/NativeScript/runtime/ArgConverter.mm @@ -500,8 +500,7 @@ return; } } else if (value->IsString()) { - if (type == BinaryTypeEncodingType::IdEncoding || - type == BinaryTypeEncodingType::InterfaceDeclarationReference) { + if (type == BinaryTypeEncodingType::IdEncoding || typeEncoding->isInterfaceReference()) { id data = tns::ToNSString(isolate, value); // this feels wrong but follows the other CFBridgingRetain calls // and also solves a leak @@ -511,7 +510,7 @@ return; } } else if (value->IsObject()) { - if (type == BinaryTypeEncodingType::InterfaceDeclarationReference || + if (typeEncoding->isInterfaceReference() || type == BinaryTypeEncodingType::InstanceTypeEncoding || type == BinaryTypeEncodingType::IdEncoding) { BaseDataWrapper* baseWrapper = tns::GetValue(isolate, value); @@ -720,8 +719,8 @@ } Isolate* isolate = v8::Isolate::GetCurrent(); - if (typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference) { - const char* name = typeEncoding->details.declarationReference.name.valuePtr(); + if (typeEncoding->isInterfaceReference()) { + const char* name = typeEncoding->interfaceName(); if (strcmp(name, "NSNumber") == 0 && tns::IsNumber(arg)) { return true; } @@ -978,9 +977,8 @@ } const Meta* ArgConverter::FindMeta(Class klass, const TypeEncoding* typeEncoding) { - if (typeEncoding != nullptr && - typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference) { - const char* name = typeEncoding->details.interfaceDeclarationReference.name.valuePtr(); + if (typeEncoding != nullptr && typeEncoding->isInterfaceReference()) { + const char* name = typeEncoding->interfaceName(); const Meta* result = GetMeta(name); if (result != nullptr && result->type() == MetaType::Interface) { return result; @@ -1259,11 +1257,11 @@ } const TypeEncoding* innerTypeEncoding = typeEncoding->details.pointer.getInnerType(); - if (innerTypeEncoding->type != BinaryTypeEncodingType::InterfaceDeclarationReference) { + if (!innerTypeEncoding->isInterfaceReference()) { return false; } - const char* name = innerTypeEncoding->details.declarationReference.name.valuePtr(); + const char* name = innerTypeEncoding->interfaceName(); if (name == nullptr) { return false; } diff --git a/NativeScript/runtime/ClassBuilder.mm b/NativeScript/runtime/ClassBuilder.mm index 61a24d11..2a0373f9 100644 --- a/NativeScript/runtime/ClassBuilder.mm +++ b/NativeScript/runtime/ClassBuilder.mm @@ -537,6 +537,7 @@ void ScopeClassNameToIsolate(std::string& name, int isolateId) { } case BinaryTypeEncodingType::ProtocolEncoding: case BinaryTypeEncodingType::InterfaceDeclarationReference: + case BinaryTypeEncodingType::InterfaceIndexReference: case BinaryTypeEncodingType::InstanceTypeEncoding: case BinaryTypeEncodingType::IdEncoding: { return "@"; diff --git a/NativeScript/runtime/FFICall.cpp b/NativeScript/runtime/FFICall.cpp index ce235b63..ee851105 100644 --- a/NativeScript/runtime/FFICall.cpp +++ b/NativeScript/runtime/FFICall.cpp @@ -12,6 +12,7 @@ ffi_type* FFICall::GetArgumentType(const TypeEncoding* typeEncoding, bool isStru } case BinaryTypeEncodingType::IdEncoding: case BinaryTypeEncodingType::InterfaceDeclarationReference: + case BinaryTypeEncodingType::InterfaceIndexReference: case BinaryTypeEncodingType::InstanceTypeEncoding: case BinaryTypeEncodingType::SelectorEncoding: case BinaryTypeEncodingType::BlockEncoding: @@ -277,10 +278,11 @@ StructInfo FFICall::GetStructInfo(size_t fieldsCount, const TypeEncoding* fieldE } ParametrizedCall* ParametrizedCall::Get(const TypeEncoding* typeEncoding, const int initialParameterIndex, const int argsCount) { - auto it = callsCache_.find(typeEncoding); - if (it != callsCache_.end()) { - return it->second; - } + CallKey key{typeEncoding, initialParameterIndex, argsCount}; + auto it = callsCache_.find(key); + if (it != callsCache_.end()) { + return it->second; + } const ffi_type** parameterTypesFFITypes = new const ffi_type*[argsCount](); ffi_type* returnType = FFICall::GetArgumentType(typeEncoding); @@ -300,12 +302,14 @@ ParametrizedCall* ParametrizedCall::Get(const TypeEncoding* typeEncoding, const tns::Assert(status == FFI_OK); ParametrizedCall* call = new ParametrizedCall(cif); - callsCache_.emplace(typeEncoding, call); + callsCache_.emplace(key, call); return call; } -robin_hood::unordered_map ParametrizedCall::callsCache_; +robin_hood::unordered_map + ParametrizedCall::callsCache_; robin_hood::unordered_map FFICall::structInfosCache_; } diff --git a/NativeScript/runtime/FFICall.h b/NativeScript/runtime/FFICall.h index 46666405..77f65344 100644 --- a/NativeScript/runtime/FFICall.h +++ b/NativeScript/runtime/FFICall.h @@ -67,7 +67,31 @@ class ParametrizedCall { std::vector ArgValueOffsets; private: - static robin_hood::unordered_map + // The cif is built from the encoding *and* the two counts, so all three + // identify it. Keying on the encoding alone aliases distinct call shapes onto + // one cif, which mis-describes the stack rather than failing outright. + struct CallKey { + const TypeEncoding* encoding; + int initialParameterIndex; + int argsCount; + + bool operator==(const CallKey& other) const { + return encoding == other.encoding && + initialParameterIndex == other.initialParameterIndex && + argsCount == other.argsCount; + } + }; + + struct CallKeyHash { + size_t operator()(const CallKey& key) const { + size_t hash = robin_hood::hash()(key.encoding); + hash = hash * 31 + static_cast(key.initialParameterIndex); + hash = hash * 31 + static_cast(key.argsCount); + return hash; + } + }; + + static robin_hood::unordered_map callsCache_; }; diff --git a/NativeScript/runtime/Interop.mm b/NativeScript/runtime/Interop.mm index fb29bbf2..7c25ba94 100644 --- a/NativeScript/runtime/Interop.mm +++ b/NativeScript/runtime/Interop.mm @@ -153,8 +153,8 @@ } bool Interop::isRefTypeEqual(const TypeEncoding* typeEncoding, const char* clazz) { - std::string n(&typeEncoding->details.interfaceDeclarationReference.name.value()); - return n.compare(clazz) == 0; + const char* name = typeEncoding->interfaceName(); + return name != nullptr && std::string(name).compare(clazz) == 0; } // this is experimental. Maybe we can have something like this to wrap all Local to avoid @@ -246,8 +246,7 @@ inline bool isBool() { FFICall::DisposeFFIType(ffiType, typeEncoding); memset(dest, 0, size); } else if (argHelper.isBool()) { - if (typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference && - isRefTypeEqual(typeEncoding, "NSNumber")) { + if (typeEncoding->isInterfaceReference() && isRefTypeEqual(typeEncoding, "NSNumber")) { bool value = tns::ToBool(arg); NSNumber* num = [NSNumber numberWithBool:value]; Interop::SetValue(dest, num); @@ -321,15 +320,14 @@ inline bool isBool() { } unichar c = (vector.size() == 0) ? 0 : vector[0]; Interop::SetValue(dest, c); - } else if (argHelper.isString() && - (typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference || - typeEncoding->type == BinaryTypeEncodingType::IdEncoding)) { + } else if (argHelper.isString() && (typeEncoding->isInterfaceReference() || + typeEncoding->type == BinaryTypeEncodingType::IdEncoding)) { NSString* result = tns::ToNSString(isolate, arg); Interop::SetValue(dest, result); } else if (Interop::IsNumbericType(typeEncoding->type) || tns::IsNumber(arg)) { double value = tns::ToNumber(isolate, arg); - if (typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference || + if (typeEncoding->isInterfaceReference() || typeEncoding->type == BinaryTypeEncodingType::IdEncoding) { // NSNumber NSNumber* num = [NSNumber numberWithDouble:value]; @@ -657,8 +655,8 @@ inline bool isBool() { } bool isNSArray = false; - if (typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference) { - std::string name = typeEncoding->details.interfaceDeclarationReference.name.valuePtr(); + if (typeEncoding->isInterfaceReference()) { + std::string name = typeEncoding->interfaceName(); isNSArray = name == "NSArray"; } @@ -1189,7 +1187,7 @@ inline bool isBool() { return instance; } - if (typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference || + if (typeEncoding->isInterfaceReference() || typeEncoding->type == BinaryTypeEncodingType::IdEncoding || typeEncoding->type == BinaryTypeEncodingType::InstanceTypeEncoding) { id result = call->GetResult(); @@ -1222,8 +1220,8 @@ inline bool isBool() { } if (marshalToPrimitive && [result isKindOfClass:[NSString class]]) { - if (typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference) { - const char* returnClassName = typeEncoding->details.declarationReference.name.valuePtr(); + if (typeEncoding->isInterfaceReference()) { + const char* returnClassName = typeEncoding->interfaceName(); Class returnClass = objc_getClass(returnClassName); if (returnClass != nil && returnClass == [NSMutableString class]) { marshalToPrimitive = false; @@ -1249,9 +1247,9 @@ inline bool isBool() { return poInstance->Get(isolate); } - // For NSProxy we will try to read the metadata from - // typeEncoding->details.interfaceDeclarationReference.name because class_getSuperclass will - // directly return NSProxy and thus missing to attach all instance members + // For NSProxy we will try to read the metadata from the encoding's interface name because + // class_getSuperclass will directly return NSProxy and thus missing to attach all instance + // members const TypeEncoding* te = [result isProxy] ? typeEncoding : nullptr; ObjCDataWrapper* wrapper = new ObjCDataWrapper(result, te); @@ -1398,9 +1396,7 @@ inline bool isBool() { const char* protocolName = (*it).valuePtr(); additionalProtocols.push_back(protocolName); } - } else if (typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference && - typeEncoding->details.interfaceDeclarationReference._protocols.offset > 0) { - PtrTo> protocols = typeEncoding->details.interfaceDeclarationReference._protocols; + } else if (const Array* protocols = typeEncoding->interfaceProtocols()) { for (auto it = protocols->begin(); it != protocols->end(); it++) { const char* protocolName = (*it).valuePtr(); additionalProtocols.push_back(protocolName); @@ -1767,9 +1763,9 @@ void ExecuteWriteValueValidationsAndStopExecutionAndLogStackTrace(Local const TypeEncoding* typeEncoding, void* dest, Local arg) { Isolate* isolate = v8::Isolate::GetCurrent(); - std::string destName = typeEncoding->details.interfaceDeclarationReference.name.valuePtr(); Local originArg = arg; - if (typeEncoding->type == BinaryTypeEncodingType::InterfaceDeclarationReference) { + if (typeEncoding->isInterfaceReference()) { + std::string destName = typeEncoding->interfaceName(); if (originArg->IsObject()) { Local originObj = originArg.As(); if ((originObj->IsArrayBuffer() || originObj->IsArrayBufferView() || @@ -1791,7 +1787,7 @@ void ExecuteWriteValueValidationsAndStopExecutionAndLogStackTrace(Local } bool IsTypeEncondingHandldedByDebugMessages(const TypeEncoding* typeEncoding) { - if (typeEncoding->type != BinaryTypeEncodingType::InterfaceDeclarationReference && + if (!typeEncoding->isInterfaceReference() && typeEncoding->type != BinaryTypeEncodingType::StructDeclarationReference && typeEncoding->type != BinaryTypeEncodingType::IdEncoding) { return true; @@ -1803,7 +1799,9 @@ bool IsTypeEncondingHandldedByDebugMessages(const TypeEncoding* typeEncoding) { void LogWriteValueTraceMessage(Local context, const TypeEncoding* typeEncoding, void* dest, Local arg) { Isolate* isolate = v8::Isolate::GetCurrent(); - std::string destName = typeEncoding->details.interfaceDeclarationReference.name.valuePtr(); + std::string destName = typeEncoding->isInterfaceReference() + ? typeEncoding->interfaceName() + : typeEncoding->details.declarationReference.name.valuePtr(); std::string originName = tns::ToString(isolate, arg); if (originName == "") { // empty string diff --git a/NativeScript/runtime/Metadata.h b/NativeScript/runtime/Metadata.h index 0d0dc701..b364008e 100644 --- a/NativeScript/runtime/Metadata.h +++ b/NativeScript/runtime/Metadata.h @@ -59,20 +59,24 @@ inline uint8_t getMinorVersion(uint8_t encodedVersion) { // Bit indices in flags section enum MetaFlags { - HasDemangledName = 8, - HasName = 7, - // IsIosAppExtensionAvailable = 6, the flag exists in metadata generator but we never use it in the runtime - FunctionReturnsUnmanaged = 3, - FunctionIsVariadic = 5, - FunctionOwnsReturnedCocoaObject = 4, - MemberIsOptional = 0, // Mustn't equal any Method or Property flag since it can be applicable to both - MethodIsInitializer = 1, - MethodIsVariadic = 2, - MethodIsNullTerminatedVariadic = 3, - MethodOwnsReturnedCocoaObject = 4, - MethodHasErrorOutParameter = 5, - PropertyHasGetter = 2, - PropertyHasSetter = 3, + HasDemangledName = 8, + HasName = 7, + // IsIosAppExtensionAvailable = 6, the flag exists in metadata generator but + // we never use it in the runtime + FunctionReturnsUnmanaged = 3, + FunctionIsVariadic = 5, + FunctionOwnsReturnedCocoaObject = 4, + MemberIsOptional = 0, // Mustn't equal any Method or Property flag since it + // can be applicable to both + MethodIsInitializer = 1, + MethodIsVariadic = 2, + MethodIsNullTerminatedVariadic = 3, + MethodOwnsReturnedCocoaObject = 4, + MethodHasErrorOutParameter = 5, + MethodHasConstructorTokens = 9, + JsCodeIsEnumTable = 10, + PropertyHasGetter = 2, + PropertyHasSetter = 3, }; @@ -97,40 +101,46 @@ enum MemberType { }; enum BinaryTypeEncodingType : uint8_t { - VoidEncoding, - BoolEncoding, - ShortEncoding, - UShortEncoding, - IntEncoding, - UIntEncoding, - LongEncoding, - ULongEncoding, - LongLongEncoding, - ULongLongEncoding, - CharEncoding, - UCharEncoding, - UnicharEncoding, - CharSEncoding, - CStringEncoding, - FloatEncoding, - DoubleEncoding, - InterfaceDeclarationReference, - StructDeclarationReference, - UnionDeclarationReference, - PointerEncoding, - VaListEncoding, - SelectorEncoding, - ClassEncoding, - ProtocolEncoding, - InstanceTypeEncoding, - IdEncoding, - ConstantArrayEncoding, - IncompleteArrayEncoding, - FunctionPointerEncoding, - BlockEncoding, - AnonymousStructEncoding, - AnonymousUnionEncoding, - ExtVectorEncoding + VoidEncoding, + BoolEncoding, + ShortEncoding, + UShortEncoding, + IntEncoding, + UIntEncoding, + LongEncoding, + ULongEncoding, + LongLongEncoding, + ULongLongEncoding, + CharEncoding, + UCharEncoding, + UnicharEncoding, + CharSEncoding, + CStringEncoding, + FloatEncoding, + DoubleEncoding, + InterfaceDeclarationReference, + StructDeclarationReference, + UnionDeclarationReference, + PointerEncoding, + VaListEncoding, + SelectorEncoding, + ClassEncoding, + ProtocolEncoding, + InstanceTypeEncoding, + IdEncoding, + ConstantArrayEncoding, + IncompleteArrayEncoding, + FunctionPointerEncoding, + BlockEncoding, + AnonymousStructEncoding, + AnonymousUnionEncoding, + ExtVectorEncoding, + // Same meaning as InterfaceDeclarationReference with an empty protocol list, + // but names the class by index into MetaFile::classNames() instead of + // carrying a name pointer and a protocols pointer. Positionally mirrored in + // the generator's binary::BinaryTypeEncodingType — append only, never + // reorder. + InterfaceIndexReference }; #pragma pack(push, 1) @@ -321,6 +331,14 @@ struct ModuleTable { } }; +/// Class names referenced by InterfaceIndexReference encodings, so a reference +/// costs a 2-byte index instead of a 4-byte name pointer. +struct ClassNameTable { + Array names; + + int sizeInBytes() const { return names.sizeInBytes(); } +}; + struct MetaFile { private: GlobalTable _globalTableJs; @@ -330,6 +348,11 @@ struct MetaFile { static MetaFile* setInstance(void* metadataPtr); + /// Resolved once by setInstance. Locating the table means walking every + /// preceding table, which is too much work to repeat per marshalled + /// argument. + static const ClassNameTable* classNames(); + const GlobalTable* globalTableJs() const { return &this->_globalTableJs; } @@ -349,9 +372,15 @@ struct MetaFile { return reinterpret_cast(offset(gt, gt->sizeInBytes())); } + const ClassNameTable* classNamesTable() const { + const ModuleTable* mt = this->topLevelModulesTable(); + return reinterpret_cast( + offset(mt, mt->sizeInBytes())); + } + const void* heap() const { - const ModuleTable* mt = this->topLevelModulesTable(); - return offset(mt, mt->sizeInBytes()); + const ClassNameTable* ct = this->classNamesTable(); + return offset(ct, ct->sizeInBytes()); } }; @@ -423,6 +452,9 @@ union TypeEncodingDetails { String name; PtrTo> _protocols; } interfaceDeclarationReference; + struct InterfaceIndexReferenceDetails { + uint16_t index; + } interfaceIndexReference; struct PointerDetails { const TypeEncoding* getInnerType() const { return reinterpret_cast(this); @@ -449,6 +481,39 @@ struct TypeEncoding { BinaryTypeEncodingType type; TypeEncodingDetails details; + /// An interface reference has two spellings; test with this rather than + /// comparing against InterfaceDeclarationReference, or the indexed form + /// silently falls through to whatever the default branch does. + bool isInterfaceReference() const { + return this->type == + BinaryTypeEncodingType::InterfaceDeclarationReference || + this->type == BinaryTypeEncodingType::InterfaceIndexReference; + } + + /// Referenced class name, for either spelling. nullptr if not an interface + /// reference. + const char* interfaceName() const { + switch (this->type) { + case BinaryTypeEncodingType::InterfaceDeclarationReference: + return this->details.interfaceDeclarationReference.name.valuePtr(); + case BinaryTypeEncodingType::InterfaceIndexReference: + return MetaFile::classNames() + ->names[this->details.interfaceIndexReference.index] + .valuePtr(); + default: + return nullptr; + } + } + + /// Conformed protocols, for either spelling. The indexed form only encodes + /// references that had none, so it reports an empty list. + const Array* interfaceProtocols() const { + return this->type == BinaryTypeEncodingType::InterfaceDeclarationReference + ? this->details.interfaceDeclarationReference._protocols + .valuePtr() + : nullptr; + } + const TypeEncoding* next() const { const TypeEncoding* afterTypePtr = reinterpret_cast(offset(this, sizeof(type))); @@ -485,6 +550,11 @@ struct TypeEncoding { case BinaryTypeEncodingType::InterfaceDeclarationReference: { return reinterpret_cast(offset(afterTypePtr, sizeof(TypeEncodingDetails::InterfaceDeclarationReferenceDetails))); } + case BinaryTypeEncodingType::InterfaceIndexReference: { + return reinterpret_cast(offset( + afterTypePtr, + sizeof(TypeEncodingDetails::InterfaceIndexReferenceDetails))); + } case BinaryTypeEncodingType::StructDeclarationReference: case BinaryTypeEncodingType::UnionDeclarationReference: { return reinterpret_cast(offset(afterTypePtr, sizeof(TypeEncodingDetails::DeclarationReferenceDetails))); @@ -704,15 +774,30 @@ struct FunctionMeta : Meta { } }; +struct EnumField { + String name; + int64_t value; +}; + struct JsCodeMeta : Meta { private: String _jsCode; public: + /// Enums carry a name/value table here instead of JS source; the two are + /// mutually exclusive, so check this before reading either. + inline bool isEnumTable() const { + return this->flag(MetaFlags::JsCodeIsEnumTable); + } + inline const char* jsCode() const { return _jsCode.valuePtr(); } + + inline const Array* enumFields() const { + return reinterpret_cast*>(_jsCode.valuePtr()); + } }; struct VarMeta : Meta { @@ -788,8 +873,13 @@ struct MethodMeta : MemberMeta { return this->_encodings.valuePtr(); } + // The trailing _constructorTokens slot is only written when the flag is + // set, so it must not be read otherwise — the bytes past _encodings belong + // to whatever the generator emitted next. inline const char* constructorTokens() const { - return this->_constructorTokens.valuePtr(); + return this->flag(MetaFlags::MethodHasConstructorTokens) + ? this->_constructorTokens.valuePtr() + : ""; } bool isImplementedInClass(Class klass, bool isStatic) const; diff --git a/NativeScript/runtime/Metadata.mm b/NativeScript/runtime/Metadata.mm index 7f75c459..298f4184 100644 --- a/NativeScript/runtime/Metadata.mm +++ b/NativeScript/runtime/Metadata.mm @@ -324,8 +324,13 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe MetaFile* MetaFile::instance() { return metaFileInstance; } +static const ClassNameTable* classNamesInstance = nullptr; + +const ClassNameTable* MetaFile::classNames() { return classNamesInstance; } + MetaFile* MetaFile::setInstance(void* metadataPtr) { metaFileInstance = reinterpret_cast(metadataPtr); + classNamesInstance = metaFileInstance->classNamesTable(); return metaFileInstance; } } // namespace tns diff --git a/NativeScript/runtime/MetadataBuilder.mm b/NativeScript/runtime/MetadataBuilder.mm index 43d2a3eb..a1370b38 100644 --- a/NativeScript/runtime/MetadataBuilder.mm +++ b/NativeScript/runtime/MetadataBuilder.mm @@ -134,16 +134,33 @@ NamedPropertyHandlerConfiguration config(MetadataBuilder::GlobalPropertyGetter, info.GetReturnValue().Set(result); } else if (meta->type() == MetaType::JsCode) { const JsCodeMeta* jsCodeMeta = static_cast(meta); - std::string jsCode = jsCodeMeta->jsCode(); - Local