Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

objc_constructInstance and objc_destructInstance are unimplemented #121

Open
d235j opened this issue Aug 7, 2019 · 8 comments
Open

objc_constructInstance and objc_destructInstance are unimplemented #121

d235j opened this issue Aug 7, 2019 · 8 comments

Comments

@d235j
Copy link

d235j commented Aug 7, 2019

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

@davidchisnall
Copy link
Member

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?

@d235j
Copy link
Author

d235j commented Aug 8, 2019

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.

@d235j
Copy link
Author

d235j commented Aug 8, 2019

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.

@davidchisnall
Copy link
Member

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).

@d235j
Copy link
Author

d235j commented Aug 9, 2019

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 swiftlang/swift@0885c56 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?

@d235j
Copy link
Author

d235j commented Aug 9, 2019

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.

@davidchisnall
Copy link
Member

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:

  1. Weak references are destroyed.
  2. The object's -dealloc method is called.
  3. 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:

  1. 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 \ -release methods that store the refcount in a look-aside table.
  2. Set the isa pointer to that (objc_getClass will return the correct thing in the presence of hidden classes).
  3. Use the _objc_weak_load hook 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.

@davidchisnall
Copy link
Member

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants