Skip to content

Commit

Permalink
Merge pull request #636 from PaulDance/manual-block-encodings
Browse files Browse the repository at this point in the history
Add ability to specify manual block encodings
  • Loading branch information
madsmtm authored Sep 17, 2024
2 parents 6b723f7 + b132646 commit f559170
Show file tree
Hide file tree
Showing 22 changed files with 1,416 additions and 155 deletions.
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.

4 changes: 4 additions & 0 deletions crates/block2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### Added
* Added `unstable-c-unwind` feature flag.
* Added `StackBlock::with_encoding` and `RcBlock::with_encoding` for creating
blocks with an encoding specified by `ManualBlockEncoding`.

This is useful for certain APIs that require blocks to have an encoding.


## 0.5.1 - 2024-05-21
Expand Down
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"]

[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
199 changes: 199 additions & 0 deletions crates/block2/src/encoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
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",
all(target_arch = "aarch64", not(target_vendor = "apple"))
))]
"*53@?0(ThisOrThat=Si)8{ThisAndThat=QjDAB}12",
#[cfg(all(target_arch = "aarch64", target_vendor = "apple"))]
"*37@?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

0 comments on commit f559170

Please sign in to comment.