Is it possible to get a static identifier to a generic type?

I'm writing some code that uses ObjectIdentifiers to store identifiers for types:

struct Box<T> { }
struct Value { }

let id = ObjectIdentifier(Box<Value>.self)

Unfortunately, every time ObjectIdentifier is called on a generic type it takes a trip through __swift_instantiateConcreteTypeFromMangledName to dynamically generate type information, even though all the types involved are in the same module:

main:
        push    rax
        lea     rdi, [rip + (demangling cache variable for type metadata for output.Box<output.Value>)]
        call    __swift_instantiateConcreteTypeFromMangledName
        mov     qword ptr [rip + (output.id : Swift.ObjectIdentifier)], rax
        xor     eax, eax
        pop     rcx
        ret

I'm generating these ObjectIdentifers fairly often for safety reasons, so I end up taking quite a performance penalty. I'm assuming this is basically for space-saving reasons — it would explode the binary if metadata for every possible permutation of a generic type was generated, though it would be nice to get them on a one-off basis.

Anyway: is it possible to get an ObjectIdentifier for generic types as quickly as they can be gotten for in-module non-generic ones?

1 Like

Probably -prespecialize-generic-metadata is what you are looking for. Compiler Explorer

3 Likes

Thanks, that looks pretty handy!

  1. Is there any reason to not use this in a publicly consumable library? (Experimental, internal, etc...)
  2. Is there any way to get this without using this flag?

It can significantly increase the size of your binary if you're not careful. It should not have a practical impact on your ABI though as external modules still need to go through the Swift runtime to reference your generic types (assuming library evolution is enabled.)

1 Like

This feature is used in the standard library, but it should be regarded as experimental elsewhere. Note also that it only results in the prespecialization of metadata which are statically determined to be needed in functions that are emitted; no profiling of your program to see which metadata are used occurs.

2 Likes

Note that __swift_instantiateConcreteTypeFromMangledName also caches the result in a unique variable per type referenced, so should be fairly fast after the first invocation instantiates and saves the metadata pointer.

1 Like

That's what I was counting on, but it turns out when you call it 5,000,000 times in a loop the cache retrieval overhead adds up, and unfortunately I'm not able to hoist it out of the loop, and generics don't support static storage, so I can't put it there either :thinking:

What I do when I need static storage in a generic context is to reach for a dictionary instead, keyed by the generic type. (Since metatypes aren't hashable, use ObjectIdentifier to cast them appropriately.)

Thread-safety is an exercise for the reader.

2 Likes

That seems to be the pattern that @mattcurtis is already implementing; it’s the creation of the ObjectIdentifier from the metatype instance that for some reason hits the runtime.

1 Like

This is a generally helpful tip yeah, thanks. If I recall correctly in this particular case Dictionary was actually slower than the cache retrieval __swift_instantiateConcreteTypeFromMangledName already does... but I'm sure there are instances where the approach you describe helps.

Isn't it always :slight_smile:

Correct. I think I expected, at least in cases where all types are visible or from the same module, that specialization would occur and prevent the runtime hit. But apparently not.

Understood now!

It is entirely unsurprising that it hits the runtime: ObjectIdentifier is basically just "safe" syntactic sugar over casting a metatype to an UnsafeRawPointer and then hashing its bits as needed. But in order to cast a metatype to a pointer, you need a unique metatype value, which for a dynamically realized type such as a generic specialization or composite protocol existential… requires creating one on first use in the runtime. Waaah waaah. :trumpet:

2 Likes