Skip to content

Commit

Permalink
Merge pull request #462 from madsmtm/new-objc-features
Browse files Browse the repository at this point in the history
New Objective-C runtime features
  • Loading branch information
madsmtm authored Jul 29, 2023
2 parents 4cb7772 + 1947278 commit 282188d
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 6 deletions.
14 changes: 14 additions & 0 deletions crates/objc2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ unstable-c-unwind = []
# For better documentation on docs.rs
unstable-docsrs = []

# Enable some new features available on ARM64 on:
# - macOS 13.0
# - iOS 16.0
# - tvOS 16.0
# - watchOS 9.0
#
# See https://developer.apple.com/videos/play/wwdc2022/110363/ for an overview
# of the features.
#
# Currently untested, might be unsound or lead to confusing compiler errors.
#
# Additionally, the message sending improvements is not yet implemented.
unstable-apple-new = ["apple"]

# Runtime selection. See `objc-sys` for details.
apple = ["objc-sys/apple"]
gnustep-1-7 = ["objc-sys/gnustep-1-7"]
Expand Down
6 changes: 3 additions & 3 deletions crates/objc2/src/rc/allocated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use core::marker::PhantomData;
use core::mem::{self, ManuallyDrop};
use core::ptr::NonNull;

use crate::ffi;
use crate::runtime::objc_release_fast;
use crate::Message;

/// A marker type that can be used to indicate that the object has been
Expand Down Expand Up @@ -77,8 +77,8 @@ impl<T: ?Sized> Drop for Allocated<T> {
// destructors are written to take into account that the object may
// not have been initialized.
//
// Rest is same as `Id`.
unsafe { ffi::objc_release(self.ptr.as_ptr().cast()) };
// Rest is same as `Id`'s `Drop`.
unsafe { objc_release_fast(self.ptr.as_ptr().cast()) };
}
}

Expand Down
13 changes: 10 additions & 3 deletions crates/objc2/src/rc/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use core::ptr::{self, NonNull};

use super::AutoreleasePool;
use crate::mutability::{IsIdCloneable, IsMutable};
use crate::runtime::{objc_release_fast, objc_retain_fast};
use crate::{ffi, ClassType, Message};

