Skip to content

Commit

Permalink
Add static classes
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed Jul 5, 2022
1 parent 98e111c commit be73526
Show file tree
Hide file tree
Showing 19 changed files with 1,036 additions and 39 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -378,12 +378,12 @@ jobs:
# Not using --all-features because that would enable e.g. gnustep
args: ${{ env.ARGS }} ${{ env.TESTARGS }} --features ${{ env.FEATURES }},${{ env.UNSTABLE_FEATURES }}

- name: Test static selectors
- name: Test static class and selectors
if: ${{ !matrix.dinghy && (matrix.runtime || 'apple') == 'apple' }}
uses: actions-rs/cargo@v1
with:
command: test
args: ${{ env.ARGS }} ${{ env.TESTARGS }} --features unstable-static-sel
args: ${{ env.ARGS }} ${{ env.TESTARGS }} --features unstable-static-sel,unstable-static-class

- name: Run assembly tests
# Not run on GNUStep yet since a lot of function labels are mangled and
Expand Down
2 changes: 2 additions & 0 deletions objc2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ malloc = ["malloc_buf"]
# https://github.com/madsmtm/objc2/issues/new
unstable-static-sel = ["objc2-proc-macros"]
unstable-static-sel-inlined = ["unstable-static-sel"]
unstable-static-class = ["objc2-proc-macros"]
unstable-static-class-inlined = ["unstable-static-class"]

# Uses nightly features to make AutoreleasePool zero-cost even in debug mode
unstable-autoreleasesafe = []
Expand Down
4 changes: 4 additions & 0 deletions objc2/examples/introspection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ use objc2::{class, msg_send, msg_send_id};
#[cfg(feature = "malloc")]
use objc2::{sel, Encode};

#[cfg(feature = "apple")]
#[link(name = "Foundation", kind = "framework")]
extern "C" {}

