Skip to content

Commit 282188d

Browse files
authored
Merge pull request #462 from madsmtm/new-objc-features
New Objective-C runtime features
2 parents 4cb7772 + 1947278 commit 282188d

File tree

5 files changed

+133
-6
lines changed

5 files changed

+133
-6
lines changed

crates/objc2/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ unstable-c-unwind = []
6565
# For better documentation on docs.rs
6666
unstable-docsrs = []
6767

68+
# Enable some new features available on ARM64 on:
69+
# - macOS 13.0
70+
# - iOS 16.0
71+
# - tvOS 16.0
72+
# - watchOS 9.0
73+
#
74+
# See https://developer.apple.com/videos/play/wwdc2022/110363/ for an overview
75+
# of the features.
76+
#
77+
# Currently untested, might be unsound or lead to confusing compiler errors.
78+
#
79+
# Additionally, the message sending improvements is not yet implemented.
80+
unstable-apple-new = ["apple"]
81+
6882
# Runtime selection. See `objc-sys` for details.
6983
apple = ["objc-sys/apple"]
7084
gnustep-1-7 = ["objc-sys/gnustep-1-7"]

crates/objc2/src/rc/allocated.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use core::marker::PhantomData;
33
use core::mem::{self, ManuallyDrop};
44
use core::ptr::NonNull;
55

6-
use crate::ffi;
6+
use crate::runtime::objc_release_fast;
77
use crate::Message;
88

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

crates/objc2/src/rc/id.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use core::ptr::{self, NonNull};
77

88
use super::AutoreleasePool;
99
use crate::mutability::{IsIdCloneable, IsMutable};
10+
use crate::runtime::{objc_release_fast, objc_retain_fast};
1011
use crate::{ffi, ClassType, Message};
1112

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

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

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

651658
// SAFETY: The `ptr` is guaranteed to be valid and have at least one
652659
// retain count.
653-
unsafe { ffi::objc_release(self.ptr.as_ptr().cast()) };
660+
unsafe { objc_release_fast(self.ptr.as_ptr().cast()) };
654661
}
655662
}
656663

crates/objc2/src/runtime/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ mod nsobject;
3838
mod nsproxy;
3939
mod nszone;
4040
mod protocol_object;
41+
mod retain_release_fast;
4142

4243
pub(crate) use self::method_encoding_iter::{EncodingParseError, MethodEncodingIter};
44+
pub(crate) use self::retain_release_fast::{objc_release_fast, objc_retain_fast};
4345
use crate::encode::__unstable::{EncodeArguments, EncodeConvertReturn, EncodeReturn};
4446
use crate::encode::{Encode, Encoding, OptionEncode, RefEncode};
4547
use crate::verify::{verify_method_signature, Inner};
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//! Optimized versions of `objc_retain` and `objc_release`.
2+
//!
3+
//! On macOS 13.0 / iOS 16.0 / tvOS 16.0 / watchOS 9.0, on ARM64, optimized
4+
//! versions of these two functions that use a different calling convention
5+
//! than the usual C calling convention, are available.
6+
//!
7+
//! Specifically, the expected input register is changed. The output register
8+
//! is unchanged.
9+
//!
10+
//! As an example, if the object is stored in the `x19` register and we need
11+
//! to release it, we usually end up emitting an extra `mov` to get the object
12+
//! into the `x0` register first, as expected by the C calling convention:
13+
//!
14+
//! ```asm
15+
//! mov x0, x19
16+
//! bl _objc_release
17+
//! ```
18+
//!
19+
//! With this optimization though, since the expected register is encoded in
20+
//! the name of the function instead, we can avoid the move altogether.
21+
//!
22+
//! ```asm
23+
//! bl _objc_release_x19
24+
//! ```
25+
//!
26+
//!
27+
//!
28+
//! Safety of our two uses of the `asm!` macro:
29+
//!
30+
//! 1. We use the register class `reg`, with the modifier `x`, which on
31+
//! Aarch64 is defined as `x[0-30]`, see [this][asm-reg-cls].
32+
//!
33+
//! The functions are only available in the variants `x[0-15]` and
34+
//! `x[19-28]` though, see [this][objc4-source], so if the register
35+
//! allocator ends up using `x16`, `x17`, `x18`, `x29` or `x30`, we will
36+
//! emit a call to e.g. `objc_retain_x29`, which will fail at link time.
37+
//!
38+
//! TODO: Before this option can be stable, we need a way to prevent that!
39+
//!
40+
//! 2. We use the `clobber_abi("C")` since we're effectively calling a C
41+
//! C function.
42+
//!
43+
//! [asm-reg-cls]: https://doc.rust-lang.org/nightly/reference/inline-assembly.html#register-operands
44+
//! [objc4-source]: https://github.com/apple-oss-distributions/objc4/blob/objc4-866.9/runtime/objc-abi.h#L442-L498
45+
use crate::ffi;
46+
47+
/// A potentially faster version of `ffi::objc_retain`.
48+
///
49+
///
50+
/// # Safety
51+
///
52+
/// Same as `ffi::objc_retain`.
53+
#[inline]
54+
pub(crate) unsafe fn objc_retain_fast(obj: *mut ffi::objc_object) -> *mut ffi::objc_object {
55+
#[cfg(all(feature = "unstable-apple-new", target_arch = "aarch64"))]
56+
// SAFETY: See the file header.
57+
//
58+
// As per the ARM64 calling convention, the return value is put in `x0`.
59+
//
60+
// That the function itself is safe to call is upheld by the caller.
61+
unsafe {
62+
let result;
63+
core::arch::asm!(
64+
"bl _objc_retain_{obj:x}",
65+
obj = in(reg) obj,
66+
lateout("x0") result,
67+
clobber_abi("C"),
68+
);
69+
result
70+
}
71+
72+
#[cfg(not(all(feature = "unstable-apple-new", target_arch = "aarch64")))]
73+
// SAFETY: Upheld by caller.
74+
unsafe {
75+
ffi::objc_retain(obj)
76+
}
77+
}
78+
79+
/// A potentially faster version of `ffi::objc_release`.
80+
///
81+
///
82+
/// # Safety
83+
///
84+
/// Same as `ffi::objc_release`.
85+
#[inline]
86+
pub(crate) unsafe fn objc_release_fast(obj: *mut ffi::objc_object) {
87+
#[cfg(all(feature = "unstable-apple-new", target_arch = "aarch64"))]
88+
// SAFETY: See the file header.
89+
//
90+
// That the function itself is safe to call is upheld by the caller.
91+
unsafe {
92+
core::arch::asm!(
93+
"bl _objc_release_{obj:x}",
94+
obj = in(reg) obj,
95+
clobber_abi("C"),
96+
)
97+
}
98+
99+
#[cfg(not(all(feature = "unstable-apple-new", target_arch = "aarch64")))]
100+
// SAFETY: Upheld by caller.
101+
unsafe {
102+
ffi::objc_release(obj)
103+
}
104+
}

0 commit comments

Comments
 (0)