Skip to content

Commit 4c5b298

Browse files
authored
Merge pull request #69 from madsmtm/global-block
Add `global_block!` macro
2 parents ec721ff + e63299e commit 4c5b298

File tree

7 files changed

+350
-1
lines changed

7 files changed

+350
-1
lines changed

block2/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## Unreleased - YYYY-MM-DD
88

9+
### Added
10+
* `GlobalBlock` and corresponding `global_block!` macro, allowing statically
11+
creating blocks that don't reference their environment.
12+
913

1014
## 0.2.0-alpha.1 - 2021-11-22
1115

block2/src/global.rs

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
use core::marker::PhantomData;
2+
use core::mem;
3+
use core::ops::Deref;
4+
use core::ptr;
5+
use std::os::raw::c_ulong;
6+
7+
use objc2_encode::{Encode, EncodeArguments};
8+
9+
use super::{ffi, Block};
10+
use crate::BlockArguments;
11+
12+
// TODO: Should this be a static to help the compiler deduplicating them?
13+
const GLOBAL_DESCRIPTOR: ffi::Block_descriptor_header = ffi::Block_descriptor_header {
14+
reserved: 0,
15+
size: mem::size_of::<ffi::Block_layout>() as c_ulong,
16+
};
17+
18+
/// An Objective-C block that does not capture it's environment.
19+
///
20+
/// This is effectively just a glorified function pointer, and can created and
21+
/// stored in static memory using the [`global_block`][`global_block!`] macro.
22+
///
23+
/// If [`ConcreteBlock`] is the [`Fn`]-block equivalent, this is likewise the
24+
/// [`fn`]-block equivalent.
25+
///
26+
/// [`ConcreteBlock`]: crate::ConcreteBlock
27+
#[repr(C)]
28+
pub struct GlobalBlock<A, R = ()> {
29+
layout: ffi::Block_layout,
30+
p: PhantomData<(A, R)>,
31+
}
32+
33+
unsafe impl<A, R> Sync for GlobalBlock<A, R>
34+
where
35+
A: BlockArguments + EncodeArguments,
36+
R: Encode,
37+
{
38+
}
39+
unsafe impl<A, R> Send for GlobalBlock<A, R>
40+
where
41+
A: BlockArguments + EncodeArguments,
42+
R: Encode,
43+
{
44+
}
45+
46+
// Note: We can't put correct bounds on A and R because we have a const fn!
47+
//
48+
// Fortunately, we don't need them, since they're present on `Sync`, so
49+
// constructing the static in `global_block!` with an invalid `GlobalBlock`
50+
// triggers an error.
51+
impl<A, R> GlobalBlock<A, R> {
52+
// TODO: Use new ABI with BLOCK_HAS_SIGNATURE
53+
const FLAGS: ffi::block_flags = ffi::BLOCK_IS_GLOBAL | ffi::BLOCK_USE_STRET;
54+
55+
#[doc(hidden)]
56+
pub const __DEFAULT_LAYOUT: ffi::Block_layout = ffi::Block_layout {
57+
// Populated in `global_block!`
58+
isa: ptr::null_mut(),
59+
flags: Self::FLAGS,
60+
reserved: 0,
61+
// Populated in `global_block!`
62+
invoke: None,
63+
descriptor: &GLOBAL_DESCRIPTOR as *const _ as *mut _,
64+
};
65+
66+
/// Use the [`global_block`] macro instead.
67+
#[doc(hidden)]
68+
pub const unsafe fn from_layout(layout: ffi::Block_layout) -> Self {
69+
Self {
70+
layout,
71+
p: PhantomData,
72+
}
73+
}
74+
}
75+
76+
impl<A, R> Deref for GlobalBlock<A, R>
77+
where
78+
A: BlockArguments + EncodeArguments,
79+
R: Encode,
80+
{
81+
type Target = Block<A, R>;
82+
83+
fn deref(&self) -> &Block<A, R> {
84+
// TODO: SAFETY
85+
unsafe { &*(self as *const Self as *const Block<A, R>) }
86+
}
87+
}
88+
89+
/// Construct a static [`GlobalBlock`].
90+
///
91+
/// The syntax is similar to a static closure. Note that the block cannot
92+
/// capture it's environment, and it's argument types and return type must be
93+
/// [`Encode`].
94+
///
95+
/// # Examples
96+
///
97+
/// ```
98+
/// use block2::global_block;
99+
/// global_block! {
100+
/// static MY_BLOCK = || -> i32 {
101+
/// 42
102+
/// }
103+
/// };
104+
/// assert_eq!(unsafe { MY_BLOCK.call(()) }, 42);
105+
/// ```
106+
///
107+
/// ```
108+
/// use block2::global_block;
109+
/// global_block! {
110+
/// static ADDER_BLOCK = |x: i32, y: i32| -> i32 {
111+
/// x + y
112+
/// }
113+
/// };
114+
/// assert_eq!(unsafe { ADDER_BLOCK.call((5, 7)) }, 12);
115+
/// ```
116+
///
117+
/// ```
118+
/// use block2::global_block;
119+
/// global_block! {
120+
/// pub static MUTATING_BLOCK = |x: &mut i32| {
121+
/// *x = *x + 42;
122+
/// }
123+
/// };
124+
/// let mut x = 5;
125+
/// unsafe { MUTATING_BLOCK.call((&mut x,)) };
126+
/// assert_eq!(x, 47);
127+
/// ```
128+
///
129+
/// The following does not compile because [`Box`] is not [`Encode`]:
130+
///
131+
/// ```compile_fail
132+
/// use block2::global_block;
133+
/// global_block! {
134+
/// pub static INVALID_BLOCK = |b: Box<i32>| {}
135+
/// };
136+
/// ```
137+
///
138+
/// [`Box`]: std::boxed::Box
139+
#[macro_export]
140+
macro_rules! global_block {
141+
// `||` is parsed as one token
142+
(
143+
$(#[$m:meta])*
144+
$vis:vis static $name:ident = || $(-> $r:ty)? $body:block
145+
) => {
146+
$crate::global_block!($(#[$m])* $vis static $name = |,| $(-> $r)? $body);
147+
};
148+
(
149+
$(#[$m:meta])*
150+
$vis:vis static $name:ident = |$($a:ident: $t:ty),* $(,)?| $(-> $r:ty)? $body:block
151+
) => {
152+
$(#[$m])*
153+
#[allow(unused_unsafe)]
154+
$vis static $name: $crate::GlobalBlock<($($t,)*) $(, $r)?> = unsafe {
155+
let mut layout = $crate::GlobalBlock::<($($t,)*) $(, $r)?>::__DEFAULT_LAYOUT;
156+
layout.isa = &$crate::ffi::_NSConcreteGlobalBlock as *const _ as *mut _;
157+
layout.invoke = Some({
158+
unsafe extern "C" fn inner(_: *mut $crate::ffi::Block_layout, $($a: $t),*) $(-> $r)? {
159+
$body
160+
}
161+
let inner: unsafe extern "C" fn(*mut $crate::ffi::Block_layout, $($a: $t),*) $(-> $r)? = inner;
162+
163+
// TODO: SAFETY
164+
::core::mem::transmute(inner)
165+
});
166+
$crate::GlobalBlock::from_layout(layout)
167+
};
168+
};
169+
}
170+
171+
#[cfg(test)]
172+
mod tests {
173+
global_block! {
174+
/// Test comments and visibility
175+
pub(super) static NOOP_BLOCK = || {}
176+
}
177+
178+
global_block! {
179+
/// Multiple arguments + trailing comma
180+
#[allow(unused)]
181+
static BLOCK = |x: i32, y: i32, z: i32, w: i32,| -> i32 {
182+
x + y + z + w
183+
}
184+
}
185+
186+
#[test]
187+
fn test_noop_block() {
188+
unsafe { NOOP_BLOCK.call(()) };
189+
}
190+
191+
#[test]
192+
fn test_defined_in_function() {
193+
global_block!(
194+
static MY_BLOCK = || -> i32 {
195+
42
196+
}
197+
);
198+
assert_eq!(unsafe { MY_BLOCK.call(()) }, 42);
199+
}
200+
}

block2/src/lib.rs

+18
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ It is important to copy your block to the heap (with the `copy` method) before
4343
passing it to Objective-C; this is because our `ConcreteBlock` is only meant
4444
to be copied once, and we can enforce this in Rust, but if Objective-C code
4545
were to copy it twice we could have a double free.
46+
47+
As an optimization if your block doesn't capture any variables, you can use
48+
the [`global_block!`] macro to create a static block:
49+
50+
```
51+
use block2::global_block;
52+
global_block! {
53+
static MY_BLOCK = || -> f32 {
54+
10.0
55+
}
56+
};
57+
assert_eq!(unsafe { MY_BLOCK.call(()) }, 10.0);
58+
```
4659
*/
4760

4861
#![no_std]
@@ -66,6 +79,11 @@ use std::os::raw::{c_int, c_ulong};
6679
pub use block_sys as ffi;
6780
use objc2_encode::{Encode, EncodeArguments, Encoding, RefEncode};
6881

82+
#[macro_use]
83+
mod global;
84+
85+
pub use global::GlobalBlock;
86+
6987
/// Types that may be used as the arguments to an Objective-C block.
7088
pub trait BlockArguments: Sized {
7189
/// Calls the given `Block` with self as the arguments.

tests/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ build = "build.rs"
1212
[dependencies]
1313
block2 = { path = "../block2" }
1414
block-sys = { path = "../block-sys" }
15+
objc2-encode = { path = "../objc2-encode" }
1516

1617
[build-dependencies]
1718
cc = "1.0"

tests/extern/block_utils.c

+26
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
#include <stdint.h>
22
#include <Block.h>
33

4+
typedef struct {
5+
float x;
6+
uint8_t y[100];
7+
} LargeStruct;
8+
49
typedef int32_t (^IntBlock)();
510
typedef int32_t (^AddBlock)(int32_t);
11+
typedef LargeStruct (^LargeStructBlock)(LargeStruct);
612

713
IntBlock get_int_block() {
814
return ^{ return (int32_t)7; };
@@ -27,3 +33,23 @@ int32_t invoke_int_block(IntBlock block) {
2733
int32_t invoke_add_block(AddBlock block, int32_t a) {
2834
return block(a);
2935
}
36+
37+
LargeStructBlock get_large_struct_block() {
38+
return ^(LargeStruct s) {
39+
s.x -= 1.0;
40+
s.y[12] += 1;
41+
s.y[99] = 123;
42+
return s;
43+
};
44+
}
45+
46+
LargeStructBlock get_large_struct_block_with(LargeStruct a) {
47+
return Block_copy(^(LargeStruct s) {
48+
(void)s; // Unused
49+
return a;
50+
});
51+
}
52+
53+
LargeStruct invoke_large_struct_block(LargeStructBlock block, LargeStruct s) {
54+
return block(s);
55+
}

tests/src/ffi.rs

+53
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use objc2_encode::{Encode, Encoding};
2+
13
/// A block that takes no arguments and returns an integer: `int32_t (^)()`.
24
#[repr(C)]
35
pub struct IntBlock {
@@ -11,6 +13,37 @@ pub struct AddBlock {
1113
_priv: [u8; 0],
1214
}
1315

16+
#[repr(C)]
17+
#[derive(Debug, Clone, Copy, PartialEq)]
18+
pub struct LargeStruct {
19+
pub x: f32,
20+
pub y: [u8; 100],
21+
}
22+
23+
impl LargeStruct {
24+
pub fn get() -> Self {
25+
let mut y = [10; 100];
26+
y[42] = 123;
27+
Self { x: 5.0, y }
28+
}
29+
30+
pub fn mutate(&mut self) {
31+
self.x -= 1.0;
32+
self.y[12] += 1;
33+
self.y[99] = 123;
34+
}
35+
}
36+
37+
unsafe impl Encode for LargeStruct {
38+
const ENCODING: Encoding<'static> =
39+
Encoding::Struct("LargeStruct", &[f32::ENCODING, <[u8; 100]>::ENCODING]);
40+
}
41+
42+
#[repr(C)]
43+
pub struct LargeStructBlock {
44+
_priv: [u8; 0],
45+
}
46+
1447
extern "C" {
1548
/// Returns a pointer to a global `IntBlock` that returns 7.
1649
pub fn get_int_block() -> *mut IntBlock;
@@ -24,6 +57,10 @@ extern "C" {
2457
pub fn invoke_int_block(block: *mut IntBlock) -> i32;
2558
/// Invokes an `AddBlock` with `a` and returns the result.
2659
pub fn invoke_add_block(block: *mut AddBlock, a: i32) -> i32;
60+
61+
pub fn get_large_struct_block() -> *mut LargeStructBlock;
62+
pub fn get_large_struct_block_with(i: LargeStruct) -> *mut LargeStructBlock;
63+
pub fn invoke_large_struct_block(block: *mut LargeStructBlock, s: LargeStruct) -> LargeStruct;
2764
}
2865

2966
#[cfg(test)]
@@ -45,4 +82,20 @@ mod tests {
4582
assert_eq!(invoke_add_block(get_add_block_with(3), 5), 8);
4683
}
4784
}
85+
86+
#[test]
87+
fn test_large_struct_block() {
88+
let data = LargeStruct::get();
89+
let mut expected = data.clone();
90+
expected.mutate();
91+
92+
assert_eq!(
93+
unsafe { invoke_large_struct_block(get_large_struct_block(), data) },
94+
expected
95+
);
96+
assert_eq!(
97+
unsafe { invoke_large_struct_block(get_large_struct_block_with(expected), data) },
98+
expected
99+
);
100+
}
48101
}

0 commit comments

Comments
 (0)