[SR-381]: API to lookup a Type given a top-level class name #834
| + foundMetadata = metadata; | ||
| + break; | ||
| + } | ||
| + } | ||
| + } | ||
| + } | ||
| + | ||
| + pthread_mutex_unlock(&C.SectionsToScanLock); | ||
| + | ||
| + return foundMetadata; | ||
| +} | ||
| + | ||
| +extern "C" | ||
| +const Metadata * | ||
| +swift_getTypeByName(const char *typeName, size_t typeNameLength) { | ||
| + std::string name(typeName, typeNameLength); |
|
jckarter
You can pass an |
| @@ -2755,6 +2755,50 @@ swift::swift_conformsToProtocol(const Metadata *type, | ||
| goto recur; | ||
| } | ||
| +/// Return the type metadata for a given type name. | ||
| +/// | ||
| +/// This is a temporary implementation to support some Foundation use cases; | ||
| +/// it will only lookup a type that directly implements a protocol as only | ||
| +/// those types will have conformance table entries. | ||
| +static const Metadata * | ||
| +_metadataForName(const std::string &typeName) { | ||
| + auto &C = Conformances.get(); | ||
| + const Metadata *foundMetadata = nullptr; | ||
| + | ||
| + installCallbacksToInspectDylib(); |
|
jckarter
Maybe we can factor |
| @@ -83,6 +83,23 @@ func _typeName(type: Any.Type, qualified: Bool = true) -> String { | ||
| input: UnsafeBufferPointer(start: stringPtr, count: count)) | ||
| } | ||
| +@_silgen_name("swift_getTypeByName") | ||
| +func _typeByNameImpl( | ||
| + name: UnsafePointer<UInt8>, | ||
| + _ nameLength: UInt) | ||
| + -> Any.Type? | ||
| + | ||
| +/// Returns a metatype given a demangled qualified name and a protocol. | ||
| +/// TODO allow P to be nil; this will require a compiler change |
|
|
| +/// it will only lookup a type that directly implements a protocol as only | ||
| +/// those types will have conformance table entries. | ||
| +static const Metadata * | ||
| +_metadataForName(const std::string &typeName) { | ||
| + auto &C = Conformances.get(); | ||
| + const Metadata *foundMetadata = nullptr; | ||
| + | ||
| + installCallbacksToInspectDylib(); | ||
| + | ||
| + pthread_mutex_lock(&C.SectionsToScanLock); | ||
| + | ||
| + unsigned sectionIdx = 0; | ||
| + unsigned endSectionIdx = C.SectionsToScan.size(); | ||
| + | ||
| + // TODO: implement typeName->metadata cache | ||
| + // TODO: support looking up types for which there is no conformance entry |
|
jckarter
You should also note that this doesn't handle generic or non-nominal types yet (which is fine, for a start). |
| @@ -2399,6 +2399,8 @@ namespace { | ||
| }; | ||
| } | ||
| +static void installCallbacksToInspectDylib(); |
|
lhoward
This change makes installCallbacksToInspectDylib() deadlock when Conformances.get() is called from swift_registerProtocolConformances(), I think because Lazy hasn't initialised yet.
jckarter
Interesting, we've had reports of similar deadlocks with the code as it is today that might have the same underlying cause. |
| @@ -2755,6 +2755,51 @@ swift::swift_conformsToProtocol(const Metadata *type, | ||
| goto recur; | ||
| } | ||
| +/// Return the type metadata for a given type name. | ||
| +/// | ||
| +/// This is a temporary implementation to support some Foundation use cases; | ||
| +/// it will only lookup a type that directly implements a protocol as only | ||
| +/// those types will have conformance table entries. | ||
| +static const Metadata * | ||
| +_metadataForName(const llvm::StringRef &typeName) { | ||
| + installCallbacksToInspectDylib(); |
|
lhoward
I left installCallbacksToInspectDylib() in both places for now, as it deadlocks when called from the Conformances constructor because the Lazy container is still being initialised. |
| @@ -2755,6 +2755,51 @@ swift::swift_conformsToProtocol(const Metadata *type, | ||
| goto recur; | ||
| } | ||
| +/// Return the type metadata for a given type name. | ||
| +/// | ||
| +/// This is a temporary implementation to support some Foundation use cases; | ||
| +/// it will only lookup a type that directly implements a protocol as only | ||
| +/// those types will have conformance table entries. | ||
| +static const Metadata * | ||
| +_metadataForName(const llvm::StringRef &typeName) { |
|
|
| @@ -2755,6 +2755,51 @@ swift::swift_conformsToProtocol(const Metadata *type, | ||
| goto recur; | ||
| } | ||
| +/// Return the type metadata for a given type name. | ||
| +/// | ||
| +/// This is a temporary implementation to support some Foundation use cases; | ||
| +/// it will only lookup a type that directly implements a protocol as only | ||
| +/// those types will have conformance table entries. | ||
| +static const Metadata * | ||
| +_metadataForName(const llvm::StringRef typeName) { | ||
| + installCallbacksToInspectDylib(); |
|
jckarter
In d0be84e I refactored the conformance cache initialization to include installing the callbacks without deadlocking (that I could see). |
| @@ -2755,6 +2755,49 @@ swift::swift_conformsToProtocol(const Metadata *type, | ||
| goto recur; | ||
| } | ||
| +/// Return the type metadata for a given type name. | ||
| +/// | ||
| +/// This is a temporary implementation to support some Foundation use cases; | ||
| +/// it will only lookup a type that directly implements a protocol as only | ||
| +/// those types will have conformance table entries. | ||
| +static const Metadata * | ||
| +_metadataForName(const llvm::StringRef typeName) { |
|
lhoward
Should we have a SWIFT_OBJC_INTEROP case that calls objc_lookUpClass()? Is it safe to static cast a Class to a Metadata or does it need to be wrapped? |
| + break; | ||
| + } | ||
| + } | ||
| + } | ||
| + } | ||
| + | ||
| + pthread_mutex_unlock(&C.SectionsToScanLock); | ||
| + | ||
| + return foundMetadata; | ||
| +} | ||
| + | ||
| +extern "C" | ||
| +const Metadata * | ||
| +swift_getTypeByName(const char *typeName, size_t typeNameLength) { | ||
| + llvm::StringRef name(typeName, typeNameLength); | ||
| + return _metadataForName(name); |
|
|
| @@ -83,6 +83,22 @@ func _typeName(type: Any.Type, qualified: Bool = true) -> String { | ||
| input: UnsafeBufferPointer(start: stringPtr, count: count)) | ||
| } | ||
| +@_silgen_name("swift_getTypeByName") | ||
| +func _typeByNameImpl( | ||
| + name: UnsafePointer<UInt8>, | ||
| + _ nameLength: UInt) | ||
| + -> Any.Type? | ||
| + | ||
| +/// Returns a metatype given a demangled qualified name | ||
| +@warn_unused_result | ||
| +public // @testable | ||
| +func _typeByName(name: String) -> Any.Type? { |
|
|
| @@ -83,6 +83,22 @@ func _typeName(type: Any.Type, qualified: Bool = true) -> String { | ||
| input: UnsafeBufferPointer(start: stringPtr, count: count)) | ||
| } | ||
| +@_silgen_name("swift_getTypeByName") | ||
| +func _typeByNameImpl( | ||
| + name: UnsafePointer<UInt8>, | ||
| + _ nameLength: UInt) | ||
| + -> Any.Type? | ||
| + | ||
| +/// Returns a metatype given a demangled qualified name | ||
| +@warn_unused_result | ||
| +public // @testable |
|
|
| + if (auto metadata = record.getCanonicalTypeMetadata()) { | ||
| + auto N = nameForMetadata(metadata, true); | ||
| + if (N == typeName) { | ||
| + foundMetadata = metadata; | ||
| + break; | ||
| + } | ||
| + } | ||
| + } | ||
| + } | ||
| + | ||
| + pthread_mutex_unlock(&C.SectionsToScanLock); | ||
| + | ||
| +#if SWIFT_OBJC_INTEROP | ||
| + if (foundMetadata == nullptr) { | ||
| + foundMetadata = | ||
| + static_cast<const Metadata *>(objc_lookUpClass(typeName.c_str()); |
|
lhoward
Accidental commit of this, haven't tested on OS X yet. Is this desired functionality? |
| @@ -665,6 +665,20 @@ Runtime.test("typeName") { | ||
| expectEqual("protocol<>.Protocol", _typeName(a.dynamicType)) | ||
| } | ||
| +protocol SomeProtocol {} | ||
| +extension SomeClass : SomeProtocol {} | ||
| +extension SomeStruct : SomeProtocol {} | ||
| +extension SomeEnum : SomeProtocol {} | ||
| + | ||
| +Runtime.test("typeByName") { |
|
|
| + case Node::Kind::BoundGenericEnum: | ||
| + case Node::Kind::BoundGenericStructure: | ||
| + foundMetadata = _metadataForBoundGenericNode(C, childNode); | ||
| + break; | ||
| + default: | ||
| + foundMetadata = _metadataForTypeNode(C, childNode); | ||
| + break; | ||
| + } | ||
| + | ||
| + return foundMetadata; | ||
| +} | ||
| + | ||
| +/// Return the type metadata for a given mangled (_Tt) name. | ||
| +static const Metadata * | ||
| +_metadataForMangledName(const llvm::StringRef mangledName) { | ||
| + if (!mangledName.startswith("_Tt")) |
|
jckarter
This API only works with types. AFAICS there's no reason to require or check for the |
| @@ -2755,6 +2805,165 @@ swift::swift_conformsToProtocol(const Metadata *type, | ||
| goto recur; | ||
| } | ||
| +static const Metadata * | ||
| +_metadataForNode(const ConformanceState &C, const NodePointer node); | ||
| + | ||
| +// returns true if the generic pattern matches the template type name; | ||
| +// note that this will only work for public symbols, but it is better | ||
| +// than nothing (or perhaps it isn't). | ||
| +static bool | ||
| +_genericPatternMatchesTemplate(const void *genericPattern, | ||
| + llvm::StringRef templateName) { | ||
| + Dl_info info; | ||
| + | ||
| + if (!dladdr(genericPattern, &info) || info.dli_sname == NULL) |
|
jckarter
Metadata patterns for generic types are in the protocol conformance table too. We shouldn't rely on the symbol table for anything.
lhoward
I couldn't find a way to get the pattern name from the conformance table (even the existing
jckarter
It's a bit hokey to get at it, but there should be a pointer to the nominal type descriptor somewhere in the pattern itself. It might be worth adding a more direct name reference to the template somewhere. |
| +/// This contains enough static information to recover type metadata from a | ||
| +/// name. It is only emitted for types that do not have an explicit protocol | ||
| +/// conformance record. | ||
| +/// | ||
| +/// This structure is notionally a subtype of a protocol conformance record | ||
| +/// but as we cannot change the conformance record layout we have to make do | ||
| +/// with some duplicated code. | ||
| +struct TypeMetadataRecord { | ||
| +private: | ||
| + // Some description of the type that is resolvable at runtime. | ||
| + union { | ||
| + /// A direct reference to the metadata. | ||
| + RelativeIndirectablePointer<Metadata> DirectType; | ||
| + | ||
| + /// An indirect reference to the metadata. | ||
| + RelativeIndirectablePointer<const ClassMetadata *> IndirectClass; |
|
jckarter
We'd only ever emit type metadata records for types in the current object file, so the indirectability isn't necessary. There should also be records for generic metadata patterns.
lhoward
We support generic types by registering them from swift_getGenericMetadata(), although I suppose we could emit a reference to the type metadata accessor for the specialised type instead, in case it hasn't been instantiated yet. Is that what you were suggesting? If you mean patterns that are specialised dynamically by the name being looked up, I wasn't sure how to do this and also validate any protocol requirements on the type. I'm probably a bit confused... |
| @@ -25,7 +25,10 @@ _buildDemanglingForNominalType(Demangle::Node::Kind boundGenericKind, | ||
| typeBytes + sizeof(void*) * description->GenericParams.Offset); | ||
| for (unsigned i = 0, e = description->GenericParams.NumPrimaryParams; | ||
| i < e; ++i, ++genericParam) { | ||
| - typeParams->addChild(_swift_buildDemanglingForMetadata(*genericParam)); | ||
| + auto demangling = _swift_buildDemanglingForMetadata(*genericParam); |
|
lhoward
Only on Linux with an Optional of MetadataKind::Opaque. I didn't investigate further as _swift_buildDemanglingForMetadata() suggests this is expected. |
| + | ||
| +/// Return the type metadata for a given mangled name, used in the | ||
| +/// implementation of _typeByName(). The human readable name returned | ||
| +/// by swift_getTypeName() is non-unique, so we used mangled names | ||
| +/// internally. | ||
| +extern "C" | ||
| +const Metadata * | ||
| +swift_getTypeByMangledName(const char *typeName, size_t typeNameLength) { | ||
| + llvm::StringRef name(typeName, typeNameLength); | ||
| + return _typeByMangledName(name); | ||
| +} | ||
| + | ||
| +/// Register a single type metadata instance in the name lookup cache, used | ||
| +/// to register instantiated generic types. | ||
| +void | ||
| +swift::_registerTypeMetadata(const Metadata *metadata) { |
|
jckarter
Can this be static? It doesn't look like it's used outside of this file.
lhoward
_registerTypeMetadata() is called when patterns are instantiated by the type metadata accessor for the specialised type (see comment above).
jckarter
I don't think that's a good approach. The table should statically reference the generic type metadata pattern, and the runtime can use that to instantiate a generic type when needed.
lhoward
I did originally implement that approach but it blew up when you tried to instantiate a generic type where the arguments had type requirements. Is it useful to dynamically specialise a type given a name? And if so, how do you do it safely? Or – by statically referencing the type metadata pattern do you mean referencing the metadata accessor for a specialised type? (i.e. the code emitted by the compiler that calls swift_getGenericMetadata())
lhoward
Given some specialised name N where some of the pattern arguments have type requirements, I'm not sure how you get (and correctly order) the witness table references that are ultimately passed by swift_getGenericMetadata() to the pattern's CreateFunction(). Let's say I want to instantiate Dictionary at runtime. I can find the pattern for Dictionary, and the type metadata for String. But I also need the protocol witness table for Hashable, and moreover, I don't know I need that given a name like "GVs10DictionarySSSS_". What am I missing?
jckarter
Referring to accessor functions from these tables is ultimately the right resilient solution for both nongeneric and generic types. I feel like we should do that as a separate stage though. For now, all the information you need to form a getGenericMetadata call from a pattern ought to be available from its nominal type descriptor (though even that's hard to recover, as you've discovered).
lhoward
via email
Sure. If I can't figure out how to get the NTD and witness tables from a pattern I'll remove generic support. (After all the public API is limited to top level classes only.) But it'd be nice to get the plumbing complete from the start.
…
lhoward
So – and thanks for your patience with me as I navigate around this code – are you suggesting we emit generic pattern metadata records for unbound generic types (so that they can be bound when _typeByName() is called)? Or only for types that have been specialised at compile time? If the latter, wouldn't it be simpler to emit references to the type metadata accessor for the specialised type rather than the pattern? If the former then, OK, I will keep digging :)
jckarter
It might be best to do nothing for generic types for now. I think we're going to be changing how this all works soon.
lhoward
Another question: when/where would a bound generic type be registered with the ObjC runtime? Can we employ a similar strategy for the non-ObjC case?
jckarter
The generic metadata pattern's instantiation function handles that when you call getGenericMetadata.
lhoward
So, playing devil's advocate: why not register the name to metadata mapping for an instantiated generic type from
jckarter
That means every generic instantiation would immediately warm up the type lookup cache, when it could be done more lazily. It also adds more memory overhead to every generic instantiation. Type lookup isn't a fast path, so we should optimize for memory efficiency. As long as name lookup can eventually find enough artifacts to instantiate a generic, whether that be the pattern or an accessor function, that should be sufficient.
lhoward
Thanks for the explanation Joe. I think using the accessor function would be simpler, I was concerned that one wouldn't be emitted if you didn't create an instance of the specialised type but:
does appear to emit it.
lhoward
I'll push to remove the generic support and if I can find an easy way to emit a reference to the accessor function for generics as an additional patch, I'll do that. Thanks again for taking the time to explain all of this to me. |
I believe getsectiondata is expensive on Darwin. I think it might be better to collect the type metadata and conformance records under one section/symbol and register them together than to keep them separate.
I can take a look at coalescing the two once the rest is fixed. Maybe it's simpler to encode the type metadata record into a protocol conformance record (even though it wastes a word or two).
@jckarter – OK, I have some code working to dynamically instantiate generic types. (I figured out how to get the nominal type descriptor from the pattern.)
Where I am stuck is – for generic patterns with associated type requirements, how do I get the corresponding protocols? If I can get these, I can call swift_conformsToProtocol() to validate the dynamic pattern and grab the witness tables. I'm sure I'm missing something obvious as usual!
I note that struct GenericParameterDescriptor has a note about including protocols in the parameter array.
I want to do:
for (size_t i = 0; i < ntd->GenericParams.NumPrimaryParams; i++) {
auto metadata = findMetadataForNode(typeListNode->getChild(i));
arguments[i] = const_cast<void *>(reinterpret_cast<const void *>(metadata));
if (arguments[i] == nullptr) {
delete arguments;
return nullptr;
}
for (size_t j = 0;
j < ntd->GenericParams.Parameters[i].NumWitnessTables; j++) {
void **witnessPointer = &arguments[witnessIndex + j];
// if there are associated type requirements then check this argument conforms
// and slam in the witness tables for the requirement
}
witnessIndex += ntd->GenericParams.Parameters[i].NumWitnessTables;
}
auto metadata =
swift::swift_getGenericMetadata(const_cast<GenericMetadata *>(generic),
arguments);
OK, I got lookup of generics with associated type requirements working by extending GenericParameterDescriptor to include the ProtocolDescriptors for any type constraints that require witness tables. See the comment in Metadata.h for more information.
Can you separate the generic instantiation stuff into its own pull request, so we can get the basic nongeneric functionality in?
| @@ -130,7 +130,9 @@ enum class ProtocolConformanceTypeKind : unsigned { | ||
| /// platforms, the class object always is the type metadata. | ||
| UniqueDirectClass = 0xF, | ||
| }; | ||
| - | ||
| + | ||
| +typedef TypeMetadataRecordKind ProtocolConformanceTypeKind; |
|
jckarter
Instead of the typedef here, can we update uses of |
| @@ -2125,6 +2125,54 @@ struct GenericWitnessTable { | ||
| void *PrivateData[swift::NumGenericMetadataPrivateDataWords]; | ||
| }; | ||
| +/// The structure of a type metadata record. | ||
| +/// | ||
| +/// This contains enough static information to recover type metadata from a | ||
| +/// name. It is only emitted for types that do not have an explicit protocol | ||
| +/// conformance record. | ||
| +/// | ||
| +/// This structure is notionally a subtype of a protocol conformance record | ||
| +/// but as we cannot change the conformance record layout we have to make do | ||
| +/// with some duplicated code. | ||
| +struct TypeMetadataRecord { | ||
| +private: | ||
| + // Some description of the type that is resolvable at runtime. | ||
| + union { | ||
| + /// A direct reference to the metadata. | ||
| + RelativeIndirectablePointer<Metadata> DirectType; |
|
jckarter
This doesn't need to be indirectable. We'll only ever emit these records for types in the current object file. |
| @@ -644,6 +665,31 @@ void IRGenModule::addProtocolConformanceRecord( | ||
| ProtocolConformances.push_back(conformance); | ||
| } | ||
| +static bool | ||
| +protocolConformanceExistsForType( | ||
| + SmallVectorImpl<NormalProtocolConformance *> &ProtocolConformances, CanType type) { | ||
| + for (auto *conformance : ProtocolConformances) { | ||
| + auto conformanceType = conformance->getType()->getCanonicalType(); |
|
jckarter
This is pretty inefficient. You should be able to ask a |
| + var name = "C" | ||
| + if components[0] == "Swift" { | ||
| + name += "Ss" | ||
| + } else { | ||
| + name += String(components[0].characters.count) + components[0] | ||
| + } | ||
| + name += String(components[1].characters.count) + components[1] | ||
| + | ||
| + let nameUTF8 = Array(name.utf8) | ||
| + return nameUTF8.withUnsafeBufferPointer { (nameUTF8) in | ||
| + let type = _getTypeByMangledName(nameUTF8.baseAddress, | ||
| + UInt(nameUTF8.endIndex)) | ||
| + | ||
| + return type | ||
| + } | ||
| +} |
|
jckarter
I think |
| @@ -27,7 +27,6 @@ if(SWIFT_HOST_VARIANT MATCHES "${SWIFT_DARWIN_VARIANTS}") | ||
| set(swift_runtime_objc_sources | ||
| ErrorObject.mm | ||
| SwiftObject.mm | ||
| - Remangle.cpp |
|
jckarter
Is |
Because there is no pattern support in this branch, there is no support for resilient non-generic type lookup. I can fix this but it will involve back-porting some code from the generic branch, but I will keep it as a separate commit in case you wish to back it out.
Argh. I've broken something and I can't build Foundation without getting duplicate symbol errors (like __TFV15SwiftFoundation18NSTextCheckingTypeg8rawValueVs6UInt64).
Seems to have disappeared on both branches, all I did was a rebase of swift against master and rebuild. I will investigate more tomorrow to see if I can figure out exactly what the cause was. Validating on Linux should be a good confirmation all is well.
Update: all good on Linux. Heisenbug I guess.
I appreciate your enthusiasm, and thanks for bearing with my nitpicks, but continuously adding functionality makes it hard to converge on review here. The metadata structures are in a state of flux too, so I'm wary of building too much on what we have now. I'd like to focus on just making sure the corelibs' work to get to parity with Darwin is sufficiently unblocked for now. If you add any new functionality, it'd be helpful if you do so in separate pull requests. Thanks!
| @@ -644,6 +665,42 @@ void IRGenModule::addProtocolConformanceRecord( | ||
| ProtocolConformances.push_back(conformance); | ||
| } | ||
| +static bool | ||
| +typeHasExplicitProtocolConformance(CanType type) { | ||
| + auto conformances = | ||
| + type->getAnyNominal()->getAllConformances(); |
|
jckarter
Might want to early-exit
lhoward
Fixed, I think we want to return true (non-nominal types should not have a type metadata record?). Arguably OK, I think having the caller do the check and changing the signature to |
Re: general comment, understood. I did keep the resilient top-level class support in a separate commit in this PR. I'm still learning about Swift and the compiler internals, so part of the issue from my end is that I didn't really understand resilient (non-generic) types until yesterday – but when I did, I thought that it should be in scope for top-level class-by-name resolution.
Implement a
_typeByName(name: String) -> Any.Type?function, currently explicitly limited to top-level classes.Type metadata for types that do not explicitly conform to a protocol is emitted in a new section,
.swift2_type_metadata. The runtime searches both this section and the protocol conformance table.Whilst implemented on top of an internal API that looks up a type given a mangled name (in order to assure that we are indeed looking up a class), no public Swift APIs are defined that expose the name mangling format.
This is to support [NS-377] Foundation component NSKeyedUnarchiver.
Update: separate generic instantiation into a separate pull request.