fn main() {
// Get a class
let cls = class!(NSObject);
Expand Down
3 changes: 3 additions & 0 deletions objc2/examples/talk_to_me.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use std::ffi::c_void;
#[cfg(feature = "apple")]
#[link(name = "AVFoundation", kind = "framework")]
extern "C" {}
#[cfg(feature = "apple")]
#[link(name = "Foundation", kind = "framework")]
extern "C" {}

const UTF8_ENCODING: NSUInteger = 4;

Expand Down
5 changes: 5 additions & 0 deletions objc2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ mod test_utils;
#[doc(hidden)]
pub mod __macro_helpers;

// Hack to make doctests work
#[cfg(all(feature = "apple", feature = "unstable-static-class"))]
#[link(name = "Foundation", kind = "framework")]
extern "C" {}

/// Hacky way to make GNUStep link properly to Foundation while testing.
///
/// This is a temporary solution to make our CI work for now!
Expand Down
162 changes: 138 additions & 24 deletions objc2/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@
/// ```
#[macro_export]
macro_rules! class {
($name:ident) => {{
$crate::__class_inner!($name)
}};
}

#[doc(hidden)]
#[macro_export]
#[cfg(not(feature = "unstable-static-class"))]
macro_rules! __class_inner {
($name:ident) => {{
use $crate::__macro_helpers::{concat, panic, stringify, CachedClass, None, Some};
static CACHED_CLASS: CachedClass = CachedClass::new();
Expand All @@ -25,7 +34,7 @@ macro_rules! class {
let cls = unsafe { CACHED_CLASS.get(name) };
match cls {
Some(cls) => cls,
None => panic!("Class with name {} could not be found", stringify!($name)),
None => panic!("Class with name {} could not be found", stringify!($name),),
}
}};
}
Expand Down Expand Up @@ -165,18 +174,12 @@ macro_rules! __sel_inner {

#[doc(hidden)]
#[macro_export]
macro_rules! __sel_inner_statics_apple_generic {
macro_rules! __inner_statics_apple_generic {
{
@image_info;
$image_info_section:literal;
$var_name_section:literal;
$selector_ref_section:literal;
$data:ident,
$($idents:ident)+
} => {
use $crate::__macro_helpers::{__hash_idents, u8, UnsafeCell};
use $crate::ffi::__ImageInfo;
use $crate::runtime::Sel;

/// We always emit the image info tag, since we need it to:
/// - End up in the same codegen unit as the other statics below.
/// - End up in the final binary so it can be read by dyld.
Expand All @@ -185,9 +188,22 @@ macro_rules! __sel_inner_statics_apple_generic {
/// reports `__DATA/__objc_imageinfo has unexpectedly large size XXX`,
/// but things still seems to work.
#[link_section = $image_info_section]
#[export_name = concat!("\x01L_OBJC_IMAGE_INFO_", __hash_idents!($($idents)+))]
#[export_name = $crate::__macro_helpers::concat!(
"\x01L_OBJC_IMAGE_INFO_",
$crate::__macro_helpers::__hash_idents!($($idents)+)
)]
#[used] // Make sure this reaches the linker
static _IMAGE_INFO: __ImageInfo = __ImageInfo::system();
static _IMAGE_INFO: $crate::ffi::__ImageInfo = $crate::ffi::__ImageInfo::system();
};
{
@sel;
$var_name_section:literal;
$selector_ref_section:literal;
$data:ident,
$($idents:ident)+
} => {
use $crate::__macro_helpers::{__hash_idents, u8, UnsafeCell};
use $crate::runtime::Sel;

const X: &[u8] = $data.as_bytes();

Expand Down Expand Up @@ -241,47 +257,108 @@ macro_rules! __sel_inner_statics_apple_generic {
UnsafeCell::new(Sel::__internal_from_ptr(NAME_DATA.as_ptr().cast()))
};
};
{
@class;
$class_ref_section:literal;
$name:ident
} => {
use $crate::__macro_helpers::{concat, stringify, __hash_idents, UnsafeCell};
use $crate::runtime::Class;

// TODO
extern "C" {
// TODO: Weak linkage?
// https://stackoverflow.com/a/16936512
// http://sealiesoftware.com/blog/archive/2010/4/8/Do-it-yourself_Objective-C_weak_import.html
#[link_name = concat!("OBJC_CLASS_$_", stringify!($name))]
static CLASS: Class;
}

// TODO
#[link_section = $class_ref_section]
#[export_name = concat!("\x01L_OBJC_CLASSLIST_REFERENCES_$_", __hash_idents!($name))]
static mut REF: UnsafeCell<&Class> = unsafe {
UnsafeCell::new(&CLASS)
};
};
}

// These sections are found by reading clang/LLVM sources
#[doc(hidden)]
#[macro_export]
#[cfg(all(feature = "apple", not(all(target_os = "macos", target_arch = "x86"))))]
macro_rules! __sel_inner_statics {
($($args:tt)*) => {
// Found by reading clang/LLVM sources
$crate::__sel_inner_statics_apple_generic! {
macro_rules! __inner_statics {
(@image_info $($args:tt)*) => {
$crate::__inner_statics_apple_generic! {
@image_info;
"__DATA,__objc_imageinfo,regular,no_dead_strip";
$($args)*
}
};
(@sel $($args:tt)*) => {
$crate::__inner_statics_apple_generic! {
@sel;
"__TEXT,__objc_methname,cstring_literals";
"__DATA,__objc_selrefs,literal_pointers,no_dead_strip";
$($args)*
}
};
(@class $($args:tt)*) => {
$crate::__inner_statics_apple_generic! {
@class;
"__DATA,__objc_classrefs,regular,no_dead_strip";
$($args)*
}
};
}

#[doc(hidden)]
#[macro_export]
#[cfg(all(feature = "apple", target_os = "macos", target_arch = "x86"))]
macro_rules! __sel_inner_statics {
($($args:tt)*) => {
$crate::__sel_inner_statics_apple_generic! {
macro_rules! __inner_statics {
(@image_info $($args:tt)*) => {
$crate::__inner_statics_apple_generic! {
@image_info;
"__OBJC,__image_info,regular";
$($args)*
}
};
(@sel $($args:tt)*) => {
$crate::__inner_statics_apple_generic! {
@sel;
"__TEXT,__cstring,cstring_literals";
"__OBJC,__message_refs,literal_pointers,no_dead_strip";
$($args)*
}
};
(@class $($args:tt)*) => {
// TODO
$crate::__macro_helpers::compile_error!(
"The `\"unstable-static-class\"` feature is not yet supported on 32bit macOS!"
)
// TODO: module info
};
}

#[doc(hidden)]
#[macro_export]
#[cfg(not(feature = "apple"))]
macro_rules! __sel_inner_statics {
($($args:tt)*) => {
macro_rules! __inner_statics {
(@image_info $($args:tt)*) => {
// TODO
};
(@sel $($args:tt)*) => {
// TODO
$crate::__macro_helpers::compile_error!(
"The `\"unstable-static-sel\"` feature is not yet supported on GNUStep!"
)
};
(@class $($args:tt)*) => {
// TODO
$crate::__macro_helpers::compile_error!(
"The `\"unstable-static-class\"` feature is not yet supported on GNUStep!"
)
};
}

#[doc(hidden)]
Expand All @@ -291,8 +368,9 @@ macro_rules! __sel_inner_statics {
not(feature = "unstable-static-sel-inlined")
))]
macro_rules! __sel_inner {
($($args:tt)*) => {{
$crate::__sel_inner_statics!($($args)*);
($data:ident, $($idents:ident)+) => {{
$crate::__inner_statics!(@image_info $($idents)+);
$crate::__inner_statics!(@sel $data, $($idents)+);

/// HACK: Wrap the access in a non-generic, `#[inline(never)]`
/// function to make the compiler group it into the same codegen unit
Expand All @@ -319,8 +397,44 @@ macro_rules! __sel_inner {
#[macro_export]
#[cfg(all(feature = "unstable-static-sel-inlined"))]
macro_rules! __sel_inner {
($($args:tt)*) => {{
$crate::__sel_inner_statics!($($args)*);
($data:ident, $($idents:ident)+) => {{
$crate::__inner_statics!(@image_info $($idents)+);
$crate::__inner_statics!(@sel $data, $($idents)+);

#[allow(unused_unsafe)]
// SAFETY: See above
unsafe { *REF.get() }
}};
}

#[doc(hidden)]
#[macro_export]
#[cfg(all(
feature = "unstable-static-class",
not(feature = "unstable-static-class-inlined")
))]
macro_rules! __class_inner {
($name:ident) => {{
$crate::__inner_statics!(@image_info $name);
$crate::__inner_statics!(@class $name);

// SAFETY: Same as __sel_inner
#[inline(never)]
fn objc_static_workaround() -> &'static Class {
unsafe { *REF.get() }
}

objc_static_workaround()
}};
}

#[doc(hidden)]
#[macro_export]
#[cfg(all(feature = "unstable-static-class-inlined"))]
macro_rules! __class_inner {
($name:ident) => {{
$crate::__inner_statics!(@image_info $name);
$crate::__inner_statics!(@class $name);

#[allow(unused_unsafe)]
// SAFETY: See above
Expand Down
5 changes: 3 additions & 2 deletions objc2/src/rc/test_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,11 @@ impl RcTestObject {
);
}

builder.register();
let _cls = builder.register();
});

class!(RcTestObject)
// Can't use `class!` here since `RcTestObject` is dynamically created.
Class::get("RcTestObject").unwrap()
}

pub(crate) fn new() -> Id<Self, Owned> {
Expand Down
5 changes: 3 additions & 2 deletions objc2/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ pub(crate) fn custom_class() -> &'static Class {
builder.register();
});

class!(CustomObject)
// Can't use `class!` here since `CustomObject` is dynamically created.
Class::get("CustomObject").unwrap()
}

pub(crate) fn custom_protocol() -> &'static Protocol {
Expand Down Expand Up @@ -225,7 +226,7 @@ pub(crate) fn custom_subclass() -> &'static Class {
builder.register();
});

class!(CustomSubclassObject)
Class::get("CustomSubclassObject").unwrap()
}

pub(crate) fn custom_subclass_object() -> CustomObject {
Expand Down
19 changes: 10 additions & 9 deletions objc2/tests/id_retain_autoreleased.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ use objc2::rc::{autoreleasepool, Id, Shared};
use objc2::runtime::Object;
use objc2::{class, msg_send};

#[cfg(feature = "gnustep-1-7")]
#[test]
fn ensure_linkage() {
unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
}

#[cfg(feature = "apple")]
#[link(name = "Foundation", kind = "framework")]
extern "C" {}

fn retain_count(obj: &Object) -> usize {
unsafe { msg_send![obj, retainCount] }
}
Expand Down Expand Up @@ -37,15 +47,6 @@ fn create_data(bytes: &[u8]) -> Id<Object, Shared> {

#[test]
fn test_retain_autoreleased() {
#[cfg(feature = "gnustep-1-7")]
unsafe {
objc2::__gnustep_hack::get_class_to_force_linkage()
};

#[cfg(feature = "apple")]
#[link(name = "Foundation", kind = "framework")]
extern "C" {}

autoreleasepool(|_| {
// Run once to allow DYLD to resolve the symbol stubs.
// Required for making `retain_autoreleased` work on x86_64.
Expand Down
10 changes: 10 additions & 0 deletions objc2/tests/no_prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@

extern crate objc2 as new_objc2;

#[cfg(feature = "gnustep-1-7")]
#[test]
fn ensure_linkage() {
unsafe { new_objc2::__gnustep_hack::get_class_to_force_linkage() };
}

#[cfg(feature = "apple")]
#[link(name = "Foundation", kind = "framework")]
extern "C" {}

mod core {}
mod std {}
mod libc {}
Expand Down
4 changes: 4 additions & 0 deletions objc2/tests/use_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ fn ensure_linkage() {
unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
}

#[cfg(feature = "apple")]
#[link(name = "Foundation", kind = "framework")]
extern "C" {}

#[test]
fn use_class_and_msg_send() {
unsafe {
Expand Down
Loading

0 comments on commit be73526

Please sign in to comment.