diff --git a/Cargo.lock b/Cargo.lock index f4299c78b8..5ba6580754 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3178,6 +3178,7 @@ dependencies = [ "roc_parse", "roc_region", "roc_serialize", + "soa", "static_assertions", "ven_pretty", ] @@ -3577,6 +3578,13 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +[[package]] +name = "soa" +version = "0.0.1" +dependencies = [ + "pretty_assertions", +] + [[package]] name = "socket2" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index a02dc7b7de..c5f6b07d0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "crates/valgrind", "crates/tracing", "crates/utils/*", + "crates/soa", "crates/docs", "crates/docs_cli", "crates/linker", @@ -74,6 +75,8 @@ inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm- "llvm16-0", ] } +soa = { path = "crates/soa" } + arrayvec = "0.7.2" # update roc_std/Cargo.toml on change backtrace = "0.3.67" base64-url = "1.4.13" @@ -137,7 +140,10 @@ maplit = "1.0.2" memmap2 = "0.5.10" mimalloc = { version = "0.1.34", default-features = false } nonempty = "0.8.1" -object = { version = "0.32.2", default-features = false, features = ["read", "write"] } +object = { version = "0.32.2", default-features = false, features = [ + "read", + "write", +] } packed_struct = "0.10.1" page_size = "0.5.0" palette = "0.6.1" diff --git a/crates/compiler/can/src/copy.rs b/crates/compiler/can/src/copy.rs index 7876723625..1a7d04d286 100644 --- a/crates/compiler/can/src/copy.rs +++ b/crates/compiler/can/src/copy.rs @@ -12,7 +12,7 @@ use roc_module::{ use roc_types::{ subs::{ self, AliasVariables, Descriptor, GetSubsSlice, OptVariable, RecordFields, Subs, SubsIndex, - SubsSlice, TupleElems, UnionLambdas, UnionTags, Variable, VariableSubsSlice, + SubsSlice, TupleElems, UnionLambdas, UnionTags, Variable, VariableSlice, }, types::{RecordField, Uls}, }; @@ -162,7 +162,7 @@ impl<'a> CopyEnv for AcrossSubs<'a> { fn clone_field_names(&mut self, field_names: SubsSlice) -> SubsSlice { SubsSlice::extend_new( &mut self.target.field_names, - self.source.get_subs_slice(field_names).iter().cloned(), + self.source.get_slice(field_names).iter().cloned(), ) } @@ -173,10 +173,7 @@ impl<'a> CopyEnv for AcrossSubs<'a> { ) -> SubsSlice { SubsSlice::extend_new( &mut self.target.tuple_elem_indices, - self.source - .get_subs_slice(tuple_elem_indices) - .iter() - .cloned(), + self.source.get_slice(tuple_elem_indices).iter().cloned(), ) } @@ -184,7 +181,7 @@ impl<'a> CopyEnv for AcrossSubs<'a> { fn clone_tag_names(&mut self, tag_names: SubsSlice) -> SubsSlice { SubsSlice::extend_new( &mut self.target.tag_names, - self.source.get_subs_slice(tag_names).iter().cloned(), + self.source.get_slice(tag_names).iter().cloned(), ) } @@ -192,7 +189,7 @@ impl<'a> CopyEnv for AcrossSubs<'a> { fn clone_lambda_names(&mut self, lambda_names: SubsSlice) -> SubsSlice { SubsSlice::extend_new( &mut self.target.symbol_names, - self.source.get_subs_slice(lambda_names).iter().cloned(), + self.source.get_slice(lambda_names).iter().cloned(), ) } @@ -203,7 +200,7 @@ impl<'a> CopyEnv for AcrossSubs<'a> { ) -> SubsSlice> { SubsSlice::extend_new( &mut self.target.record_fields, - self.source.get_subs_slice(record_fields).iter().copied(), + self.source.get_slice(record_fields).iter().copied(), ) } } diff --git a/crates/soa/Cargo.toml b/crates/soa/Cargo.toml new file mode 100644 index 0000000000..e66861025b --- /dev/null +++ b/crates/soa/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "soa" +description = "Struct-of-Array helpers" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +[dependencies] +pretty_assertions.workspace = true + +[dev-dependencies] diff --git a/crates/soa/src/lib.rs b/crates/soa/src/lib.rs new file mode 100644 index 0000000000..13f4068a02 --- /dev/null +++ b/crates/soa/src/lib.rs @@ -0,0 +1,5 @@ +mod soa_index; +mod soa_slice; + +pub use soa_index::*; +pub use soa_slice::*; diff --git a/crates/soa/src/soa_index.rs b/crates/soa/src/soa_index.rs new file mode 100644 index 0000000000..5374866e2e --- /dev/null +++ b/crates/soa/src/soa_index.rs @@ -0,0 +1,50 @@ +use core::fmt; + +use crate::soa_slice::Slice; + +/// An index into an array of values, based +/// on an offset into the array rather than a pointer. +/// +/// Unlike a Rust pointer, this is a u32 offset +/// rather than usize. +pub struct Index { + pub index: u32, + pub(crate) _marker: core::marker::PhantomData, +} + +impl fmt::Debug for Index { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Index<{}>({})", core::any::type_name::(), self.index) + } +} + +// derive of copy and clone does not play well with PhantomData + +impl Copy for Index {} + +impl Clone for Index { + fn clone(&self) -> Self { + *self + } +} + +impl Index { + pub const fn new(start: u32) -> Self { + Self { + index: start, + _marker: core::marker::PhantomData, + } + } + + pub fn push_new(vector: &mut Vec, value: T) -> Self { + let index = Self::new(vector.len() as _); + + vector.push(value); + + index + } + + pub const fn as_slice(self) -> Slice { + Slice::new(self.index, 1) + } +} diff --git a/crates/soa/src/soa_slice.rs b/crates/soa/src/soa_slice.rs new file mode 100644 index 0000000000..ca8eba2f5c --- /dev/null +++ b/crates/soa/src/soa_slice.rs @@ -0,0 +1,112 @@ +use core::fmt; +use core::iter::Map; + +use crate::soa_index::Index; + +/// A slice into an array of values, based +/// on an offset into the array rather than a pointer. +/// +/// Unlike a Rust slice, this is a u32 offset +/// rather than a pointer, and the length is u16. +pub struct Slice { + pub start: u32, + pub length: u16, + _marker: core::marker::PhantomData, +} + +impl fmt::Debug for Slice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Slice {{ start: {}, length: {} }}", + self.start, self.length + ) + } +} + +// derive of copy and clone does not play well with PhantomData + +impl Copy for Slice {} + +impl Clone for Slice { + fn clone(&self) -> Self { + *self + } +} + +impl Default for Slice { + fn default() -> Self { + Self::empty() + } +} + +impl Slice { + pub fn empty() -> Self { + Self { + start: 0, + length: 0, + _marker: Default::default(), + } + } + + pub fn get_slice<'a>(&self, slice: &'a [T]) -> &'a [T] { + &slice[self.indices()] + } + + pub fn get_slice_mut<'a>(&self, slice: &'a mut [T]) -> &'a mut [T] { + &mut slice[self.indices()] + } + + #[inline(always)] + pub fn indices(&self) -> core::ops::Range { + self.start as usize..(self.start as usize + self.length as usize) + } + + pub const fn len(&self) -> usize { + self.length as usize + } + + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub const fn new(start: u32, length: u16) -> Self { + Self { + start, + length, + _marker: std::marker::PhantomData, + } + } + + pub fn extend_new(vec: &mut Vec, it: impl IntoIterator) -> Self { + let start = vec.len(); + + vec.extend(it); + + let end = vec.len(); + + Self::new(start as u32, (end - start) as u16) + } +} + +impl IntoIterator for Slice { + type Item = Index; + + #[allow(clippy::type_complexity)] + type IntoIter = Map, fn(u32) -> Self::Item>; + + fn into_iter(self) -> Self::IntoIter { + (self.start..(self.start + self.length as u32)).map(u32_to_index) + } +} + +fn u32_to_index(i: u32) -> Index { + Index { + index: i, + _marker: core::marker::PhantomData, + } +} + +pub trait GetSlice { + fn get_slice(&self, slice: Slice) -> &[T]; +}