From 4f80073c1aed7a537a5595498a21f554713f0154 Mon Sep 17 00:00:00 2001 From: Tim Harding Date: Thu, 9 May 2024 15:00:37 -0700 Subject: [PATCH 1/6] FooArray creation conditioned behind #[soa_array] --- soa-rs-derive/src/fields.rs | 179 ++++++++++++++++++------------------ soa-rs-derive/src/lib.rs | 106 ++++++++++++--------- soa-rs-derive/src/zst.rs | 1 - soa-rs-testing/src/lib.rs | 3 + src/soars.rs | 7 +- 5 files changed, 161 insertions(+), 135 deletions(-) diff --git a/soa-rs-derive/src/fields.rs b/soa-rs-derive/src/fields.rs index 619672b..ba8c777 100644 --- a/soa-rs-derive/src/fields.rs +++ b/soa-rs-derive/src/fields.rs @@ -1,6 +1,6 @@ use crate::{ zst::{zst_struct, ZstKind}, - SoaDerive, + SoaAttrs, SoaDerive, }; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; @@ -11,15 +11,19 @@ pub fn fields_struct( vis: Visibility, fields: Punctuated, kind: FieldKind, - soa_derive: SoaDerive, + soa_attrs: SoaAttrs, ) -> Result { - let SoaDerive { - r#ref: derive_ref, - ref_mut: derive_ref_mut, - slices: derive_slices, - slices_mut: derive_slices_mut, - array: derive_array, - } = soa_derive; + let SoaAttrs { + derive: + SoaDerive { + r#ref: derive_ref, + ref_mut: derive_ref_mut, + slices: derive_slices, + slices_mut: derive_slices_mut, + array: derive_array, + }, + include_array, + } = soa_attrs; let fields_len = fields.len(); let (vis_all, (ty_all, (ident_all, attrs_all))): (Vec<_>, (Vec<_>, (Vec<_>, Vec<_>))) = fields @@ -204,91 +208,93 @@ pub fn fields_struct( #vis struct #slices_mut<'a> #slices_mut_def }); - let array_def = define(&|ty| quote! { [#ty; N] }); - let uninit_def = define(&|ty| quote! { [::std::mem::MaybeUninit<#ty>; K] }); - out.append_all(quote! { - #derive_array - #[automatically_derived] - #vis struct #array #array_def - - #[automatically_derived] - impl #array { - #vis const fn from_array(array: [#ident; N]) -> Self { - let array = ::std::mem::ManuallyDrop::new(array); - let array = ::std::ptr::from_ref::<::std::mem::ManuallyDrop<[#ident; N]>>(&array); - let array = array.cast::<[#ident; N]>(); - let array = unsafe { &*array }; - - struct Uninit #uninit_def; - - let mut uninit: Uninit = Uninit { - #( - // https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-an-array-element-by-element - // - // TODO: Prefer when stablized: - // https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#method.uninit_array - #ident_all: unsafe { ::std::mem::MaybeUninit::uninit().assume_init() }, - )* - }; - - let mut i = 0; - while i < N { - #( - let src = ::std::ptr::from_ref(&array[i].#ident_all); - unsafe { - uninit.#ident_all[i] = ::std::mem::MaybeUninit::new(src.read()); + if include_array { + let array_def = define(&|ty| quote! { [#ty; N] }); + let uninit_def = define(&|ty| quote! { [::std::mem::MaybeUninit<#ty>; K] }); + out.append_all(quote! { + #derive_array + #[automatically_derived] + #vis struct #array #array_def + + #[automatically_derived] + impl #array { + #vis const fn from_array(array: [#ident; N]) -> Self { + let array = ::std::mem::ManuallyDrop::new(array); + let array = ::std::ptr::from_ref::<::std::mem::ManuallyDrop<[#ident; N]>>(&array); + let array = array.cast::<[#ident; N]>(); + let array = unsafe { &*array }; + + struct Uninit #uninit_def; + + let mut uninit: Uninit = Uninit { + #( + // https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-an-array-element-by-element + // + // TODO: Prefer when stablized: + // https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#method.uninit_array + #ident_all: unsafe { ::std::mem::MaybeUninit::uninit().assume_init() }, + )* + }; + + let mut i = 0; + while i < N { + #( + let src = ::std::ptr::from_ref(&array[i].#ident_all); + unsafe { + uninit.#ident_all[i] = ::std::mem::MaybeUninit::new(src.read()); + } + )* + + i += 1; } - )* - i += 1; - } - - Self { - #( - // TODO: Prefer when stabilized: - // https://doc.rust-lang.org/std/primitive.array.html#method.transpose - #ident_all: unsafe { - ::std::mem::transmute_copy(&::std::mem::ManuallyDrop::new(uninit.#ident_all)) - }, - )* + Self { + #( + // TODO: Prefer when stabilized: + // https://doc.rust-lang.org/std/primitive.array.html#method.transpose + #ident_all: unsafe { + ::std::mem::transmute_copy(&::std::mem::ManuallyDrop::new(uninit.#ident_all)) + }, + )* + } } } - } - - #[automatically_derived] - impl ::soa_rs::AsSlice for #array { - type Item = #ident; - fn as_slice(&self) -> ::soa_rs::SliceRef<'_, Self::Item> { - let raw = #raw { - #( - #ident_all: { - let ptr = self.#ident_all.as_slice().as_ptr().cast_mut(); - unsafe { ::std::ptr::NonNull::new_unchecked(ptr) } - }, - )* - }; - let slice = ::soa_rs::Slice::with_raw(raw); - unsafe { ::soa_rs::SliceRef::from_slice(slice, N) } + #[automatically_derived] + impl ::soa_rs::AsSlice for #array { + type Item = #ident; + + fn as_slice(&self) -> ::soa_rs::SliceRef<'_, Self::Item> { + let raw = #raw { + #( + #ident_all: { + let ptr = self.#ident_all.as_slice().as_ptr().cast_mut(); + unsafe { ::std::ptr::NonNull::new_unchecked(ptr) } + }, + )* + }; + let slice = ::soa_rs::Slice::with_raw(raw); + unsafe { ::soa_rs::SliceRef::from_slice(slice, N) } + } } - } - #[automatically_derived] - impl ::soa_rs::AsMutSlice for #array { - fn as_mut_slice(&mut self) -> ::soa_rs::SliceMut<'_, Self::Item> { - let raw = #raw { - #( - #ident_all: { - let ptr = self.#ident_all.as_mut_slice().as_mut_ptr(); - unsafe { ::std::ptr::NonNull::new_unchecked(ptr) } - }, - )* - }; - let slice = ::soa_rs::Slice::with_raw(raw); - unsafe { ::soa_rs::SliceMut::from_slice(slice, N) } + #[automatically_derived] + impl ::soa_rs::AsMutSlice for #array { + fn as_mut_slice(&mut self) -> ::soa_rs::SliceMut<'_, Self::Item> { + let raw = #raw { + #( + #ident_all: { + let ptr = self.#ident_all.as_mut_slice().as_mut_ptr(); + unsafe { ::std::ptr::NonNull::new_unchecked(ptr) } + }, + )* + }; + let slice = ::soa_rs::Slice::with_raw(raw); + unsafe { ::soa_rs::SliceMut::from_slice(slice, N) } + } } - } - }); + }); + } let indices = std::iter::repeat(()).enumerate().map(|(i, ())| i); let offsets_len = fields_len - 1; @@ -345,7 +351,6 @@ pub fn fields_struct( type Deref = #deref; type Ref<'a> = #item_ref<'a> where Self: 'a; type RefMut<'a> = #item_ref_mut<'a> where Self: 'a; - type Array = #array; type Slices<'a> = #slices<'a> where Self: 'a; type SlicesMut<'a> = #slices_mut<'a> where Self: 'a; } diff --git a/soa-rs-derive/src/lib.rs b/soa-rs-derive/src/lib.rs index 107ff81..077e2c0 100644 --- a/soa-rs-derive/src/lib.rs +++ b/soa-rs-derive/src/lib.rs @@ -14,7 +14,7 @@ use std::{ use syn::{parse_macro_input, Attribute, Data, DeriveInput, Fields}; use zst::{zst_struct, ZstKind}; -#[proc_macro_derive(Soars, attributes(align, soa_derive))] +#[proc_macro_derive(Soars, attributes(align, soa_derive, soa_array))] pub fn soa(input: TokenStream) -> TokenStream { let input: DeriveInput = parse_macro_input!(input); let span = input.ident.span(); @@ -39,9 +39,7 @@ fn soa_inner(input: DeriveInput) -> Result { generics: _, } = input; - let mut soa_derive = SoaDeriveParse::new(); - soa_derive.append(attrs)?; - let soa_derive = soa_derive.into_derive(); + let attrs = SoaAttrs::new(attrs)?; match data { Data::Struct(strukt) => match strukt.fields { Fields::Named(fields) => Ok(fields_struct( @@ -49,14 +47,14 @@ fn soa_inner(input: DeriveInput) -> Result { vis, fields.named, FieldKind::Named, - soa_derive, + attrs, )?), Fields::Unnamed(fields) => Ok(fields_struct( ident, vis, fields.unnamed, FieldKind::Unnamed, - soa_derive, + attrs, )?), Fields::Unit => Ok(zst_struct(ident, vis, ZstKind::Unit)), }, @@ -76,6 +74,34 @@ impl From for SoarsError { } } +#[derive(Debug, Clone)] +struct SoaAttrs { + pub derive: SoaDerive, + pub include_array: bool, +} + +impl SoaAttrs { + pub fn new(attributes: Vec) -> Result { + let mut derive_parse = SoaDeriveParse::new(); + let mut include_array = false; + for attr in attributes { + let path = attr.path(); + if path.is_ident("soa_derive") { + derive_parse.append(attr)?; + } else if path.is_ident("soa_array") { + include_array = true; + } else { + return Err(syn::Error::new_spanned(attr, "Unknown SOA attribute")); + } + } + + Ok(Self { + derive: derive_parse.into_derive(), + include_array, + }) + } +} + #[derive(Debug, Clone, Default)] struct SoaDeriveParse { r#ref: Vec, @@ -123,44 +149,42 @@ impl SoaDeriveParse { } } - pub fn append(&mut self, value: Vec) -> Result<(), syn::Error> { - for attr in value { - if attr.path().is_ident("soa_derive") { - let mut collected = vec![]; - let mut mask = SoaDeriveMask::new(); - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("include") { - mask = SoaDeriveMask::splat(false); - meta.parse_nested_meta(|meta| { - mask.set_by_path(&meta.path, true).map_err(|_| { - meta.error(format!("unknown include specifier {:?}", meta.path)) - }) - })?; - } else if meta.path.is_ident("exclude") { - meta.parse_nested_meta(|meta| { - mask.set_by_path(&meta.path, false).map_err(|_| { - meta.error(format!("unknown exclude specifier {:?}", meta.path)) - }) - })?; - } else { - collected.push(meta.path); - } - Ok(()) + pub fn append(&mut self, attr: Attribute) -> Result<(), syn::Error> { + let mut collected = vec![]; + let mut mask = SoaDeriveMask::new(); + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("include") { + mask = SoaDeriveMask::splat(false); + meta.parse_nested_meta(|meta| { + mask.set_by_path(&meta.path, true).map_err(|_| { + meta.error(format!("unknown include specifier {:?}", meta.path)) + }) })?; - - let to_extend = mask - .r#ref - .then_some(&mut self.r#ref) - .into_iter() - .chain(mask.ref_mut.then_some(&mut self.ref_mut).into_iter()) - .chain(mask.slice.then_some(&mut self.slices).into_iter()) - .chain(mask.slice_mut.then_some(&mut self.slices_mut).into_iter()) - .chain(mask.array.then_some(&mut self.array).into_iter()); - for set in to_extend { - set.extend(collected.iter().cloned()); - } + } else if meta.path.is_ident("exclude") { + meta.parse_nested_meta(|meta| { + mask.set_by_path(&meta.path, false).map_err(|_| { + meta.error(format!("unknown exclude specifier {:?}", meta.path)) + }) + })?; + } else { + collected.push(meta.path); } + Ok(()) + })?; + + let to_extend = mask + .r#ref + .then_some(&mut self.r#ref) + .into_iter() + .chain(mask.ref_mut.then_some(&mut self.ref_mut).into_iter()) + .chain(mask.slice.then_some(&mut self.slices).into_iter()) + .chain(mask.slice_mut.then_some(&mut self.slices_mut).into_iter()) + .chain(mask.array.then_some(&mut self.array).into_iter()); + + for set in to_extend { + set.extend(collected.iter().cloned()); } + Ok(()) } } diff --git a/soa-rs-derive/src/zst.rs b/soa-rs-derive/src/zst.rs index 7b15f37..c63836e 100644 --- a/soa-rs-derive/src/zst.rs +++ b/soa-rs-derive/src/zst.rs @@ -21,7 +21,6 @@ pub fn zst_struct(ident: Ident, vis: Visibility, kind: ZstKind) -> TokenStream { type RefMut<'a> = #ident; type Slices<'a> = #ident; type SlicesMut<'a> = #ident; - type Array = #array; } #[automatically_derived] diff --git a/soa-rs-testing/src/lib.rs b/soa-rs-testing/src/lib.rs index 346d3b1..456d755 100644 --- a/soa-rs-testing/src/lib.rs +++ b/soa-rs-testing/src/lib.rs @@ -24,6 +24,7 @@ impl Drop for SingleDrop { } #[derive(Soars, Debug, Clone, PartialEq, Eq, Hash)] +#[soa_array] #[soa_derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] struct El { foo: u64, @@ -385,6 +386,7 @@ pub fn field_getters() { } #[derive(Debug, Clone, Copy, PartialEq, Soars)] +#[soa_array] #[soa_derive(Debug, PartialEq, PartialOrd)] struct Alignment { #[align(64)] @@ -596,6 +598,7 @@ fn slices_mut() { #[test] fn array_with_box() { #[derive(Soars)] + #[soa_array] #[soa_derive(PartialEq)] struct Example { foo: Box, diff --git a/src/soars.rs b/src/soars.rs index 93a7747..360608b 100644 --- a/src/soars.rs +++ b/src/soars.rs @@ -1,4 +1,4 @@ -use crate::{as_slice::AsMutSlice, AsSlice, AsSoaRef, SoaDeref, SoaRaw}; +use crate::{AsSoaRef, SoaDeref, SoaRaw}; #[diagnostic::on_unimplemented( label = "SOA type", @@ -43,11 +43,6 @@ pub unsafe trait Soars: AsSoaRef { where Self: 'a; - /// The SoA array type. - /// - /// For each field with type `T`, this type has a field with type `[T; N]`. - type Array: AsSlice + AsMutSlice; - /// The slices that make up a [`Slice`]. /// /// For each field with type `T`, this type has a field with type `&[T]`. From 32e1d04977a48e35d5d545cc5b705feec5df8ba8 Mon Sep 17 00:00:00 2001 From: Tim Harding Date: Thu, 9 May 2024 15:03:15 -0700 Subject: [PATCH 2/6] Addressed new Clippy warnings --- soa-rs-derive/src/lib.rs | 8 ++++---- soa-rs-testing/src/lib.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/soa-rs-derive/src/lib.rs b/soa-rs-derive/src/lib.rs index 077e2c0..a8362ed 100644 --- a/soa-rs-derive/src/lib.rs +++ b/soa-rs-derive/src/lib.rs @@ -176,10 +176,10 @@ impl SoaDeriveParse { .r#ref .then_some(&mut self.r#ref) .into_iter() - .chain(mask.ref_mut.then_some(&mut self.ref_mut).into_iter()) - .chain(mask.slice.then_some(&mut self.slices).into_iter()) - .chain(mask.slice_mut.then_some(&mut self.slices_mut).into_iter()) - .chain(mask.array.then_some(&mut self.array).into_iter()); + .chain(mask.ref_mut.then_some(&mut self.ref_mut)) + .chain(mask.slice.then_some(&mut self.slices)) + .chain(mask.slice_mut.then_some(&mut self.slices_mut)) + .chain(mask.array.then_some(&mut self.array)); for set in to_extend { set.extend(collected.iter().cloned()); diff --git a/soa-rs-testing/src/lib.rs b/soa-rs-testing/src/lib.rs index 456d755..23e83e0 100644 --- a/soa-rs-testing/src/lib.rs +++ b/soa-rs-testing/src/lib.rs @@ -1,4 +1,5 @@ #![cfg(test)] +#![allow(clippy::disallowed_names)] use soa_rs::{soa, AsMutSlice, AsSlice, AsSoaRef, Soa, Soars}; @@ -606,8 +607,7 @@ fn array_with_box() { let foo = Box::new(42_u8); let x = ExampleArray::from_array([Example { foo }]); let s = x.as_slice(); - let v: &Box = s.get(0).unwrap().foo; - dbg!(v); + let _ = s.get(0).unwrap().foo; } fn assert_send(_t: T) {} From 3901b9adfedaacd8a257df77fef7afb0c6a3cfc5 Mon Sep 17 00:00:00 2001 From: Tim Harding Date: Thu, 9 May 2024 15:03:58 -0700 Subject: [PATCH 3/6] Bumped crate versions to 0.6.0 --- Cargo.toml | 2 +- soa-rs-derive/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c6c4b3..a2486db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "soa-rs" -version = "0.5.1" +version = "0.6.0" edition = "2021" license = "MIT" description = "A Vec-like structure-of-arrays container" diff --git a/soa-rs-derive/Cargo.toml b/soa-rs-derive/Cargo.toml index 7b66649..17ee335 100644 --- a/soa-rs-derive/Cargo.toml +++ b/soa-rs-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "soa-rs-derive" -version = "0.4.0" +version = "0.6.0" edition = "2021" license = "MIT" description = "Proc macro derive for soa-rs" From f3d735a38400ff4ce91eed4ac238f4f2f0e293d6 Mon Sep 17 00:00:00 2001 From: Tim Harding Date: Thu, 9 May 2024 15:05:05 -0700 Subject: [PATCH 4/6] Depend on updated soa-rs-derive version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a2486db..4daf019 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ debug = true members = ["soa-rs-derive", "soa-rs-testing"] [dependencies.soa-rs-derive] -version = "0.4.0" +version = "0.6.0" path = "soa-rs-derive" [dependencies.serde] From c26efef5a85c3de1030c374699c6a619332cbc7d Mon Sep 17 00:00:00 2001 From: Tim Harding Date: Thu, 9 May 2024 15:11:55 -0700 Subject: [PATCH 5/6] Documented the #[soa_array] attribute --- soa-rs-testing/Cargo.toml | 4 ++-- src/lib.rs | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/soa-rs-testing/Cargo.toml b/soa-rs-testing/Cargo.toml index e153699..eb87821 100644 --- a/soa-rs-testing/Cargo.toml +++ b/soa-rs-testing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "soa-rs-testing" -version = "0.4.0" +version = "0.6.0" edition = "2021" [dependencies] @@ -10,7 +10,7 @@ serde = "1.0.199" serde_json = "1.0.116" [dependencies.soa-rs] -version = "0.5.1" +version = "0.6.0" path = ".." features = ["serde"] diff --git a/src/lib.rs b/src/lib.rs index 0b96cfd..3217f58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -214,6 +214,14 @@ mod serde; /// The [`Soars`] trait implementation for `Foo` references these as associated /// types. [`AsSoaRef`] is also implemented for `Foo`, `FooRef`, and `FooRefMut`. /// +/// # Arrays +/// +/// The `FooArray` type is only generated when the `#[soa_array]` attribute is +/// added to the struct. Only structs without interior mutability support this +/// attribute for the time being, due to [this +/// issue](https://github.com/rust-lang/rust/issues/80384). SOA array types are +/// stack-allocated like normal arrays and are `const`-initializable. +/// /// # Derive for generated types /// /// The `soa_derive` attribute can be used to derive traits for the generated From e845dc0ce1702a39c70b5191bbdfbf47e211dd46 Mon Sep 17 00:00:00 2001 From: Tim Harding Date: Thu, 9 May 2024 15:12:19 -0700 Subject: [PATCH 6/6] Patch-bumped the crate version for docs --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4daf019..520cd46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "soa-rs" -version = "0.6.0" +version = "0.6.1" edition = "2021" license = "MIT" description = "A Vec-like structure-of-arrays container"