@@ -274,12 +274,82 @@ impl<T: Message, O: Ownership> Id<T, O> {
274274
275275 /// TODO
276276 #[ doc( alias = "objc_retainAutoreleasedReturnValue" ) ]
277+ // This relies heavily on being inlined right after `objc_msgSend`.
277278 #[ inline( always) ]
278279 pub unsafe fn retain_autoreleased ( ptr : NonNull < T > ) -> Id < T , O > {
279- // SAFETY: Same as `retain`, `objc_retainAutoreleasedReturnValue` is
280- // just an optimization.
280+ // Not supported on TARGET_OS_WIN32
281+ #[ cfg( all( apple, not( target_os = "windows" ) ) ) ]
282+ {
283+ // Add magic nop instruction to participate in the fast
284+ // autorelease scheme.
285+ //
286+ // We will unconditionally emit these instructions, even if they
287+ // end up being unused (for example because we're unlucky with
288+ // inlining, some other work is done between the objc_msgSend and
289+ // this, or the runtime version is too old to support it).
290+ //
291+ // See `callerAcceptsOptimizedReturn` in `objc-object.h`:
292+ // https://github.com/apple-oss-distributions/objc4/blob/objc4-838/runtime/objc-object.h#L1209-L1377
293+ // and this StackOverflow answer for some background on why the
294+ // design is like it is: https://stackoverflow.com/a/23765612.
295+ //
296+ // It may seem like there should be a better way to do this, but
297+ // emitting raw assembly is exactly what Clang and Swift does:
298+ // swiftc: https://github.com/apple/swift/blob/swift-5.5.3-RELEASE/lib/IRGen/GenObjC.cpp#L148-L173
299+ // Clang: https://github.com/llvm/llvm-project/blob/889317d47b7f046cf0e68746da8f7f264582fb5b/clang/lib/CodeGen/CGObjC.cpp#L2339-L2373
300+ //
301+ // SAFETY:
302+ // Based on https://doc.rust-lang.org/stable/reference/inline-assembly.html#rules-for-inline-assembly
303+ //
304+ // We don't care about the value of the register (so it's okay to
305+ // be undefined), and its value is preserved.
306+ //
307+ // nomem: No reads or writes to memory are performed (this `mov`
308+ // operates entirely on registers).
309+ // preserves_flags: `mov` doesn't modify any flags.
310+ // nostack: We don't touch the stack.
311+
312+ // Supported since macOS 10.7.
313+ #[ cfg( target_arch = "x86_64" ) ]
314+ { } // x86_64 looks at the next call instruction
315+
316+ // Supported since macOS 10.8.
317+ #[ cfg( target_arch = "arm" ) ]
318+ unsafe {
319+ core:: arch:: asm!( "mov r7, r7" , options( nomem, preserves_flags, nostack) )
320+ } ;
321+
322+ // Supported since macOS 10.10.
323+ #[ cfg( target_arch = "aarch64" ) ]
324+ unsafe {
325+ core:: arch:: asm!( "mov fp, fp" , options( nomem, preserves_flags, nostack) )
326+ } ;
327+
328+ // Supported since macOS 10.12.
329+ #[ cfg( target_arch = "x86" ) ]
330+ unsafe {
331+ core:: arch:: asm!( "mov ebp, ebp" , options( nomem, preserves_flags, nostack) )
332+ } ;
333+ }
281334
282335 let ptr = ptr. as_ptr ( ) as * mut objc_sys:: objc_object ;
336+
337+ // objc_autoreleaseReturnValue / objc_retainAutoreleasedReturnValue:
338+
339+ // #![feature(asm_sym)]
340+ // #[cfg(target_arch = "x86_64")]
341+ // unsafe {
342+ // core::arch::asm!(
343+ // "mov rdi, rax",
344+ // "call {}",
345+ // sym objc2::ffi::objc_retainAutoreleasedReturnValue,
346+ // inout("rax") obj,
347+ // clobber_abi("C"),
348+ // );
349+ // }
350+
351+ // SAFETY: Same as `retain`, `objc_retainAutoreleasedReturnValue` is
352+ // just an optimization.
283353 let res = unsafe { objc_sys:: objc_retainAutoreleasedReturnValue ( ptr) } ;
284354 debug_assert_eq ! (
285355 res, ptr,
0 commit comments