Skip to content

Commit

Permalink
Merge pull request #469 from madsmtm/assembly-improvements
Browse files Browse the repository at this point in the history
Assembly improvements
  • Loading branch information
madsmtm authored Jun 23, 2023
2 parents 55703bc + 0405a38 commit 6849481
Show file tree
Hide file tree
Showing 53 changed files with 10,002 additions and 592 deletions.
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 58 additions & 20 deletions crates/objc2/src/__macro_helpers/cache.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use core::ptr;
use core::str;
use core::sync::atomic::{AtomicPtr, Ordering};
use std::ffi::CStr;
use std::os::raw::c_char;

use crate::ffi;
use crate::runtime::{AnyClass, Sel};

/// Allows storing a [`Sel`] in a static and lazily loading it.
#[doc(hidden)]
pub struct CachedSel {
ptr: AtomicPtr<ffi::objc_selector>,
}
Expand All @@ -18,30 +20,38 @@ impl CachedSel {
}
}

// Mark as cold since this should only ever be called once (or maybe twice
// if running on multiple threads).
#[cold]
unsafe fn fetch(&self, name: *const c_char) -> Sel {
// The panic inside `Sel::register_unchecked` is unfortunate, but
// strict correctness is more important than speed

// SAFETY: Input is a non-null, NUL-terminated C-string pointer.
//
// We know this, because we construct it in `sel!` ourselves
let sel = unsafe { Sel::register_unchecked(name) };
self.ptr
.store(sel.as_ptr() as *mut ffi::objc_selector, Ordering::Relaxed);
sel
}

/// Returns the cached selector. If no selector is yet cached, registers
/// one with the given name and stores it.
#[inline]
#[doc(hidden)]
pub unsafe fn get(&self, name: &str) -> Sel {
// `Relaxed` should be fine since `sel_registerName` is thread-safe.
let ptr = self.ptr.load(Ordering::Relaxed);
unsafe { Sel::from_ptr(ptr) }.unwrap_or_else(|| {
// The panic inside `Sel::register_unchecked` is unfortunate, but
// strict correctness is more important than speed

// SAFETY: Input is a non-null, NUL-terminated C-string pointer.
//
// We know this, because we construct it in `sel!` ourselves
let sel = unsafe { Sel::register_unchecked(name.as_ptr().cast()) };
self.ptr
.store(sel.as_ptr() as *mut ffi::objc_selector, Ordering::Relaxed);
if let Some(sel) = unsafe { Sel::from_ptr(ptr) } {
sel
})
} else {
// SAFETY: Checked by caller
unsafe { self.fetch(name.as_ptr().cast()) }
}
}
}

/// Allows storing a [`AnyClass`] reference in a static and lazily loading it.
#[doc(hidden)]
pub struct CachedClass {
ptr: AtomicPtr<AnyClass>,
}
Expand All @@ -54,19 +64,47 @@ impl CachedClass {
}
}

// Mark as cold since this should only ever be called once (or maybe twice
// if running on multiple threads).
#[cold]
#[track_caller]
unsafe fn fetch(&self, name: *const c_char) -> &'static AnyClass {
let ptr: *const AnyClass = unsafe { ffi::objc_getClass(name) }.cast();
self.ptr.store(ptr as *mut AnyClass, Ordering::Relaxed);
if let Some(cls) = unsafe { ptr.as_ref() } {
cls
} else {
// Recover the name from the pointer. We do it like this so that
// we don't have to pass the length of the class to this method,
// improving binary size.
let name = unsafe { CStr::from_ptr(name) };
let name = str::from_utf8(name.to_bytes()).unwrap();
panic!("class {name} could not be found")
}
}

/// Returns the cached class. If no class is yet cached, gets one with
/// the given name and stores it.
#[inline]
#[doc(hidden)]
pub unsafe fn get(&self, name: &str) -> Option<&'static AnyClass> {
#[track_caller]
pub unsafe fn get(&self, name: &str) -> &'static AnyClass {
// `Relaxed` should be fine since `objc_getClass` is thread-safe.
let ptr = self.ptr.load(Ordering::Relaxed);
if let Some(cls) = unsafe { ptr.as_ref() } {
Some(cls)
cls
} else {
let ptr: *const AnyClass = unsafe { ffi::objc_getClass(name.as_ptr().cast()) }.cast();
self.ptr.store(ptr as *mut AnyClass, Ordering::Relaxed);
unsafe { ptr.as_ref() }
// SAFETY: Checked by caller
unsafe { self.fetch(name.as_ptr().cast()) }
}
}
}

