diff --git a/CHANGELOG.md b/CHANGELOG.md index aaa8435..71e8834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 36.1.1-rc4 (api=2.0.0, abi=2.0.0) +- Add support for `#[repr(transparent)]` and `#[repr(align(n))]` in `#[stabby::stabby]` structs up to n=64kiB. + # 36.1.1-rc3 (api=2.0.0, abi=2.0.0) - Allow structures to compute their single-niche evaluation properly. diff --git a/Cargo.toml b/Cargo.toml index b336357..5b1673f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,12 +33,12 @@ license = " EPL-2.0 OR Apache-2.0" categories = ["development-tools::ffi", "no-std::no-alloc"] repository = "https://github.com/ZettaScaleLabs/stabby" readme = "stabby/README.md" -version = "36.1.1-rc3" # Track +version = "36.1.1-rc4" # Track [workspace.dependencies] -stabby-macros = { path = "./stabby-macros/", version = "36.1.1-rc3", default-features = false } # Track -stabby-abi = { path = "./stabby-abi/", version = "36.1.1-rc3", default-features = false } # Track -stabby = { path = "./stabby/", version = "36.1.1-rc3", default-features = false } # Track +stabby-macros = { path = "./stabby-macros/", version = "36.1.1-rc4", default-features = false } # Track +stabby-abi = { path = "./stabby-abi/", version = "36.1.1-rc4", default-features = false } # Track +stabby = { path = "./stabby/", version = "36.1.1-rc4", default-features = false } # Track abi_stable = "0.11.0" libc = "0.2" diff --git a/stabby-abi/build.rs b/stabby-abi/build.rs index eeda0f7..7f3b937 100644 --- a/stabby-abi/build.rs +++ b/stabby-abi/build.rs @@ -54,8 +54,12 @@ fn typenum_unsigned() -> std::io::Result<()> { } } for i in 0..128 { - let u = u(1 << i); + let p = 1 << i; + let u = u(p); writeln!(file, "/// {i}\npub type U2pow{i} = {u};")?; + if p > SEQ_MAX { + writeln!(file, "/// {i}\npub type U{p} = {u};")?; + } } Ok(()) } diff --git a/stabby-abi/src/istable.rs b/stabby-abi/src/istable.rs index 3713150..555c734 100644 --- a/stabby-abi/src/istable.rs +++ b/stabby-abi/src/istable.rs @@ -18,7 +18,7 @@ use self::unsigned::{Alignment, IUnsignedBase}; use super::typenum2::*; use super::unsigned::{IBitBase, NonZero}; -use super::{FieldPair, Struct, Union}; +use super::{AlignedStruct, FieldPair, Struct, Union}; use stabby_macros::tyeval; /// A trait to describe the layout of a type, marking it as ABI-stable. @@ -568,6 +568,22 @@ unsafe impl IStable for Struct { primitive_report!("FP"); } +unsafe impl IStable for AlignedStruct { + type Size = ::NextMultipleOf; + type Align = ::Max; + type ForbiddenValues = T::ForbiddenValues; + type UnusedBits = ::BitOr< + <::NextMultipleOf - T::Size) as IUnsignedBase>::PaddingBitMask as IBitMask>::Shift>; + type HasExactlyOneNiche = <::Equal as Bit>::SaddTernary< + T::HasExactlyOneNiche, + Saturator, + >; + type ContainsIndirections = T::ContainsIndirections; + #[cfg(feature = "experimental-ctypes")] + type CType = (); + primitive_report!("FP"); +} + /// Used by `stabby` to prevent proof cycles in types that contain indirections to themselves. #[crate::stabby] pub struct _Self; diff --git a/stabby-abi/src/lib.rs b/stabby-abi/src/lib.rs index 8132838..972d9b5 100644 --- a/stabby-abi/src/lib.rs +++ b/stabby-abi/src/lib.rs @@ -365,6 +365,9 @@ pub struct FieldPair(core::marker::PhantomData<(A, B)>); #[repr(transparent)] pub struct Struct(T); +/// Used by proc-macros to ensure a list of fields gets the proper end padding when specific alignments are requested. +pub struct AlignedStruct(core::marker::PhantomData<(T, Align)>); + /// Used by [`crate::result::Result`] #[repr(C)] pub union Union { diff --git a/stabby-abi/src/typenum2/unsigned.rs b/stabby-abi/src/typenum2/unsigned.rs index dbe1364..b86ad2b 100644 --- a/stabby-abi/src/typenum2/unsigned.rs +++ b/stabby-abi/src/typenum2/unsigned.rs @@ -531,6 +531,33 @@ impl Alignment for U16 { type Max = as IBitBase>::_ATernary; type AsUint = u128; } +macro_rules! gen_align { + ($n: literal, $path: ident, $u: ident, $backing: ty) => { + /// A type with the alignment specified in its name. + /// + /// This type is also valid as a contiguous buffer. + #[crate::stabby] + #[repr(align($n))] + #[derive(Debug, Default, Copy, Clone)] + pub struct $path(pub $backing); + impl Alignment for $u { + type Max = as IBitBase>::_ATernary; + type AsUint = $path; + } + }; +} +gen_align!(32, Align32, U32, [u8; 32]); +gen_align!(64, Align64, U64, [[u8; 32]; 2]); +gen_align!(128, Align128, U128, [[u8; 32]; 4]); +gen_align!(256, Align256, U256, [[u8; 32]; 8]); +gen_align!(512, Align512, U512, [[u8; 32]; 16]); +gen_align!(1024, Align1024, U1024, [[u8; 32]; 32]); +gen_align!(2048, Align2048, U2048, [[[u8; 32]; 32]; 2]); +gen_align!(4096, Align4096, U4096, [[[u8; 32]; 32]; 4]); +gen_align!(8192, Align8192, U8192, [[[u8; 32]; 32]; 8]); +gen_align!(16384, Align16384, U16384, [[[u8; 32]; 32]; 16]); +gen_align!(32768, Align32768, U32768, [[[u8; 32]; 32]; 32]); +gen_align!(65536, Align65536, U65536, [[[[u8; 32]; 32]; 32]; 2]); #[allow(unknown_lints)] #[allow(clippy::missing_transmute_annotations)] diff --git a/stabby-macros/src/structs.rs b/stabby-macros/src/structs.rs index 54dc928..2510268 100644 --- a/stabby-macros/src/structs.rs +++ b/stabby-macros/src/structs.rs @@ -14,7 +14,7 @@ use proc_macro2::Ident; use quote::quote; -use syn::{Attribute, DataStruct, Generics, Visibility}; +use syn::{spanned::Spanned, Attribute, DataStruct, Generics, Visibility}; use crate::Unself; @@ -55,6 +55,49 @@ impl syn::parse::Parse for Args { Ok(this) } } +#[derive(Copy, Clone)] +enum AllowedRepr { + C, + Transparent, + Align(usize), +} +impl syn::parse::Parse for AllowedRepr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut content = input.fork(); + if input.peek(syn::token::Paren) { + syn::parenthesized!(content in input); + } + let ident: Ident = content.parse()?; + Ok(match ident.to_string().as_str() { + "C" => AllowedRepr::C, + "transparent" => AllowedRepr::Transparent, + "packed" => return Err(input.error("stabby does not support packed structs, though you may implement IStable manually if you're very comfident")), + "align" => { + let input = content; + syn::parenthesized!(content in input); + let lit: syn::LitInt = content.parse()?; + AllowedRepr::Align(lit.base10_parse()?) + } + _ => { + return Err(input.error( + "Only #[repr(C)] and #[repr(transparent)] are allowed for stabby structs", + )) + } + }) + } +} +impl quote::ToTokens for AllowedRepr { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend(match self { + AllowedRepr::C => quote!(#[repr(C)]), + AllowedRepr::Transparent => quote!(#[repr(transparent)]), + AllowedRepr::Align(n) => { + let n = syn::LitInt::new(&format!("{n}"), tokens.span()); + quote!(#[repr(align(#n))]) + } + }) + } +} pub fn stabby( attrs: Vec, @@ -79,6 +122,17 @@ pub fn stabby( let clauses = where_clause.as_ref().map(|w| &w.predicates); let mut layout = None; let mut report = crate::Report::r#struct(ident.to_string(), version, module); + let repr = attrs + .iter() + .find_map(|attr| { + if attr.path.is_ident("repr") { + syn::parse2::(attr.tokens.clone()).ok() + } else { + None + } + }) + .unwrap_or(AllowedRepr::C); + optimize &= !matches!(repr, AllowedRepr::Align(_)); let struct_code = match &fields { syn::Fields::Named(fields) => { let fields = &fields.named; @@ -92,7 +146,7 @@ pub fn stabby( } quote! { #(#attrs)* - #[repr(C)] + #repr #vis struct #ident #generics #where_clause { #fields } @@ -110,19 +164,33 @@ pub fn stabby( } quote! { #(#attrs)* - #[repr(C)] + #repr #vis struct #ident #generics #where_clause (#fields); } } syn::Fields::Unit => { quote! { #(#attrs)* - #[repr(C)] + #repr #vis struct #ident #generics #where_clause; } } }; - let layout = layout.map_or_else(|| quote!(()), |layout| quote!(#st::Struct<#layout>)); + let layout = layout.map_or_else( + || quote!(()), + |layout| { + if let AllowedRepr::Align(mut n) = repr { + let mut align = quote!(#st::U1); + while n > 1 { + n /= 2; + align = quote!(#st::UInt<#align, #st::B0>); + } + quote!(#st::AlignedStruct<#layout, #align>) + } else { + quote!(#st::Struct<#layout>) + } + }, + ); let opt_id = quote::format_ident!("OptimizedLayoutFor{ident}"); let size_bug = format!( "{ident}'s size was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" diff --git a/stabby/src/tests/layouts.rs b/stabby/src/tests/layouts.rs index d245270..9ef6e48 100644 --- a/stabby/src/tests/layouts.rs +++ b/stabby/src/tests/layouts.rs @@ -254,26 +254,15 @@ fn layouts() { >(), 3 * 16 ); + let _ = Align1024::ID; } #[allow(dead_code)] +#[stabby::stabby] #[repr(align(16))] struct Align128(u128); -unsafe impl stabby::abi::IStable for Align128 { - type Size = U16; - type Align = U16; - type ForbiddenValues = End; - type UnusedBits = End; - type HasExactlyOneNiche = B0; - type ContainsIndirections = B0; - #[cfg(feature = "experimental-ctypes")] - type CType = Align128; - const REPORT: &'static stabby::abi::report::TypeReport = &stabby::abi::report::TypeReport { - name: stabby::abi::str::Str::new("Align128"), - module: stabby::abi::str::Str::new(core::module_path!()), - fields: stabby::abi::StableLike::new(None), - version: 0, - tyty: stabby::abi::report::TyTy::Struct, - }; - const ID: u64 = stabby::abi::report::gen_id(Self::REPORT); -} + +#[allow(dead_code)] +#[stabby::stabby] +#[repr(align(1024))] +struct Align1024(u8);