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

New Objective-C runtime features #462

Merged
merged 3 commits into from
Jul 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}