#[cfg(test)]
mod tests {
#[test]
#[should_panic = "class NonExistantClass could not be found"]
#[cfg(not(feature = "unstable-static-class"))]
fn test_not_found() {
let _ = crate::class!(NonExistantClass);
}
}
30 changes: 30 additions & 0 deletions crates/objc2/src/__macro_helpers/common_selectors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Common selectors.
//!
//! These are put here to deduplicate the cached selector, and when using
//! `unstable-static-sel`, the statics.
//!
//! Note that our assembly tests of `unstable-static-sel-inlined` output a GOT
//! entry for such accesses, but that is just a limitation of our tests - the
//! actual assembly is as one would expect.
use crate::runtime::Sel;
use crate::{__sel_data, __sel_inner};

#[inline]
pub fn alloc_sel() -> Sel {
__sel_inner!(__sel_data!(alloc), "alloc")
}

#[inline]
pub fn init_sel() -> Sel {
__sel_inner!(__sel_data!(init), "init")
}

#[inline]
pub fn new_sel() -> Sel {
__sel_inner!(__sel_data!(new), "new")
}

#[inline]
pub fn dealloc_sel() -> Sel {
__sel_inner!(__sel_data!(dealloc), "dealloc")
}
26 changes: 3 additions & 23 deletions crates/objc2/src/__macro_helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use crate::rc::{Allocated, Id};
use crate::runtime::MethodDescription;
use crate::runtime::{AnyClass, AnyObject, AnyProtocol, Sel};
use crate::{Message, MessageArguments, MessageReceiver};
use crate::{__sel_data, __sel_inner};

pub use core::borrow::{Borrow, BorrowMut};
pub use core::cell::UnsafeCell;
Expand All @@ -29,36 +28,16 @@ pub use core::{compile_error, concat, panic, stringify};
pub use std::sync::Once;

mod cache;
mod common_selectors;
mod declare_class;

pub use self::cache::{CachedClass, CachedSel};
pub use self::common_selectors::{alloc_sel, dealloc_sel, init_sel, new_sel};
pub use self::declare_class::{
assert_mutability_matches_superclass_mutability, MaybeOptionId, MessageRecieveId,
ValidSubclassMutability,
};

// Common selectors.
//
// These are put here to deduplicate the cached selector, and when using
// `unstable-static-sel`, the statics.
//
// Note that our assembly tests of `unstable-static-sel-inlined` output a GOT
// entry for such accesses, but that is just a limitation of our tests - the
// actual assembly is as one would expect.

#[inline]
pub fn alloc_sel() -> Sel {
__sel_inner!(__sel_data!(alloc), "alloc")
}
#[inline]
pub fn init_sel() -> Sel {
__sel_inner!(__sel_data!(init), "init")
}
#[inline]
pub fn new_sel() -> Sel {
__sel_inner!(__sel_data!(new), "new")
}

/// Helper for specifying the retain semantics for a given selector family.
///
/// Note that we can't actually check if a method is in a method family; only
Expand Down Expand Up @@ -595,6 +574,7 @@ impl ClassProtocolMethodsBuilder<'_, '_> {
}
}

#[inline]
pub fn __finish(self) {
#[cfg(all(debug_assertions, feature = "verify"))]
if let Some(protocol) = self.protocol {
Expand Down
2 changes: 1 addition & 1 deletion crates/objc2/src/declare/declare_class_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ fn test_unreachable() {
}

#[test]
#[should_panic = "Failed to add ivar _ivar"]
#[should_panic = "failed to add ivar _ivar"]
fn test_duplicate_ivar() {
declare_class!(
struct DeclareClassDuplicateIvar {
Expand Down
Loading

0 comments on commit 6849481

Please sign in to comment.