@@ -210,6 +210,9 @@ impl<T: Message, O: Ownership> Id<T, O> {
210210 /// some API, and you would like to ensure that the object stays around
211211 /// so that you can work with it.
212212 ///
213+ /// If said API is a normal Objective-C method, you probably want to use
214+ /// [`Id::retain_autoreleased`] instead.
215+ ///
213216 /// This is rarely used to construct owned [`Id`]s, see [`Id::new`] for
214217 /// that.
215218 ///
@@ -249,6 +252,122 @@ impl<T: Message, O: Ownership> Id<T, O> {
249252 unsafe { Self :: new ( res as * mut T ) }
250253 }
251254
255+ /// Retains a previously autoreleased object pointer.
256+ ///
257+ /// This is useful when calling Objective-C methods that return
258+ /// autoreleased objects, see [Cocoa's Memory Management Policy][mmRules].
259+ ///
260+ /// This has exactly the same semantics as [`Id::retain`], except it can
261+ /// sometimes avoid putting the object into the autorelease pool, possibly
262+ /// yielding increased speed and reducing memory pressure.
263+ ///
264+ /// Note: This relies heavily on being inlined right after [`msg_send!`],
265+ /// be careful not accidentally require instructions between these.
266+ ///
267+ /// [mmRules]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html
268+ ///
269+ /// # Safety
270+ ///
271+ /// Same as [`Id::retain`].
272+ #[ doc( alias = "objc_retainAutoreleasedReturnValue" ) ]
273+ #[ inline( always) ]
274+ pub unsafe fn retain_autoreleased ( ptr : * mut T ) -> Option < Id < T , O > > {
275+ // Add magic nop instruction to participate in the fast autorelease
276+ // scheme.
277+ //
278+ // See `callerAcceptsOptimizedReturn` in `objc-object.h`:
279+ // https://github.com/apple-oss-distributions/objc4/blob/objc4-838/runtime/objc-object.h#L1209-L1377
280+ //
281+ // We will unconditionally emit these instructions, even if they end
282+ // up being unused (for example because we're unlucky with inlining,
283+ // some other work is done between the objc_msgSend and this, or the
284+ // runtime version is too old to support it).
285+ //
286+ // It may seem like there should be a better way to do this, but
287+ // emitting raw assembly is exactly what Clang and Swift does:
288+ // swiftc: https://github.com/apple/swift/blob/swift-5.5.3-RELEASE/lib/IRGen/GenObjC.cpp#L148-L173
289+ // Clang: https://github.com/llvm/llvm-project/blob/889317d47b7f046cf0e68746da8f7f264582fb5b/clang/lib/CodeGen/CGObjC.cpp#L2339-L2373
290+ //
291+ // Resources:
292+ // - https://www.mikeash.com/pyblog/friday-qa-2011-09-30-automatic-reference-counting.html
293+ // - https://www.galloway.me.uk/2012/02/how-does-objc_retainautoreleasedreturnvalue-work/
294+ // - https://github.com/gfx-rs/metal-rs/issues/222
295+ // - https://news.ycombinator.com/item?id=29311736
296+ // - https://stackoverflow.com/a/23765612
297+ //
298+ // SAFETY:
299+ // Based on https://doc.rust-lang.org/stable/reference/inline-assembly.html#rules-for-inline-assembly
300+ //
301+ // We don't care about the value of the register (so it's okay to be
302+ // undefined), and its value is preserved.
303+ //
304+ // nomem: No reads or writes to memory are performed (this `mov`
305+ // operates entirely on registers).
306+ // preserves_flags: `mov` doesn't modify any flags.
307+ // nostack: We don't touch the stack.
308+
309+ // Only worth doing on the Apple runtime.
310+ // Not supported on TARGET_OS_WIN32.
311+ #[ cfg( all( apple, not( target_os = "windows" ) ) ) ]
312+ {
313+ // Supported since macOS 10.7.
314+ #[ cfg( target_arch = "x86_64" ) ]
315+ { } // x86_64 looks at the next call instruction
316+
317+ // Supported since macOS 10.8.
318+ #[ cfg( target_arch = "arm" ) ]
319+ unsafe {
320+ core:: arch:: asm!( "mov r7, r7" , options( nomem, preserves_flags, nostack) )
321+ } ;
322+
323+ // Supported since macOS 10.10.
324+ #[ cfg( target_arch = "aarch64" ) ]
325+ unsafe {
326+ core:: arch:: asm!( "mov fp, fp" , options( nomem, preserves_flags, nostack) )
327+ } ;
328+
329+ // Supported since macOS 10.12.
330+ #[ cfg( target_arch = "x86" ) ]
331+ unsafe {
332+ core:: arch:: asm!( "mov ebp, ebp" , options( nomem, preserves_flags, nostack) )
333+ } ;
334+ }
335+
336+ let ptr = ptr as * mut ffi:: objc_object ;
337+
338+ // SAFETY: Same as `retain`, `objc_retainAutoreleasedReturnValue` is
339+ // just an optimization.
340+ let res = unsafe { ffi:: objc_retainAutoreleasedReturnValue ( ptr) } ;
341+
342+ // Ideally, we'd be able to specify that the above call should never
343+ // be tail-call optimized (become a `jmp` instruction instead of a
344+ // `call`); Rust doesn't really have a way of doing this currently, so
345+ // we just emit a simple `nop` to make such tail-call optimizations
346+ // less likely to occur.
347+ //
348+ // This is brittle! We should find a better solution!
349+ #[ cfg( all( apple, not( target_os = "windows" ) , target_arch = "x86_64" ) ) ]
350+ {
351+ // SAFETY: Similar to above.
352+ unsafe { core:: arch:: asm!( "nop" , options( nomem, preserves_flags, nostack) ) } ;
353+ // TODO: Possibly more efficient alternative?
354+ // #![feature(asm_sym)]
355+ // core::arch::asm!(
356+ // "mov rdi, rax",
357+ // "call {}",
358+ // sym objc2::ffi::objc_retainAutoreleasedReturnValue,
359+ // inout("rax") obj,
360+ // clobber_abi("C"),
361+ // );
362+ }
363+
364+ debug_assert_eq ! (
365+ res, ptr,
366+ "objc_retainAutoreleasedReturnValue did not return the same pointer"
367+ ) ;
368+ unsafe { Self :: new ( res as * mut T ) }
369+ }
370+
252371 #[ inline]
253372 fn autorelease_inner ( self ) -> * mut T {
254373 // Note that this (and the actual `autorelease`) is not an associated
@@ -267,7 +386,6 @@ impl<T: Message, O: Ownership> Id<T, O> {
267386 res as * mut T
268387 }
269388
270- // TODO: objc_retainAutoreleasedReturnValue
271389 // TODO: objc_autoreleaseReturnValue
272390 // TODO: objc_retainAutorelease
273391 // TODO: objc_retainAutoreleaseReturnValue
0 commit comments