├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── comparisons.rs ├── build.rs └── src ├── fatptr.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /.criterion 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ### Categories each change fall into 8 | 9 | * **Added**: for new features. 10 | * **Changed**: for changes in existing functionality. 11 | * **Deprecated**: for soon-to-be removed features. 12 | * **Removed**: for now removed features. 13 | * **Fixed**: for any bug fixes. 14 | * **Security**: in case of vulnerabilities. 15 | 16 | 17 | ## [Unreleased] 18 | ### Added 19 | - Add support for building without the standard library. 20 | - Add a `const fn`, `DynStack::new_unchecked`. Allows static initialization. This makes the 21 | minimum required compiler version 1.39. 22 | - Implement `Iterator::size_hint` and `ExactSizeIterator` for `DynStackIter` and `DynStackIterMut`. 23 | 24 | ### Changed 25 | - Don't allocate memory in `DynStack::new`. Postpone allocation until the first push. 26 | - Upgrade the crate to Rust 2018 edition. 27 | - Implement `Send` and/or `Sync` for `DynStack` if `T` is `Send`/`Sync`. 28 | 29 | 30 | ## [0.3.0] - 2019-04-24 31 | ### Fixed 32 | - Assert that `T` is a trait object in `DynStack::new`. Prevents using the stack on normal 33 | objects, that could have caused undefined behavior. 34 | - Check fat pointer memory layout at build time. Makes this crate fail to build if the memory 35 | representation of a trait object ever changes. Hopefully preventing undefined behavior. 36 | 37 | 38 | ## [0.2.1] - 2019-04-24 39 | Just removed large binary files that were accidentally included in the package uploaded to 40 | crates.io. Otherwise identical to 0.2.0. 41 | 42 | 43 | ## [0.2.0] - 2018-12-16 44 | ### Fixed 45 | - Correctly drop in remove_last, prevent overflow panics. 46 | - Correctly handle alignments greater than 16 by moving data if necessary. 47 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dynstack" 3 | version = "0.4.0" 4 | authors = ["Gui Andrade "] 5 | description = "A stack for trait objects that minimizes allocations" 6 | repository = "https://github.com/archshift/dynstack" 7 | license = "MIT" 8 | readme = "README.md" 9 | edition = "2018" 10 | 11 | [dev-dependencies] 12 | criterion = "0.1.2" 13 | 14 | [[bench]] 15 | name = "comparisons" 16 | harness = false 17 | 18 | [features] 19 | default = ["std"] 20 | 21 | # Enables std. Currently used to make tests work. But the library itself 22 | # works without the standard library. 23 | std = [] 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Gui Andrade 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dynstack 2 | 3 | ## A stack for trait objects that minimizes allocations 4 | 5 | **COMPATIBILITY NOTE:** `dynstack` relies on an underspecified fat pointer representation. Though 6 | it isn't expected to change in the foreseeable future, this crate expects Rust 1.34's representation. 7 | 8 | ### Usage 9 | 10 | `dynstack` can mostly replace anywhere you'd use a stack, or a vector that doesn't 11 | require removal from its center. 12 | 13 | ```rust 14 | let mut stack = DynStack::::new(); 15 | dyn_push!(stack, "hello, world!"); 16 | dyn_push!(stack, 0usize); 17 | dyn_push!(stack, [1, 2, 3, 4, 5, 6]); 18 | 19 | for item in stack.iter() { 20 | println!("{:?}", item); 21 | } 22 | 23 | // prints: 24 | // "hello, world!" 25 | // 0 26 | // [1, 2, 3, 4, 5, 6] 27 | ``` 28 | -------------------------------------------------------------------------------- /benches/comparisons.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Bencher, Criterion}; 2 | use dynstack::{dyn_push, DynStack}; 3 | use std::fmt::Display; 4 | 5 | trait ATrait {} 6 | 7 | struct Large([u8; 950]); 8 | 9 | impl Large { 10 | pub fn new() -> Self { 11 | Self([3; 950]) 12 | } 13 | } 14 | 15 | impl ATrait for Large {} 16 | 17 | fn new_speed_naive(b: &mut Bencher) { 18 | b.iter(Vec::>::new); 19 | } 20 | 21 | fn new_speed_dynstack(b: &mut Bencher) { 22 | b.iter(DynStack::::new); 23 | } 24 | 25 | fn push_large_speed_naive(b: &mut Bencher) { 26 | b.iter(|| { 27 | let mut vec = Vec::>::new(); 28 | vec.push(Box::new(Large::new())); 29 | vec 30 | }); 31 | } 32 | 33 | fn push_large_speed_dynstack(b: &mut Bencher) { 34 | b.iter(|| { 35 | let mut stack = DynStack::::new(); 36 | dyn_push!(stack, Large::new()); 37 | stack 38 | }); 39 | } 40 | 41 | fn push_speed_naive(b: &mut Bencher) { 42 | let mut vec = Vec::>::new(); 43 | b.iter(|| { 44 | vec.push(Box::new(0xF00BAAusize)); 45 | vec.push(Box::new(0xABBAu16)); 46 | vec.push(Box::new(0xBA7123AAu32)); 47 | vec.push(Box::new(12u8)); 48 | }); 49 | } 50 | 51 | fn push_speed_dynstack(b: &mut Bencher) { 52 | let mut stack = DynStack::::new(); 53 | b.iter(|| { 54 | dyn_push!(stack, 0xF00BAAusize); 55 | dyn_push!(stack, 0xABBAu16); 56 | dyn_push!(stack, 0xBA7123AAu32); 57 | dyn_push!(stack, 12u8); 58 | }); 59 | } 60 | 61 | fn push_and_run_naive(b: &mut Bencher) { 62 | b.iter(|| { 63 | let mut stack = Vec:: usize>>::new(); 64 | fn pseudorecursive(stack: &mut Vec usize>>, n: usize) { 65 | stack.push(Box::new(move || n - 1)); 66 | } 67 | 68 | let mut n = 100; 69 | let mut i = 0; 70 | while n > 0 { 71 | pseudorecursive(&mut stack, n); 72 | n = (stack.get(i).unwrap())(); 73 | i += 1; 74 | } 75 | }); 76 | } 77 | 78 | fn push_and_run_dynstack(b: &mut Bencher) { 79 | b.iter(|| { 80 | let mut stack = DynStack:: usize>::new(); 81 | fn pseudorecursive(stack: &mut DynStack usize>, n: usize) { 82 | dyn_push!(stack, move || n - 1); 83 | } 84 | 85 | let mut n = 100; 86 | let mut i = 0; 87 | while n > 0 { 88 | pseudorecursive(&mut stack, n); 89 | n = (stack.get(i).unwrap())(); 90 | i += 1; 91 | } 92 | }); 93 | } 94 | 95 | fn pseudorecursive2_naive(b: &mut Bencher) { 96 | b.iter(|| { 97 | let mut state: Box usize> = Box::new(|| 0); 98 | fn pseudorecursive(state: &mut Box usize>, n: usize) { 99 | *state = Box::new(move || n - 1); 100 | } 101 | 102 | let mut n = 100; 103 | while n > 0 { 104 | pseudorecursive(&mut state, n); 105 | n = state(); 106 | } 107 | }); 108 | } 109 | 110 | fn pseudorecursive2_dynstack(b: &mut Bencher) { 111 | b.iter(|| { 112 | let mut stack = DynStack:: usize>::new(); 113 | fn pseudorecursive(stack: &mut DynStack usize>, n: usize) { 114 | dyn_push!(stack, move || n - 1); 115 | } 116 | 117 | let mut n = 100; 118 | while n > 0 { 119 | pseudorecursive(&mut stack, n); 120 | n = (stack.peek().unwrap())(); 121 | stack.remove_last(); 122 | } 123 | }); 124 | } 125 | 126 | trait AsUsize { 127 | fn make(&self) -> usize; 128 | } 129 | 130 | impl AsUsize for usize { 131 | fn make(&self) -> usize { 132 | *self 133 | } 134 | } 135 | 136 | fn access_naive(b: &mut Bencher) { 137 | let mut stack = Vec::>::new(); 138 | for _ in 0..1000 { 139 | stack.push(Box::new(0xF00BAAusize)); 140 | } 141 | b.iter(|| { 142 | for i in &stack { 143 | criterion::black_box(i.make()); 144 | } 145 | }); 146 | } 147 | 148 | fn access_dynstack(b: &mut Bencher) { 149 | let mut stack = DynStack::::new(); 150 | for _ in 0..1000 { 151 | dyn_push!(stack, 0xF00BAAusize); 152 | } 153 | b.iter(|| { 154 | for i in &stack { 155 | criterion::black_box(i.make()); 156 | } 157 | }); 158 | } 159 | 160 | fn criterion_benchmark(c: &mut Criterion) { 161 | c.bench_function("new_speed_naive", new_speed_naive); 162 | c.bench_function("new_speed_dynstack", new_speed_dynstack); 163 | c.bench_function("push_large_speed_naive", push_large_speed_naive); 164 | c.bench_function("push_large_speed_dynstack", push_large_speed_dynstack); 165 | c.bench_function("push_speed_naive", push_speed_naive); 166 | c.bench_function("push_speed_dynstack", push_speed_dynstack); 167 | c.bench_function("push_and_run_naive", push_and_run_naive); 168 | c.bench_function("push_and_run_dynstack", push_and_run_dynstack); 169 | c.bench_function("pseudorecursive2_naive", pseudorecursive2_naive); 170 | c.bench_function("pseudorecursive2_dynstack", pseudorecursive2_dynstack); 171 | c.bench_function("access_naive", access_naive); 172 | c.bench_function("access_dynstack", access_dynstack); 173 | } 174 | 175 | criterion_group!(benches, criterion_benchmark); 176 | criterion_main!(benches); 177 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | #[path = "src/fatptr.rs"] 4 | mod fatptr; 5 | 6 | fn main() { 7 | // This build script sanity checks the memory layout of 8 | // trait objects/fat pointers. So if the Rust compiler ever change 9 | // layout of these, this crate should hopefully fail to compile 10 | // instead of producing programs that has undefined behavior. 11 | 12 | assert_eq!( 13 | mem::size_of::<&dyn TestTrait>(), 14 | mem::size_of::<[usize; 2]>(), 15 | "Trait objects does not have the expected size" 16 | ); 17 | 18 | let instance1 = Implementer1(1); 19 | let instance2 = Implementer2(2); 20 | let [data1, vtable1] = unsafe { fatptr::decomp(&instance1 as &dyn TestTrait) }; 21 | let [data2, vtable2] = unsafe { fatptr::decomp(&instance2 as &dyn TestTrait) }; 22 | 23 | assert_eq!( 24 | data1, &instance1 as *const Implementer1 as usize, 25 | "First part of the fat pointer does not point to the data" 26 | ); 27 | 28 | let data1_vtable2: &dyn TestTrait = unsafe { &*fatptr::recomp([data1, vtable2]) }; 29 | let data2_vtable1: &dyn TestTrait = unsafe { &*fatptr::recomp([data2, vtable1]) }; 30 | assert_eq!( 31 | data1_vtable2.calc(), 32 | 1 + 20, 33 | "Recombining fat pointer from parts yielded unexpected result" 34 | ); 35 | assert_eq!( 36 | data2_vtable1.calc(), 37 | 2 + 10, 38 | "Recombining fat pointer from parts yielded unexpected result" 39 | ); 40 | } 41 | 42 | trait TestTrait { 43 | fn calc(&self) -> u8; 44 | } 45 | 46 | struct Implementer1(u8); 47 | impl TestTrait for Implementer1 { 48 | fn calc(&self) -> u8 { 49 | self.0 + 10 50 | } 51 | } 52 | 53 | struct Implementer2(u8); 54 | impl TestTrait for Implementer2 { 55 | fn calc(&self) -> u8 { 56 | self.0 + 20 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/fatptr.rs: -------------------------------------------------------------------------------- 1 | /// Decompose a fat pointer into its constituent [pointer, extdata] pair 2 | /// 3 | /// # Safety 4 | /// 5 | /// Must only be called with the generic, `T`, being a trait object. 6 | pub unsafe fn decomp(ptr: *const T) -> [usize; 2] { 7 | let ptr_ref: *const *const T = &ptr; 8 | let decomp_ref = ptr_ref as *const [usize; 2]; 9 | *decomp_ref 10 | } 11 | 12 | /// Recompose a fat pointer from its constituent [pointer, extdata] pair 13 | /// 14 | /// # Safety 15 | /// 16 | /// Must only be called with the generic, `T`, being a trait object. 17 | pub unsafe fn recomp(components: [usize; 2]) -> *mut T { 18 | let component_ref: *const [usize; 2] = &components; 19 | let ptr_ref = component_ref as *const *mut T; 20 | *ptr_ref 21 | } 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! `dynstack` can mostly replace anywhere you'd use a stack, or a vector that doesn't 3 | //! require removal from its center. 4 | //! 5 | //! ``` 6 | //! # use dynstack::{DynStack, dyn_push}; 7 | //! # use std::fmt::Debug; 8 | //! let mut stack = DynStack::::new(); 9 | //! dyn_push!(stack, "hello, world!"); 10 | //! dyn_push!(stack, 0usize); 11 | //! dyn_push!(stack, [1, 2, 3, 4, 5, 6]); 12 | //! 13 | //! for item in stack.iter() { 14 | //! println!("{:?}", item); 15 | //! } 16 | //! 17 | //! // prints: 18 | //! // "hello, world!" 19 | //! // 0 20 | //! // [1, 2, 3, 4, 5, 6] 21 | //! ``` 22 | 23 | #![cfg_attr(not(feature = "std"), no_std)] 24 | #![deny(rust_2018_idioms)] 25 | 26 | extern crate alloc; 27 | 28 | use alloc::{ 29 | alloc::{alloc, dealloc, Layout}, 30 | vec::Vec, 31 | }; 32 | use core::{ 33 | marker::PhantomData, 34 | mem, 35 | ops::{Index, IndexMut}, 36 | ptr, 37 | }; 38 | 39 | mod fatptr; 40 | 41 | /// Rounds up an integer to the nearest `align` 42 | fn align_up(num: usize, align: usize) -> usize { 43 | let align_bits = align.trailing_zeros(); 44 | (num + align - 1) >> align_bits << align_bits 45 | } 46 | 47 | #[test] 48 | fn test_align_up() { 49 | let alignment = 4; 50 | let input = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 51 | let expected = &[0, 4, 4, 4, 4, 8, 8, 8, 8, 12]; 52 | let output = input.iter().map(|x| align_up(*x, alignment)); 53 | let both = expected.iter().zip(output); 54 | for (l, r) in both { 55 | assert_eq!(*l, r); 56 | } 57 | } 58 | 59 | /// Iterator over trait object references 60 | pub struct DynStackIter<'a, T: ?Sized> { 61 | stack: &'a DynStack, 62 | index: usize, 63 | } 64 | 65 | impl<'a, T: 'a + ?Sized> Iterator for DynStackIter<'a, T> { 66 | type Item = &'a T; 67 | 68 | fn next(&mut self) -> Option<&'a T> { 69 | self.stack.get(self.index).map(|out| { 70 | self.index += 1; 71 | out 72 | }) 73 | } 74 | 75 | fn size_hint(&self) -> (usize, Option) { 76 | let size = self.stack.len() - self.index; 77 | (size, Some(size)) 78 | } 79 | } 80 | 81 | impl<'a, T: 'a + ?Sized> ExactSizeIterator for DynStackIter<'a, T> {} 82 | 83 | /// Iterator over mutable trait object references 84 | pub struct DynStackIterMut<'a, T: ?Sized> { 85 | stack: *mut DynStack, 86 | index: usize, 87 | _spooky: PhantomData<&'a mut DynStack>, 88 | } 89 | 90 | impl<'a, T: 'a + ?Sized> Iterator for DynStackIterMut<'a, T> { 91 | type Item = &'a mut T; 92 | 93 | fn next(&mut self) -> Option<&'a mut T> { 94 | unsafe { 95 | (*self.stack).get_mut(self.index).map(|out| { 96 | self.index += 1; 97 | out 98 | }) 99 | } 100 | } 101 | 102 | fn size_hint(&self) -> (usize, Option) { 103 | let size = unsafe { &*self.stack }.len() - self.index; 104 | (size, Some(size)) 105 | } 106 | } 107 | 108 | impl<'a, T: 'a + ?Sized> ExactSizeIterator for DynStackIterMut<'a, T> {} 109 | 110 | pub struct DynStack { 111 | offs_table: Vec<(usize, usize)>, 112 | dyn_data: *mut u8, 113 | dyn_size: usize, 114 | dyn_cap: usize, 115 | max_align: usize, 116 | _spooky: PhantomData, 117 | } 118 | 119 | unsafe impl Send for DynStack {} 120 | unsafe impl Sync for DynStack {} 121 | 122 | impl DynStack { 123 | fn make_layout(cap: usize) -> Layout { 124 | unsafe { Layout::from_size_align_unchecked(cap, 16) } 125 | } 126 | fn layout(&self) -> Layout { 127 | Self::make_layout(self.dyn_cap) 128 | } 129 | 130 | /// Creates a new, empty, [`DynStack`]. 131 | /// 132 | /// # Panics 133 | /// 134 | /// Panics if `T` is not a trait object. 135 | pub fn new() -> Self { 136 | assert_eq!( 137 | mem::size_of::<*const T>(), 138 | mem::size_of::<[usize; 2]>(), 139 | "Used on non trait object!" 140 | ); 141 | // SAFETY: We verify above that T is indeed a trait object. 142 | unsafe { Self::new_unchecked() } 143 | } 144 | 145 | /// Creates a new, empty, [`DynStack`]. This method is a `const fn`, so instances can be 146 | /// statically initialized. This comes at the cost of no runtime sanity check that the stack 147 | /// is properly used with trait objects, which is why it is unsafe to call. 148 | /// 149 | /// # Safety 150 | /// 151 | /// Must only be called with the generic, `T`, being a trait object. 152 | /// 153 | /// # Example 154 | /// 155 | /// ``` 156 | /// # use core::fmt::Display; 157 | /// # use dynstack::DynStack; 158 | /// 159 | /// // SAFETY: Storing trait objects, as required 160 | /// static CORRECT: DynStack = unsafe { DynStack::new_unchecked() }; 161 | /// 162 | /// // DONT do the following, Sting is not a trait object. This compiles 163 | /// // but has undefined behavior. 164 | /// static WRONG: DynStack = unsafe { DynStack::new_unchecked() }; 165 | /// ``` 166 | /// 167 | /// Storing it directly in a static, like above, does of course not make much sense, because 168 | /// you can't modify it. But if you put it inside a synchronazation primitive that allows 169 | /// static initialization, such as `parking_lot::RwLock`, it can be used as a globally 170 | /// accessible, modifiable stack. 171 | #[inline] 172 | pub const unsafe fn new_unchecked() -> Self { 173 | Self { 174 | offs_table: Vec::new(), 175 | dyn_data: ptr::null_mut(), 176 | dyn_size: 0, 177 | dyn_cap: 0, 178 | max_align: 16, 179 | _spooky: PhantomData, 180 | } 181 | } 182 | 183 | /// Called on first push to allocate heap data. 184 | /// `DynStack::new` does not perform any allocation, 185 | /// since it makes creating `DynStack` instances a lot faster. 186 | fn allocate(&mut self, item_size: usize) { 187 | // Always allocate a power of two size, fitting the first item. 188 | // At least 16 bytes. 189 | let alloc_size = item_size.next_power_of_two().max(16); 190 | self.dyn_cap = alloc_size; 191 | self.dyn_data = unsafe { alloc(Self::make_layout(alloc_size)) }; 192 | } 193 | 194 | #[cfg(test)] 195 | fn reallocate(&mut self, new_cap: usize) { 196 | let old_layout = self.layout(); 197 | self.dyn_cap = new_cap; 198 | unsafe { 199 | // The point of this is to maximize the chances of having changed alignment 200 | // characteristics, for testing purposes. 201 | let new_data = alloc(self.layout()); 202 | ptr::copy_nonoverlapping(self.dyn_data, new_data, self.dyn_size); 203 | dealloc(self.dyn_data, old_layout); 204 | self.dyn_data = new_data; 205 | } 206 | } 207 | 208 | #[cfg(not(test))] 209 | fn reallocate(&mut self, new_cap: usize) { 210 | use alloc::alloc::realloc; 211 | self.dyn_cap = new_cap; 212 | self.dyn_data = unsafe { realloc(self.dyn_data, self.layout(), self.dyn_cap) }; 213 | } 214 | 215 | /// Double the stack's capacity 216 | fn grow(&mut self) { 217 | let align_mask = self.max_align - 1; 218 | let prev_align = self.dyn_data as usize & align_mask; 219 | 220 | let new_cap = self.dyn_cap * 2; 221 | self.reallocate(new_cap); 222 | 223 | let new_align = self.dyn_data as usize & align_mask; 224 | let mut align_diff = (new_align as isize) - (prev_align as isize); 225 | 226 | if align_diff != 0 { 227 | // It's possible that, if we have an item with alignment > 16, it becomes unaligned when 228 | // reallocating our buffer (since we realloc with the default alignment of 16). 229 | // If that happens, we need to realign all of our buffer contents with a memmove and adjust the 230 | // offset table appropriately. 231 | 232 | let first_offset = self.offs_table[0].0 as isize; 233 | if align_diff > 0 || first_offset + align_diff < 0 { 234 | // Not enough padding at the start of the buf; must move foreward to align 235 | align_diff = ((align_diff as usize) & align_mask) as isize; 236 | } 237 | 238 | unsafe { 239 | let start_ptr = self.dyn_data.offset(first_offset); 240 | let dst = start_ptr.offset(align_diff); 241 | debug_assert!(dst as usize >= self.dyn_data as usize); 242 | debug_assert!(dst as usize <= (self.dyn_data as usize) + self.dyn_cap); 243 | ptr::copy(start_ptr, dst, self.dyn_size); 244 | } 245 | for (ref mut offs, _) in &mut self.offs_table { 246 | *offs = offs.wrapping_add(align_diff as usize); 247 | } 248 | } 249 | } 250 | 251 | /// Push a trait object onto the stack. 252 | /// 253 | /// This method is unsafe because in lieu of moving a trait object onto `push`'s stack 254 | /// (not possible in rust as of 1.30.0) we copy it from the provided mutable pointer. 255 | /// 256 | /// The user of this method must therefore either ensure that `item` has no `Drop` impl, 257 | /// or explicitly call `std::mem::forget` on `item` after pushing. 258 | /// 259 | /// It is highly recommended to use the `dyn_push` macro instead of calling this directly. 260 | pub unsafe fn push(&mut self, item: *mut T) { 261 | let size = mem::size_of_val(&*item); 262 | let align = mem::align_of_val(&*item); 263 | 264 | // If we have not yet allocated any data, start by doing so. 265 | if self.dyn_data.is_null() { 266 | self.allocate(size); 267 | } 268 | 269 | let align_offs = loop { 270 | let curr_ptr = self.dyn_data as usize + self.dyn_size; 271 | let aligned_ptr = align_up(curr_ptr, align); 272 | let align_offs = aligned_ptr - curr_ptr; 273 | 274 | if self.dyn_size + align_offs + size > self.dyn_cap { 275 | self.grow(); 276 | } else { 277 | break align_offs; 278 | } 279 | }; 280 | 281 | self.dyn_data 282 | .add(self.dyn_size) 283 | .add(align_offs) 284 | .copy_from_nonoverlapping(item as *const u8, size); 285 | 286 | let ptr_components = fatptr::decomp(item); 287 | self.offs_table 288 | .push((self.dyn_size + align_offs, ptr_components[1])); 289 | 290 | self.dyn_size += align_offs + size; 291 | self.max_align = align.max(self.max_align); 292 | } 293 | 294 | /// Remove the last trait object from the stack. 295 | /// Returns true if any items were removed. 296 | pub fn remove_last(&mut self) -> bool { 297 | if let Some(last_item) = self.peek_mut() { 298 | unsafe { ptr::drop_in_place(last_item) }; 299 | } else { 300 | return false; 301 | } 302 | let (last_offs, _) = self.offs_table.pop().unwrap(); 303 | self.dyn_size = last_offs; 304 | true 305 | } 306 | 307 | /// mem::forget the last trait object from the stack. 308 | /// Returns true if any items were forgotten. 309 | pub fn forget_last(&mut self) -> bool { 310 | if let Some((last_offs, _)) = self.offs_table.pop() { 311 | self.dyn_size = last_offs; 312 | true 313 | } else { 314 | false 315 | } 316 | } 317 | 318 | /// Retrieve a trait object reference at the provided index. 319 | pub fn get<'a>(&'a self, index: usize) -> Option<&'a T> { 320 | let item = self.offs_table.get(index)?; 321 | let components = [self.dyn_data as usize + item.0, item.1]; 322 | let out = unsafe { &*fatptr::recomp(components) }; 323 | Some(out) 324 | } 325 | 326 | /// Retrieve a mutable trait object reference at the provided index. 327 | pub fn get_mut<'a>(&'a mut self, index: usize) -> Option<&'a mut T> { 328 | let item = self.offs_table.get(index)?; 329 | let components = [self.dyn_data as usize + item.0, item.1]; 330 | let out = unsafe { &mut *fatptr::recomp(components) }; 331 | Some(out) 332 | } 333 | 334 | /// Retrieve the trait object reference at the top of the stack. 335 | pub fn peek<'a>(&'a self) -> Option<&'a T> { 336 | self.get(self.len().wrapping_sub(1)) 337 | } 338 | 339 | /// Retrieve the mutable trait object reference at the top of the stack. 340 | pub fn peek_mut<'a>(&'a mut self) -> Option<&'a mut T> { 341 | let index = self.len().wrapping_sub(1); 342 | self.get_mut(index) 343 | } 344 | 345 | /// Returns the number of trait objects stored on the stack. 346 | pub fn len(&self) -> usize { 347 | self.offs_table.len() 348 | } 349 | } 350 | 351 | impl<'a, T: 'a + ?Sized> DynStack { 352 | /// Returns an iterator over trait object references 353 | pub fn iter(&'a self) -> DynStackIter<'a, T> { 354 | DynStackIter { 355 | stack: self, 356 | index: 0, 357 | } 358 | } 359 | 360 | /// Returns an iterator over mutable trait object references 361 | pub fn iter_mut(&'a mut self) -> DynStackIterMut<'a, T> { 362 | DynStackIterMut { 363 | stack: self, 364 | index: 0, 365 | _spooky: PhantomData, 366 | } 367 | } 368 | } 369 | 370 | impl Index for DynStack { 371 | type Output = T; 372 | 373 | fn index(&self, idx: usize) -> &T { 374 | self.get(idx).unwrap() 375 | } 376 | } 377 | 378 | impl IndexMut for DynStack { 379 | fn index_mut(&mut self, idx: usize) -> &mut T { 380 | self.get_mut(idx).unwrap() 381 | } 382 | } 383 | 384 | impl<'a, T: 'a + ?Sized> IntoIterator for &'a DynStack { 385 | type Item = &'a T; 386 | type IntoIter = DynStackIter<'a, T>; 387 | 388 | fn into_iter(self) -> Self::IntoIter { 389 | self.iter() 390 | } 391 | } 392 | 393 | impl<'a, T: 'a + ?Sized> IntoIterator for &'a mut DynStack { 394 | type Item = &'a mut T; 395 | type IntoIter = DynStackIterMut<'a, T>; 396 | 397 | fn into_iter(self) -> Self::IntoIter { 398 | self.iter_mut() 399 | } 400 | } 401 | 402 | impl Drop for DynStack { 403 | fn drop(&mut self) { 404 | while self.remove_last() {} 405 | unsafe { dealloc(self.dyn_data, self.layout()) } 406 | } 407 | } 408 | 409 | /// Push an item onto the back of the specified stack 410 | #[macro_export] 411 | macro_rules! dyn_push { 412 | { $stack:expr, $item:expr } => {{ 413 | let mut t = $item; 414 | 415 | unsafe { $stack.push(&mut t) }; 416 | core::mem::forget(t); 417 | }} 418 | } 419 | 420 | #[test] 421 | fn test_push_pop() { 422 | use std::fmt::Debug; 423 | let mut stack = DynStack::::new(); 424 | let bunch = vec![1u32, 2, 3, 4, 5, 6, 7, 8, 9]; 425 | dyn_push!(stack, 1u8); 426 | dyn_push!(stack, 1u32); 427 | dyn_push!(stack, 1u16); 428 | dyn_push!(stack, 1u64); 429 | dyn_push!(stack, 1u128); 430 | dyn_push!(stack, bunch); 431 | dyn_push!(stack, { 432 | #[derive(Debug)] 433 | struct ZST; 434 | ZST 435 | }); 436 | 437 | if let Some(item) = stack.peek() { 438 | println!("{:?}", item); 439 | assert!(format!("{:?}", item) == "ZST"); 440 | } else { 441 | unreachable!(); 442 | } 443 | assert!(stack.remove_last()); 444 | 445 | if let Some(item) = stack.peek() { 446 | println!("{:?}", item); 447 | assert!(format!("{:?}", item) == "[1, 2, 3, 4, 5, 6, 7, 8, 9]"); 448 | } 449 | assert!(stack.remove_last()); 450 | 451 | for _i in 0..5 { 452 | if let Some(item) = stack.peek() { 453 | println!("{:?}", item); 454 | assert!(format!("{:?}", item) == "1"); 455 | } else { 456 | unreachable!(); 457 | } 458 | assert!(stack.remove_last()); 459 | } 460 | 461 | assert!(stack.len() == 0); 462 | assert!(stack.dyn_size == 0); 463 | } 464 | 465 | #[test] 466 | fn test_fn() { 467 | let mut stack = DynStack:: usize>::new(); 468 | for i in 0..100 { 469 | dyn_push!(stack, move || i); 470 | } 471 | 472 | let mut item2 = 0; 473 | for func in stack.iter() { 474 | item2 += func(); 475 | } 476 | assert_eq!(item2, 4950); 477 | } 478 | 479 | #[test] 480 | fn test_drop() { 481 | use core::any::Any; 482 | use std::collections::HashSet; 483 | 484 | static mut DROP_NUM: Option> = None; 485 | unsafe { DROP_NUM = Some(HashSet::new()) }; 486 | fn drop_num() -> &'static HashSet { 487 | unsafe { DROP_NUM.as_ref().unwrap() } 488 | } 489 | fn drop_num_mut() -> &'static mut HashSet { 490 | unsafe { DROP_NUM.as_mut().unwrap() } 491 | } 492 | 493 | struct Droppable { 494 | counter: usize, 495 | }; 496 | impl Drop for Droppable { 497 | fn drop(&mut self) { 498 | drop_num_mut().insert(self.counter); 499 | } 500 | } 501 | 502 | { 503 | let mut stack = DynStack::::new(); 504 | dyn_push!(stack, Droppable { counter: 1 }); 505 | dyn_push!(stack, Droppable { counter: 2 }); 506 | dyn_push!(stack, Droppable { counter: 3 }); 507 | dyn_push!(stack, Droppable { counter: 4 }); 508 | dyn_push!(stack, Droppable { counter: 5 }); 509 | dyn_push!(stack, Droppable { counter: 6 }); 510 | assert!(drop_num().is_empty()); 511 | } 512 | 513 | let expected: HashSet = [1, 2, 3, 4, 5, 6].iter().cloned().collect(); 514 | assert_eq!(drop_num(), &expected); 515 | } 516 | 517 | #[test] 518 | fn test_align() { 519 | trait Aligned { 520 | fn alignment(&self) -> usize; 521 | } 522 | impl Aligned for u32 { 523 | fn alignment(&self) -> usize { 524 | core::mem::align_of::() 525 | } 526 | } 527 | impl Aligned for u64 { 528 | fn alignment(&self) -> usize { 529 | core::mem::align_of::() 530 | } 531 | } 532 | 533 | #[repr(align(32))] 534 | struct Aligned32 { 535 | _dat: [u8; 32], 536 | } 537 | impl Aligned for Aligned32 { 538 | fn alignment(&self) -> usize { 539 | core::mem::align_of::() 540 | } 541 | } 542 | 543 | #[repr(align(64))] 544 | struct Aligned64 { 545 | _dat: [u8; 64], 546 | } 547 | impl Aligned for Aligned64 { 548 | fn alignment(&self) -> usize { 549 | core::mem::align_of::() 550 | } 551 | } 552 | 553 | fn new32() -> Aligned32 { 554 | let mut dat = [0u8; 32]; 555 | for i in 0..32 { 556 | dat[i] = i as u8; 557 | } 558 | Aligned32 { _dat: dat } 559 | } 560 | fn new64() -> Aligned64 { 561 | let mut dat = [0u8; 64]; 562 | for i in 0..64 { 563 | dat[i] = i as u8; 564 | } 565 | Aligned64 { _dat: dat } 566 | } 567 | 568 | let assert_aligned = |item: &dyn Aligned| { 569 | let thin_ptr = item as *const dyn Aligned as *const () as usize; 570 | println!( 571 | "item expects alignment {}, got offset {}", 572 | item.alignment(), 573 | thin_ptr & (item.alignment() - 1) 574 | ); 575 | assert!(thin_ptr & (item.alignment() - 1) == 0); 576 | }; 577 | 578 | let mut stack = DynStack::::new(); 579 | 580 | dyn_push!(stack, new32()); 581 | dyn_push!(stack, new64()); 582 | assert_aligned(stack.peek().unwrap()); 583 | 584 | for i in 0..256usize { 585 | let randomized = (i.pow(7) % 13) % 4; 586 | match randomized { 587 | 0 => dyn_push!(stack, 0xF0B0D0E0u32), 588 | 1 => dyn_push!(stack, 0x01020304F0B0D0E0u64), 589 | 2 => dyn_push!(stack, new32()), 590 | 3 => dyn_push!(stack, new64()), 591 | _ => unreachable!(), 592 | } 593 | assert_aligned(stack.peek().unwrap()); 594 | } 595 | } 596 | 597 | #[test] 598 | #[should_panic] 599 | fn test_non_dyn() { 600 | let _stack: DynStack = DynStack::new(); 601 | } 602 | 603 | #[test] 604 | fn test_send() { 605 | use std::{fmt::Display, sync::mpsc, thread}; 606 | 607 | let mut stack: DynStack = DynStack::new(); 608 | dyn_push!(stack, String::from("1")); 609 | 610 | let (sender, receiver) = mpsc::channel(); 611 | thread::spawn(move || { 612 | dyn_push!(stack, String::from("2")); 613 | sender.send(stack).unwrap(); 614 | }); 615 | let stack = receiver.recv().unwrap(); 616 | assert_eq!(stack.len(), 2); 617 | assert_eq!(stack[0].to_string(), "1"); 618 | assert_eq!(stack[1].to_string(), "2"); 619 | } 620 | 621 | #[test] 622 | fn test_sync() { 623 | use std::sync::{ 624 | atomic::{AtomicI32, AtomicU64, Ordering}, 625 | Arc, 626 | }; 627 | use std::thread; 628 | 629 | trait AtomicInt: Send + Sync { 630 | fn increment(&self); 631 | 632 | fn get(&self) -> u64; 633 | } 634 | 635 | impl AtomicInt for AtomicI32 { 636 | fn increment(&self) { 637 | self.fetch_add(1, Ordering::Relaxed); 638 | } 639 | 640 | fn get(&self) -> u64 { 641 | self.load(Ordering::Relaxed) as u64 642 | } 643 | } 644 | 645 | impl AtomicInt for AtomicU64 { 646 | fn increment(&self) { 647 | self.fetch_add(1, Ordering::Relaxed); 648 | } 649 | 650 | fn get(&self) -> u64 { 651 | self.load(Ordering::Relaxed) 652 | } 653 | } 654 | 655 | // Create a stack with different type of atomic integers in it. 656 | // We use quite a lot of integers, so the thread execution later 657 | // is likely to interleave. 658 | let mut stack: DynStack = DynStack::new(); 659 | for i in 0..10_000 { 660 | if i % 2 == 0 { 661 | dyn_push!(stack, AtomicI32::new(0)); 662 | } else { 663 | dyn_push!(stack, AtomicU64::new(0)); 664 | } 665 | } 666 | 667 | let stack = Arc::new(stack); 668 | // Loop over the stack and increment every value once in each thread 669 | let mut threads = Vec::new(); 670 | for _ in 0..100 { 671 | let stack = stack.clone(); 672 | threads.push(thread::spawn(move || { 673 | for int in stack.iter() { 674 | int.increment(); 675 | } 676 | })); 677 | } 678 | for thread in threads { 679 | thread.join().expect("Joining threads"); 680 | } 681 | 682 | for int in stack.iter() { 683 | assert_eq!(int.get(), 100); 684 | } 685 | } 686 | 687 | #[test] 688 | fn test_iter_size_hint() { 689 | let mut stack = DynStack:: usize>::new(); 690 | { 691 | let (min, max) = stack.iter().size_hint(); 692 | assert_eq!(min, 0); 693 | assert_eq!(max, Some(0)); 694 | assert_eq!(stack.iter().len(), 0); 695 | } 696 | { 697 | let (min, max) = stack.iter_mut().size_hint(); 698 | assert_eq!(min, 0); 699 | assert_eq!(max, Some(0)); 700 | assert_eq!(stack.iter_mut().len(), 0); 701 | } 702 | 703 | for i in 0..10 { 704 | dyn_push!(stack, move || i); 705 | } 706 | 707 | { 708 | let mut iter = stack.iter(); 709 | let (min, max) = iter.size_hint(); 710 | assert_eq!(min, 10); 711 | assert_eq!(max, Some(10)); 712 | assert_eq!(iter.len(), 10); 713 | 714 | iter.next(); 715 | let (min, max) = iter.size_hint(); 716 | assert_eq!(min, 9); 717 | assert_eq!(max, Some(9)); 718 | assert_eq!(iter.len(), 9); 719 | } 720 | { 721 | let mut iter = stack.iter_mut(); 722 | let (min, max) = iter.size_hint(); 723 | assert_eq!(min, 10); 724 | assert_eq!(max, Some(10)); 725 | assert_eq!(iter.len(), 10); 726 | 727 | iter.next(); 728 | let (min, max) = iter.size_hint(); 729 | assert_eq!(min, 9); 730 | assert_eq!(max, Some(9)); 731 | assert_eq!(iter.len(), 9); 732 | } 733 | } 734 | --------------------------------------------------------------------------------