#![crate_type = "lib"] #![no_std] use core::fmt; pub mod alloca; // A list of C functions that are being imported extern "C" { pub fn printf(format: *const u8, ...) -> i32; } const REFCOUNT_1: usize = isize::MIN as usize; #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RocOrder { Eq = 0, Gt = 1, Lt = 2, } //#[macro_export] //macro_rules! roclist { // () => ( // $crate::RocList::empty() // ); // ($($x:expr),+ $(,)?) => ( // $crate::RocList::from_slice(&[$($x),+]) // ); //} #[repr(C)] pub struct RocList { elements: *mut T, length: usize, } #[derive(Clone, Copy, Debug)] pub enum Storage { ReadOnly, Refcounted(usize), Capacity(usize), } impl RocList { pub fn len(&self) -> usize { self.length } pub fn is_empty(&self) -> bool { self.length == 0 } pub fn empty() -> Self { RocList { length: 0, elements: core::ptr::null_mut(), } } pub fn get(&self, index: usize) -> Option<&T> { if index < self.len() { Some(unsafe { let raw = self.elements.add(index); &*raw }) } else { None } } pub fn storage(&self) -> Option { use core::cmp::Ordering::*; if self.length == 0 { return None; } unsafe { let value = *self.get_storage_ptr(); // NOTE doesn't work with elements of 16 or more bytes match isize::cmp(&(value as isize), &0) { Equal => Some(Storage::ReadOnly), Less => Some(Storage::Refcounted(value)), Greater => Some(Storage::Capacity(value)), } } } fn get_storage_ptr(&self) -> *const usize { let ptr = self.elements as *const usize; unsafe { ptr.offset(-1) } } fn get_storage_ptr_mut(&mut self) -> *mut usize { self.get_storage_ptr() as *mut usize } fn get_element_ptr(elements: *const T) -> *const T { let elem_alignment = core::mem::align_of::(); let ptr = elements as *const usize; unsafe { if elem_alignment <= core::mem::align_of::() { ptr.offset(1) as *const T } else { // If elements have an alignment bigger than usize (e.g. an i128), // we will have necessarily allocated two usize slots worth of // space for the storage value (with the first usize slot being // padding for alignment's sake), and we need to skip past both. ptr.offset(2) as *const T } } } pub fn from_slice_with_capacity(slice: &[T], capacity: usize) -> RocList where T: Clone, { assert!(slice.len() <= capacity); let ptr = slice.as_ptr(); let element_bytes = capacity * core::mem::size_of::(); let padding = { if core::mem::align_of::() <= core::mem::align_of::() { // aligned on usize (8 bytes on 64-bit systems) 0 } else { // aligned on 2*usize (16 bytes on 64-bit systems) core::mem::size_of::() } }; let num_bytes = core::mem::size_of::() + padding + element_bytes; let elements = unsafe { let raw_ptr = libc::malloc(num_bytes) as *mut u8; // pointer to the first element let raw_ptr = Self::get_element_ptr(raw_ptr as *mut T) as *mut T; // write the capacity let capacity_ptr = raw_ptr as *mut usize; *(capacity_ptr.offset(-1)) = capacity; { // NOTE: using a memcpy here causes weird issues let target_ptr = raw_ptr as *mut T; let source_ptr = ptr as *const T; for index in 0..slice.len() { let source = &*source_ptr.add(index); let target = &mut *target_ptr.add(index); // NOTE for a weird reason, it's important that we clone onto the stack // and explicitly forget the swapped-in value // cloning directly from source to target causes some garbage memory (cast to a // RocStr) to end up in the drop implementation of RocStr and cause havoc by // freeing NULL let mut temporary = source.clone(); core::mem::swap(target, &mut temporary); core::mem::forget(temporary); } } raw_ptr }; RocList { length: slice.len(), elements, } } pub fn from_slice(slice: &[T]) -> RocList where T: Clone, { Self::from_slice_with_capacity(slice, slice.len()) } pub fn as_slice(&self) -> &[T] { unsafe { core::slice::from_raw_parts(self.elements, self.length) } } } impl fmt::Debug for RocList { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // RocList { storage: Refcounted(3), elements: [ 1,2,3,4] } f.debug_struct("RocList") .field("storage", &self.storage()) .field("elements", &self.as_slice()) .finish() } } impl PartialEq for RocList { fn eq(&self, other: &Self) -> bool { if self.length != other.length { return false; } for i in 0..(self.length as isize) { unsafe { if *self.elements.offset(i) != *other.elements.offset(i) { return false; } } } true } } impl Eq for RocList {} impl Drop for RocList { fn drop(&mut self) { use Storage::*; match self.storage() { None | Some(ReadOnly) => {} Some(Capacity(_)) | Some(Refcounted(REFCOUNT_1)) => unsafe { libc::free(self.get_storage_ptr() as *mut libc::c_void); }, Some(Refcounted(rc)) => { let sptr = self.get_storage_ptr_mut(); unsafe { *sptr = rc - 1; } } } } } #[repr(C)] pub struct RocStr { elements: *mut u8, length: usize, } impl RocStr { pub fn len(&self) -> usize { if self.is_small_str() { let bytes = self.length.to_ne_bytes(); let last_byte = bytes[bytes.len() - 1]; (last_byte ^ 0b1000_0000) as usize } else { self.length } } pub fn is_empty(&self) -> bool { self.len() == 0 } pub fn is_small_str(&self) -> bool { (self.length as isize) < 0 } pub fn empty() -> Self { RocStr { // The first bit of length is 1 to specify small str. length: 0, elements: core::ptr::null_mut(), } } pub fn get(&self, index: usize) -> Option<&u8> { if index < self.len() { Some(unsafe { let raw = if self.is_small_str() { self.get_small_str_ptr().add(index) } else { self.elements.add(index) }; &*raw }) } else { None } } pub fn storage(&self) -> Option { use core::cmp::Ordering::*; if self.is_small_str() || self.length == 0 { return None; } unsafe { let value = *self.get_storage_ptr(); // NOTE doesn't work with elements of 16 or more bytes match isize::cmp(&(value as isize), &0) { Equal => Some(Storage::ReadOnly), Less => Some(Storage::Refcounted(value)), Greater => Some(Storage::Capacity(value)), } } } fn get_storage_ptr(&self) -> *const usize { let ptr = self.elements as *const usize; unsafe { ptr.offset(-1) } } fn get_storage_ptr_mut(&mut self) -> *mut usize { self.get_storage_ptr() as *mut usize } fn get_element_ptr(elements: *const u8) -> *const usize { let elem_alignment = core::mem::align_of::(); let ptr = elements as *const usize; unsafe { if elem_alignment <= core::mem::align_of::() { ptr.offset(1) } else { // If elements have an alignment bigger than usize (e.g. an i128), // we will have necessarily allocated two usize slots worth of // space for the storage value (with the first usize slot being // padding for alignment's sake), and we need to skip past both. ptr.offset(2) } } } fn get_small_str_ptr(&self) -> *const u8 { (self as *const RocStr).cast() } fn get_small_str_ptr_mut(&mut self) -> *mut u8 { (self as *mut RocStr).cast() } fn from_slice_with_capacity_str(slice: &[u8], capacity: usize) -> RocStr { assert!( slice.len() <= capacity, "RocStr::from_slice_with_capacity_str length bigger than capacity {} {}", slice.len(), capacity ); if capacity < core::mem::size_of::() { let mut rocstr = RocStr::empty(); let target_ptr = rocstr.get_small_str_ptr_mut(); let source_ptr = slice.as_ptr() as *const u8; for index in 0..slice.len() { unsafe { *target_ptr.add(index) = *source_ptr.add(index); } } // Write length and small string bit to last byte of length. let mut bytes = rocstr.length.to_ne_bytes(); bytes[bytes.len() - 1] = capacity as u8 ^ 0b1000_0000; rocstr.length = usize::from_ne_bytes(bytes); rocstr } else { let ptr = slice.as_ptr(); let element_bytes = capacity; let num_bytes = core::mem::size_of::() + element_bytes; let elements = unsafe { let raw_ptr = libc::malloc(num_bytes); // write the capacity let capacity_ptr = raw_ptr as *mut usize; *capacity_ptr = capacity; let raw_ptr = Self::get_element_ptr(raw_ptr as *mut u8); { // NOTE: using a memcpy here causes weird issues let target_ptr = raw_ptr as *mut u8; let source_ptr = ptr as *const u8; let length = slice.len() as isize; for index in 0..length { *target_ptr.offset(index) = *source_ptr.offset(index); } } raw_ptr as *mut u8 }; RocStr { length: slice.len(), elements, } } } pub fn from_slice(slice: &[u8]) -> RocStr { Self::from_slice_with_capacity_str(slice, slice.len()) } pub fn from_str(slice: &str) -> RocStr { Self::from_slice_with_capacity_str(slice.as_bytes(), slice.len()) } pub fn as_slice(&self) -> &[u8] { if self.is_small_str() { unsafe { core::slice::from_raw_parts(self.get_small_str_ptr(), self.len()) } } else { unsafe { core::slice::from_raw_parts(self.elements, self.length) } } } #[allow(clippy::missing_safety_doc)] pub unsafe fn as_str(&self) -> &str { let slice = self.as_slice(); core::str::from_utf8_unchecked(slice) } } impl From<&str> for RocStr { fn from(str: &str) -> Self { Self::from_slice(str.as_bytes()) } } impl fmt::Debug for RocStr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // RocStr { is_small_str: false, storage: Refcounted(3), elements: [ 1,2,3,4] } f.debug_struct("RocStr") .field("is_small_str", &self.is_small_str()) .field("storage", &self.storage()) .field("elements", &self.as_slice()) .finish() } } impl PartialEq for RocStr { fn eq(&self, other: &Self) -> bool { self.as_slice() == other.as_slice() } } impl Eq for RocStr {} impl Clone for RocStr { fn clone(&self) -> Self { if self.is_small_str() || self.is_empty() { Self { elements: self.elements, length: self.length, } } else { let capacity_size = core::mem::size_of::(); let copy_length = self.length + capacity_size; let elements = unsafe { let raw = libc::malloc(copy_length); libc::memcpy( raw, self.elements.offset(-(capacity_size as isize)) as *mut libc::c_void, copy_length, ); *(raw as *mut usize) = self.length; (raw as *mut u8).add(capacity_size) }; Self { elements, length: self.length, } } } } impl Drop for RocStr { fn drop(&mut self) { if !self.is_small_str() { use Storage::*; match self.storage() { None | Some(ReadOnly) => {} Some(Capacity(_)) | Some(Refcounted(REFCOUNT_1)) => unsafe { libc::free(self.get_storage_ptr() as *mut libc::c_void); }, Some(Refcounted(rc)) => { let sptr = self.get_storage_ptr_mut(); unsafe { *sptr = rc - 1; } } } } } } #[allow(non_camel_case_types)] type c_char = u8; #[repr(u64)] pub enum RocCallResult { Success(T), Failure(*mut c_char), } impl Into> for RocCallResult { fn into(self) -> Result { use RocCallResult::*; match self { Success(value) => Ok(value), Failure(failure) => Err({ let msg = unsafe { let mut null_byte_index = 0; loop { if *failure.offset(null_byte_index) == 0 { break; } null_byte_index += 1; } let bytes = core::slice::from_raw_parts(failure, null_byte_index as usize); core::str::from_utf8_unchecked(bytes) }; msg }), } } } impl<'a, T: Sized + Copy> Into> for &'a RocCallResult { fn into(self) -> Result { use RocCallResult::*; match self { Success(value) => Ok(*value), Failure(failure) => Err({ let msg = unsafe { let mut null_byte_index = 0; loop { if *failure.offset(null_byte_index) == 0 { break; } null_byte_index += 1; } let bytes = core::slice::from_raw_parts(*failure, null_byte_index as usize); core::str::from_utf8_unchecked(bytes) }; msg }), } } }