├── .gitignore ├── Cargo.toml ├── README.md ├── src └── lib.rs └── tests └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nanbox" 3 | version = "0.1.0" 4 | authors = ["Markus Westerlind "] 5 | 6 | description = "NaN boxing implementation" 7 | license = "MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/Marwes/nanbox" 10 | documentation = "https://docs.rs/crate/nanbox" 11 | 12 | [dependencies] 13 | unreachable = "0.1.1" 14 | 15 | [dev-dependencies] 16 | quickcheck = "0.4.1" 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nanbox 2 | [![Docs](https://docs.rs/nanbox/badge.svg)](https://docs.rs/nanbox) 3 | 4 | A Rust crate which defines a macro which generates a safe* [nanboxed type][]. 5 | 6 | \* Some invariants need to be checked manually when defining your type. The crate is currently alpha quality so expect bugs. 7 | 8 | [nanboxed type]:https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations 9 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Defines the `unsafe_make_nanbox` macro which defines a type which packs values of different types 2 | //! into the unused space of the NaN representation of `f64`. 3 | 4 | #[doc(hidden)] 5 | pub extern crate unreachable; 6 | 7 | use std::cmp::Ordering; 8 | use std::fmt; 9 | use std::marker::PhantomData; 10 | use std::mem; 11 | 12 | const TAG_SHIFT: u64 = 48; 13 | const DOUBLE_MAX_TAG: u32 = 0b11111_11111_11000_0; 14 | const SHIFTED_DOUBLE_MAX_TAG: u64 = ((DOUBLE_MAX_TAG as u64) << TAG_SHIFT) | 0xFFFFFFFF; 15 | 16 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] 17 | pub struct NanBox(u64); 18 | 19 | impl fmt::Debug for NanBox { 20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | write!(f, 22 | "NanBox {{ tag: {:?}, payload: {:?} }}", 23 | self.tag(), 24 | self.0 & ((1 << TAG_SHIFT) - 1)) 25 | } 26 | } 27 | 28 | pub trait NanBoxable: Sized { 29 | unsafe fn from_nan_box(n: NanBox) -> Self; 30 | 31 | fn into_nan_box(self) -> NanBox; 32 | 33 | fn pack_nan_box(self, tag: u8) -> NanBox { 34 | let mut b = self.into_nan_box(); 35 | 36 | let shifted_tag = ((DOUBLE_MAX_TAG as u64) | (tag as u64)) << TAG_SHIFT; 37 | b.0 |= shifted_tag; 38 | debug_assert!(b.tag() == u32::from(tag), "{} == {}", b.tag(), tag); 39 | b 40 | } 41 | 42 | unsafe fn unpack_nan_box(value: NanBox) -> Self { 43 | let mask = (1 << TAG_SHIFT) - 1; 44 | let b = NanBox(value.0 & mask); 45 | Self::from_nan_box(b) 46 | } 47 | } 48 | 49 | impl NanBoxable for f64 { 50 | unsafe fn from_nan_box(n: NanBox) -> f64 { 51 | mem::transmute(n) 52 | } 53 | 54 | fn into_nan_box(self) -> NanBox { 55 | unsafe { NanBox(mem::transmute(self)) } 56 | } 57 | 58 | fn pack_nan_box(self, tag: u8) -> NanBox { 59 | debug_assert!(tag == 0); 60 | self.into_nan_box() 61 | } 62 | 63 | unsafe fn unpack_nan_box(value: NanBox) -> Self { 64 | Self::from_nan_box(value) 65 | } 66 | } 67 | 68 | macro_rules! impl_cast { 69 | ($($typ: ident)+) => { 70 | $( 71 | impl NanBoxable for $typ { 72 | unsafe fn from_nan_box(n: NanBox) -> $typ { 73 | n.0 as $typ 74 | } 75 | 76 | fn into_nan_box(self) -> NanBox { 77 | NanBox(self as u64) 78 | } 79 | } 80 | )* 81 | } 82 | } 83 | 84 | impl_cast!{ u8 u16 u32 i8 i16 i32 } 85 | 86 | impl NanBoxable for char { 87 | unsafe fn from_nan_box(n: NanBox) -> char { 88 | std::char::from_u32_unchecked(n.0 as u32) 89 | } 90 | 91 | fn into_nan_box(self) -> NanBox { 92 | NanBox(self as u64) 93 | } 94 | } 95 | 96 | impl<'a, T> NanBoxable for &'a T { 97 | unsafe fn from_nan_box(n: NanBox) -> Self { 98 | &*(n.0 as *const T) 99 | } 100 | 101 | fn into_nan_box(self) -> NanBox { 102 | NanBox(self as *const T as u64) 103 | } 104 | } 105 | 106 | impl<'a, T> NanBoxable for Option<&'a T> { 107 | unsafe fn from_nan_box(n: NanBox) -> Self { 108 | (n.0 as *const T).as_ref() 109 | } 110 | 111 | fn into_nan_box(self) -> NanBox { 112 | use std::ptr::null; 113 | (match self { 114 | Some(p) => p as *const T, 115 | None => null(), 116 | }) 117 | .into_nan_box() 118 | } 119 | } 120 | 121 | macro_rules! impl_array { 122 | ($($typ: ty)+) => { 123 | $( 124 | impl NanBoxable for $typ { 125 | unsafe fn from_nan_box(n: NanBox) -> Self { 126 | use std::ptr::copy_nonoverlapping; 127 | use std::mem::size_of; 128 | debug_assert!(size_of::() <= 6); 129 | let mut result = Self::default(); 130 | copy_nonoverlapping( 131 | &n as *const NanBox as *const _, 132 | result.as_mut_ptr(), 133 | result.len()); 134 | result 135 | } 136 | 137 | fn into_nan_box(self) -> NanBox { 138 | unsafe { 139 | use std::ptr::copy_nonoverlapping; 140 | use std::mem::size_of; 141 | debug_assert!(size_of::() <= 6); 142 | let mut result = NanBox(0); 143 | copy_nonoverlapping( 144 | self.as_ptr(), 145 | &mut result as *mut NanBox as *mut _, 146 | self.len()); 147 | result 148 | } 149 | } 150 | } 151 | )* 152 | } 153 | } 154 | 155 | impl_array!{ [u8; 1] [u8; 2] [u8; 3] [u8; 4] [u8; 5] [u8; 6] } 156 | impl_array!{ [i8; 1] [i8; 2] [i8; 3] [i8; 4] [i8; 5] [i8; 6] } 157 | impl_array!{ [i16; 1] [i16; 2] [i16; 3] } 158 | impl_array!{ [u16; 1] [u16; 2] [u16; 3] } 159 | impl_array!{ [i32; 1] } 160 | impl_array!{ [u32; 1] } 161 | impl_array!{ [f32; 1] } 162 | 163 | macro_rules! impl_cast_t { 164 | ($param: ident, $($typ: ty)+) => { 165 | $( 166 | impl<$param> NanBoxable for $typ { 167 | unsafe fn from_nan_box(n: NanBox) -> $typ { 168 | n.0 as $typ 169 | } 170 | 171 | fn into_nan_box(self) -> NanBox { 172 | debug_assert!((self as u64) >> TAG_SHIFT == 0); 173 | NanBox(self as u64) 174 | } 175 | } 176 | )* 177 | } 178 | } 179 | 180 | impl_cast_t! { T, *mut T *const T } 181 | 182 | impl NanBox { 183 | pub unsafe fn new(tag: u8, value: T) -> NanBox 184 | where T: NanBoxable 185 | { 186 | debug_assert!(tag < 1 << 4, 187 | "Nanboxes must have tags smaller than {}", 188 | 1 << 4); 189 | value.pack_nan_box(tag) 190 | } 191 | 192 | pub unsafe fn unpack(self) -> T 193 | where T: NanBoxable 194 | { 195 | T::unpack_nan_box(self) 196 | } 197 | 198 | pub fn tag(self) -> u32 { 199 | if self.0 <= SHIFTED_DOUBLE_MAX_TAG { 200 | 0 201 | } else { 202 | (self.0 >> TAG_SHIFT) as u32 & !DOUBLE_MAX_TAG 203 | } 204 | } 205 | } 206 | 207 | pub struct TypedNanBox { 208 | nanbox: NanBox, 209 | _marker: PhantomData, 210 | } 211 | 212 | impl Copy for TypedNanBox where T: From> + Into> + Copy {} 213 | 214 | impl Clone for TypedNanBox 215 | where T: From> + Into> + Clone 216 | { 217 | fn clone(&self) -> Self { 218 | T::from(TypedNanBox { 219 | nanbox: self.nanbox, 220 | _marker: PhantomData, 221 | }) 222 | .clone() 223 | .into() 224 | } 225 | } 226 | 227 | impl fmt::Debug for TypedNanBox 228 | where T: From> + Into> + fmt::Debug + Clone 229 | { 230 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 231 | write!(f, "{:?}", T::from(self.clone())) 232 | } 233 | } 234 | 235 | impl fmt::Display for TypedNanBox 236 | where T: From> + Into> + fmt::Display + Clone 237 | { 238 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 239 | write!(f, "{}", T::from(self.clone())) 240 | } 241 | } 242 | 243 | impl PartialEq for TypedNanBox 244 | where T: From> + Into> + PartialEq + Clone 245 | { 246 | fn eq(&self, other: &TypedNanBox) -> bool { 247 | T::from(self.clone()) == T::from(other.clone()) 248 | } 249 | } 250 | 251 | impl Eq for TypedNanBox where T: From> + Into> + Eq + Clone {} 252 | 253 | impl PartialOrd for TypedNanBox 254 | where T: From> + Into> + PartialOrd + Clone 255 | { 256 | fn partial_cmp(&self, other: &TypedNanBox) -> Option { 257 | T::from(self.clone()).partial_cmp(&T::from(other.clone())) 258 | } 259 | } 260 | 261 | impl Ord for TypedNanBox 262 | where T: From> + Into> + Ord + Clone 263 | { 264 | fn cmp(&self, other: &TypedNanBox) -> Ordering { 265 | T::from(self.clone()).cmp(&T::from(other.clone())) 266 | } 267 | } 268 | 269 | impl From for TypedNanBox 270 | where T: From> 271 | { 272 | fn from(value: T) -> TypedNanBox { 273 | value.into() 274 | } 275 | } 276 | 277 | impl TypedNanBox { 278 | pub unsafe fn new(tag: u8, value: U) -> TypedNanBox 279 | where U: NanBoxable 280 | { 281 | TypedNanBox { 282 | nanbox: NanBox::new(tag, value), 283 | _marker: PhantomData, 284 | } 285 | } 286 | 287 | pub unsafe fn unpack(self) -> U 288 | where U: NanBoxable 289 | { 290 | self.nanbox.unpack() 291 | } 292 | 293 | pub fn tag(&self) -> u32 { 294 | self.nanbox.tag() 295 | } 296 | } 297 | 298 | /// Creates an `enum` which is packed into the signaling NaN representation of `f64`. 299 | /// 300 | /// Some limitations apply to make this work in a safe manner. 301 | /// 302 | /// * The first and only the first variant must hold a `f64`. 303 | /// * There must be 8 or fewer variants in the defined enum (this is only checked with 304 | /// `debug_assert!`) 305 | /// * Pointers stored in a nanbox must only use the lower 48 bits (checked via `debug_assert!` only). 306 | /// 307 | /// ``` 308 | /// #[macro_use] 309 | /// extern crate nanbox; 310 | /// 311 | /// // Creates one `nanbox` type called `Value` and one normal enum called `Variant`. 312 | /// // `From` implementations are generated to converted between these two types as well as `From` 313 | /// // implementation for each of the types in the match arms (`From` etc). 314 | /// unsafe_make_nanbox!{ 315 | /// pub enum Value, Variant { 316 | /// Float(f64), 317 | /// Byte(u8), 318 | /// Int(i32), 319 | /// Pointer(*mut Value) 320 | /// } 321 | /// } 322 | /// 323 | /// # fn main() { } 324 | /// 325 | /// ``` 326 | #[macro_export] 327 | macro_rules! unsafe_make_nanbox { 328 | ( 329 | $(#[$meta:meta])* 330 | pub enum $name: ident, $enum_name: ident { 331 | $($field: ident ($typ: ty)),* 332 | } 333 | ) => { 334 | $(#[$meta])* 335 | pub struct $name { 336 | value: $crate::TypedNanBox<$enum_name>, 337 | } 338 | 339 | $(#[$meta])* 340 | pub enum $enum_name { 341 | $( 342 | $field($typ), 343 | )+ 344 | } 345 | 346 | $( 347 | impl From<$typ> for $name { 348 | fn from(value: $typ) -> $name { 349 | $name::from($enum_name::$field(value)) 350 | } 351 | } 352 | )+ 353 | 354 | impl From<$enum_name> for $name { 355 | fn from(value: $enum_name) -> $name { 356 | #[allow(unused_assignments)] 357 | unsafe { 358 | let mut tag = 0; 359 | $( 360 | if let $enum_name::$field(value) = value { 361 | return $name { 362 | value: $crate::TypedNanBox::new(tag, value) 363 | }; 364 | } 365 | tag += 1; 366 | )+ 367 | $crate::unreachable::unreachable() 368 | } 369 | } 370 | } 371 | 372 | impl From<$name> for $enum_name { 373 | fn from(value: $name) -> $enum_name { 374 | value.value.into() 375 | } 376 | } 377 | 378 | impl From<$crate::TypedNanBox<$enum_name>> for $enum_name { 379 | fn from(value: $crate::TypedNanBox<$enum_name>) -> $enum_name { 380 | #[allow(unused_assignments)] 381 | unsafe { 382 | let mut expected_tag = 0; 383 | $( 384 | if expected_tag == value.tag() { 385 | return $enum_name::$field(value.unpack()); 386 | } 387 | expected_tag += 1; 388 | )* 389 | debug_assert!(false, "Unexpected tag {}", value.tag()); 390 | $crate::unreachable::unreachable() 391 | } 392 | } 393 | } 394 | 395 | impl $name { 396 | pub fn into_variant(self) -> $enum_name { 397 | self.into() 398 | } 399 | } 400 | } 401 | } 402 | 403 | #[cfg(test)] 404 | #[macro_use] 405 | extern crate quickcheck; 406 | 407 | #[cfg(test)] 408 | mod tests { 409 | use super::*; 410 | 411 | use std::f64; 412 | use std::fmt; 413 | 414 | use quickcheck::TestResult; 415 | 416 | fn test_eq(l: T, r: T) -> TestResult 417 | where T: PartialEq + fmt::Debug 418 | { 419 | if l == r { 420 | TestResult::passed() 421 | } else { 422 | TestResult::error(format!("{:?} != {:?}", l, r)) 423 | } 424 | } 425 | 426 | quickcheck!{ 427 | fn nanbox_f64(f: f64) -> TestResult { 428 | unsafe { 429 | test_eq(NanBox::new(0, f).unpack(), f) 430 | } 431 | } 432 | 433 | fn nanbox_u32(tag: u8, v: u32) -> TestResult { 434 | if tag == 0 || tag >= 8 { 435 | return TestResult::discard(); 436 | } 437 | unsafe { 438 | TestResult::from_bool(NanBox::new(tag, v).tag() == tag as u32) 439 | } 440 | } 441 | 442 | fn nanbox_ptr(tag: u8, v: u32) -> TestResult { 443 | if tag == 0 || tag >= 8 { 444 | return TestResult::discard(); 445 | } 446 | unsafe { 447 | let nanbox = NanBox::new(tag, Box::into_raw(Box::new(v))); 448 | TestResult::from_bool(nanbox.tag() == tag as u32) 449 | } 450 | } 451 | } 452 | 453 | unsafe_make_nanbox!{ 454 | #[derive(Clone, Debug, PartialEq)] 455 | pub enum Value, Variant { 456 | Float(f64), 457 | Int(i32), 458 | Pointer(*mut ()), 459 | Array([u8; 6]) 460 | } 461 | } 462 | 463 | #[test] 464 | fn box_test() { 465 | assert_eq!(Value::from(123).into_variant(), Variant::Int(123)); 466 | assert_eq!(Value::from(3000 as *mut ()).into_variant(), 467 | Variant::Pointer(3000 as *mut ())); 468 | assert_eq!(Value::from(3.14).into_variant(), Variant::Float(3.14)); 469 | 470 | let array = [1, 2, 3, 4, 5, 6]; 471 | assert_eq!(Value::from(array).into_variant(), Variant::Array(array)); 472 | 473 | let array = [255, 255, 255, 255, 255, 255]; 474 | assert_eq!(Value::from(array).into_variant(), Variant::Array(array)); 475 | } 476 | 477 | #[test] 478 | fn nan_box_nan() { 479 | match Value::from(f64::NAN).into_variant() { 480 | Variant::Float(x) => assert!(x.is_nan()), 481 | x => panic!("Unexpected {:?}", x), 482 | } 483 | } 484 | 485 | #[should_panic] 486 | #[test] 487 | fn invalid_pointer() { 488 | ((1u64 << TAG_SHIFT) as *const ()).into_nan_box(); 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate nanbox; 3 | 4 | unsafe_make_nanbox!{ 5 | pub enum Value, Variant { 6 | Float(f64), 7 | Byte(u8), 8 | Int(i32), 9 | Pointer(*mut Value) 10 | } 11 | } 12 | --------------------------------------------------------------------------------