Skip to content

Commit

Permalink
Deprecate repr(C)-only enums
Browse files Browse the repository at this point in the history
  • Loading branch information
p-avital committed Jun 25, 2024
1 parent 0adae35 commit eaab145
Show file tree
Hide file tree
Showing 14 changed files with 88 additions and 35 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Potato>` 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:
Expand Down Expand Up @@ -72,7 +72,7 @@ In order for `stabby::dynptr!(Box<dyn Traits + 'a>)` 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.

Expand Down Expand Up @@ -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.
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.
2 changes: 1 addition & 1 deletion stabby-abi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
28 changes: 27 additions & 1 deletion stabby-abi/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Tuple{i}<{generics}>> 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();
Expand All @@ -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(())
Expand All @@ -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");
Expand Down
8 changes: 4 additions & 4 deletions stabby-abi/src/future.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
Expand Down Expand Up @@ -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 ()>,
Expand Down
2 changes: 1 addition & 1 deletion stabby-abi/src/istable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions stabby-abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -426,15 +426,15 @@ 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()
}
};
}

/// 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.
///
Expand All @@ -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 {
Expand All @@ -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.
///
Expand All @@ -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 {
Expand Down
4 changes: 0 additions & 4 deletions stabby-abi/src/stable_impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down
10 changes: 8 additions & 2 deletions stabby-macros/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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",
Expand Down Expand Up @@ -218,6 +223,7 @@ pub fn stabby(
quote! {
#(#new_attrs)*
#reprattr
#deprecation
#vis enum #ident #generics {
#variants
}
Expand Down
12 changes: 9 additions & 3 deletions stabby/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Potato>` 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:
Expand Down Expand Up @@ -72,7 +72,7 @@ In order for `stabby::dynptr!(Box<dyn Traits + 'a>)` 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.

Expand Down Expand Up @@ -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.
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.
12 changes: 9 additions & 3 deletions stabby/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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;
}
2 changes: 2 additions & 0 deletions stabby/tests/enums.rs → stabby/src/tests/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// Pierre Avital, <[email protected]>
//

use crate as stabby;

#[test]
fn enums() {
use core::num::{NonZeroU16, NonZeroU8};
Expand Down
Loading

0 comments on commit eaab145

Please sign in to comment.