1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
//! This module contains APIs that allocate large struct directly on the heap, such that we can //! overcome the obstacle of using the `Box::new` syntax in certain situations, for example, a struct //! that is too large to fit into the default thread stack. The syntax limit is caused by how `box` //! is currently created: the struct will be created on the stack, and then moved to the heap, which //! implies that the struct must be first of all be able to fit into the (limited) stack size in the //! first place. //! //! This limit has been quite inconvenient for buffer struct where a buffer array can be of `MB` in //! size. Using APIs provided by this module can help mitigate the gap -- we will allocate a well aligned //! memory in the heap, where caller can pack the memory with valid and meaningful values. //! //! That said, the APIs can be extremely dangerous for struct that can be undefined if not properly //! initialized. There are 2 APIs marked as `safe`, which provides ways to initialize the object before //! yielding the instance to the caller, which could provide some warrants that the crafted struct //! shall be valid and away from undefined behaviors. //! //! # Examples //! //! ```rust //! use syncpool; //! //! struct BigStruct { //! a: u32, //! b: u32, //! c: [u8; 0x1_000_000], //! d: Vec<u8>, //! } //! //! // create the object on the heap directly //! let big: Box<BigStruct> = syncpool::make_box(|mut src: Box<BigStruct>| { //! src.a = 1; //! src.b = 42; //! //! for i in 0..0x1_000_000 { //! src.c[i] = (i % 256) as u8; //! } //! //! src.d = Vec::with_capacity(0x1_000_000); //! for i in 0..0x1_000_000 { //! src.d.push((i % 256) as u8) //! } //! //! src //! }); //! //! assert_eq!(big.a, 1); //! assert_eq!(big.b, 42); //! //! assert_eq!(big.c[255], 255); //! assert_eq!(big.c[4200], 104); //! //! assert_eq!(big.d[255], 255); //! assert_eq!(big.d[4200], 104); //! ``` #![allow(unused)] use std::alloc::{alloc, alloc_zeroed, Layout}; use std::ptr; /// Create a box structure without moving the wrapped value from the stack to the heap. This API is /// most useful when the wrapped value is too large for the default stack size, such that initializing /// and packing the valuing into the box is a pain. /// /// Note that calling the API is unsafe, because it only creates a well-aligned memory structure in /// the heap, but all fields are in the state of undefined behavior at the moment. You *must* initialize /// the fields with default values, or pack it with meaningful placeholders. Using the object directly /// after being created by the API is *extremely* dangerous and will almost certainly lead to undefined /// behaviors. /// /// # Examples /// /// Create a boxed `BigStruct` /// /// ``` /// use syncpool::raw_box; /// /// struct BigStruct { /// a: u32, /// b: u32, /// c: [u8; 0x1_000_000], /// } /// /// // create the object on the heap directly /// let mut big: Box<BigStruct> = unsafe { raw_box::<BigStruct>() }; /// /// // initialize the fields /// big.a = 0; /// big.b = 42; /// big.c = [0u8; 0x1_000_000]; /// /// // the fields are now valid /// assert_eq!(big.c.len(), 0x1_000_000); /// assert_eq!(big.c[4200], 0); /// assert_eq!(big.a, 0); /// ``` pub unsafe fn raw_box<T>() -> Box<T> { let layout = Layout::new::<T>(); Box::from_raw(alloc(layout) as *mut T) } /// Similar to `raw_box`, this API creates a box structure without moving the wrapped value from the /// stack to the heap. This API is most useful when the wrapped value is too large for the default /// stack size, such that initializing and packing the valuing into the box is a pain. /// /// The only difference is that all fields in the object will be initialized to 0. This will initialize /// most primitive types, however, there is no warrant that the boxed object is valid or meaningful. /// For example, if the source struct contains pointers or another `Box`ed object, the fields are still /// undefined since they're pointing to the `null` pointer (i.e. default pointer created by /// `std::ptr::null_mut()`). /// /// # Examples /// /// Create a boxed `DangerousStruct` /// /// ``` /// use syncpool::raw_box_zeroed; /// /// struct BigStruct { /// a: u32, /// b: u32, /// c: [u8; 0x1_000_000], /// } /// /// // create the object on the heap directly /// let mut big: Box<BigStruct> = unsafe { raw_box_zeroed::<BigStruct>() }; /// /// // the fields are now valid /// assert_eq!(big.c.len(), 0x1_000_000); /// assert_eq!(big.c[4200], 0); /// assert_eq!(big.a, 0); /// ``` pub unsafe fn raw_box_zeroed<T>() -> Box<T> { let layout = Layout::new::<T>(); Box::from_raw(alloc_zeroed(layout) as *mut T) } /// This API is a wrapper on the unsafer version of the direct-to-the-heap-box APIs. The API is safe /// because it is the caller's responsiblity to supply the struct initialier as a closure, such that /// after calling the struct initializer, the returned object shall be valid and meaningful. /// /// The closure will take the raw box object as the input parameter, which maybe invalid, and it is /// the closure's responsiblity to assign valid values to the fields. /// /// # Examples /// /// Create the dangerous struct and pack valid values with it. /// /// ``` /// use syncpool::{raw_box_zeroed, make_box}; /// use std::mem::MaybeUninit; /// use std::ptr::NonNull; /// use std::sync::atomic::{AtomicBool, Ordering}; /// /// struct BigStruct { /// a: u32, /// b: u32, /// c: [u8; 0x1_000_000], /// } /// /// struct DangerousStruct { /// a: u32, /// b: MaybeUninit<AtomicBool>, /// c: NonNull<BigStruct>, /// } /// /// // create the object directly on the heap /// let mut boxed: Box<DangerousStruct> = make_box(|mut src: Box<DangerousStruct>| { /// // initialize the fields in the handler /// let mut big: &mut BigStruct = unsafe { Box::leak(raw_box_zeroed::<BigStruct>()) }; /// big.a = 42; /// big.b = 4 * 42; /// big.c[4200] = 125; /// /// // make sure we initialize the fields /// src.a = 42; /// src.b = MaybeUninit::new(AtomicBool::new(false)); /// src.c = NonNull::new(big).unwrap(); /// /// src /// }); /// /// // the fields are now valid /// let big_ref = unsafe { boxed.c.as_ref() }; /// /// assert_eq!(big_ref.c.len(), 0x1_000_000); /// assert_eq!(big_ref.c[4200], 125); /// assert_eq!(big_ref.a, 42); /// assert_eq!(big_ref.b, 168); /// ``` pub fn make_box<T, F: Fn(Box<T>) -> Box<T>>(packer: F) -> Box<T> { let boxed = unsafe { raw_box_zeroed::<T>() }; packer(boxed) } /// Similar to the `make_box` API, the `default_box` API is a wrapper over the unsafer version of the /// directly-to-the-heap ones. If the struct wrapped in the box has implemented the `Default` trait, /// then one can call this API that will invoke the `Default::default` to initialize the object, such /// that the caller won't need to supply a closure to initialize all the fields. /// /// # Examples /// /// Create the box on the heap with the default implementation of the struct /// /// ```rust /// use syncpool::default_box; /// use std::vec; /// /// struct BigStruct { /// a: u32, /// b: u32, /// c: Vec<u8>, /// } /// /// impl Default for BigStruct { /// fn default() -> Self { /// BigStruct { /// a: 1, /// b: 42, /// c: vec::from_elem(0, 0x1_000_000), /// } /// } /// } /// /// // create the object directly on the heap /// let boxed: Box<BigStruct> = default_box(); /// /// // the fields are now valid /// assert_eq!(boxed.c.len(), 0x1_000_000); /// assert_eq!(boxed.a, 1); /// assert_eq!(boxed.b, 42); ///``` pub fn default_box<T: Default>() -> Box<T> { unsafe { let p = alloc(Layout::new::<T>()) as *mut T; ptr::write(p, Default::default()); Box::from_raw(p) } } #[cfg(test)] mod boxed_tests { use super::*; use std::{ mem::MaybeUninit, ptr::NonNull, sync::atomic::{AtomicBool, Ordering}, vec, }; struct BigStruct { a: u32, b: u32, c: [u8; 0x1_000_000], } struct BigStruct2 { a: u32, b: u32, c: Vec<u8>, } impl Default for BigStruct2 { fn default() -> Self { BigStruct2 { a: 1, b: 42, c: vec::from_elem(0, 0x1_000_000), } } } struct DangerousStruct { a: u32, b: MaybeUninit<AtomicBool>, c: NonNull<BigStruct>, } impl Drop for DangerousStruct { fn drop(&mut self) { let _ = unsafe { Box::from_raw(self.c.as_ptr()) }; } } fn make_test_box<T>(zeroed: bool) -> Box<T> { unsafe { if zeroed { raw_box_zeroed::<T>() } else { raw_box::<T>() } } } fn make_dangerous() -> Box<DangerousStruct> { let mut boxed = make_test_box::<DangerousStruct>(true); let mut big: &mut BigStruct = Box::leak(make_test_box::<BigStruct>(true)); big.a = 42; big.b = 4 * 42; big.c[4200] = 125; // make sure we initialize the fields boxed.b = MaybeUninit::new(AtomicBool::new(false)); boxed.c = NonNull::new(big).unwrap(); boxed } #[test] fn raw_box_test() { let boxed = make_test_box::<BigStruct>(true); assert_eq!(boxed.c.len(), 0x1_000_000); assert_eq!(boxed.c[4200], 0); assert_eq!(boxed.a, 0); } #[test] fn pack() { // create the object on the heap directly let mut boxed: Box<DangerousStruct> = make_box(|mut src: Box<DangerousStruct>| { // initialize the fields in the handler let mut big: &mut BigStruct = unsafe { Box::leak(raw_box_zeroed::<BigStruct>()) }; big.a = 42; big.b = 4 * 42; big.c[4200] = 125; // make sure we initialize the fields src.b = MaybeUninit::new(AtomicBool::new(false)); src.c = NonNull::new(big).unwrap(); src }); // the fields are now valid let big_ref = unsafe { boxed.c.as_ref() }; assert_eq!(big_ref.c.len(), 0x1_000_000); assert_eq!(big_ref.c[4200], 125); assert_eq!(big_ref.a, 42); assert_eq!(big_ref.b, 168); } #[test] fn init() { let mut boxed = make_test_box::<BigStruct>(false); boxed.a = 1; boxed.b = 42; boxed.c = [0u8; 0x1_000_000]; assert_eq!(boxed.a, 1); assert_eq!(boxed.b, 42); assert_eq!(boxed.c.len(), 0x1_000_000); assert_eq!(boxed.c[4200], 0); } #[test] fn raw() { let mut boxed = make_dangerous(); let big_ref = unsafe { boxed.c.as_ref() }; assert_eq!(big_ref.a, 42); assert_eq!(big_ref.b, 168); assert_eq!(big_ref.c.len(), 0x1_000_000); assert_eq!(big_ref.c[4200], 125); let atomic = unsafe { &*boxed.b.as_ptr() }; assert_eq!(atomic.load(Ordering::Acquire), false); } #[test] fn defaulted() { // create the object directly on the heap let mut boxed: Box<BigStruct2> = default_box(); // the fields are now valid assert_eq!(boxed.c.len(), 0x1_000_000); assert_eq!(boxed.a, 1); assert_eq!(boxed.b, 42); } }