Skip to content

Commit

Permalink
WIP: Add BlockMut
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed Aug 15, 2022
1 parent e737d8c commit 4210620
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 35 deletions.
4 changes: 4 additions & 0 deletions block2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased - YYYY-MM-DD

### Added
* Added `BlockMut` to allow representing cases where you know that you're not
modifying the block concurrently, and as such may mutate the contents.

### Fixed
* **BREAKING**: Cleaned up `BlockArguments` trait, it is now sealed and a
subtrait of `EncodeArguments`.
Expand Down
93 changes: 67 additions & 26 deletions block2/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub unsafe trait BlockArguments: EncodeArguments + Sized {
#[doc(hidden)]
unsafe fn __call_block<R: Encode>(
invoke: unsafe extern "C" fn(),
block: *mut Block<Self, R>,
block: *mut BlockMut<Self, R>,
args: Self,
) -> R;
}
Expand All @@ -29,9 +29,9 @@ macro_rules! block_args_impl {
($($a:ident: $t:ident),*) => (
unsafe impl<$($t: Encode),*> BlockArguments for ($($t,)*) {
#[inline]
unsafe fn __call_block<R: Encode>(invoke: unsafe extern "C" fn(), block: *mut Block<Self, R>, ($($a,)*): Self) -> R {
unsafe fn __call_block<R: Encode>(invoke: unsafe extern "C" fn(), block: *mut BlockMut<Self, R>, ($($a,)*): Self) -> R {
// Very similar to `MessageArguments::__invoke`
let invoke: unsafe extern "C" fn(*mut Block<Self, R> $(, $t)*) -> R = unsafe {
let invoke: unsafe extern "C" fn(*mut BlockMut<Self, R> $(, $t)*) -> R = unsafe {
mem::transmute(invoke)
};

Expand Down Expand Up @@ -80,39 +80,80 @@ block_args_impl!(
l: L
);

/// An Objective-C block that takes arguments of `A` when called and
/// returns a value of `R`.
/// A block that can be called repeatedly and may mutate state.
///
/// This is the Objective-C equivalent of [`dyn FnMut`][FnMut].
///
/// Takes arguments `A` and returns `R`.
#[repr(C)]
pub struct Block<A, R> {
pub struct BlockMut<A, R> {
_inner: [u8; 0],
// We effectively store `Block_layout` + a bit more, but `Block` has to
// remain an empty type otherwise the compiler thinks we only have
// provenance over `Block_layout`.
/// We effectively store `Block_layout` + a bit more, but `Block` has to
/// remain an empty type otherwise the compiler thinks we only have
/// provenance over `Block_layout`.
_layout: PhantomData<ffi::Block_layout>,
// To get correct variance on args and return types
_p: PhantomData<fn(A) -> R>,
/// To get correct variance on args and return types
_p: PhantomData<dyn FnMut(A) -> R>,
}

unsafe impl<A: BlockArguments, R: Encode> RefEncode for Block<A, R> {
unsafe impl<A: BlockArguments, R: Encode> RefEncode for BlockMut<A, R> {
const ENCODING_REF: Encoding<'static> = Encoding::Block;
}

impl<A: BlockArguments, R: Encode> Block<A, R> {
/// Call self with the given arguments.
///
/// # Safety
///
/// This invokes foreign code that the caller must verify doesn't violate
/// any of Rust's safety rules.
///
/// For example, if this block is shared with multiple references, the
/// caller must ensure that calling it will not cause a data race.
pub unsafe fn call(&self, args: A) -> R {
impl<A: BlockArguments, R: Encode> BlockMut<A, R> {
fn get_invoke(&self) -> unsafe extern "C" fn() {
let ptr: *const Self = self;
let layout = unsafe { ptr.cast::<ffi::Block_layout>().as_ref().unwrap_unchecked() };
let ptr: *const ffi::Block_layout = ptr.cast();
// SAFETY: `BlockMut` is `Block_layout` + extern type
let layout = unsafe { ptr.as_ref().unwrap_unchecked() };
// TODO: Is `invoke` actually ever null?
let invoke = layout.invoke.unwrap();
layout.invoke.unwrap()
}

/// Performs the call operation.
pub fn call_mut(&mut self, args: A) -> R {
let invoke = self.get_invoke();
let ptr: *mut Self = self;

unsafe { A::__call_block(invoke, ptr.cast(), args) }
}
}

/// A block that can be called repeatedly without mutating state.
///
/// This is the Objective-C equivalent of [`dyn Fn`][Fn].
///
/// Takes arguments `A` and returns `R`.
#[repr(C)]
pub struct Block<A, R> {
/// Same as BlockMut, with the added ability that this may be called
/// immutably.
inner: BlockMut<A, R>,
}

unsafe impl<A: BlockArguments, R: Encode> RefEncode for Block<A, R> {
const ENCODING_REF: Encoding<'static> = Encoding::Block;
}

impl<A: BlockArguments, R: Encode> Block<A, R> {
/// Performs the call operation.
pub fn call(&self, args: A) -> R {
let invoke = self.inner.get_invoke();
let ptr: *const BlockMut<A, R> = &self.inner;

// SAFETY: `Block` is safe to call from an immutable reference
unsafe { A::__call_block(invoke, ptr as *mut BlockMut<A, R>, args) }
}
}

impl<A, R> AsRef<BlockMut<A, R>> for Block<A, R> {
fn as_ref(&self) -> &BlockMut<A, R> {
&self.inner
}
}

unsafe { A::__call_block(invoke, ptr as *mut Self, args) }
impl<A, R> AsMut<BlockMut<A, R>> for Block<A, R> {
fn as_mut(&mut self) -> &mut BlockMut<A, R> {
&mut self.inner
}
}
12 changes: 11 additions & 1 deletion block2/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use core::fmt::{Debug, DebugStruct, Error, Formatter};
use core::ptr;
use std::ffi::CStr;

use crate::{ffi, Block, ConcreteBlock, GlobalBlock, RcBlock};
use crate::{ffi, Block, BlockMut, ConcreteBlock, GlobalBlock, RcBlock};

#[derive(Clone, Copy, PartialEq, Eq)]
struct Isa(*const ffi::Class);
Expand Down Expand Up @@ -52,6 +52,16 @@ fn debug_block_layout(layout: &ffi::Block_layout, f: &mut DebugStruct<'_, '_>) {
);
}

impl<A, R> Debug for BlockMut<A, R> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
let mut f = f.debug_struct("BlockMut");
let ptr: *const Self = self;
let layout = unsafe { ptr.cast::<ffi::Block_layout>().as_ref().unwrap() };
debug_block_layout(layout, &mut f);
f.finish_non_exhaustive()
}
}

impl<A, R> Debug for Block<A, R> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
let mut f = f.debug_struct("Block");
Expand Down
17 changes: 10 additions & 7 deletions block2/src/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const GLOBAL_DESCRIPTOR: ffi::Block_descriptor_header = ffi::Block_descriptor_he
#[repr(C)]
pub struct GlobalBlock<A, R = ()> {
pub(crate) layout: ffi::Block_layout,
p: PhantomData<(A, R)>,
p: PhantomData<Block<A, R>>,
}

unsafe impl<A, R> Sync for GlobalBlock<A, R>
Expand Down Expand Up @@ -85,7 +85,10 @@ where
fn deref(&self) -> &Self::Target {
let ptr: *const Self = self;
let ptr: *const Block<A, R> = ptr.cast();
// TODO: SAFETY
// SAFETY: This has the same layout as `Block`
//
// A global block does not hold any data, so it is safe to call
// immutably.
unsafe { ptr.as_ref().unwrap_unchecked() }
}
}
Expand All @@ -105,7 +108,7 @@ where
/// 42
/// };
/// }
/// assert_eq!(unsafe { MY_BLOCK.call(()) }, 42);
/// assert_eq!(MY_BLOCK.call(()), 42);
/// ```
///
/// ```
Expand All @@ -115,7 +118,7 @@ where
/// x + y
/// };
/// }
/// assert_eq!(unsafe { ADDER_BLOCK.call((5, 7)) }, 12);
/// assert_eq!(ADDER_BLOCK.call((5, 7)), 12);
/// ```
///
/// ```
Expand All @@ -126,7 +129,7 @@ where
/// };
/// }
/// let mut x = 5;
/// unsafe { MUTATING_BLOCK.call((&mut x,)) };
/// MUTATING_BLOCK.call((&mut x,));
/// assert_eq!(x, 47);
/// ```
///
Expand Down Expand Up @@ -206,15 +209,15 @@ mod tests {

#[test]
fn test_noop_block() {
unsafe { NOOP_BLOCK.call(()) };
NOOP_BLOCK.call(());
}

#[test]
fn test_defined_in_function() {
global_block!(static MY_BLOCK = || -> i32 {
42
});
assert_eq!(unsafe { MY_BLOCK.call(()) }, 42);
assert_eq!(MY_BLOCK.call(()), 42);
}

#[cfg(feature = "apple")]
Expand Down
2 changes: 1 addition & 1 deletion block2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ mod debug;
mod global;
mod rc_block;

pub use block::{Block, BlockArguments};
pub use block::{Block, BlockArguments, BlockMut};
pub use concrete_block::{ConcreteBlock, IntoConcreteBlock};
pub use global::GlobalBlock;
pub use rc_block::RcBlock;

0 comments on commit 4210620

Please sign in to comment.