lazynodes.rs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. //! Support for storing lazy-nodes on the stack
  2. //!
  3. //! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls
  4. //! to `rsx!` more efficient.
  5. //!
  6. //! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on NodeFactory closures.
  7. //!
  8. //! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case,
  9. //! we build a tiny alloactor in the stack and allocate the closure into that.
  10. //!
  11. //! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
  12. //! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
  13. use crate::innerlude::{NodeFactory, VNode};
  14. use std::mem;
  15. /// A concrete type provider for closures that build VNode structures.
  16. ///
  17. /// This struct wraps lazy structs that build VNode trees Normally, we cannot perform a blanket implementation over
  18. /// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of IntoVNode.
  19. ///
  20. ///
  21. /// ```rust, ignore
  22. /// LazyNodes::new(|f| f.element("div", [], [], [] None))
  23. /// ```
  24. pub struct LazyNodes<'a, 'b> {
  25. inner: StackNodeStorage<'a, 'b>,
  26. }
  27. type StackHeapSize = [usize; 16];
  28. enum StackNodeStorage<'a, 'b> {
  29. Stack(LazyStack),
  30. Heap(Box<dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b>),
  31. }
  32. impl<'a, 'b> LazyNodes<'a, 'b> {
  33. pub fn new_some<F>(_val: F) -> Self
  34. where
  35. F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
  36. {
  37. Self::new(_val)
  38. }
  39. /// force this call onto the stack
  40. pub fn new_boxed<F>(_val: F) -> Self
  41. where
  42. F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
  43. {
  44. // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
  45. let mut slot = Some(_val);
  46. let val = move |fac: Option<NodeFactory<'a>>| {
  47. let inner = slot.take().unwrap();
  48. fac.map(inner)
  49. };
  50. Self {
  51. inner: StackNodeStorage::Heap(Box::new(val)),
  52. }
  53. }
  54. pub fn new<F>(_val: F) -> Self
  55. where
  56. F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
  57. {
  58. // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
  59. let mut slot = Some(_val);
  60. let val = move |fac: Option<NodeFactory<'a>>| {
  61. let inner = slot.take().unwrap();
  62. fac.map(inner)
  63. };
  64. // miri does not know how to work with mucking directly into bytes
  65. if cfg!(miri) {
  66. Self {
  67. inner: StackNodeStorage::Heap(Box::new(val)),
  68. }
  69. } else {
  70. unsafe { LazyNodes::new_inner(val) }
  71. }
  72. }
  73. unsafe fn new_inner<F>(val: F) -> Self
  74. where
  75. F: FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b,
  76. {
  77. let mut ptr: *const _ = &val as &dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>;
  78. assert_eq!(
  79. ptr as *const u8, &val as *const _ as *const u8,
  80. "MISUSE: Closure returned different pointer"
  81. );
  82. assert_eq!(
  83. std::mem::size_of_val(&*ptr),
  84. std::mem::size_of::<F>(),
  85. "MISUSE: Closure returned a subset pointer"
  86. );
  87. let words = ptr_as_slice(&mut ptr);
  88. assert!(
  89. words[0] == &val as *const _ as usize,
  90. "BUG: Pointer layout is not (data_ptr, info...)"
  91. );
  92. // - Ensure that Self is aligned same as data requires
  93. assert!(
  94. std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
  95. "TODO: Enforce alignment >{} (requires {})",
  96. std::mem::align_of::<Self>(),
  97. std::mem::align_of::<F>()
  98. );
  99. let info = &words[1..];
  100. let data = words[0] as *mut ();
  101. let size = mem::size_of::<F>();
  102. let stored_size = info.len() * mem::size_of::<usize>() + size;
  103. let max_size = mem::size_of::<StackHeapSize>();
  104. if stored_size > max_size {
  105. Self {
  106. inner: StackNodeStorage::Heap(Box::new(val)),
  107. }
  108. } else {
  109. let mut buf: StackHeapSize = StackHeapSize::default();
  110. assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
  111. // Place pointer information at the end of the region
  112. // - Allows the data to be at the start for alignment purposes
  113. {
  114. let info_ofs = buf.as_ref().len() - info.len();
  115. let info_dst = &mut buf.as_mut()[info_ofs..];
  116. for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
  117. *d = *v;
  118. }
  119. }
  120. let src_ptr = data as *const u8;
  121. let dataptr = buf.as_mut()[..].as_mut_ptr() as *mut u8;
  122. for i in 0..size {
  123. *dataptr.add(i) = *src_ptr.add(i);
  124. }
  125. std::mem::forget(val);
  126. Self {
  127. inner: StackNodeStorage::Stack(LazyStack {
  128. _align: [],
  129. buf,
  130. dropped: false,
  131. }),
  132. }
  133. }
  134. }
  135. pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> {
  136. match self.inner {
  137. StackNodeStorage::Heap(mut lazy) => lazy(Some(f)).unwrap(),
  138. StackNodeStorage::Stack(mut stack) => stack.call(f),
  139. }
  140. }
  141. }
  142. struct LazyStack {
  143. _align: [u64; 0],
  144. buf: StackHeapSize,
  145. dropped: bool,
  146. }
  147. impl LazyStack {
  148. fn call<'a>(&mut self, f: NodeFactory<'a>) -> VNode<'a> {
  149. let LazyStack { buf, .. } = self;
  150. let data = buf.as_ref();
  151. let info_size =
  152. mem::size_of::<*mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>>()
  153. / mem::size_of::<usize>()
  154. - 1;
  155. let info_ofs = data.len() - info_size;
  156. let g: *mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> =
  157. unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
  158. self.dropped = true;
  159. let clos = unsafe { &mut *g };
  160. clos(Some(f)).unwrap()
  161. }
  162. }
  163. impl Drop for LazyStack {
  164. fn drop(&mut self) {
  165. if !self.dropped {
  166. let LazyStack { buf, .. } = self;
  167. let data = buf.as_ref();
  168. let info_size = mem::size_of::<
  169. *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>>,
  170. >() / mem::size_of::<usize>()
  171. - 1;
  172. let info_ofs = data.len() - info_size;
  173. let g: *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>> =
  174. unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
  175. self.dropped = true;
  176. let clos = unsafe { &mut *g };
  177. clos(None);
  178. }
  179. }
  180. }
  181. /// Obtain mutable access to a pointer's words
  182. fn ptr_as_slice<T>(ptr: &mut T) -> &mut [usize] {
  183. assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0);
  184. let words = mem::size_of::<T>() / mem::size_of::<usize>();
  185. // SAFE: Points to valid memory (a raw pointer)
  186. unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) }
  187. }
  188. /// Re-construct a fat pointer
  189. unsafe fn make_fat_ptr<T: ?Sized>(data_ptr: usize, meta_vals: &[usize]) -> *mut T {
  190. let mut rv = mem::MaybeUninit::<*mut T>::uninit();
  191. {
  192. let s = ptr_as_slice(&mut rv);
  193. s[0] = data_ptr;
  194. s[1..].copy_from_slice(meta_vals);
  195. }
  196. let rv = rv.assume_init();
  197. assert_eq!(rv as *const (), data_ptr as *const ());
  198. rv
  199. }
  200. fn round_to_words(len: usize) -> usize {
  201. (len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
  202. }
  203. #[test]
  204. fn it_works() {
  205. let bump = bumpalo::Bump::new();
  206. let factory = NodeFactory { bump: &bump };
  207. let caller = LazyNodes::new_some(|f| {
  208. //
  209. f.text(format_args!("hello world!"))
  210. });
  211. let g = caller.call(factory);
  212. dbg!(g);
  213. }
  214. #[test]
  215. fn it_drops() {
  216. use std::rc::Rc;
  217. let bump = bumpalo::Bump::new();
  218. let factory = NodeFactory { bump: &bump };
  219. struct DropInner {
  220. id: i32,
  221. }
  222. impl Drop for DropInner {
  223. fn drop(&mut self) {
  224. log::debug!("dropping inner");
  225. }
  226. }
  227. let val = Rc::new(10);
  228. let caller = {
  229. let it = (0..10)
  230. .map(|i| {
  231. let val = val.clone();
  232. LazyNodes::new_some(move |f| {
  233. log::debug!("hell closure");
  234. let inner = DropInner { id: i };
  235. f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
  236. })
  237. })
  238. .collect::<Vec<_>>();
  239. LazyNodes::new_some(|f| {
  240. log::debug!("main closure");
  241. f.fragment_from_iter(it)
  242. })
  243. };
  244. let _ = caller.call(factory);
  245. assert_eq!(Rc::strong_count(&val), 1);
  246. }