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

Manual block encodings #636

Merged
merged 14 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions crates/block2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ unstable-private = []
[dependencies]
objc2 = { path = "../objc2", version = "0.5.2", default-features = false }

[dev-dependencies.objc2-foundation]
path = "../../framework-crates/objc2-foundation"
default-features = false
features = ["NSError"]

madsmtm marked this conversation as resolved.
Show resolved Hide resolved
[package.metadata.docs.rs]
default-target = "aarch64-apple-darwin"
features = ["unstable-private"]
Expand Down
49 changes: 43 additions & 6 deletions crates/block2/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use core::ffi::{c_char, c_int, c_ulong, c_void};
use core::fmt;
use core::mem::MaybeUninit;
use core::ops::{BitAnd, BitOr};

use alloc::format;

Expand Down Expand Up @@ -81,6 +82,45 @@ impl BlockFlags {

/// Note: Not public ABI.
const BLOCK_HAS_EXTENDED_LAYOUT: Self = Self(1 << 31);

/// `const` version of [`PartialEq`].
pub(crate) const fn equals(self, other: Self) -> bool {
self.0 == other.0
}

/// `const` version of [`BitOr`]: adds the flags together.
pub(crate) const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}

/// `const` version of [`BitAnd`]: only keeps the common flags.
pub(crate) const fn intersect(self, other: Self) -> Self {
Self(self.0 & other.0)
}

/// Returns `true` if and only if all the flags from `other` are enabled,
/// i.e. `self & other == other`.
pub(crate) const fn has(self, other: Self) -> bool {
self.intersect(other).equals(other)
}
}

/// See [`BlockFlags::union`].
impl BitOr for BlockFlags {
type Output = Self;

fn bitor(self, other: Self) -> Self {
self.union(other)
}
}

/// See [`BlockFlags::intersect`].
impl BitAnd for BlockFlags {
type Output = Self;

fn bitand(self, other: Self) -> Self {
self.intersect(other)
}
}

