Skip to content

Commit 17eba81

Browse files
committed
feat(closure): add Closure
It's essentially `&'static FnOnce()` but can be consumed easily by inline assembly and external code, and doesn't waste memory for vtable. Its constructor `from_fn_const` can store the given closure's environ- ment on a CTFE-heap-allocated memory region. This will prove useful in a lower-level kernel configuration function as it will be able to pass defined objects to a defined task's entry point.
1 parent 2563a3f commit 17eba81

File tree

2 files changed

+274
-0
lines changed

2 files changed

+274
-0
lines changed

src/r3_core/src/closure.rs

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
//! Provides [`Closure`], a light-weight closure type.
2+
use core::{
3+
fmt,
4+
mem::{align_of, size_of},
5+
};
6+
7+
use crate::utils::{mem::transmute, Init};
8+
9+
/// The environment parameter type for [`Closure`]. It's ABI-compatible with
10+
/// `*mut ()` but might not be fully initialized.
11+
///
12+
/// It's something that would usually be just `intptr_t` or `void *` in C code.
13+
/// It's designed to have the following properties:
14+
///
15+
/// - It's ABI-compatible with a C pointer, making it possible to pass the
16+
/// components of a [`Closure`] to kernel implementations written in inline
17+
/// assembly or other languages without needing to wrap it with another
18+
/// trampoline.
19+
///
20+
/// - Unlike `dyn FnOnce()`, it doesn't waste memory for vtable, most entries
21+
/// of which will never be used for static closures.
22+
///
23+
/// - Constructing it from a pointer doens't require a pointer-to-integer cast,
24+
/// which is disallowed in a constant context.
25+
///
26+
/// Currently it must be filled with initialized bytes because of compiler
27+
/// restrictions. This may change in the future.
28+
#[derive(Copy, Clone)]
29+
#[repr(transparent)]
30+
pub struct ClosureEnv(Option<&'static ()>);
31+
// FIXME: The contained type must be an initialized reference to avoid compile
32+
// errors that occur with the current compiler. Ideally it should be
33+
// `MaybeUninit<*mut ()>`, which, however, when a CTFE-heap allocation is
34+
// stored, produces an enigmatic error "untyped pointers are not allowed
35+
// in constant".
36+
37+
impl const Default for ClosureEnv {
38+
#[inline]
39+
fn default() -> Self {
40+
Self::INIT
41+
}
42+
}
43+
44+
impl Init for ClosureEnv {
45+
const INIT: Self = Self(None);
46+
}
47+
48+
impl fmt::Debug for ClosureEnv {
49+
#[inline]
50+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51+
f.write_str("ClosureEnv")
52+
}
53+
}
54+
55+
// FIMXE: Want to parameterize, but there's no way to make the `fn` pointer
56+
// high-ranked with generics
57+
/// A light-weight closure, which is comprised of a function pointer and an
58+
/// environment parameter.
59+
#[derive(Debug, Copy, Clone)]
60+
pub struct Closure {
61+
/// The function pointer.
62+
func: unsafe extern "C" fn(ClosureEnv),
63+
env: ClosureEnv,
64+
}
65+
66+
impl Init for Closure {
67+
const INIT: Closure = (|| {}).into_closure_const();
68+
}
69+
70+
impl Default for Closure {
71+
#[inline]
72+
fn default() -> Self {
73+
Self::INIT
74+
}
75+
}
76+
77+
impl Closure {
78+
/// Construct a `Self` from a function pointer and an associated pointer
79+
/// parameter.
80+
///
81+
/// # Safety
82+
///
83+
/// Safe code that has access to the constructed `Self` will be able to
84+
/// execute `func(env, _)`. A corollary is that, if `func` has additional
85+
/// safety requirements that are not covered by `Closure`, they are lost by
86+
/// this function, which means the resulting `Closure` mustn't be exposed to
87+
/// safe code.
88+
#[inline]
89+
pub const unsafe fn from_raw_parts(
90+
func: unsafe extern "C" fn(ClosureEnv),
91+
env: ClosureEnv,
92+
) -> Self {
93+
Self { func, env }
94+
}
95+
96+
/// Construct a `Self` from the given closure at compile time.
97+
///
98+
/// The conversion may involve compile-time heap allocation
99+
/// ([`core::intrinsics::const_allocate`]). **It's illegal to call this
100+
/// function at runtime.**
101+
///
102+
/// # Examples
103+
///
104+
/// ```
105+
/// use r3_core::closure::Closure;
106+
///
107+
/// // Zero-sized
108+
/// const C1: Closure = Closure::from_fn_const(|| {});
109+
///
110+
/// // CTFE-heap-allocated
111+
/// const C2: Closure = {
112+
/// let x = 42;
113+
/// Closure::from_fn_const(move || assert_eq!(x, 42))
114+
/// };
115+
///
116+
/// C1.call();
117+
/// C2.call();
118+
/// ```
119+
pub const fn from_fn_const<T: FnOnce() + Copy + Send + 'static>(func: T) -> Self {
120+
let size = size_of::<T>();
121+
let align = align_of::<T>();
122+
unsafe {
123+
// FIXME: `ClosureEnv` can hold up to `size_of::<ClosureEnv>()`
124+
// bytes in-line, but this can't be leveraged because its
125+
// current representation `Option<&()>` requires that it be
126+
// devoid of uninitialized bytes.
127+
if size == 0 {
128+
Self::from_raw_parts(trampoline_zst::<T>, ClosureEnv(None))
129+
} else {
130+
let env = core::intrinsics::const_allocate(size, align);
131+
env.cast::<T>().write(func);
132+
Self::from_raw_parts(trampoline_indirect::<T>, transmute(env))
133+
}
134+
}
135+
}
136+
137+
/// Call the closure.
138+
#[inline]
139+
pub fn call(self) {
140+
// Safety: `self.env` is provided as the first parameter
141+
unsafe { (self.func)(self.env) }
142+
}
143+
144+
/// Get the function pointer.
145+
#[inline]
146+
pub const fn func(self) -> unsafe extern "C" fn(ClosureEnv) {
147+
self.func
148+
}
149+
150+
/// Get the pojnter parameter.
151+
#[inline]
152+
pub const fn env(self) -> ClosureEnv {
153+
self.env
154+
}
155+
156+
/// Decompose `self` into raw components.
157+
#[inline]
158+
pub const fn as_raw_parts(self) -> (unsafe extern "C" fn(ClosureEnv), ClosureEnv) {
159+
(self.func, self.env)
160+
}
161+
}
162+
163+
/// A trait for converting a value into a [`Closure`] at compile time.
164+
///
165+
/// The conversion may involve compile-time heap allocation
166+
/// ([`core::intrinsics::const_allocate`]). It's illegal to use this trait's
167+
/// method at runtime.
168+
///
169+
/// # Examples
170+
///
171+
/// ```
172+
/// #![feature(const_trait_impl)]
173+
/// use r3_core::closure::{Closure, IntoClosureConst};
174+
///
175+
/// // `impl FnOnce()` → `Closure`
176+
/// const _: Closure = (|| {}).into_closure_const();
177+
/// ```
178+
pub trait IntoClosureConst {
179+
/// Perform conversion to [`Closure`], potentially using a compile-time
180+
/// heap.
181+
fn into_closure_const(self) -> Closure;
182+
}
183+
184+
/// Perform conversion using [`Closure::from_fn_const`].
185+
impl<T: FnOnce() + Copy + Send + 'static> const IntoClosureConst for T {
186+
fn into_closure_const(self) -> Closure {
187+
Closure::from_fn_const(self)
188+
}
189+
}
190+
191+
unsafe extern "C" fn trampoline_zst<T: FnOnce()>(_: ClosureEnv) {
192+
let func: T = unsafe { transmute(()) };
193+
func()
194+
}
195+
196+
unsafe extern "C" fn trampoline_indirect<T: FnOnce()>(env: ClosureEnv) {
197+
let p_func: *const T = unsafe { transmute(env) };
198+
// FIXME: Since there's no trait indicating the lack of interior mutability,
199+
// we have to copy `T` onto stack
200+
let func: T = unsafe { p_func.read() };
201+
func()
202+
}
203+
204+
#[cfg(test)]
205+
mod tests {
206+
use super::*;
207+
use core::sync::atomic::{AtomicUsize, Ordering};
208+
209+
#[test]
210+
fn nested() {
211+
static STATE: AtomicUsize = AtomicUsize::new(0);
212+
213+
const C1: Closure = {
214+
let value = 0x1234;
215+
(move || {
216+
STATE.fetch_add(value, Ordering::Relaxed);
217+
})
218+
.into_closure_const()
219+
};
220+
const C2: Closure = {
221+
let c = C1;
222+
(move || {
223+
c.call();
224+
c.call();
225+
})
226+
.into_closure_const()
227+
};
228+
const C3: Closure = {
229+
let c = C2;
230+
(move || {
231+
c.call();
232+
c.call();
233+
})
234+
.into_closure_const()
235+
};
236+
const C4: Closure = {
237+
let c = C3;
238+
(move || {
239+
c.call();
240+
c.call();
241+
})
242+
.into_closure_const()
243+
};
244+
245+
STATE.store(0, Ordering::Relaxed);
246+
C4.call();
247+
assert_eq!(STATE.load(Ordering::Relaxed), 0x1234 * 8);
248+
}
249+
250+
#[test]
251+
fn same_fn_different_env() {
252+
static STATE: AtomicUsize = AtomicUsize::new(0);
253+
254+
const fn adder(x: usize) -> impl FnOnce() + Copy + Send {
255+
move || {
256+
STATE.fetch_add(x, Ordering::Relaxed);
257+
}
258+
}
259+
260+
const ADD1: Closure = adder(1).into_closure_const();
261+
const ADD2: Closure = adder(2).into_closure_const();
262+
const ADD4: Closure = adder(4).into_closure_const();
263+
264+
STATE.store(0, Ordering::Relaxed);
265+
ADD1.call();
266+
assert_eq!(STATE.load(Ordering::Relaxed), 1);
267+
ADD4.call();
268+
assert_eq!(STATE.load(Ordering::Relaxed), 1 + 4);
269+
ADD2.call();
270+
assert_eq!(STATE.load(Ordering::Relaxed), 1 + 4 + 2);
271+
}
272+
}

src/r3_core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#![feature(const_refs_to_cell)]
2323
#![feature(const_ptr_as_ref)]
2424
#![feature(const_ptr_write)]
25+
#![feature(const_impl_trait)]
2526
#![feature(core_intrinsics)]
2627
#![feature(const_heap)]
2728
#![feature(let_else)]
@@ -68,6 +69,7 @@ pub mod utils;
6869
#[macro_use]
6970
pub mod kernel;
7071
pub mod bag;
72+
pub mod closure;
7173
pub mod hunk;
7274
pub mod time;
7375

0 commit comments

Comments
 (0)