/// A reference counted pointer type for Objective-C objects.
Expand Down Expand Up @@ -321,7 +322,7 @@ impl<T: Message> Id<T> {
#[inline]
pub unsafe fn retain(ptr: *mut T) -> Option<Id<T>> {
// SAFETY: The caller upholds that the pointer is valid
let res: *mut T = unsafe { ffi::objc_retain(ptr.cast()) }.cast();
let res: *mut T = unsafe { objc_retain_fast(ptr.cast()) }.cast();
debug_assert_eq!(res, ptr, "objc_retain did not return the same pointer");
// SAFETY: We just retained the object, so it has +1 retain count
unsafe { Self::new(res) }
Expand Down Expand Up @@ -404,8 +405,14 @@ impl<T: Message> Id<T> {
};

// Supported since macOS 10.10.
#[cfg(target_arch = "aarch64")]
//
// On macOS 13.0 / iOS 16.0 / tvOS 16.0 / watchOS 9.0, the runtime
// instead checks the return pointer address, so we no longer need
// to emit these extra instructions, see this video from WWDC22:
// https://developer.apple.com/videos/play/wwdc2022/110363/
#[cfg(all(target_arch = "aarch64", not(feature = "unstable-apple-new")))]
unsafe {
// Same as `mov x29, x29`
core::arch::asm!("mov fp, fp", options(nomem, preserves_flags, nostack))
};

Expand Down Expand Up @@ -650,7 +657,7 @@ impl<T: ?Sized> Drop for Id<T> {

// SAFETY: The `ptr` is guaranteed to be valid and have at least one
// retain count.
unsafe { ffi::objc_release(self.ptr.as_ptr().cast()) };
unsafe { objc_release_fast(self.ptr.as_ptr().cast()) };
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/objc2/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ mod nsobject;
mod nsproxy;
mod nszone;
mod protocol_object;
mod retain_release_fast;

pub(crate) use self::method_encoding_iter::{EncodingParseError, MethodEncodingIter};
pub(crate) use self::retain_release_fast::{objc_release_fast, objc_retain_fast};
use crate::encode::__unstable::{EncodeArguments, EncodeConvertReturn, EncodeReturn};
use crate::encode::{Encode, Encoding, OptionEncode, RefEncode};
use crate::verify::{verify_method_signature, Inner};
Expand Down
104 changes: 104 additions & 0 deletions crates/objc2/src/runtime/retain_release_fast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! Optimized versions of `objc_retain` and `objc_release`.
//!
//! On macOS 13.0 / iOS 16.0 / tvOS 16.0 / watchOS 9.0, on ARM64, optimized
//! versions of these two functions that use a different calling convention
//! than the usual C calling convention, are available.
//!
//! Specifically, the expected input register is changed. The output register
//! is unchanged.
//!
//! As an example, if the object is stored in the `x19` register and we need
//! to release it, we usually end up emitting an extra `mov` to get the object
//! into the `x0` register first, as expected by the C calling convention:
//!
//! ```asm
//! mov x0, x19
//! bl _objc_release
//! ```
//!
//! With this optimization though, since the expected register is encoded in
//! the name of the function instead, we can avoid the move altogether.
//!
//! ```asm
//! bl _objc_release_x19
//! ```
//!
//!
//!
//! Safety of our two uses of the `asm!` macro:
//!
//! 1. We use the register class `reg`, with the modifier `x`, which on
//! Aarch64 is defined as `x[0-30]`, see [this][asm-reg-cls].
//!
//! The functions are only available in the variants `x[0-15]` and
//! `x[19-28]` though, see [this][objc4-source], so if the register
//! allocator ends up using `x16`, `x17`, `x18`, `x29` or `x30`, we will
//! emit a call to e.g. `objc_retain_x29`, which will fail at link time.
//!
//! TODO: Before this option can be stable, we need a way to prevent that!
//!
//! 2. We use the `clobber_abi("C")` since we're effectively calling a C
//! C function.
//!
//! [asm-reg-cls]: https://doc.rust-lang.org/nightly/reference/inline-assembly.html#register-operands
//! [objc4-source]: https://github.com/apple-oss-distributions/objc4/blob/objc4-866.9/runtime/objc-abi.h#L442-L498
use crate::ffi;

/// A potentially faster version of `ffi::objc_retain`.
///
///
/// # Safety
///
/// Same as `ffi::objc_retain`.
#[inline]
pub(crate) unsafe fn objc_retain_fast(obj: *mut ffi::objc_object) -> *mut ffi::objc_object {
#[cfg(all(feature = "unstable-apple-new", target_arch = "aarch64"))]
// SAFETY: See the file header.
//
// As per the ARM64 calling convention, the return value is put in `x0`.
//
// That the function itself is safe to call is upheld by the caller.
unsafe {
let result;
core::arch::asm!(
"bl _objc_retain_{obj:x}",
obj = in(reg) obj,
lateout("x0") result,
clobber_abi("C"),
);
result
}

#[cfg(not(all(feature = "unstable-apple-new", target_arch = "aarch64")))]
// SAFETY: Upheld by caller.
unsafe {
ffi::objc_retain(obj)
}
}

/// A potentially faster version of `ffi::objc_release`.
///
///
/// # Safety
///
/// Same as `ffi::objc_release`.
#[inline]
pub(crate) unsafe fn objc_release_fast(obj: *mut ffi::objc_object) {
#[cfg(all(feature = "unstable-apple-new", target_arch = "aarch64"))]
// SAFETY: See the file header.
//
// That the function itself is safe to call is upheld by the caller.
unsafe {
core::arch::asm!(
"bl _objc_release_{obj:x}",
obj = in(reg) obj,
clobber_abi("C"),
)
}

#[cfg(not(all(feature = "unstable-apple-new", target_arch = "aarch64")))]
// SAFETY: Upheld by caller.
unsafe {
ffi::objc_release(obj)
}
}

0 comments on commit 282188d

Please sign in to comment.