123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- //! Support for storing lazy-nodes on the stack
- //!
- //! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls
- //! to `rsx!` more efficient.
- //!
- //! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`NodeFactory`] closures.
- //!
- //! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case,
- //! we build a tiny alloactor in the stack and allocate the closure into that.
- //!
- //! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
- //! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
- use crate::innerlude::{NodeFactory, VNode};
- use std::mem;
- /// A concrete type provider for closures that build [`VNode`] structures.
- ///
- /// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over
- /// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of [`IntoVNode`].
- ///
- ///
- /// ```rust, ignore
- /// LazyNodes::new(|f| f.element("div", [], [], [] None))
- /// ```
- pub struct LazyNodes<'a, 'b> {
- inner: StackNodeStorage<'a, 'b>,
- }
- type StackHeapSize = [usize; 16];
- enum StackNodeStorage<'a, 'b> {
- Stack(LazyStack),
- Heap(Box<dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b>),
- }
- impl<'a, 'b> LazyNodes<'a, 'b> {
- /// Create a new [`LazyNodes`] closure, optimistically placing it onto the stack.
- ///
- /// If the closure cannot fit into the stack allocation (16 bytes), then it
- /// is placed on the heap. Most closures will fit into the stack, and is
- /// the most optimal way to use the creation function.
- pub fn new(val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self {
- // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
- let mut slot = Some(val);
- let val = move |fac: Option<NodeFactory<'a>>| {
- fac.map(
- slot.take()
- .expect("LazyNodes closure to be called only once"),
- )
- };
- // miri does not know how to work with mucking directly into bytes
- // just use a heap allocated type when miri is running
- if cfg!(miri) {
- Self {
- inner: StackNodeStorage::Heap(Box::new(val)),
- }
- } else {
- unsafe { LazyNodes::new_inner(val) }
- }
- }
- /// Create a new [`LazyNodes`] closure, but force it onto the heap.
- pub fn new_boxed<F>(inner: F) -> Self
- where
- F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
- {
- // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
- let mut slot = Some(inner);
- Self {
- inner: StackNodeStorage::Heap(Box::new(move |fac: Option<NodeFactory<'a>>| {
- fac.map(
- slot.take()
- .expect("LazyNodes closure to be called only once"),
- )
- })),
- }
- }
- unsafe fn new_inner<F>(val: F) -> Self
- where
- F: FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b,
- {
- let mut ptr: *const _ = &val as &dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>;
- assert_eq!(
- ptr as *const u8, &val as *const _ as *const u8,
- "MISUSE: Closure returned different pointer"
- );
- assert_eq!(
- std::mem::size_of_val(&*ptr),
- std::mem::size_of::<F>(),
- "MISUSE: Closure returned a subset pointer"
- );
- let words = ptr_as_slice(&mut ptr);
- assert!(
- words[0] == &val as *const _ as usize,
- "BUG: Pointer layout is not (data_ptr, info...)"
- );
- // - Ensure that Self is aligned same as data requires
- assert!(
- std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
- "TODO: Enforce alignment >{} (requires {})",
- std::mem::align_of::<Self>(),
- std::mem::align_of::<F>()
- );
- let info = &words[1..];
- let data = words[0] as *mut ();
- let size = mem::size_of::<F>();
- let stored_size = info.len() * mem::size_of::<usize>() + size;
- let max_size = mem::size_of::<StackHeapSize>();
- if stored_size > max_size {
- Self {
- inner: StackNodeStorage::Heap(Box::new(val)),
- }
- } else {
- let mut buf: StackHeapSize = StackHeapSize::default();
- assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
- // Place pointer information at the end of the region
- // - Allows the data to be at the start for alignment purposes
- {
- let info_ofs = buf.as_ref().len() - info.len();
- let info_dst = &mut buf.as_mut()[info_ofs..];
- for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
- *d = *v;
- }
- }
- let src_ptr = data as *const u8;
- let dataptr = buf.as_mut_ptr().cast::<u8>();
- for i in 0..size {
- *dataptr.add(i) = *src_ptr.add(i);
- }
- std::mem::forget(val);
- Self {
- inner: StackNodeStorage::Stack(LazyStack {
- _align: [],
- buf,
- dropped: false,
- }),
- }
- }
- }
- /// Call the closure with the given factory to produce real [`VNode`].
- ///
- /// ```rust, ignore
- /// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None));
- ///
- /// let fac = NodeFactory::new(&cx);
- ///
- /// let node = f.call(cac);
- /// ```
- #[must_use]
- pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> {
- match self.inner {
- StackNodeStorage::Heap(mut lazy) => {
- lazy(Some(f)).expect("Closure should not be called twice")
- }
- StackNodeStorage::Stack(mut stack) => stack.call(f),
- }
- }
- }
- struct LazyStack {
- _align: [u64; 0],
- buf: StackHeapSize,
- dropped: bool,
- }
- impl LazyStack {
- fn call<'a>(&mut self, f: NodeFactory<'a>) -> VNode<'a> {
- let LazyStack { buf, .. } = self;
- let data = buf.as_ref();
- let info_size =
- mem::size_of::<*mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>>()
- / mem::size_of::<usize>()
- - 1;
- let info_ofs = data.len() - info_size;
- let g: *mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> =
- unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
- self.dropped = true;
- let clos = unsafe { &mut *g };
- clos(Some(f)).unwrap()
- }
- }
- impl Drop for LazyStack {
- fn drop(&mut self) {
- if !self.dropped {
- let LazyStack { buf, .. } = self;
- let data = buf.as_ref();
- let info_size = mem::size_of::<
- *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>>,
- >() / mem::size_of::<usize>()
- - 1;
- let info_ofs = data.len() - info_size;
- let g: *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>> =
- unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
- self.dropped = true;
- let clos = unsafe { &mut *g };
- clos(None);
- }
- }
- }
- /// Obtain mutable access to a pointer's words
- fn ptr_as_slice<T>(ptr: &mut T) -> &mut [usize] {
- assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0);
- let words = mem::size_of::<T>() / mem::size_of::<usize>();
- // SAFE: Points to valid memory (a raw pointer)
- unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) }
- }
- /// Re-construct a fat pointer
- unsafe fn make_fat_ptr<T: ?Sized>(data_ptr: usize, meta_vals: &[usize]) -> *mut T {
- let mut rv = mem::MaybeUninit::<*mut T>::uninit();
- {
- let s = ptr_as_slice(&mut rv);
- s[0] = data_ptr;
- s[1..].copy_from_slice(meta_vals);
- }
- let rv = rv.assume_init();
- assert_eq!(rv as *const (), data_ptr as *const ());
- rv
- }
- fn round_to_words(len: usize) -> usize {
- (len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
- }
- // #[cfg(test)]
- // mod tests {
- // use super::*;
- // use crate::innerlude::{Element, Scope, VirtualDom};
- // #[test]
- // fn it_works() {
- // fn app(cx: Scope<()>) -> Element {
- // cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!"))))
- // }
- // let mut dom = VirtualDom::new(app);
- // dom.rebuild();
- // let g = dom.base_scope().root_node();
- // dbg!(g);
- // }
- // #[test]
- // fn it_drops() {
- // use std::rc::Rc;
- // struct AppProps {
- // inner: Rc<i32>,
- // }
- // fn app(cx: Scope<AppProps>) -> Element {
- // struct DropInner {
- // id: i32,
- // }
- // impl Drop for DropInner {
- // fn drop(&mut self) {
- // eprintln!("dropping inner");
- // }
- // }
- // let caller = {
- // let it = (0..10).map(|i| {
- // let val = cx.props.inner.clone();
- // LazyNodes::new(move |f| {
- // eprintln!("hell closure");
- // let inner = DropInner { id: i };
- // f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
- // })
- // });
- // LazyNodes::new(|f| {
- // eprintln!("main closure");
- // f.fragment_from_iter(it)
- // })
- // };
- // cx.render(caller)
- // }
- // let inner = Rc::new(0);
- // let mut dom = VirtualDom::new_with_props(
- // app,
- // AppProps {
- // inner: inner.clone(),
- // },
- // );
- // dom.rebuild();
- // drop(dom);
- // assert_eq!(Rc::strong_count(&inner), 1);
- // }
- // }
|