libobjc2
libobjc2 copied to clipboard
objc_constructInstance and objc_destructInstance are unimplemented
The Apple runtime has the following methods for instantiating and destroying an instance of a class at an already-allocated memory location.
id objc_constructInstance(Class cls, void *bytes); (This mirrors class_createinstance)
void * objc_destructInstance(id obj);
These are unimplemented in the GNUstep runtime and are used in Swift at the following locations: https://github.com/apple/swift/blob/01823ca52138a9844e84ee7e8efba13970e1e25d/stdlib/public/runtime/SwiftValue.mm#L191
https://github.com/apple/swift/blob/01823ca52138a9844e84ee7e8efba13970e1e25d/stdlib/public/runtime/SwiftValue.mm#L285
These look like terrible APIs. Presumably it's the responsibility of the caller to ensure that there's enough space, but how does it know how much space the runtime will use? The GNUstep runtime will use the object size plus one word for the refcount. Is this used only for immutable non-releaseable classes? If so, we can implement that without a refcount (there's sufficient logic for that already). If they're embedded in other objects, presumably they don't get separate refcounts?
Presumably it's the responsibility of the caller to ensure that there's enough space
Yes, this appears to be the case.
but how does it know how much space the runtime will use?
According to the documentation here (https://developer.apple.com/documentation/objectivec/1441663-objc_constructinstance?language=objc):
bytes must point to at least class_getInstanceSize(cls) bytes of well-aligned, zero-filled memory.
Is this used only for immutable non-releaseable classes?
Swift only uses it with the SwiftValue class (the above source file), which is immutable and non-releaseable. I don't know of anywhere else where it may be used.
Oh I missed one additional use, which is headlined with the comment
// We need to let the ObjC runtime clean up any associated objects or weak
// references associated with this object.
at https://github.com/apple/swift/blob/01823ca52138a9844e84ee7e8efba13970e1e25d/stdlib/public/runtime/HeapObject.cpp#L594 .
This seems to be part of the implementation of -dealloc.
That last use appears to be very dangerous. A weak reference can be turned into a strong reference at any point. If one thread tries to dealloc the object and another tries to turn the weak ref into a strong ref, then there needs to be some synchronisation. Since objc_destructInstance can't fail (or, at least, Swift doesn't check it for failure), this looks dangerous (though in keeping with Swift's complete lack of a sane concurrency model).
Indeed, it looks like Swift does not have a concurrency model and encourages programmers to rely on OS abstractions for concurrency (see https://gist.github.com/lattner/31ed37682ef1576b16bca1432ea9f782).
It looks like the last use was added to deal with associated objects set using objc_setAssociatedObject — see https://github.com/apple/swift/commit/0885c5617e6adc39f816dd4512381ad6b6fd086a for the full commit where it was added and the associated test. Perhaps this is not the best way to clean it up and it would make more sense that this be improved Swift-side?
Perhaps this is not the best way to clean it up and it would make more sense that this be improved Swift-side?
e.g. using objc_removeAssociatedObjects instead.
Possibly. There's still the question of what to do if you have a Swift object that wraps an Objective-C object and something takes a weak reference to it. Calling objc_removeAssociatedObjects would remove the associated objects, but leave the weak object. You need to order the destruction so that:
- Weak references are destroyed.
- The object's
-deallocmethod is called. - The object's memory is deallocated.
objc_release does all of this correctly for objects that have their lifetimes managed by ARC. If Swift is managing the lifetime of the object then it needs hooks into the runtime that allow it to do the same sort of dance. The runtime does expose a objc_delete_weak_refs hook, but it is probably only safe to use for objects that are reference counted by ARC (which means that they have their refcount in the object header).
I think that the way we could support this is probably:
- If a class supports fast ARC and is passed to this API, create a hidden class that is marked as not supporting fast ARC, which adds
-retain\-releasemethods that store the refcount in a look-aside table. - Set the isa pointer to that (
objc_getClasswill return the correct thing in the presence of hidden classes). - Use the
_objc_weak_loadhook to see if an object that is being loaded corresponds to a Swift object that is being deallocated and handle it correctly.
I'm happy to take a PR that does this. I suspect that there's some more subtle interaction with Swift here though. How are Swift objects allocated? Do they use objc_createInstance and do they use the ARC functions for refcounting? Are Objective-C objects that are embedded in Swift objects always stored at a fixed offset? If so, then we could add another flag to indicate that an Objective-C object is being used inside a Swift object and teach the ARC functions how to find the enclosing object's refcount. Is any of this documented anywere? I'm not particularly keen on implementing APIs that leak implementation details of the runtime (especially ones that leak implementation details of the Apple runtime that we're having to emulate) without properly understanding how they're used.
Additionally, should the retain / release mechanism for these objects actually manipulate the enclosing object? We could have a much simpler API along the lines of this:
id objc_createEmbeddedInstance(id enclosingObject, Class cls, ptrdiff_t offset);
void objc_destroyEmbeddedInstance(id enclosingObject, ptrdiff_t offset);
The first of these would create an Objective-C object that is physically embedded within enclosingObject and set it up so that its reference count manipulations are all forwarded to the enclosing object. Would Swift be willing to use these APIs if they existed? Or does Swift already define retain / release / autorelease methods on these objects so that they will forward to some Swift equivalents?