From eaab1452ac9f8ecc261f93ebe0b6d1221650d456 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Tue, 25 Jun 2024 11:59:08 +0200 Subject: [PATCH] Deprecate repr(C)-only enums --- CHANGELOG.md | 6 +++++- README.md | 12 +++++++++--- stabby-abi/Cargo.toml | 2 +- stabby-abi/build.rs | 28 +++++++++++++++++++++++++++- stabby-abi/src/future.rs | 8 ++++---- stabby-abi/src/istable.rs | 2 +- stabby-abi/src/lib.rs | 12 ++++++------ stabby-abi/src/stable_impls/mod.rs | 4 ---- stabby-macros/src/enums.rs | 10 ++++++++-- stabby/README.md | 12 +++++++++--- stabby/src/lib.rs | 12 +++++++++--- stabby/{ => src}/tests/enums.rs | 2 ++ stabby/{ => src}/tests/layouts.rs | 8 ++++---- stabby/{ => src}/tests/traits.rs | 5 +++-- 14 files changed, 88 insertions(+), 35 deletions(-) rename stabby/{ => src}/tests/enums.rs (99%) rename stabby/{ => src}/tests/layouts.rs (98%) rename stabby/{ => src}/tests/traits.rs (98%) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc1d8fb..8b296be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 6.1.1 +- Add support for multi-fields variants in `repr(C, u*)` enums. + - Deprecate support for `repr(C)` by deprecating any enum that uses it without also specifying a determinant size. +- Prepare integration of `stabby` and [`safer-ffi`](https://crates.io/crates/safer-ffi) by adding `CType` and `is_invalid` to `IStable`. + # 5.1.0 - Introducing `stabby::collections::arc_btree`, a set of copy-on-write btrees: - `ArcBtreeSet` and `ArcBtreeMap` behave like you would expect, but share nodes with their clones. @@ -8,7 +13,6 @@ - This is notably how `stabby` global set of vtables is implemented to support stable Rust from version 1.78 onward, until the [static-promotion regression](https://github.com/rust-lang/rust/issues/123281) is [fixed](https://github.com/rust-lang/rfcs/pull/3633), and this global set can be removed. - Add some missing `Send` and `Sync` implementations for container types. - Fix a lot of nightly lints. -- Officially switch to [Humane SemVer](https://p-avital.github.io/humane-semver) # 5.0.1 - Fix a regression in MSRV diff --git a/README.md b/README.md index 5841518..77e51fb 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The problem with that comes when dynamic linkage is involved: since the ABI for Concretely, this could mean that your executable thinks the leftmost 8 bytes of `Vec` is the pointer to the heap allocation, while the library believes them to be its length. This could also mean the library thinks it's free to clobber registers when its functions are called, while the executable relied on it to save them and restore them before returning. -`stabby` seeks to help you solve these issues by helping you pin the ABI for a subset of your program, while helping you retain some of the layout optimizations `rustc` provides when using its unstable ABI. On top of this, stabby allows you to annotate function exports and imports in a way that also serves as a check of your dependency versionning for types that are `stabby::abi::IStable`. +`stabby` seeks to help you solve these issues by helping you pin the ABI for a subset of your program, while helping you retain some of the layout optimizations `rustc` provides when using its unstable ABI. On top of this, stabby allows you to annotate function exports and imports in a way that also serves as a check of your dependency versioning for types that are `stabby::abi::IStable`. ## Structures When you annotate structs with `#[stabby::stabby]`, two things happen: @@ -72,7 +72,7 @@ In order for `stabby::dynptr!(Box)` to have `Trait`'s methods, `stabby::closure` exports the `CallN`, `CallMutN` and `CallOnceN` traits, where `N` (in `0..=9`) is the number of arguments, as ABI-stable equivalents of `Fn`, `FnMut` and `FnOnce` respectively. -Since version `1.0.1`, the v-tables generated by `#[stabby::stabby]` always assume all of their method arguments to be ABI-stable, to prevent the risk of freezing rustc. +Since version `1.0.1`, the v-tables generated by `#[stabby::stabby]` always assume all of their method arguments to be ABI-stable, to prevent the risk of freezing `rustc`. Unless your trait has methods referencing its own v-table, it's advised to use `#[stabby::stabby(checked)]` instead to avoid the v-table being marked as stable despite some types in its interface not actually being stable. @@ -154,4 +154,10 @@ My hope with `stabby` comes in two flavors: # `stabby`'s SemVer policy Stabby includes all of its `stabby_abi::IStable` implementation in its public API: any change to an `IStable` type's memory representation is a breaking change which will lead to a `MAJOR` version change. -Stabby follows [Humane SemVer](https://p-avital.github.io/humane-semver): small major bumps should require no thinking to perform (they may break binary compatibility, though); while large major bump indicate that you may have to think a bit before upgrading. \ No newline at end of file +From `6.1.1` onwards, Stabby follows [SemVer Prime](https://p-avital.github.io/semver-prime), using the `api, abi` key. Here's a few ways you can interpret that: +- `stabby.version[level] = 2^(api[level]) * 3^(abi[level])` lets you compute the exact versions of stabby's ABI and API. +- When upgrading stabby, you can check what has changed by dividing the new version by the previous one: if the division result is a multiple of 2, the change affected API; and it affected ABI if it's a multiple of 3. + - ABI versioning: + - Adding a new type to the set of ABI stable type will bump ABI patch. + - Modifying an existing type's ABI in any way will bump the ABI major. + - API versioning strictly follows SemVer policy. Any API visible in the docs is considered public, as well as whatever contracts are mentioned in said docs. \ No newline at end of file diff --git a/stabby-abi/Cargo.toml b/stabby-abi/Cargo.toml index 3890b05..3636ff3 100644 --- a/stabby-abi/Cargo.toml +++ b/stabby-abi/Cargo.toml @@ -36,7 +36,7 @@ abi_stable-channels = ["abi_stable", "abi_stable/channels"] # `stabby::future::Future::poll` may need to allocate in order to provide stable wakers. # If you're confident enough that `core::task::Waker`'s ABI will not change between your targetted versions # of rustc, you may enable this feature to pass them across the FFI boundary directly. -# unsafe_wakers = [] # unsafe_wakers is no longer a feature, but a compile option: you can enable them using `RUST_FLAGS='--cfg unsafe_wakers="true"'` +# stabby_unsafe_wakers = [] # stabby_unsafe_wakers is no longer a feature, but a compile option: you can enable them using `RUST_FLAGS='--cfg stabby_unsafe_wakers="true"'` [dependencies] stabby-macros = { path = "../stabby-macros/", version = "5.1.0" } diff --git a/stabby-abi/build.rs b/stabby-abi/build.rs index b100119..b801dec 100644 --- a/stabby-abi/build.rs +++ b/stabby-abi/build.rs @@ -66,9 +66,23 @@ fn tuples() -> std::io::Result<()> { for i in 0..128 { writeln!( file, - r##"/// An ABI stable tuple + r##"/// An ABI stable tuple of {i} elements. #[crate::stabby] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Tuple{i}<{generics}>({fields}); +impl<{generics}> From<({generics})> for Tuple{i}<{generics}> {{ + fn from(value: ({generics})) -> Self {{ + let ({named_fields}) = value; + Self({named_fields}) + }} +}} +#[allow(clippy::unused_unit)] +impl<{generics}> From> for ({generics}) {{ + fn from(value: Tuple{i}<{generics}>) -> Self {{ + let Tuple{i}({named_fields}) = value; + ({named_fields}) + }} +}} "##, generics = (0..i).fold(String::new(), |mut acc, it| { write!(acc, "T{it}, ").unwrap(); @@ -78,6 +92,10 @@ pub struct Tuple{i}<{generics}>({fields}); write!(acc, "pub T{it}, ").unwrap(); acc }), + named_fields = (0..i).fold(String::new(), |mut acc, it| { + write!(acc, "field{it}, ").unwrap(); + acc + }), )?; } Ok(()) @@ -86,6 +104,14 @@ pub struct Tuple{i}<{generics}>({fields}); fn main() { typenum_unsigned().unwrap(); tuples().unwrap(); + println!("cargo:rustc-check-cfg=cfg(stabby_nightly, values(none()))"); + println!( + r#"cargo:rustc-check-cfg=cfg(stabby_check_unreachable, values(none(), "true", "false"))"# + ); + println!(r#"cargo:rustc-check-cfg=cfg(stabby_unsafe_wakers, values(none(), "true", "false"))"#); + println!( + r#"cargo:rustc-check-cfg=cfg(stabby_vtables, values(none(), "vec", "btree", "no_alloc"))"# + ); if let Ok(toolchain) = std::env::var("RUSTUP_TOOLCHAIN") { if toolchain.starts_with("nightly") { println!("cargo:rustc-cfg=stabby_nightly"); diff --git a/stabby-abi/src/future.rs b/stabby-abi/src/future.rs index 4c2109a..dc67f02 100644 --- a/stabby-abi/src/future.rs +++ b/stabby-abi/src/future.rs @@ -20,14 +20,14 @@ use crate::vtable::HasDropVt; use crate::{IPtrMut, IPtrOwned, IStable}; pub use stable_waker::StableWaker; -#[cfg(unsafe_wakers = "true")] +#[cfg(stabby_unsafe_wakers = "true")] mod stable_waker { use core::task::Waker; use crate::StableLike; /// A waker that promises to be ABI stable. /// - /// With the `unsafe_wakers` feature enabled, this is actually a lie: the waker is not guaranteed to + /// With the `stabby_unsafe_wakers` feature enabled, this is actually a lie: the waker is not guaranteed to /// be ABI-stable! However, since building ABI-stable wakers that are compatible with Rust's wakers is /// costly in terms of runtime, you might want to experiment with unsafe wakers, to bench the cost of /// stable wakers if nothing else. @@ -47,7 +47,7 @@ mod stable_waker { } } } -#[cfg(any(not(unsafe_wakers), unsafe_wakers = "false"))] +#[cfg(any(not(stabby_unsafe_wakers), stabby_unsafe_wakers = "false"))] mod stable_waker { use core::{ mem::ManuallyDrop, @@ -114,7 +114,7 @@ mod stable_waker { /// /// While this is the only way to guarantee ABI-stability when interacting with futures, this does add /// a layer of indirection, and cloning this waker will cause an allocation. To bench the performance cost - /// of this wrapper and decide if you want to risk ABI-unstability on wakers, you may use `RUST_FLAGS='--cfg unsafe_wakers="true"'`, which will turn [`StableWaker`] into a newtype of [`core::task::Waker`]. + /// of this wrapper and decide if you want to risk ABI-unstability on wakers, you may use `RUST_FLAGS='--cfg stabby_unsafe_wakers="true"'`, which will turn [`StableWaker`] into a newtype of [`core::task::Waker`]. #[crate::stabby] pub struct StableWaker<'a, Alloc: IAlloc = crate::alloc::DefaultAllocator> { waker: StableLike<&'a Waker, &'a ()>, diff --git a/stabby-abi/src/istable.rs b/stabby-abi/src/istable.rs index 876b110..686c735 100644 --- a/stabby-abi/src/istable.rs +++ b/stabby-abi/src/istable.rs @@ -89,7 +89,7 @@ pub unsafe trait IStable: Sized { /// but might not be limited to: /// - The type doesn't contain pointers, as they may not point to the same memory on the recipient's end. /// - The type doesn't have a destructor, as destructors generally imply a context needs to be cleaned up, -/// implying that a context exists. +/// implying that a context exists. /// /// In some circumstances, a POD type may be used as a key in a context (index in an array, key in a HashMap...) that /// may not be available to all potential recipient. In such a case, you can wrap that type in [`NotPod`] to strip it diff --git a/stabby-abi/src/lib.rs b/stabby-abi/src/lib.rs index 82663d0..284bb6a 100644 --- a/stabby-abi/src/lib.rs +++ b/stabby-abi/src/lib.rs @@ -413,7 +413,7 @@ mod boundtests { } } -/// Expands to [`unreachable!()`](core::unreachable) in debug builds or if `--cfg check_unreachable=true` has been set in the `RUST_FLAGS`, and to [`core::hint::unreachable_unchecked`] otherwise. +/// Expands to [`unreachable!()`](core::unreachable) in debug builds or if `--cfg stabby_check_unreachable=true` has been set in the `RUST_FLAGS`, and to [`core::hint::unreachable_unchecked`] otherwise. /// /// This lets the compiler take advantage of the fact that the code is unreachable in release builds, and optimize accordingly, while giving you the opportunity to double check this at runtime in case of doubts. /// @@ -426,7 +426,7 @@ mod boundtests { #[macro_export] macro_rules! unreachable_unchecked { () => { - if cfg!(any(debug_assertions, check_unreachable = "true")) { + if cfg!(any(debug_assertions, stabby_check_unreachable = "true")) { ::core::unreachable!() } else { ::core::hint::unreachable_unchecked() @@ -434,7 +434,7 @@ macro_rules! unreachable_unchecked { }; } -/// Expands to [`assert!(condition)`](core::assert) in debug builds or if `--cfg check_unreachable=true` has been set in the `RUST_FLAGS`, and to [`if condition {core::hint::unreachable_unchecked()}`](core::hint::unreachable_unchecked) otherwise. +/// Expands to [`assert!(condition)`](core::assert) in debug builds or if `--cfg stabby_check_unreachable=true` has been set in the `RUST_FLAGS`, and to [`if condition {core::hint::unreachable_unchecked()}`](core::hint::unreachable_unchecked) otherwise. /// /// This lets the compiler take advantage of the fact that the condition is always true in release builds, and optimize accordingly, while giving you the opportunity to double check this at runtime in case of doubts. /// @@ -447,7 +447,7 @@ macro_rules! unreachable_unchecked { #[macro_export] macro_rules! assert_unchecked { ($e: expr, $($t: tt)*) => { - if cfg!(any(debug_assertions, check_unreachable = "true")) { + if cfg!(any(debug_assertions, stabby_check_unreachable = "true")) { ::core::assert!($e, $($t)*); } else { if !$e { @@ -457,7 +457,7 @@ macro_rules! assert_unchecked { }; } -/// Expands to [`assert_eq`](core::assert_eq) in debug builds or if `--cfg check_unreachable=true` has been set in the `RUST_FLAGS`, and to [`if a != b {core::hint::unreachable_unchecked()}`](core::hint::unreachable_unchecked) otherwise. +/// Expands to [`assert_eq`](core::assert_eq) in debug builds or if `--cfg stabby_check_unreachable=true` has been set in the `RUST_FLAGS`, and to [`if a != b {core::hint::unreachable_unchecked()}`](core::hint::unreachable_unchecked) otherwise. /// /// This lets the compiler take advantage of the fact that the condition is always true in release builds, and optimize accordingly, while giving you the opportunity to double check this at runtime in case of doubts. /// @@ -470,7 +470,7 @@ macro_rules! assert_unchecked { #[macro_export] macro_rules! assert_eq_unchecked { ($a: expr, $b: expr, $($t: tt)*) => { - if cfg!(any(debug_assertions, check_unreachable = "true")) { + if cfg!(any(debug_assertions, stabby_check_unreachable = "true")) { ::core::assert_eq!($a, $b, $($t)*); } else { if $a != $b { diff --git a/stabby-abi/src/stable_impls/mod.rs b/stabby-abi/src/stable_impls/mod.rs index 7a24c0f..78d4d84 100644 --- a/stabby-abi/src/stable_impls/mod.rs +++ b/stabby-abi/src/stable_impls/mod.rs @@ -323,8 +323,6 @@ unsafe impl IStable for usize { same_as!(u32, "usize"); #[cfg(target_pointer_width = "16")] same_as!(u16, "usize"); - #[cfg(target_pointer_width = "8")] - same_as!(u8, "usize"); type ContainsIndirections = B0; } @@ -336,8 +334,6 @@ unsafe impl IStable for core::num::NonZeroUsize { same_as!(core::num::NonZeroU32, "core::num::NonZeroUsize"); #[cfg(target_pointer_width = "16")] same_as!(core::num::NonZeroU16, "core::num::NonZeroUsize"); - #[cfg(target_pointer_width = "8")] - same_as!(core::num::NonZeroU8, "core::num::NonZeroUsize"); type ContainsIndirections = B0; } diff --git a/stabby-macros/src/enums.rs b/stabby-macros/src/enums.rs index b6b56eb..50090b5 100644 --- a/stabby-macros/src/enums.rs +++ b/stabby-macros/src/enums.rs @@ -170,9 +170,10 @@ pub fn stabby( } } } + let mut deprecation = None; let reprstr = match repr .as_ref() - .and_then(|r| if r.is_c { Some(Repr::C) } else { r.repr }) + .and_then(|r| r.repr.or_else(|| r.is_c.then_some(Repr::C))) { None | Some(Repr::Stabby) => { if !has_non_empty_fields { @@ -188,7 +189,11 @@ pub fn stabby( repr.is_none(), ); } - Some(Repr::C) => "u8", // TODO: Remove support for `#[repr(C)]` alone on the next API-breaking release + Some(Repr::C) => { + let msg = format!("{repr:?} stabby doesn't support variable size repr and implicitly replaces repr(C) with repr(C, u8), you can silence this warning by picking an explict fixed-size repr"); + deprecation = Some(quote!(#[deprecated = #msg])); + "u8" + } // TODO: Remove support for `#[repr(C)]` alone on the next API-breaking release Some(Repr::U8) => "u8", Some(Repr::U16) => "u16", Some(Repr::U32) => "u32", @@ -218,6 +223,7 @@ pub fn stabby( quote! { #(#new_attrs)* #reprattr + #deprecation #vis enum #ident #generics { #variants } diff --git a/stabby/README.md b/stabby/README.md index 5841518..d1277f2 100644 --- a/stabby/README.md +++ b/stabby/README.md @@ -32,7 +32,7 @@ The problem with that comes when dynamic linkage is involved: since the ABI for Concretely, this could mean that your executable thinks the leftmost 8 bytes of `Vec` is the pointer to the heap allocation, while the library believes them to be its length. This could also mean the library thinks it's free to clobber registers when its functions are called, while the executable relied on it to save them and restore them before returning. -`stabby` seeks to help you solve these issues by helping you pin the ABI for a subset of your program, while helping you retain some of the layout optimizations `rustc` provides when using its unstable ABI. On top of this, stabby allows you to annotate function exports and imports in a way that also serves as a check of your dependency versionning for types that are `stabby::abi::IStable`. +`stabby` seeks to help you solve these issues by helping you pin the ABI for a subset of your program, while helping you retain some of the layout optimizations `rustc` provides when using its unstable ABI. On top of this, stabby allows you to annotate function exports and imports in a way that also serves as a check of your dependency versioning for types that are `stabby::abi::IStable`. ## Structures When you annotate structs with `#[stabby::stabby]`, two things happen: @@ -72,7 +72,7 @@ In order for `stabby::dynptr!(Box)` to have `Trait`'s methods, `stabby::closure` exports the `CallN`, `CallMutN` and `CallOnceN` traits, where `N` (in `0..=9`) is the number of arguments, as ABI-stable equivalents of `Fn`, `FnMut` and `FnOnce` respectively. -Since version `1.0.1`, the v-tables generated by `#[stabby::stabby]` always assume all of their method arguments to be ABI-stable, to prevent the risk of freezing rustc. +Since version `1.0.1`, the v-tables generated by `#[stabby::stabby]` always assume all of their method arguments to be ABI-stable, to prevent the risk of freezing `rustc`. Unless your trait has methods referencing its own v-table, it's advised to use `#[stabby::stabby(checked)]` instead to avoid the v-table being marked as stable despite some types in its interface not actually being stable. @@ -154,4 +154,10 @@ My hope with `stabby` comes in two flavors: # `stabby`'s SemVer policy Stabby includes all of its `stabby_abi::IStable` implementation in its public API: any change to an `IStable` type's memory representation is a breaking change which will lead to a `MAJOR` version change. -Stabby follows [Humane SemVer](https://p-avital.github.io/humane-semver): small major bumps should require no thinking to perform (they may break binary compatibility, though); while large major bump indicate that you may have to think a bit before upgrading. \ No newline at end of file +From `6.1.1` onwards, Stabby follows [SemVer Prime](https://p-avital.github.io/semver-prime), using the `api, abi` key. Here's a few ways you can interpret that: +- `stabby.version[level] = 2^(api[level]) * 3^(abi[level])` lets you compute the exact versions of stabby's ABI and API, there's a decoder for that [here](https://p-avital.github.io/semver-prime#:~:text=semver%20prime%20translation). +- When upgrading stabby, you can check what has changed by dividing the new version by the previous one: if the division result is a multiple of 2, the change affected API; and it affected ABI if it's a multiple of 3. + - ABI versioning: + - Adding a new type to the set of ABI stable type will bump ABI patch. + - Modifying an existing type's ABI in any way will bump the ABI major. + - API versioning strictly follows SemVer policy. Any API visible in the docs is considered public, as well as whatever contracts are mentioned in said docs. \ No newline at end of file diff --git a/stabby/src/lib.rs b/stabby/src/lib.rs index 8ddd459..fc4204d 100644 --- a/stabby/src/lib.rs +++ b/stabby/src/lib.rs @@ -37,12 +37,12 @@ pub use stabby_abi::{Dyn, DynRef}; pub mod compiler_version; /// ABI-stable tuples -pub mod tuple; +pub use crate::abi::tuples as tuple; /// Futures can be ABI-stable if you wish hard enough #[cfg_attr( - unsafe_wakers = "true", - deprecated = "Warning! you are using the `stabby/unsafe_wakers` feature. This could cause UB if you poll a future received from another shared library with mismatching ABI! (this API isn't actually deprecated)" + stabby_unsafe_wakers = "true", + deprecated = "Warning! you are using the `stabby/stabby_unsafe_wakers` feature. This could cause UB if you poll a future received from another shared library with mismatching ABI! (this API isn't actually deprecated)" )] pub mod future { pub use crate::abi::future::*; @@ -85,3 +85,9 @@ macro_rules! format { #[cfg(doc)] #[doc = include_str!("../TUTORIAL.md")] pub mod _tutorial_ {} +#[cfg(test)] +mod tests { + mod enums; + mod layouts; + mod traits; +} diff --git a/stabby/tests/enums.rs b/stabby/src/tests/enums.rs similarity index 99% rename from stabby/tests/enums.rs rename to stabby/src/tests/enums.rs index b173b9b..c1596b8 100644 --- a/stabby/tests/enums.rs +++ b/stabby/src/tests/enums.rs @@ -12,6 +12,8 @@ // Pierre Avital, // +use crate as stabby; + #[test] fn enums() { use core::num::{NonZeroU16, NonZeroU8}; diff --git a/stabby/tests/layouts.rs b/stabby/src/tests/layouts.rs similarity index 98% rename from stabby/tests/layouts.rs rename to stabby/src/tests/layouts.rs index 5fbc513..66302e7 100644 --- a/stabby/tests/layouts.rs +++ b/stabby/src/tests/layouts.rs @@ -12,9 +12,9 @@ // Pierre Avital, // -use core::num::{NonZeroU16, NonZeroU32}; -use std::num::NonZeroU8; +use core::num::{NonZeroU16, NonZeroU32, NonZeroU8}; +use crate as stabby; use stabby::tuple::{Tuple2, Tuple3, Tuple8}; use stabby_abi::{typenum2::*, Array, End, Result}; @@ -36,7 +36,7 @@ pub enum NoFields { _B, } #[stabby::stabby] -#[repr(C)] +#[repr(C, u8)] pub enum FieldsC { _A(NonZeroU32), _B, @@ -47,7 +47,7 @@ pub enum FieldsStabby { _B, } #[stabby::stabby] -#[repr(C)] +#[repr(C, u8)] #[allow(dead_code)] pub enum MultiFieldsC { A(NonZeroU16), diff --git a/stabby/tests/traits.rs b/stabby/src/tests/traits.rs similarity index 98% rename from stabby/tests/traits.rs rename to stabby/src/tests/traits.rs index 0868fd4..d52ef51 100644 --- a/stabby/tests/traits.rs +++ b/stabby/src/tests/traits.rs @@ -14,10 +14,11 @@ // MYTRAIT -#![cfg_attr(unsafe_wakers = "true", allow(deprecated))] +#![cfg_attr(stabby_unsafe_wakers = "true", allow(deprecated))] -use std::time::Duration; +use core::time::Duration; +pub use crate as stabby; use stabby::boxed::Box; use stabby::future::DynFuture;