diff --git a/CHANGELOG.md b/CHANGELOG.md index dc1d8fb..3744876 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 6.1.1 (api=1.0.0, abi=1.0.0) +- 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. +- Add support for `Result`, which is fully identical to `T` down to its reports. +- Remove `CompilerVersion_X_Y_Z` types: keeping them up to date with new compiler release was too much maintenance. +- Remove `CurrentCompilerVersion` type alias: as it could break your code if you upgraded to a version of the compiler that `stabby` didn't know of. +- Prepare integration of `stabby` and [`safer-ffi`](https://crates.io/crates/safer-ffi) by adding `CType` and `is_invalid` to `IStable`. +- Switch versioning system to [SemVer Prime](https://p-avital.github.io/semver-prime), using the `api, abi` as the key. +- [RFC 3633](https://github.com/rust-lang/rfcs/pull/3633) which seeks to address the breaking change in Rust 1.78 is scheduled to be discussed in July 2024. + # 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 +18,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/Cargo.toml b/Cargo.toml index fac2666..f64fa8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,13 @@ 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 = "6.1.1" # Track [workspace.dependencies] +stabby-macros = { path = "./stabby-macros/", version = "6.1.1", default-features = false } # Track +stabby-abi = { path = "./stabby-abi/", version = "6.1.1", default-features = false } # Track +stabby = { path = "./stabby/", version = "6.1.1", default-features = false } # Track + abi_stable = "0.11.2" criterion = "0.5.1" lazy_static = "1.4.0" diff --git a/README.md b/README.md index 5841518..b300445 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` as the 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/examples/dynlinkage/Cargo.toml b/examples/dynlinkage/Cargo.toml index 12729a9..b675eef 100644 --- a/examples/dynlinkage/Cargo.toml +++ b/examples/dynlinkage/Cargo.toml @@ -18,5 +18,5 @@ version = "0.1.0" edition = "2021" [dependencies] -stabby = { path = "../../stabby/" } -library = { path = "../library/" } # This ensures proper compilation order +stabby.workspace = true +library = { path = "../library/" } # This ensures proper compilation order diff --git a/examples/libloading/Cargo.toml b/examples/libloading/Cargo.toml index 5a38ae4..a33e64d 100644 --- a/examples/libloading/Cargo.toml +++ b/examples/libloading/Cargo.toml @@ -20,5 +20,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -stabby = { path = "../../stabby/", features = ["libloading"] } +stabby = { workspace = true, features = ["libloading"] } libloading = "0.8" diff --git a/examples/library/Cargo.toml b/examples/library/Cargo.toml index d8a801f..ab3564a 100644 --- a/examples/library/Cargo.toml +++ b/examples/library/Cargo.toml @@ -22,4 +22,4 @@ crate-type = ["cdylib", "staticlib"] [dependencies] -stabby = { path = "../../stabby/" } +stabby.workspace = true diff --git a/release.py b/release.py index a079750..582963c 100644 --- a/release.py +++ b/release.py @@ -1,6 +1,17 @@ import sys, os, re ws_root = os.path.dirname(__file__) crates = ["stabby-macros", "stabby-abi", "stabby"] + +def factor(x, base): + n = 0 + while x > 1 and x % base == 0: + x /= base + n += 1 + return n + +def factor_version(version, base): + return ".".join([str(factor(int(x), base)) for x in version.split(".")]) + if __name__ == "__main__": if len(sys.argv) > 1 and sys.argv[1] == "publish": for crate in crates: @@ -9,19 +20,28 @@ raise f"Failed to release {crate}, stopping publication" else: changelog = f"{ws_root}/CHANGELOG.md" + print("Close the CHANGELOG to continue, the topmost version will be picked") os.system(f"code --wait {changelog}") version = None + changelog_text = None with open(changelog) as clog: - while version is None: - line = clog.readline() - versions = re.findall("^#\s+([^:\n]+)", line) + changelog_text = clog.read() + for line in changelog_text.splitlines(): + versions = re.findall(r"^#\s+([\d\.]+)", line) version = versions[0] if len(versions) else None + if version is not None: + break + header = f"# {version} (api={factor_version(version, 2)}, abi={factor_version(version, 3)})" + print(header) + changelog_text = re.sub(r"^#\s+([\d\.]+)\s*(\(api[^\)]+\))?", header, changelog_text) + with open(changelog, "w") as clog: + clog.write(changelog_text) + print(f"Updating Cargo.tomls with version={version}") - for crate in crates: - vupdate = f"""sed -i -E 's/^version\s*=.*/version = \"{version}\"/g' {ws_root}/{crate}/Cargo.toml""" - os.system(vupdate) - print(vupdate) - dupdate = f"""sed -i -E 's/(stabby-.*version\s*=\s*)\"[^\"]*\"(.*)/\\1\"{version}\"\\2/g' {ws_root}/{crate}/Cargo.toml""" - print(dupdate) - os.system(dupdate) + ws_toml = None + with open(f"{ws_root}/Cargo.toml") as toml: + ws_toml = toml.read() + ws_toml = re.sub(r"version\s*=\s*\"[^\"]+(?P\".*Track)", f"version = \"{version}\\g", ws_toml) + with open(f"{ws_root}/Cargo.toml", "w") as toml: + toml.write(ws_toml) \ No newline at end of file diff --git a/stabby-abi/Cargo.toml b/stabby-abi/Cargo.toml index 3890b05..e07f300 100644 --- a/stabby-abi/Cargo.toml +++ b/stabby-abi/Cargo.toml @@ -14,7 +14,7 @@ [package] name = "stabby-abi" -version = "5.1.0" +version = { workspace = true } edition = "2021" authors = { workspace = true } license = { workspace = true } @@ -36,10 +36,11 @@ 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" } +stabby-macros.workspace = true + abi_stable = { workspace = true, optional = true } libc = { workspace = true, optional = true } rustversion = { workspace = true } diff --git a/stabby-abi/build.rs b/stabby-abi/build.rs index 07ef29d..9bdc000 100644 --- a/stabby-abi/build.rs +++ b/stabby-abi/build.rs @@ -12,6 +12,13 @@ // Pierre Avital, // +use std::{ + fmt::Write as FmtWrite, + fs::File, + io::{BufWriter, Write}, + path::PathBuf, +}; + fn u(mut i: u128) -> String { let mut result = "UTerm".into(); let mut ids = Vec::new(); @@ -26,35 +33,89 @@ fn u(mut i: u128) -> String { result } -fn main() { - use std::{ - fs::File, - io::{BufWriter, Write}, - path::PathBuf, - }; +fn typenum_unsigned() -> std::io::Result<()> { const SEQ_MAX: u128 = 1000; - let padding_rs = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("unsigned.rs"); - let mut padding_file = BufWriter::new(File::create(padding_rs).unwrap()); + let filename = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("unsigned.rs"); + let mut file = BufWriter::new(File::create(filename).unwrap()); for i in 0..=SEQ_MAX { let u = u(i); - writeln!(padding_file, "/// {i}\npub type U{i} = {u};").unwrap(); - writeln!(padding_file, "/// {i}\npub type Ux{i:X} = {u};").unwrap(); - writeln!(padding_file, "/// {i}\npub type Ub{i:b} = {u};").unwrap(); + writeln!(file, "/// {i}\npub type U{i} = {u};")?; + writeln!(file, "/// {i}\npub type Ux{i:X} = {u};")?; + writeln!(file, "/// {i}\npub type Ub{i:b} = {u};")?; } for i in 0..39 { let ipow = 10u128.pow(i); let u = u(ipow); - writeln!(padding_file, "/// {i}\npub type U10pow{i} = {u};").unwrap(); + writeln!(file, "/// {i}\npub type U10pow{i} = {u};")?; if ipow > SEQ_MAX { - writeln!(padding_file, "/// {i}\npub type U{ipow} = {u};").unwrap(); - writeln!(padding_file, "/// {i}\npub type Ux{ipow:X} = {u};").unwrap(); - writeln!(padding_file, "/// {i}\npub type Ub{ipow:b} = {u};").unwrap(); + writeln!(file, "/// {i}\npub type U{ipow} = {u};")?; + writeln!(file, "/// {i}\npub type Ux{ipow:X} = {u};")?; + writeln!(file, "/// {i}\npub type Ub{ipow:b} = {u};")?; } } for i in 0..128 { let u = u(1 << i); - writeln!(padding_file, "/// {i}\npub type U2pow{i} = {u};").unwrap(); + writeln!(file, "/// {i}\npub type U2pow{i} = {u};")?; + } + Ok(()) +} + +fn tuples(max_tuple: usize) -> std::io::Result<()> { + let filename = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("tuples.rs"); + let mut file = BufWriter::new(File::create(filename).unwrap()); + for i in 0..=max_tuple { + writeln!( + file, + 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(); + acc + }), + fields = (0..i).fold(String::new(), |mut acc, it| { + write!(acc, "pub T{it}, ").unwrap(); + acc + }), + named_fields = (0..i).fold(String::new(), |mut acc, it| { + write!(acc, "field{it}, ").unwrap(); + acc + }), + )?; } + Ok(()) +} + +fn main() { + typenum_unsigned().unwrap(); + println!("cargo:rustc-check-cfg=cfg(stabby_max_tuple, values(any()))"); + let max_tuple = std::env::var("CARGO_CFG_STABBY_MAX_TUPLE") + .map_or(32, |s| s.parse().unwrap_or(32)) + .max(10); + tuples(max_tuple).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/alloc/collections/arc_btree.rs b/stabby-abi/src/alloc/collections/arc_btree.rs index 6c9a9a6..7278352 100644 --- a/stabby-abi/src/alloc/collections/arc_btree.rs +++ b/stabby-abi/src/alloc/collections/arc_btree.rs @@ -175,6 +175,7 @@ where type UnusedBits = <*const T as IStable>::UnusedBits; type HasExactlyOneNiche = crate::B0; type ContainsIndirections = crate::B1; + type CType = <*const T as IStable>::CType; const REPORT: &'static crate::report::TypeReport = &crate::report::TypeReport { name: crate::str::Str::new("ArcBTreeSet"), module: crate::str::Str::new("stabby_abi::alloc::collections::arc_btree"), @@ -248,7 +249,7 @@ impl const fn as_ptr( &self, ) -> *mut ArcBTreeSetNodeInner { - unsafe { core::mem::transmute_copy(self) } + unsafe { core::mem::transmute(core::ptr::read(self)) } } fn copy_from_ptr( ptr: *const ArcBTreeSetNodeInner, diff --git a/stabby-abi/src/alloc/sync.rs b/stabby-abi/src/alloc/sync.rs index fe9328e..7140a15 100644 --- a/stabby-abi/src/alloc/sync.rs +++ b/stabby-abi/src/alloc/sync.rs @@ -230,10 +230,16 @@ impl Arc { pub fn downgrade(this: &Self) -> Weak { this.into() } + #[rustversion::since(1.73)] /// Returns a reference to the allocator used to construct `this` pub const fn allocator(this: &Self) -> &Alloc { unsafe { &this.ptr.prefix().alloc } } + #[rustversion::before(1.73)] + /// Returns a reference to the allocator used to construct `this` + pub fn allocator(this: &Self) -> &Alloc { + unsafe { &this.ptr.prefix().alloc } + } } impl Drop for Arc { fn drop(&mut self) { diff --git a/stabby-abi/src/enums/mod.rs b/stabby-abi/src/enums/mod.rs index 45b8a32..677768f 100644 --- a/stabby-abi/src/enums/mod.rs +++ b/stabby-abi/src/enums/mod.rs @@ -55,6 +55,7 @@ unsafe impl IStable for BitDeterminant { type UnusedBits = Array; type HasExactlyOneNiche = Saturator; type ContainsIndirections = B0; + type CType = u8; primitive_report!("BitDeterminant"); } @@ -95,6 +96,7 @@ unsafe impl IStable for ValueIsErr core::fmt::Debug @@ -186,6 +188,7 @@ unsafe impl IStable for Not { type UnusedBits = Determinant::UnusedBits; type HasExactlyOneNiche = Determinant::HasExactlyOneNiche; type ContainsIndirections = Determinant::ContainsIndirections; + type CType = Determinant::CType; primitive_report!("Not", Determinant); } impl IDeterminant for Not diff --git a/stabby-abi/src/future.rs b/stabby-abi/src/future.rs index 4c2109a..207e32c 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 `RUSTFLAGS='--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 b65ac9b..ab1c59f 100644 --- a/stabby-abi/src/istable.rs +++ b/stabby-abi/src/istable.rs @@ -20,18 +20,7 @@ use super::typenum2::*; use super::unsigned::{IBitBase, NonZero}; use super::{FieldPair, Struct, Union}; use stabby_macros::tyeval; -macro_rules! same_as { - ($t: ty) => { - type Align = <$t as IStable>::Align; - type Size = <$t as IStable>::Size; - type UnusedBits = <$t as IStable>::UnusedBits; - type ForbiddenValues = <$t as IStable>::ForbiddenValues; - type HasExactlyOneNiche = <$t as IStable>::HasExactlyOneNiche; - type ContainsIndirections = <$t as IStable>::ContainsIndirections; - const REPORT: &'static TypeReport = <$t as IStable>::REPORT; - const ID: u64 = <$t as IStable>::ID; - }; -} + /// A trait to describe the layout of a type, marking it as ABI-stable. /// /// Every layout is assumed to start at the type's first byte. @@ -55,6 +44,8 @@ pub unsafe trait IStable: Sized { type HasExactlyOneNiche: ISaturatingAdd; /// Whether or not the type contains indirections (pointers, indices in independent data-structures...) type ContainsIndirections: Bit; + /// A support mechanism for [`safer-ffi`](https://crates.io/crates/safer-ffi), allowing all [`IStable`] types to also be `safer_ffi::ReprC` + type CType: IStable; /// A compile-time generated report of the fields of the type, allowing for compatibility inspection. const REPORT: &'static TypeReport; /// A stable (and ideally unique) identifier for the type. Often generated using [`crate::report::gen_id`], but can be manually set. @@ -69,6 +60,17 @@ pub unsafe trait IStable: Sized { fn align() -> usize { Self::Align::USIZE } + /// Returns `true` if `ptr` points to memory that cannot be a valid value of `Self`. + /// + /// Note that this function returning `false` is not a guarantee that the value is valid, + /// as no heuristic can guarantee that. Notably, this heuristic will generally not look + /// through indirections. + /// + /// # Safety + /// Calling this may result in UB if `ptr` points to uninitialized memory at offsets where a forbidden value in `Self` exists. + unsafe fn is_invalid(ptr: *const u8) -> bool { + Self::ForbiddenValues::is_invalid(ptr) + } } /// A static proof that a type is "Plain Old Data". @@ -78,7 +80,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 @@ -121,6 +123,7 @@ unsafe impl IStable for NotPod { type ForbiddenValues = T::ForbiddenValues; type HasExactlyOneNiche = T::HasExactlyOneNiche; type UnusedBits = T::UnusedBits; + type CType = T::CType; primitive_report!("NotPod", T); } @@ -185,6 +188,7 @@ unsafe impl< type UnusedBits = UnusedBits; type HasExactlyOneNiche = HasExactlyOneNiche; type ContainsIndirections = B0; + type CType = (); primitive_report!("NicheExporter"); } @@ -270,6 +274,11 @@ pub trait IForbiddenValues { type SelectFrom: ISingleForbiddenValue; /// Extract the first available forbidden value. type SelectOne: ISingleForbiddenValue; + /// Returns `true` if `ptr` points to a forbidden value. + /// + /// # Safety + /// Calling this on uninitialized memory is UB. + unsafe fn is_invalid(ptr: *const u8) -> bool; } /// A single multi-byte forbidden value. pub trait ISingleForbiddenValue { @@ -287,6 +296,9 @@ impl IForbiddenValues for End { type Or = T; type SelectFrom = End; type SelectOne = End; + unsafe fn is_invalid(_: *const u8) -> bool { + false + } } impl ISingleForbiddenValue for Saturator { type Push = Saturator; @@ -308,7 +320,9 @@ impl ISingleForbiddenValue type And = V; type Resolve = Self; } -impl IForbiddenValues for Array { +impl IForbiddenValues + for Array +{ type Shift = Array, T, Rest::Shift>; type Or = Or; type SelectFrom = @@ -316,6 +330,9 @@ impl IForbiddenValues for Array as ISingleForbiddenValue>::Push, >; type SelectOne = Array; + unsafe fn is_invalid(ptr: *const u8) -> bool { + ptr.add(Offset::USIZE).read() == T::U8 && Rest::is_invalid(ptr) + } } impl IForbiddenValues for Or { type Shift = Or, B::Shift>; @@ -323,6 +340,26 @@ impl IForbiddenValues for Or { type SelectFrom = as ISingleForbiddenValue>::Or>; type SelectOne = A::SelectOne; + unsafe fn is_invalid(ptr: *const u8) -> bool { + A::is_invalid(ptr) || B::is_invalid(ptr) + } +} +/// An inclusive range of forbidden values for a single byte. +pub struct ForbiddenRange = B1>, Offset: Unsigned>( + core::marker::PhantomData<(Min, Max, Offset)>, +); +impl = B1>, Offset: Unsigned> IForbiddenValues + for ForbiddenRange +{ + type Shift = ForbiddenRange>; + type Or = Or; + type SelectFrom = + as IBitBase>::_SfvTernary; + type SelectOne = Array; + unsafe fn is_invalid(ptr: *const u8) -> bool { + let v = ptr.add(Offset::USIZE).read(); + Min::U8 <= v && v <= Max::U8 + } } /// The union of 2 sets. pub struct Or(core::marker::PhantomData<(A, B)>); @@ -349,6 +386,7 @@ unsafe impl IStable for FieldPair { as IStable>::HasExactlyOneNiche, >; type ContainsIndirections = ::Or; + type CType = (); primitive_report!("FP"); } /// Runtime values for [`ISaturatingAdd`] @@ -472,27 +510,18 @@ impl IncludesComputer<(O1, T1, type Output = End; } -unsafe impl IStable for Union -where - (Self, tyeval!(A::Align == B::Align)): IStable, -{ - same_as!((Self, tyeval!(A::Align == B::Align))); -} -unsafe impl IStable for (Union, B1) { +unsafe impl IStable for Union { type ForbiddenValues = End; type UnusedBits = End; type Size = ::Max; type Align = ::Max; type HasExactlyOneNiche = B0; type ContainsIndirections = ::Or; + type CType = <::Divide as IUnsignedBase>::Array< + ::AsUint, + >; primitive_report!("Union"); } -unsafe impl IStable for (Union, B0) -where - Struct<(Union, B1)>: IStable, -{ - same_as!(Struct<(Union, B1)>); -} /// Computes a `T`-typed field's layout when it's after `Start` bytes, taking `T`'s alignment into account. pub struct AlignedAfter(core::marker::PhantomData<(T, Start)>); @@ -508,6 +537,7 @@ unsafe impl IStable for AlignedAfter { >; type HasExactlyOneNiche = T::HasExactlyOneNiche; type ContainsIndirections = T::ContainsIndirections; + type CType = (); primitive_report!("FP"); } @@ -519,5 +549,6 @@ unsafe impl IStable for Struct { <::NextMultipleOf - T::Size) as IUnsignedBase>::PaddingBitMask as IBitMask>::Shift>; type HasExactlyOneNiche = Saturator; type ContainsIndirections = T::ContainsIndirections; + type CType = (); primitive_report!("FP"); } diff --git a/stabby-abi/src/lib.rs b/stabby-abi/src/lib.rs index 198d174..43255dc 100644 --- a/stabby-abi/src/lib.rs +++ b/stabby-abi/src/lib.rs @@ -40,9 +40,7 @@ use core::fmt::{Debug, Display}; pub const fn assert_stable() {} /// An ABI-stable tuple. -#[crate::stabby] -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] -pub struct Tuple(pub A, pub B); +pub use tuple::Tuple2 as Tuple; /// Generate the [`IStable::REPORT`] and [`IStable::ID`] fields for an implementation of [`IStable`]. #[macro_export] @@ -263,6 +261,7 @@ unsafe impl IStable for StableLike { type UnusedBits = As::UnusedBits; type HasExactlyOneNiche = As::HasExactlyOneNiche; type ContainsIndirections = As::ContainsIndirections; + type CType = As::CType; const ID: u64 = crate::report::gen_id(Self::REPORT); const REPORT: &'static report::TypeReport = As::REPORT; } @@ -295,6 +294,7 @@ unsafe impl< type UnusedBits = End; type HasExactlyOneNiche = HasExactlyOneNiche; type ContainsIndirections = ContainsIndirections; + type CType = (); primitive_report!("NoNiches"); } @@ -346,6 +346,7 @@ unsafe impl IStable for StableIf { type UnusedBits = T::UnusedBits; type HasExactlyOneNiche = T::HasExactlyOneNiche; type ContainsIndirections = T::ContainsIndirections; + type CType = T::CType; const REPORT: &'static report::TypeReport = T::REPORT; const ID: u64 = crate::report::gen_id(Self::REPORT); } @@ -390,6 +391,10 @@ pub mod report; pub mod slice; /// ABI-stable strs. pub mod str; +/// ABI-stable tuples. +pub mod tuple { + include!(concat!(env!("OUT_DIR"), "/tuples.rs")); +} pub use istable::{Array, End, IStable}; @@ -409,7 +414,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. /// @@ -422,7 +427,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() @@ -430,7 +435,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. /// @@ -443,7 +448,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 { @@ -453,7 +458,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. /// @@ -466,7 +471,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/num.rs b/stabby-abi/src/num.rs index de02603..bf365cf 100644 --- a/stabby-abi/src/num.rs +++ b/stabby-abi/src/num.rs @@ -63,6 +63,7 @@ macro_rules! define_non_max { type UnusedBits = <$NonZeroU8 as crate::IStable>::UnusedBits; type HasExactlyOneNiche = <$NonZeroU8 as crate::IStable>::HasExactlyOneNiche; type ContainsIndirections = <$NonZeroU8 as crate::IStable>::ContainsIndirections; + type CType = <$NonZeroU8 as crate::IStable>::CType; const ID: u64 = $crate::report::gen_id(Self::REPORT); const REPORT: &'static $crate::report::TypeReport = &$crate::report::TypeReport { name: $crate::str::Str::new(stringify!($NonMaxU8)), @@ -139,6 +140,7 @@ macro_rules! define_non_x { type UnusedBits = <$NonZeroU8 as crate::IStable>::UnusedBits; type HasExactlyOneNiche = <$NonZeroU8 as crate::IStable>::HasExactlyOneNiche; type ContainsIndirections = <$NonZeroU8 as crate::IStable>::ContainsIndirections; + type CType = <$NonZeroU8 as crate::IStable>::CType; const ID: u64 = $crate::report::gen_id(Self::REPORT); const REPORT: &'static $crate::report::TypeReport = &$crate::report::TypeReport { name: $crate::str::Str::new(stringify!($NonMaxU8)), diff --git a/stabby-abi/src/padding.rs b/stabby-abi/src/padding.rs index f931076..fc5df07 100644 --- a/stabby-abi/src/padding.rs +++ b/stabby-abi/src/padding.rs @@ -35,6 +35,7 @@ unsafe impl IStable for Padded { >; type HasExactlyOneNiche = Saturator; type ContainsIndirections = T::ContainsIndirections; + type CType = Tuple<::CType, T::CType>; const REPORT: &'static report::TypeReport = T::REPORT; const ID: u64 = crate::report::gen_id(Self::REPORT); } diff --git a/stabby-abi/src/result.rs b/stabby-abi/src/result.rs index 932eac3..3b6340f 100644 --- a/stabby-abi/src/result.rs +++ b/stabby-abi/src/result.rs @@ -53,6 +53,7 @@ where <>::NicheExporter as IStable>::ForbiddenValues; type UnusedBits = <, ::AsUint> as IStable>::UnusedBits as IBitMask>::BitOr<<<>::NicheExporter as IStable>::UnusedBits as IBitMask>::Shift<< as IStable>::Size as Unsigned>::NextMultipleOf>>; type HasExactlyOneNiche = B0; + type CType = ::Size, ::Align> as IStable>::CType; const REPORT: &'static crate::report::TypeReport = &crate::report::TypeReport { name: Str::new("Result"), module: Str::new("stabby_abi::result"), @@ -70,10 +71,13 @@ where }; const ID: u64 = crate::report::gen_id(Self::REPORT); } - -#[stabby::stabby] -struct Storage { - inner: as IUnsignedBase>::Array, +use seal::Storage; +mod seal { + use super::*; + #[stabby::stabby] + pub struct Storage { + inner: as IUnsignedBase>::Array, + } } impl Storage { diff --git a/stabby-abi/src/stable_impls/abi_stable.rs b/stabby-abi/src/stable_impls/abi_stable.rs index 711ca3d..42e2397 100644 --- a/stabby-abi/src/stable_impls/abi_stable.rs +++ b/stabby-abi/src/stable_impls/abi_stable.rs @@ -23,6 +23,7 @@ unsafe impl IStable for abi_stable::std_types::RVec { type UnusedBits = End; type HasExactlyOneNiche = B1; type ContainsIndirections = B1; + type CType = [*const T; 4]; primitive_report!("abi_stable::std_types::RVec", T); } check!(abi_stable::std_types::RVec); @@ -41,6 +42,7 @@ unsafe impl<'a, T: IStable> IStable for abi_stable::std_types::RSlice<'a, T> { type UnusedBits = End; type HasExactlyOneNiche = B0; type ContainsIndirections = B1; + type CType = [*const T; 2]; primitive_report!("abi_stable::std_types::RSlice", T); } check!(abi_stable::std_types::RSlice); @@ -59,6 +61,7 @@ unsafe impl<'a, T: IStable> IStable for abi_stable::std_types::RSliceMut<'a, T> type UnusedBits = End; type HasExactlyOneNiche = B0; type ContainsIndirections = B1; + type CType = [*const T; 2]; primitive_report!("abi_stable::std_types::RSliceMut", T); } check!(abi_stable::std_types::RSliceMut); @@ -73,6 +76,7 @@ where type UnusedBits = End; type HasExactlyOneNiche = B1; type ContainsIndirections = B1; + type CType = [*const (); 3]; primitive_report!("abi_stable::std_types::RHashMap", Tuple); } check!(abi_stable::std_types::RHashMap); @@ -91,6 +95,7 @@ unsafe impl IStable for abi_stable::std_types::RBox { type UnusedBits = End; type HasExactlyOneNiche = B1; type ContainsIndirections = B1; + type CType = [*const T; 2]; primitive_report!("abi_stable::std_types::RBox", T); } check!(abi_stable::std_types::RBox); @@ -102,6 +107,7 @@ unsafe impl IStable for abi_stable::std_types::RBoxError { type UnusedBits = End; type HasExactlyOneNiche = B1; type ContainsIndirections = B1; + type CType = [*const (); 3]; primitive_report!("abi_stable::std_types::RBoxError"); } check!(abi_stable::std_types::RBoxError); diff --git a/stabby-abi/src/stable_impls/mod.rs b/stabby-abi/src/stable_impls/mod.rs index 4e694b4..e8f15e4 100644 --- a/stabby-abi/src/stable_impls/mod.rs +++ b/stabby-abi/src/stable_impls/mod.rs @@ -12,7 +12,7 @@ // Pierre Avital, // -use crate::{istable::Or, str::Str, *}; +use crate::{str::Str, *}; macro_rules! same_as { ($t: ty, $($name: tt)*) => { @@ -21,6 +21,7 @@ macro_rules! same_as { type UnusedBits = <$t as IStable>::UnusedBits; type ForbiddenValues = <$t as IStable>::ForbiddenValues; type HasExactlyOneNiche = <$t as IStable>::HasExactlyOneNiche; + type CType = <$t as IStable>::CType; primitive_report!($($name)*); }; ($t: ty) => { @@ -29,6 +30,7 @@ macro_rules! same_as { type UnusedBits = <$t as IStable>::UnusedBits; type ForbiddenValues = <$t as IStable>::ForbiddenValues; type HasExactlyOneNiche = <$t as IStable>::HasExactlyOneNiche; + type CType = <$t as IStable>::CType; }; } @@ -172,6 +174,7 @@ unsafe impl IStable for () { type UnusedBits = End; type HasExactlyOneNiche = B0; type ContainsIndirections = B0; + type CType = (); primitive_report!("()"); } unsafe impl IStable for core::marker::PhantomData { @@ -181,6 +184,7 @@ unsafe impl IStable for core::marker::PhantomData { type UnusedBits = End; type HasExactlyOneNiche = B0; type ContainsIndirections = B0; + type CType = (); primitive_report!("core::marker::PhantomData"); } unsafe impl IStable for core::marker::PhantomPinned { @@ -190,119 +194,17 @@ unsafe impl IStable for core::marker::PhantomPinned { type UnusedBits = End; type HasExactlyOneNiche = B0; type ContainsIndirections = B0; + type CType = (); primitive_report!("core::marker::PhantomPinned"); } -macro_rules! illegal_values { - ([$($l: tt)*], [$($r: tt)*]) => { - Or - }; - ($t: ty, $($tt: tt)*) => { - Or, illegal_values!($($tt)*)> - }; - ($t: ty) => { - Array - }; -} unsafe impl IStable for bool { type Align = U1; type Size = U1; - type ForbiddenValues = illegal_values!( - [ - [ - [ - [ - [[[U2], [U3, U4]], [[U5, U6], [U7, U8]]], - [[[U9, U10], [U11, U12]], [[U13, U14], [U15, U16]]] - ], - [ - [[[U17, U18], [U19, U20]], [[U21, U22], [U23, U24]]], - [[[U25, U26], [U27, U28]], [[U29, U30], [U31, U32]]] - ] - ], - [ - [ - [[[U33, U34], [U35, U36]], [[U37, U38], [U39, U40]]], - [[[U41, U42], [U43, U44]], [[U45, U46], [U47, U48]]] - ], - [ - [[[U49, U50], [U51, U52]], [[U53, U54], [U55, U56]]], - [[[U57, U58], [U59, U60]], [[U61, U62], [U63, U64]]] - ] - ] - ], - [ - [ - [ - [[[U65, U66], [U67, U68]], [[U69, U70], [U71, U72]]], - [[[U73, U74], [U75, U76]], [[U77, U78], [U79, U80]]] - ], - [ - [[[U81, U82], [U83, U84]], [[U85, U86], [U87, U88]]], - [[[U89, U90], [U91, U92]], [[U93, U94], [U95, U96]]] - ] - ], - [ - [ - [[[U97, U98], [U99, U100]], [[U101, U102], [U103, U104]]], - [[[U105, U106], [U107, U108]], [[U109, U110], [U111, U112]]] - ], - [ - [[[U113, U114], [U115, U116]], [[U117, U118], [U119, U120]]], - [[[U121, U122], [U123, U124]], [[U125, U126], [U127, U128]]] - ] - ] - ] - ], - [ - [ - [ - [ - [[[U129], [U130, U131]], [[U132, U133], [U134, U135]]], - [[[U136, U137], [U138, U139]], [[U140, U141], [U142, U143]]] - ], - [ - [[[U144, U145], [U146, U147]], [[U148, U149], [U150, U151]]], - [[[U152, U153], [U154, U155]], [[U156, U157], [U158, U159]]] - ] - ], - [ - [ - [[[U160, U161], [U162, U163]], [[U164, U165], [U166, U167]]], - [[[U168, U169], [U170, U171]], [[U172, U173], [U174, U175]]] - ], - [ - [[[U176, U177], [U178, U179]], [[U180, U181], [U182, U183]]], - [[[U184, U185], [U186, U187]], [[U188, U189], [U190, U191]]] - ] - ] - ], - [ - [ - [ - [[[U192, U193], [U194, U195]], [[U196, U197], [U198, U199]]], - [[[U200, U201], [U202, U203]], [[U204, U205], [U206, U207]]] - ], - [ - [[[U208, U209], [U210, U211]], [[U212, U213], [U214, U215]]], - [[[U216, U217], [U218, U219]], [[U220, U221], [U222, U223]]] - ] - ], - [ - [ - [[[U224, U225], [U226, U227]], [[U228, U229], [U230, U231]]], - [[[U232, U233], [U234, U235]], [[U236, U237], [U238, U239]]] - ], - [ - [[[U240, U241], [U242, U243]], [[U244, U245], [U246, U247]]], - [[[U248, U249], [U250, U251]], [[U252, U253], [U254, U255]]] - ] - ] - ] - ] - ); + type ForbiddenValues = crate::istable::ForbiddenRange; type UnusedBits = End; type HasExactlyOneNiche = B0; type ContainsIndirections = B0; + type CType = ::AsUint; primitive_report!("bool"); } @@ -313,6 +215,7 @@ unsafe impl IStable for u8 { type Size = U1; type HasExactlyOneNiche = B0; type ContainsIndirections = B0; + type CType = ::AsUint; primitive_report!("u8"); } check!(u8); @@ -323,6 +226,7 @@ unsafe impl IStable for core::num::NonZeroU8 { type ForbiddenValues = nz_holes!(U0); type HasExactlyOneNiche = B1; type ContainsIndirections = B0; + type CType = ::AsUint; primitive_report!("core::num::NonZeroU8"); } unsafe impl IStable for u16 { @@ -332,6 +236,7 @@ unsafe impl IStable for u16 { type Size = U2; type HasExactlyOneNiche = B0; type ContainsIndirections = B0; + type CType = ::AsUint; primitive_report!("u16"); } check!(u16); @@ -342,6 +247,7 @@ unsafe impl IStable for core::num::NonZeroU16 { type Size = U2; type HasExactlyOneNiche = B1; type ContainsIndirections = B0; + type CType = ::AsUint; primitive_report!("core::num::NonZeroU16"); } unsafe impl IStable for u32 { @@ -351,6 +257,7 @@ unsafe impl IStable for u32 { type Size = U4; type HasExactlyOneNiche = B0; type ContainsIndirections = B0; + type CType = ::AsUint; primitive_report!("u32"); } check!(u32); @@ -361,6 +268,7 @@ unsafe impl IStable for core::num::NonZeroU32 { type Size = U4; type HasExactlyOneNiche = B1; type ContainsIndirections = B0; + type CType = ::AsUint; primitive_report!("core::num::NonZeroU32"); } unsafe impl IStable for u64 { @@ -370,6 +278,7 @@ unsafe impl IStable for u64 { type Size = U8; type HasExactlyOneNiche = B0; type ContainsIndirections = B0; + type CType = ::AsUint; primitive_report!("u64"); } check!(u64); @@ -380,6 +289,7 @@ unsafe impl IStable for core::num::NonZeroU64 { type Size = U8; type HasExactlyOneNiche = B1; type ContainsIndirections = B0; + type CType = ::AsUint; primitive_report!("core::num::NonZeroU64"); } @@ -397,6 +307,7 @@ unsafe impl IStable for u128 { #[cfg(target_arch = "aarch64")] type Align = U16; type ContainsIndirections = B0; + type CType = ::AsUint; #[rustversion::before(1.77)] #[cfg(not(target_arch = "aarch64"))] primitive_report!("u128(8)"); @@ -416,6 +327,7 @@ unsafe impl IStable for core::num::NonZeroU128 { type Size = U16; type HasExactlyOneNiche = B1; type Align = ::Align; + type CType = ::AsUint; type ContainsIndirections = B0; primitive_report!("core::num::NonZeroU128"); } @@ -427,8 +339,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; } @@ -440,8 +350,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; } @@ -606,6 +514,7 @@ unsafe impl IStable for HasExactlyOneNiche, type UnusedBits = End; type HasExactlyOneNiche = B0; type ContainsIndirections = T::ContainsIndirections; + type CType = T::CType; const REPORT: &'static report::TypeReport = &report::TypeReport { name: Str::new("Option"), module: Str::new("core::option"), @@ -619,6 +528,14 @@ unsafe impl IStable for HasExactlyOneNiche, }; const ID: u64 = crate::report::gen_id(Self::REPORT); } + +unsafe impl IStable for core::result::Result { + same_as!(T); + type ContainsIndirections = T::ContainsIndirections; + const REPORT: &'static report::TypeReport = T::REPORT; + const ID: u64 = T::ID; +} + unsafe impl IStable for core::result::Result where HasExactlyOneNiche: IStable, @@ -637,6 +554,7 @@ unsafe impl IStable type ForbiddenValues = End; type UnusedBits = End; type HasExactlyOneNiche = B0; + type CType = Ok::CType; type ContainsIndirections = ::Or; const REPORT: &'static report::TypeReport = &report::TypeReport { name: Str::new("Result"), @@ -670,6 +588,7 @@ unsafe impl IStable type ForbiddenValues = End; type UnusedBits = End; type HasExactlyOneNiche = B0; + type CType = Err::CType; type ContainsIndirections = ::Or; const REPORT: &'static report::TypeReport = &report::TypeReport { name: Str::new("Result"), @@ -693,6 +612,7 @@ unsafe impl IStable for NameAggregator { type UnusedBits = End; type HasExactlyOneNiche = B0; type ContainsIndirections = B0; + type CType = (); const REPORT: &'static report::TypeReport = &report::TypeReport { name: Str::new("signature"), module: Str::new("stabby"), @@ -811,6 +731,7 @@ macro_rules! sliceimpl { <<$size as Unsigned>::Equal as Bit>::SaddTernary, >; type ContainsIndirections = T::ContainsIndirections; + type CType = T::CType; primitive_report!(ARRAY_NAME[<$size as Unsigned>::USIZE], T); } }; @@ -836,102 +757,10 @@ sliceimpl!( unsafe impl IStable for core::cmp::Ordering { type Align = U1; type Size = U1; - type ForbiddenValues = illegal_values!( - [ - [ - [ - [ - [[[U2], [U3, U4]], [[U5, U6], [U7, U8]]], - [[[U9, U10], [U11, U12]], [[U13, U14], [U15, U16]]] - ], - [ - [[[U17, U18], [U19, U20]], [[U21, U22], [U23, U24]]], - [[[U25, U26], [U27, U28]], [[U29, U30], [U31, U32]]] - ] - ], - [ - [ - [[[U33, U34], [U35, U36]], [[U37, U38], [U39, U40]]], - [[[U41, U42], [U43, U44]], [[U45, U46], [U47, U48]]] - ], - [ - [[[U49, U50], [U51, U52]], [[U53, U54], [U55, U56]]], - [[[U57, U58], [U59, U60]], [[U61, U62], [U63, U64]]] - ] - ] - ], - [ - [ - [ - [[[U65, U66], [U67, U68]], [[U69, U70], [U71, U72]]], - [[[U73, U74], [U75, U76]], [[U77, U78], [U79, U80]]] - ], - [ - [[[U81, U82], [U83, U84]], [[U85, U86], [U87, U88]]], - [[[U89, U90], [U91, U92]], [[U93, U94], [U95, U96]]] - ] - ], - [ - [ - [[[U97, U98], [U99, U100]], [[U101, U102], [U103, U104]]], - [[[U105, U106], [U107, U108]], [[U109, U110], [U111, U112]]] - ], - [ - [[[U113, U114], [U115, U116]], [[U117, U118], [U119, U120]]], - [[[U121, U122], [U123, U124]], [[U125, U126], [U127, U128]]] - ] - ] - ] - ], - [ - [ - [ - [ - [[[U129], [U130, U131]], [[U132, U133], [U134, U135]]], - [[[U136, U137], [U138, U139]], [[U140, U141], [U142, U143]]] - ], - [ - [[[U144, U145], [U146, U147]], [[U148, U149], [U150, U151]]], - [[[U152, U153], [U154, U155]], [[U156, U157], [U158, U159]]] - ] - ], - [ - [ - [[[U160, U161], [U162, U163]], [[U164, U165], [U166, U167]]], - [[[U168, U169], [U170, U171]], [[U172, U173], [U174, U175]]] - ], - [ - [[[U176, U177], [U178, U179]], [[U180, U181], [U182, U183]]], - [[[U184, U185], [U186, U187]], [[U188, U189], [U190, U191]]] - ] - ] - ], - [ - [ - [ - [[[U192, U193], [U194, U195]], [[U196, U197], [U198, U199]]], - [[[U200, U201], [U202, U203]], [[U204, U205], [U206, U207]]] - ], - [ - [[[U208, U209], [U210, U211]], [[U212, U213], [U214, U215]]], - [[[U216, U217], [U218, U219]], [[U220, U221], [U222, U223]]] - ] - ], - [ - [ - [[[U224, U225], [U226, U227]], [[U228, U229], [U230, U231]]], - [[[U232, U233], [U234, U235]], [[U236, U237], [U238, U239]]] - ], - [ - [[[U240, U241], [U242, U243]], [[U244, U245], [U246, U247]]], - [[[U248, U249], [U250, U251]], [[U252, U253], [U254]]] - ] - ] - ] - ] - ); + type ForbiddenValues = crate::istable::ForbiddenRange; type UnusedBits = End; type HasExactlyOneNiche = B0; type ContainsIndirections = B0; + type CType = u8; primitive_report!("core::cmp::Ordering"); } diff --git a/stabby-abi/src/typenum2/unsigned.rs b/stabby-abi/src/typenum2/unsigned.rs index afcc6dd..32bcc05 100644 --- a/stabby-abi/src/typenum2/unsigned.rs +++ b/stabby-abi/src/typenum2/unsigned.rs @@ -44,6 +44,7 @@ unsafe impl IStable for PadByte { type UnusedBits = Array; type HasExactlyOneNiche = B0; type ContainsIndirections = B0; + type CType = u8; primitive_report!("PadByte"); } @@ -72,6 +73,8 @@ pub trait IBitBase { /// Support for [`IBit`] type _FvTernary: IForbiddenValues; /// Support for [`IBit`] + type _SfvTernary: ISingleForbiddenValue; + /// Support for [`IBit`] type _UbTernary: IBitMask; /// Support for [`IBit`] type _SaddTernary: ISaturatingAdd; @@ -97,6 +100,7 @@ impl IBitBase for B0 { type _BmTernary = B; type _PTernary = B; type _FvTernary = B; + type _SfvTernary = B; type _UbTernary = B; type _SaddTernary = B; type _StabTernary = B; @@ -118,6 +122,7 @@ impl IBitBase for B1 { type _BmTernary = A; type _PTernary = A; type _FvTernary = A; + type _SfvTernary = A; type _UbTernary = A; type _SaddTernary = A; type _StabTernary = A; @@ -428,6 +433,7 @@ unsafe impl IStable for OneMoreByte { type UnusedBits = ::BitOr>; type HasExactlyOneNiche = L::HasExactlyOneNiche; type ContainsIndirections = L::ContainsIndirections; + type CType = Tuple; primitive_report!("OneMoreByte"); } impl NonZero for UInt { diff --git a/stabby-macros/Cargo.toml b/stabby-macros/Cargo.toml index 55246ea..150c0e1 100644 --- a/stabby-macros/Cargo.toml +++ b/stabby-macros/Cargo.toml @@ -14,7 +14,7 @@ [package] name = "stabby-macros" -version = "5.1.0" +version = { workspace = true } edition = "2021" authors = { workspace = true } license = { workspace = true } diff --git a/stabby-macros/build.rs b/stabby-macros/build.rs index 71b41c6..3343f85 100644 --- a/stabby-macros/build.rs +++ b/stabby-macros/build.rs @@ -24,6 +24,7 @@ fn main() -> Result<(), std::io::Error> { io::{BufWriter, Write}, path::PathBuf, }; + // println!("cargo:rustc-check-cfg=cfg(stabby_max_tuple, values(any()))"); let rustc = std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()); let output = String::from_utf8( Command::new(rustc) diff --git a/stabby-macros/src/enums.rs b/stabby-macros/src/enums.rs index fa7a0f6..399404c 100644 --- a/stabby-macros/src/enums.rs +++ b/stabby-macros/src/enums.rs @@ -18,7 +18,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{Attribute, DataEnum, Generics, Ident, Visibility}; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum Repr { Stabby, C, @@ -53,6 +53,25 @@ impl syn::parse::Parse for Repr { } } } +impl core::fmt::Debug for Repr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Repr::Stabby => "stabby", + Repr::C => "C", + Repr::U8 => "u8", + Repr::U16 => "u16", + Repr::U32 => "u32", + Repr::U64 => "u64", + Repr::Usize => "usize", + Repr::I8 => "i8", + Repr::I16 => "i16", + Repr::I32 => "i32", + Repr::I64 => "i64", + Repr::Isize => "isize", + }) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct FullRepr { repr: Option, @@ -170,9 +189,10 @@ pub fn stabby( } } } - let reprstr = match repr + let mut deprecation = None; + let trepr = 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,40 +208,54 @@ 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::U8) => "u8", - Some(Repr::U16) => "u16", - Some(Repr::U32) => "u32", - Some(Repr::U64) => "u64", - Some(Repr::Usize) => "usize", - Some(Repr::I8) => "i8", - Some(Repr::I16) => "i16", - Some(Repr::I32) => "i32", - Some(Repr::I64) => "i64", - Some(Repr::Isize) => "isize", + 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])); + Repr::U8 + } // TODO: Remove support for `#[repr(C)]` alone on the next API-breaking release + Some(Repr::U8) => Repr::U8, + Some(Repr::U16) => Repr::U16, + Some(Repr::U32) => Repr::U32, + Some(Repr::U64) => Repr::U64, + Some(Repr::Usize) => Repr::Usize, + Some(Repr::I8) => Repr::I8, + Some(Repr::I16) => Repr::I16, + Some(Repr::I32) => Repr::I32, + Some(Repr::I64) => Repr::I64, + Some(Repr::Isize) => Repr::Isize, }; - let reprid = quote::format_ident!("{}", reprstr); + let reprid = quote::format_ident!("{trepr:?}"); let reprattr = if repr.map_or(false, |r| r.is_c) { quote!(#[repr(C, #reprid)]) } else { quote!(#[repr(#reprid)]) }; layout = quote!(#st::Tuple<#reprid, #layout>); - report.tyty = quote!(#st::report::TyTy::Enum(#st::str::Str::new(#reprstr))); + report.tyty = crate::Tyty::Enum(trepr); let report_bounds = report.bounds(); + let ctype = report.crepr(); let size_bug = format!( - "{ident}'s size was mis-evaluated by stabby, this is a definitely a bug and may cause UB, please file an issue" + "{ident}'s size was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" ); let align_bug = format!( - "{ident}'s align was mis-evaluated by stabby, this is a definitely a bug and may cause UB, please file an issue" + "{ident}'s align was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" ); + let reprc_bug = format!( + "{ident}'s CType was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" + ); + let assertion = generics + .params + .is_empty() + .then(|| quote!(const _: () = {<#ident as #st::IStable>::ID;};)); + quote! { #(#new_attrs)* #reprattr + #deprecation #vis enum #ident #generics { #variants } - + #assertion #[automatically_derived] unsafe impl #generics #st::IStable for #ident <#unbound_generics> where #report_bounds #layout: #st::IStable { type ForbiddenValues = <#layout as #st::IStable>::ForbiddenValues; @@ -230,8 +264,13 @@ pub fn stabby( type Align = <#layout as #st::IStable>::Align; type HasExactlyOneNiche = #st::B0; type ContainsIndirections = <#layout as #st::IStable>::ContainsIndirections; + type CType = #ctype; const REPORT: &'static #st::report::TypeReport = & #report; const ID: u64 ={ + if (<::Size as #st::Unsigned>::USIZE != <<::CType as #st::IStable>::Size as #st::Unsigned>::USIZE) + || (<::Align as #st::Unsigned>::USIZE != <<::CType as #st::IStable>::Align as #st::Unsigned>::USIZE) { + panic!(#reprc_bug) + } if core::mem::size_of::() != <::Size as #st::Unsigned>::USIZE { panic!(#size_bug) } @@ -335,13 +374,13 @@ impl Variants { } } -pub fn repr_stabby( +pub(crate) fn repr_stabby( attrs: &Vec, vis: &Visibility, ident: &Ident, generics: &Generics, data: DataEnum, - mut report: crate::Report, + report: crate::Report, check: bool, ) -> TokenStream { let st = crate::tl_mod(); @@ -453,7 +492,6 @@ pub fn repr_stabby( let bounds2 = generics.where_clause.as_ref().map(|c| &c.predicates); let bounds = quote!(#bounds #bounds2); - report.tyty = quote!(#st::report::TyTy::Enum(#st::str::Str::new("stabby"))); let report_bounds = report.bounds(); let enum_as_struct = quote! { #(#attrs)* @@ -487,10 +525,10 @@ pub fn repr_stabby( }) }); let size_bug = format!( - "{ident}'s size was mis-evaluated by stabby, this is a definitely a bug and may cause UB, please fill an issue" + "{ident}'s size was mis-evaluated by stabby, this is definitely a bug and may cause UB, please fill an issue" ); let align_bug = format!( - "{ident}'s align was mis-evaluated by stabby, this is a definitely a bug and may cause UB, please fill an issue" + "{ident}'s align was mis-evaluated by stabby, this is definitely a bug and may cause UB, please fill an issue" ); quote! { const _: () = { @@ -516,6 +554,7 @@ pub fn repr_stabby( type Align = <#layout as #st::IStable>::Align; type HasExactlyOneNiche = #st::B0; type ContainsIndirections = <#layout as #st::IStable>::ContainsIndirections; + type CType = <#layout as #st::IStable>::CType; const REPORT: &'static #st::report::TypeReport = & #report; const ID: u64 = #st::report::gen_id(Self::REPORT); } diff --git a/stabby-macros/src/lib.rs b/stabby-macros/src/lib.rs index c8d666b..560468a 100644 --- a/stabby-macros/src/lib.rs +++ b/stabby-macros/src/lib.rs @@ -253,6 +253,7 @@ pub fn gen_closures_impl(_: TokenStream) -> TokenStream { gen_closures::gen_closures().into() } +#[derive(Clone)] enum Type<'a> { Syn(&'a syn::Type), Report(Report<'a>), @@ -267,29 +268,49 @@ impl<'a> From> for Type<'a> { Self::Report(value) } } + +#[derive(Debug, Clone, Copy)] +pub(crate) enum Tyty { + Struct, + Union, + Enum(enums::Repr), +} +impl ToTokens for Tyty { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let st = crate::tl_mod(); + let tyty = match self { + Tyty::Struct => quote!(#st::report::TyTy::Struct), + Tyty::Union => quote!(#st::report::TyTy::Union), + Tyty::Enum(r) => { + let s = format!("{r:?}"); + quote!(#st::report::TyTy::Enum(#st::str::Str::new(#s))) + } + }; + tokens.extend(tyty); + } +} +#[derive(Clone)] pub(crate) struct Report<'a> { name: String, fields: Vec<(String, Type<'a>)>, version: u32, - pub tyty: proc_macro2::TokenStream, + pub tyty: Tyty, } impl<'a> Report<'a> { pub fn r#struct(name: impl Into, version: u32) -> Self { - let st = crate::tl_mod(); Self { name: name.into(), fields: Vec::new(), version, - tyty: quote!(#st::report::TyTy::Struct), + tyty: Tyty::Struct, } } pub fn r#enum(name: impl Into, version: u32) -> Self { - let st = crate::tl_mod(); Self { name: name.into(), fields: Vec::new(), version, - tyty: quote!(#st::report::TyTy::Struct), + tyty: Tyty::Enum(enums::Repr::Stabby), } } pub fn add_field(&mut self, name: String, ty: impl Into>) { @@ -320,6 +341,50 @@ impl<'a> Report<'a> { let mut bounded_types = HashSet::new(); self.__bounds(&mut bounded_types, quote!(), &st) } + + pub fn crepr(&self) -> proc_macro2::TokenStream { + let st = crate::tl_mod(); + match self.tyty { + Tyty::Struct => { + // TODO: For user comfort, having this would be better, but reading from env vars doesn't work in proc-macros. + // let max_tuple = std::env::var("CARGO_CFG_STABBY_MAX_TUPLE") + // .map_or(32, |s| s.parse().unwrap_or(32)) + // .max(10); + // panic!("{max_tuple}"); + // if self.fields.len() > max_tuple { + // panic!("stabby doesn't support structures with more than {max_tuple} direct fields, you should probably split it at that point; or you can also raise this limit using `--cfg stabby_max_tuple=N` to your RUSTFLAGS at the cost of higher compile times") + // } + let tuple = quote::format_ident!("Tuple{}", self.fields.len()); + let fields = self.fields.iter().map(|f| match &f.1 { + Type::Syn(ty) => quote! (<#ty as #st::IStable>::CType), + Type::Report(r) => r.crepr(), + }); + quote! { + #st::tuple::#tuple <#(#fields,)*> + } + } + Tyty::Union => { + let mut crepr = quote!(()); + for f in &self.fields { + let ty = match &f.1 { + Type::Syn(ty) => quote! (#ty), + Type::Report(r) => r.crepr(), + }; + crepr = quote!(#st::Union<#ty, #crepr>); + } + quote! {<#crepr as #st::IStable>::CType} + } + Tyty::Enum(r) => { + let mut clone = self.clone(); + clone.tyty = Tyty::Union; + let crepr = clone.crepr(); + let determinant = quote::format_ident!("{r:?}"); + quote! { + #st::tuple::Tuple2<#determinant, #crepr> + } + } + } + } } impl ToTokens for Report<'_> { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { diff --git a/stabby-macros/src/structs.rs b/stabby-macros/src/structs.rs index dcf00d6..6c6fb5e 100644 --- a/stabby-macros/src/structs.rs +++ b/stabby-macros/src/structs.rs @@ -85,11 +85,14 @@ pub fn stabby( let layout = layout.map_or_else(|| quote!(()), |layout| 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 a definitely a bug and may cause UB, please file an issue" + "{ident}'s size was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" ); let align_bug = format!( - "{ident}'s align was mis-evaluated by stabby, this is a definitely a bug and may cause UB, please file an issue" + "{ident}'s align was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" ); + // let reprc_bug = format!( + // "{ident}'s CType was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" + // ); let assertion = opt.then(|| { let sub_optimal_message = format!( "{ident}'s layout is sub-optimal, reorder fields or use `#[stabby::stabby(no_opt)]`" @@ -103,6 +106,7 @@ pub fn stabby( } }); let report_bounds = report.bounds(); + let ctype = report.crepr(); let optdoc = format!("Returns true if the layout for [`{ident}`] is smaller or equal to that Rust would have generated for it."); quote! { #struct_code @@ -115,8 +119,13 @@ pub fn stabby( type Align = <#layout as #st::IStable>::Align; type HasExactlyOneNiche = <#layout as #st::IStable>::HasExactlyOneNiche; type ContainsIndirections = <#layout as #st::IStable>::ContainsIndirections; + type CType = #ctype; const REPORT: &'static #st::report::TypeReport = &#report; const ID: u64 = { + // if (<::Size as #st::Unsigned>::USIZE != <<::CType as #st::IStable>::Size as #st::Unsigned>::USIZE) + // || (<::Align as #st::Unsigned>::USIZE != <<::CType as #st::IStable>::Align as #st::Unsigned>::USIZE) { + // panic!(#reprc_bug) + // } if core::mem::size_of::() != <::Size as #st::Unsigned>::USIZE { panic!(#size_bug) } diff --git a/stabby-macros/src/unions.rs b/stabby-macros/src/unions.rs index 9937d9c..90bc2aa 100644 --- a/stabby-macros/src/unions.rs +++ b/stabby-macros/src/unions.rs @@ -51,6 +51,7 @@ pub fn stabby( type Align = <#layout as #st::IStable>::Align; type HasExactlyOneNiche = #st::B0; type ContainsIndirections = <#layout as #st::IStable>::ContainsIndirections; + type CType = <#layout as #st::IStable>::CType; const REPORT: &'static #st::report::TypeReport = & #st::report::TypeReport { name: #st::str::Str::new(#sident), module: #st::str::Str::new(core::module_path!()), diff --git a/stabby/Cargo.toml b/stabby/Cargo.toml index ab92a08..d25469b 100644 --- a/stabby/Cargo.toml +++ b/stabby/Cargo.toml @@ -14,7 +14,7 @@ [package] name = "stabby" -version = "5.1.0" +version = { workspace = true } edition = "2021" authors = { workspace = true } license = { workspace = true } @@ -30,7 +30,7 @@ libloading = ["dep:libloading", "std"] libc = ["stabby-abi/libc"] [dependencies] -stabby-abi = { path = "../stabby-abi/", version = "5.1.0" } +stabby-abi = { workspace = true } lazy_static = { workspace = true } libloading = { workspace = true, optional = true } @@ -40,7 +40,7 @@ rustversion = { workspace = true } smol = { workspace = true } criterion = { workspace = true } rand = { workspace = true } -stabby-abi = { path = "../stabby-abi/", version = "5.1.0", features = ["test"] } +stabby-abi = { workspace = true, features = ["test"] } [package.metadata.docs.rs] all-features = true diff --git a/stabby/README.md b/stabby/README.md index 5841518..b300445 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` as the 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/build.rs b/stabby/build.rs deleted file mode 100644 index 3a0a7ca..0000000 --- a/stabby/build.rs +++ /dev/null @@ -1,71 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// Pierre Avital, -// - -use std::{ - fs::File, - io::{BufWriter, Write}, - path::PathBuf, -}; - -fn main() { - let compiler_versions = - PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("compiler_versions.rs"); - let mut compiler_versions = BufWriter::new(File::create(compiler_versions).unwrap()); - writeln!(compiler_versions, r"use crate::abi::IStable;").unwrap(); - for version in [ - "1.65.0", "1.66.0", "1.66.1", "1.67.0", "1.67.1", "1.68.0", "1.69.0", "1.70.0", "1.71.0", - "1.72.0", "1.72.1", "1.73.0", "1.74.0", "1.74.1", "1.75.0", "1.76.0", - ] { - let snake_version = version.replace('.', "_"); - writeln!( - compiler_versions, - r#" -/// A ZST that allows inserting some information about the expected compiler for a type. -#[allow(non_camel_case_types)] -pub struct CompilerVersion_{snake_version}(core::marker::PhantomData); -impl CompilerVersion_{snake_version} {{ - /// The constructor for the compiler version. - pub const UNIT: Self = Self(core::marker::PhantomData); -}} - -#[rustversion::stable({version})] -/// This type alias resolves to the compiler that is currently in use to compile the crate -pub type LocalCompiler = CompilerVersion_{snake_version}; - -#[rustversion::stable({version})] -unsafe impl IStable for CompilerVersion_{snake_version} {{ - type Size = Layout::Size; - type Align = Layout::Align; - type ForbiddenValues = Layout::ForbiddenValues; - type UnusedBits = Layout::UnusedBits; - type HasExactlyOneNiche = Layout::HasExactlyOneNiche; - type ContainsIndirections = Layout::ContainsIndirections; - const REPORT: &'static crate::abi::report::TypeReport = &crate::abi::report::TypeReport {{ - name: crate::abi::str::Str::new("CompilerVersion_{snake_version}"), - module: crate::abi::str::Str::new(core::module_path!()), - fields: crate::abi::StableLike::new(Some(&crate::abi::report::FieldReport {{ - name: crate::abi::str::Str::new("inner"), - ty: ::REPORT, - next_field: crate::abi::StableLike::new(None), - }})), - version: 0, - tyty: crate::abi::report::TyTy::Struct, - }}; - const ID: u64 = crate::abi::report::gen_id(Self::REPORT); -}} -"# - ) - .unwrap(); - } -} diff --git a/stabby/src/compiler_version.rs b/stabby/src/compiler_version.rs deleted file mode 100644 index 93c9242..0000000 --- a/stabby/src/compiler_version.rs +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// Pierre Avital, -// - -//! Provides ZSTs that only implement `IStable` when built with their corresponding version of the compiler. -//! -//! This allow the `StableLike>` pattern. -//! -//! `CompilerVersion_MAJ_MIN_PATCH` will only `impl IStable` as if it was `Layout`, but only if -//! compiled with the specified version of the compiler, providing you with a compile-time proof that you -//! are using the expected compiler version. -//! -//! Note that it is EXTREMELY memory-unsafe to lie about `Layout` if any type that contains this is -//! used in a `#[repr(stabby)]` enum, since `CompilerVersion` is ALWAYS a ZST, and non-`()` -//! layouts should only be used in combination with `StableLike`. -//! -//! You can also add a `compiler_version: CompilerVersion_VERSION<()>` marker field in your structs to ensure -//! that they are marked as stable only if compiled with the appropriate compiler version, however since the -//! rest of the fields of the struct need to bi ABI-stable for IStable to be implemented, I think the -//! applications are few and far between. - -include!(concat!(env!("OUT_DIR"), "/compiler_versions.rs")); diff --git a/stabby/src/lib.rs b/stabby/src/lib.rs index 8ddd459..305cd6e 100644 --- a/stabby/src/lib.rs +++ b/stabby/src/lib.rs @@ -34,15 +34,13 @@ pub use stabby_abi::alloc::{self, boxed, collections, string, sync, vec}; pub use stabby_abi::{Dyn, DynRef}; -pub mod compiler_version; - /// ABI-stable tuples -pub mod tuple; +pub use stabby_abi::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 +83,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..ca0aa21 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), @@ -250,6 +250,7 @@ unsafe impl stabby::abi::IStable for Align128 { type UnusedBits = End; type HasExactlyOneNiche = B0; type ContainsIndirections = B0; + 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!()), 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; diff --git a/stabby/src/tuple.rs b/stabby/src/tuple.rs deleted file mode 100644 index 66264ed..0000000 --- a/stabby/src/tuple.rs +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// Pierre Avital, -// - -/// A tuple of 2 elements. -#[crate::stabby] -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] -pub struct Tuple2(pub A, pub B); - -/// A tuple of 3 elements. -#[crate::stabby] -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] -pub struct Tuple3(pub A, pub B, pub C); - -/// A tuple of 4 elements. -#[crate::stabby] -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] -pub struct Tuple4(pub A, pub B, pub C, pub D); - -/// A tuple of 5 elements. -#[crate::stabby] -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] -pub struct Tuple5(pub A, pub B, pub C, pub D, pub E); - -/// A tuple of 6 elements. -#[crate::stabby] -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] -pub struct Tuple6(pub A, pub B, pub C, pub D, pub E, pub F); - -/// A tuple of 7 elements. -#[crate::stabby] -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] -pub struct Tuple7(pub A, pub B, pub C, pub D, pub E, pub F, pub G); - -/// A tuple of 8 elements. -#[crate::stabby] -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] -pub struct Tuple8(pub A, pub B, pub C, pub D, pub E, pub F, pub G, pub H); - -/// A tuple of 9 elements. -#[crate::stabby] -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] -pub struct Tuple9( - pub A, - pub B, - pub C, - pub D, - pub E, - pub F, - pub G, - pub H, - pub I, -);