impl fmt::Debug for BlockFlags {
Expand All @@ -94,7 +134,7 @@ impl fmt::Debug for BlockFlags {
$name:ident: $flag:ident
);* $(;)?} => ($(
$(#[$m])?
f.field(stringify!($name), &(self.0 & Self::$flag.0 != 0));
f.field(stringify!($name), &self.has(Self::$flag));
)*)
}
test_flags! {
Expand All @@ -119,13 +159,10 @@ impl fmt::Debug for BlockFlags {
has_extended_layout: BLOCK_HAS_EXTENDED_LAYOUT;
}

f.field(
"over_referenced",
&(self.0 & Self::BLOCK_REFCOUNT_MASK.0 == Self::BLOCK_REFCOUNT_MASK.0),
);
f.field("over_referenced", &self.has(Self::BLOCK_REFCOUNT_MASK));
f.field(
"reference_count",
&((self.0 & Self::BLOCK_REFCOUNT_MASK.0) >> 1),
&((*self & Self::BLOCK_REFCOUNT_MASK).0 >> 1),
);

f.finish_non_exhaustive()
Expand Down
4 changes: 2 additions & 2 deletions crates/block2/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ pub(crate) fn debug_block_header(header: &BlockHeader, f: &mut DebugStruct<'_, '
f.field(
"descriptor",
&BlockDescriptorHelper {
has_copy_dispose: header.flags.0 & BlockFlags::BLOCK_HAS_COPY_DISPOSE.0 != 0,
has_signature: header.flags.0 & BlockFlags::BLOCK_HAS_SIGNATURE.0 != 0,
has_copy_dispose: header.flags.has(BlockFlags::BLOCK_HAS_COPY_DISPOSE),
has_signature: header.flags.has(BlockFlags::BLOCK_HAS_SIGNATURE),
descriptor: header.descriptor,
},
);
Expand Down
194 changes: 194 additions & 0 deletions crates/block2/src/encoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
use alloc::ffi::CString;
use alloc::string::ToString;
use alloc::vec::Vec;
use core::mem;

use objc2::encode::{EncodeArguments, EncodeReturn, Encoding};

/// Computes the raw signature string of the object corresponding to the block
/// taking `A` as inputs and returning `R`.
///
/// Although this is currently implemented on a best-effort basis, this should
/// still serve as a good way to obtain what to fill in the encoding string
/// when implementing [`crate::ManualBlockEncoding`].
///
/// # Example
///
/// ```ignore
/// assert_eq!(block_signature_string::<(i32, f32), u8>(), "C16@?0i8f12");
/// ```
#[allow(unused)]
pub fn block_signature_string<A, R>() -> CString
where
A: EncodeArguments,
R: EncodeReturn,
{
block_signature_string_inner(A::ENCODINGS, &R::ENCODING_RETURN)
}

#[allow(unused)]
fn block_signature_string_inner(args: &[Encoding], ret: &Encoding) -> CString {
// TODO: alignment?
let arg_sizes = args
.iter()
.map(Encoding::size)
.map(Option::unwrap_or_default)
.collect::<Vec<_>>();
let args_size = arg_sizes.iter().sum::<usize>();

// Take the hidden block parameter into account.
let mut off = mem::size_of::<*const ()>();
let mut res = ret.to_string();
res.push_str(&(off + args_size).to_string());
res.push_str("@?0");

for (arg_enc, arg_size) in args.iter().zip(arg_sizes) {
res.push_str(&arg_enc.to_string());
res.push_str(&off.to_string());
off += arg_size;
}

// UNWRAP: The above construction only uses controlled `ToString`
// implementations that do not include nul bytes.
CString::new(res).unwrap()
}

#[cfg(test)]
mod tests {
use super::*;
use alloc::borrow::ToOwned;

#[test]
fn test_block_signature_string() {
for (args, ret, val) in [
(
&[][..],
&Encoding::Void,
#[cfg(target_pointer_width = "64")]
"v8@?0",
#[cfg(target_pointer_width = "32")]
"v4@?0",
),
(
&[],
&Encoding::Int,
#[cfg(target_pointer_width = "64")]
"i8@?0",
#[cfg(target_pointer_width = "32")]
"i4@?0",
),
(
&[],
&Encoding::Float,
#[cfg(target_pointer_width = "64")]
"f8@?0",
#[cfg(target_pointer_width = "32")]
"f4@?0",
),
(
&[],
&Encoding::Bool,
#[cfg(target_pointer_width = "64")]
"B8@?0",
#[cfg(target_pointer_width = "32")]
"B4@?0",
),
(
&[Encoding::Int],
&Encoding::Void,
#[cfg(target_pointer_width = "64")]
"v12@?0i8",
#[cfg(target_pointer_width = "32")]
"v8@?0i4",
),
(
&[Encoding::Int],
&Encoding::Int,
#[cfg(target_pointer_width = "64")]
"i12@?0i8",
#[cfg(target_pointer_width = "32")]
"i8@?0i4",
),
(
&[Encoding::Long, Encoding::Double, Encoding::FloatComplex],
&Encoding::Atomic(&Encoding::UChar),
#[cfg(target_pointer_width = "64")]
"AC32@?0l8d16jf24",
#[cfg(target_pointer_width = "32")]
"AC24@?0l4d8jf16",
),
(
&[
Encoding::Union("ThisOrThat", &[Encoding::UShort, Encoding::Int]),
Encoding::Struct(
"ThisAndThat",
&[
Encoding::ULongLong,
Encoding::LongDoubleComplex,
Encoding::Atomic(&Encoding::Bool),
],
),
],
&Encoding::String,
// Probably unaligned.
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64",))]
"*53@?0(ThisOrThat=Si)8{ThisAndThat=QjDAB}12",
#[cfg(all(target_arch = "x86", target_vendor = "apple"))]
"*45@?0(ThisOrThat=Si)4{ThisAndThat=QjDAB}8",
#[cfg(all(target_arch = "x86", not(target_vendor = "apple")))]
"*41@?0(ThisOrThat=Si)4{ThisAndThat=QjDAB}8",
#[cfg(target_arch = "arm")]
"*37@?0(ThisOrThat=Si)4{ThisAndThat=QjDAB}8",
),
(
&[
Encoding::Block,
Encoding::Class,
Encoding::Object,
Encoding::Pointer(&Encoding::Char),
Encoding::Sel,
Encoding::String,
Encoding::Unknown,
Encoding::Unknown,
Encoding::Unknown,
],
&Encoding::Pointer(&Encoding::Atomic(&Encoding::UChar)),
#[cfg(target_pointer_width = "64")]
"^AC56@?0@?8#16@24^c32:40*48?56?56?56",
#[cfg(target_pointer_width = "32")]
"^AC28@?0@?4#8@12^c16:20*24?28?28?28",
),
(
&[Encoding::Array(123, &Encoding::Object)],
&Encoding::Pointer(&Encoding::Class),
#[cfg(target_pointer_width = "64")]
"^#992@?0[123@]8",
#[cfg(target_pointer_width = "32")]
"^#496@?0[123@]4",
),
// Bitfields can probably not be passed around through functions,
// so this may be a bit nonsensical, but let's test it anyway.
(
&[
Encoding::BitField(1, None),
Encoding::BitField(2, None),
Encoding::BitField(3, None),
Encoding::BitField(6, None),
Encoding::BitField(8, None),
Encoding::BitField(42, None),
Encoding::BitField(28, Some(&(2, Encoding::UInt))),
],
&Encoding::Sel,
#[cfg(target_pointer_width = "64")]
":25@?0b18b29b310b611b812b4213b2I2821",
#[cfg(target_pointer_width = "32")]
":21@?0b14b25b36b67b88b429b2I2817",
),
] {
assert_eq!(
block_signature_string_inner(args, ret),
CString::new(val.to_owned().into_bytes()).unwrap()
);
}
}
}
3 changes: 1 addition & 2 deletions crates/block2/src/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ unsafe impl<F: ?Sized + BlockFn> Send for GlobalBlock<F> {}
// triggers an error.
impl<F: ?Sized> GlobalBlock<F> {
// TODO: Use new ABI with BLOCK_HAS_SIGNATURE
const FLAGS: BlockFlags =
BlockFlags(BlockFlags::BLOCK_IS_GLOBAL.0 | BlockFlags::BLOCK_USE_STRET.0);
const FLAGS: BlockFlags = BlockFlags::BLOCK_IS_GLOBAL.union(BlockFlags::BLOCK_USE_STRET);

#[doc(hidden)]
#[allow(clippy::declare_interior_mutable_const)]
Expand Down
3 changes: 2 additions & 1 deletion crates/block2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ extern crate objc2 as _;
mod abi;
mod block;
mod debug;
mod encoding;
pub mod ffi;
mod global;
mod rc_block;
Expand All @@ -378,7 +379,7 @@ pub use self::block::Block;
pub use self::global::GlobalBlock;
pub use self::rc_block::RcBlock;
pub use self::stack::StackBlock;
pub use self::traits::{BlockFn, IntoBlock};
pub use self::traits::{BlockFn, IntoBlock, ManualBlockEncoding};

/// Deprecated alias for a `'static` `StackBlock`.
#[deprecated = "renamed to `StackBlock`"]
Expand Down
Loading