├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── dynlinkage │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ └── main.rs ├── libloading │ ├── Cargo.toml │ └── src │ │ └── main.rs └── library │ ├── Cargo.toml │ └── src │ └── lib.rs ├── release.py ├── repr-stabby.md ├── rust-toolchain ├── stabby-abi ├── Cargo.toml ├── build.rs └── src │ ├── alloc │ ├── allocators │ │ ├── libc_alloc.rs │ │ ├── mod.rs │ │ └── rust_alloc.rs │ ├── boxed.rs │ ├── collections │ │ ├── arc_btree.rs │ │ └── mod.rs │ ├── mod.rs │ ├── single_or_vec.rs │ ├── string.rs │ ├── sync.rs │ └── vec.rs │ ├── as_mut.rs │ ├── checked_import.rs │ ├── closure.rs │ ├── enums │ ├── err_non_empty.rs │ ├── err_size_0.rs │ └── mod.rs │ ├── fatptr.rs │ ├── future.rs │ ├── istable.rs │ ├── iter.rs │ ├── lib.rs │ ├── num.rs │ ├── option.rs │ ├── report.rs │ ├── result.rs │ ├── slice.rs │ ├── stable_impls │ ├── abi_stable.rs │ └── mod.rs │ ├── str.rs │ ├── typenum2 │ ├── mod.rs │ └── unsigned.rs │ └── vtable │ └── mod.rs ├── stabby-macros ├── Cargo.toml ├── build.rs └── src │ ├── enums.rs │ ├── functions.rs │ ├── gen_closures.rs │ ├── lib.rs │ ├── structs.rs │ ├── traits.rs │ ├── tyops.rs │ ├── unions.rs │ └── utils.rs └── stabby ├── Cargo.toml ├── README.md ├── TUTORIAL.md ├── benches ├── allocators.rs ├── boxed_slices.rs ├── dynptr.rs └── enums.rs ├── build.rs └── src ├── allocs └── borrow.rs ├── lib.rs ├── libloading.rs ├── map.rs ├── tests ├── enums.rs ├── layouts.rs └── traits.rs └── time.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2022 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # Pierre Avital, 13 | # 14 | name: CI 15 | 16 | on: 17 | push: 18 | branches: ["**"] 19 | pull_request: 20 | branches: ["**"] 21 | schedule: 22 | - cron: "0 6 * * 1-5" 23 | 24 | jobs: 25 | check: 26 | name: Run checks on ${{ matrix.os }} 27 | runs-on: [self-hosted, "${{ matrix.os }}"] 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | os: [ubuntu-22.04, windows-11, macOS] 32 | 33 | steps: 34 | - uses: actions/checkout@v2 35 | 36 | - name: Install Rust toolchain 37 | uses: actions-rs/toolchain@v1 38 | with: 39 | components: rustfmt, clippy 40 | 41 | - name: Code format check 42 | uses: actions-rs/cargo@v1 43 | with: 44 | command: fmt 45 | args: -- --check 46 | 47 | - name: Clippy 48 | uses: actions-rs/cargo@v1 49 | with: 50 | command: clippy 51 | args: --all-targets -- -D warnings 52 | - name: Clean artifacts 53 | uses: actions-rs/cargo@v1 54 | with: 55 | command: clean 56 | 57 | check-1-72: 58 | name: Run 1.72 checks on ${{ matrix.os }} 59 | runs-on: [self-hosted, "${{ matrix.os }}"] 60 | strategy: 61 | fail-fast: false 62 | matrix: 63 | os: [ubuntu-22.04] 64 | 65 | steps: 66 | - uses: actions/checkout@v2 67 | - name: Install Rust toolchain 1.72 68 | uses: actions-rs/toolchain@v1 69 | with: 70 | toolchain: 1.72.0 71 | components: clippy 72 | 73 | - name: Clippy 74 | uses: actions-rs/cargo@v1 75 | with: 76 | command: clippy 77 | args: --all-targets -- -D warnings 78 | - name: Clean artifacts 79 | uses: actions-rs/cargo@v1 80 | with: 81 | command: clean 82 | 83 | check-wasm: 84 | name: Run wasm checks on ${{ matrix.os }} 85 | runs-on: [self-hosted, "${{ matrix.os }}"] 86 | strategy: 87 | fail-fast: false 88 | matrix: 89 | os: [ubuntu-22.04] 90 | steps: 91 | - uses: actions/checkout@v2 92 | - name: Install Rust toolchain 1.72 93 | uses: actions-rs/toolchain@v1 94 | with: 95 | profile: minimal 96 | target: wasm32-unknown-unknown 97 | - name: Check 98 | uses: actions-rs/cargo@v1 99 | with: 100 | command: check 101 | args: -p stabby 102 | - name: Clean artifacts 103 | uses: actions-rs/cargo@v1 104 | with: 105 | command: clean 106 | 107 | check-32bits: 108 | name: Run 32bits checks on ${{ matrix.os }} 109 | runs-on: [self-hosted, "${{ matrix.os }}"] 110 | strategy: 111 | fail-fast: false 112 | matrix: 113 | os: [ubuntu-22.04] 114 | steps: 115 | - uses: actions/checkout@v2 116 | - name: Install Rust toolchain 1.72 117 | uses: actions-rs/toolchain@v1 118 | with: 119 | profile: minimal 120 | target: i686-unknown-linux-gnu 121 | - name: Check 122 | uses: actions-rs/cargo@v1 123 | with: 124 | command: check 125 | args: -p stabby 126 | - name: Clean artifacts 127 | uses: actions-rs/cargo@v1 128 | with: 129 | command: clean 130 | 131 | test: 132 | name: Run tests on ${{ matrix.os }} 133 | runs-on: [self-hosted, "${{ matrix.os }}"] 134 | strategy: 135 | fail-fast: false 136 | matrix: 137 | os: [ubuntu-22.04, macOS] 138 | 139 | steps: 140 | - uses: actions/checkout@v2 141 | 142 | - name: Install latest Rust toolchain 143 | uses: actions-rs/toolchain@v1 144 | 145 | - name: Set rustflags 146 | shell: bash 147 | run: | 148 | case ${{ matrix.os }} in 149 | *windows*) echo "RUSTFLAGS=-Clink-arg=/DEBUG:NONE" >> $GITHUB_ENV ;; 150 | esac 151 | 152 | - name: Build 153 | uses: actions-rs/cargo@v1 154 | with: 155 | command: build 156 | args: --verbose --all-targets 157 | 158 | - name: Run tests 159 | uses: actions-rs/cargo@v1 160 | with: 161 | command: test 162 | 163 | - name: Test load-time linkage 164 | uses: actions-rs/cargo@v1 165 | with: 166 | command: run 167 | args: --bin dynlinkage 168 | 169 | - name: Test run-time linkage 170 | uses: actions-rs/cargo@v1 171 | with: 172 | command: run 173 | args: --bin libloader 174 | 175 | - name: Clean artifacts 176 | uses: actions-rs/cargo@v1 177 | with: 178 | command: clean 179 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | .vscode 4 | .idea -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This repository is under dual EPL/Apache2.0 license. 2 | 3 | As such, an ECA check is performed in PRs to formally confirm that you accept that any work you commit to this repository will be licensed equally. 4 | 5 | This ECA check requires you to create an account on Eclipse's official website, which some of you may feel overcomplicated when submitting one-off, small PRs. 6 | 7 | In such cases, you may place the following text in your PR to explicitly state that you agree for that PR to not break the current licensing. 8 | ``` 9 | I state that I was not able to sign ECA with my GitHub private email, 10 | since I wouldn't want to disclose my public email for this particular commit, 11 | and that therefore I grant the permission to copy/merge my PR if accepted 12 | ``` 13 | 14 | Note that I am not a lawyer, and this is not legal advice: just a tradition passed down from other projects with the hope to avoid license issues in the future. 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # Pierre Avital, 13 | # 14 | 15 | [workspace] 16 | members = [ 17 | "stabby-macros", 18 | "stabby", 19 | "stabby-abi", 20 | "examples/library", 21 | "examples/libloading", 22 | "examples/dynlinkage", 23 | ] 24 | resolver = "2" 25 | 26 | [profile.dev] 27 | opt-level = 3 28 | 29 | [workspace.lints.clippy] 30 | needless_lifetimes = "allow" 31 | unnecessary_map_or = "allow" 32 | 33 | 34 | [workspace.package] 35 | authors = ["Pierre Avital "] 36 | license = " EPL-2.0 OR Apache-2.0" 37 | categories = ["development-tools::ffi", "no-std::no-alloc"] 38 | repository = "https://github.com/ZettaScaleLabs/stabby" 39 | readme = "stabby/README.md" 40 | version = "72.1.1" # Track 41 | 42 | [workspace.dependencies] 43 | stabby-macros = { path = "./stabby-macros/", version = "72.1.1", default-features = false } # Track 44 | stabby-abi = { path = "./stabby-abi/", version = "72.1.1", default-features = false } # Track 45 | stabby = { path = "./stabby/", version = "72.1.1", default-features = false } # Track 46 | 47 | abi_stable = "0.11.0" 48 | libc = "0.2" 49 | libloading = ">=0.7.3, <0.9" 50 | proc-macro2 = "1.0" 51 | proc-macro-crate = ">=1, <4" 52 | quote = "1.0" 53 | rustversion = "<2" 54 | sha2-const-stable = "0.1" 55 | syn = "1.0.86" 56 | 57 | # dev-dependencies 58 | criterion = "0.5.1" 59 | rand = "0.8" 60 | serde = "1.0.203" 61 | smol = ">=1, <3" 62 | -------------------------------------------------------------------------------- /examples/dynlinkage/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # Pierre Avital, 13 | # 14 | 15 | [package] 16 | name = "dynlinkage" 17 | version = "0.1.0" 18 | edition = "2021" 19 | 20 | [dependencies] 21 | stabby.workspace = true 22 | library = { path = "../library/" } # This ensures proper compilation order 23 | -------------------------------------------------------------------------------- /examples/dynlinkage/build.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | fn main() { 16 | let Ok(dir) = std::env::var("PROFILE") else { 17 | return; 18 | }; 19 | println!( 20 | "cargo:rustc-link-search=native={}", 21 | [".", "target", &dir] 22 | .into_iter() 23 | .collect::() 24 | .to_str() 25 | .unwrap() 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /examples/dynlinkage/src/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | #[stabby::import(name = "library")] 16 | extern "C" { 17 | pub fn stable_fn(v: u8) -> stabby::option::Option<()>; 18 | } 19 | 20 | #[stabby::import(canaries = "", name = "library")] 21 | #[allow(improper_ctypes)] 22 | extern "C" { 23 | pub fn unstable_fn(v: &[u8]); 24 | } 25 | 26 | fn main() { 27 | stable_fn(5); 28 | unsafe { unstable_fn(&[1, 2, 3, 4]) }; 29 | } 30 | -------------------------------------------------------------------------------- /examples/libloading/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # Pierre Avital, 13 | # 14 | 15 | [package] 16 | name = "libloader" 17 | version = "0.1.0" 18 | edition = "2021" 19 | 20 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 21 | 22 | [dependencies] 23 | stabby = { workspace = true, features = ["libloading"] } 24 | libloading = { workspace = true } 25 | -------------------------------------------------------------------------------- /examples/libloading/src/main.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | fn main() { 16 | use stabby::libloading::StabbyLibrary; 17 | unsafe { 18 | let path = if cfg!(target_os = "linux") { 19 | "./target/debug/liblibrary.so" 20 | } else if cfg!(target_os = "windows") { 21 | "./target/debug/library.dll" 22 | } else if cfg!(target_os = "macos") { 23 | "./target/debug/liblibrary.dylib" 24 | } else { 25 | "" 26 | }; 27 | let lib = libloading::Library::new(path).unwrap_or_else(|e| { 28 | panic!( 29 | "{e}\n\nreaddir(./target/debug)={:?}", 30 | std::fs::read_dir("./target/debug") 31 | .map(|d| d.map(|f| f.unwrap().file_name()).collect::>()) 32 | ) 33 | }); 34 | let stable_fn = lib 35 | .get_stabbied:: stabby::option::Option<()>>(b"stable_fn") 36 | .unwrap(); 37 | let unstable_fn = lib 38 | .get_canaried::(b"unstable_fn") 39 | .unwrap(); 40 | stable_fn(5); 41 | unstable_fn(&[1, 2, 3, 4]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/library/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # Pierre Avital, 13 | # 14 | 15 | [package] 16 | name = "library" 17 | version = "0.1.0" 18 | edition = "2021" 19 | 20 | [lib] 21 | crate-type = ["cdylib", "staticlib"] 22 | 23 | 24 | [dependencies] 25 | stabby.workspace = true 26 | -------------------------------------------------------------------------------- /examples/library/src/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | #[stabby::export] 16 | pub extern "C" fn stable_fn(v: u8) -> stabby::option::Option<()> { 17 | println!("{v}"); 18 | Default::default() 19 | } 20 | 21 | #[stabby::export(canaries)] 22 | pub extern "C" fn unstable_fn(v: &[u8]) { 23 | println!("{v:?}") 24 | } 25 | -------------------------------------------------------------------------------- /release.py: -------------------------------------------------------------------------------- 1 | import sys, os, re 2 | ws_root = os.path.dirname(__file__) 3 | crates = ["stabby-macros", "stabby-abi", "stabby"] 4 | 5 | def factor(x, base): 6 | n = 0 7 | while x > 1 and x % base == 0: 8 | x /= base 9 | n += 1 10 | return n 11 | 12 | def factor_version(version, base): 13 | v = re.sub(r'([0-9\.]+).*', "\\g<1>", version) 14 | return ".".join([str(factor(int(x), base)) for x in v.split(".")]) 15 | 16 | if __name__ == "__main__": 17 | if len(sys.argv) > 1 and sys.argv[1] == "publish": 18 | for crate in crates: 19 | failure = os.system(f"cd {ws_root}/{crate} && cargo publish {' '.join(sys.argv[2:])}") 20 | if failure: 21 | raise f"Failed to release {crate}, stopping publication" 22 | else: 23 | changelog = f"{ws_root}/CHANGELOG.md" 24 | print("Close the CHANGELOG to continue, the topmost version will be picked") 25 | os.system(f"code --wait {changelog}") 26 | version = None 27 | changelog_text = None 28 | with open(changelog) as clog: 29 | changelog_text = clog.read() 30 | for line in changelog_text.splitlines(): 31 | versions = re.findall(r"^#\s+([\.\w\-]+)", line) 32 | version = versions[0] if len(versions) else None 33 | if version is not None: 34 | break 35 | header = f"# {version} (api={factor_version(version, 2)}, abi={factor_version(version, 3)})" 36 | print(header) 37 | changelog_text = re.sub(r"^#\s+([\.\w\-]+)(\s*\(api[^\)]+\))?", header, changelog_text) 38 | with open(changelog, "w") as clog: 39 | clog.write(changelog_text) 40 | 41 | print(f"Updating Cargo.tomls with version={version}") 42 | 43 | ws_toml = None 44 | with open(f"{ws_root}/Cargo.toml") as toml: 45 | ws_toml = toml.read() 46 | ws_toml = re.sub(r"version\s*=\s*\"[^\"]+(?P\".*Track)", f"version = \"{version}\\g", ws_toml) 47 | with open(f"{ws_root}/Cargo.toml", "w") as toml: 48 | toml.write(ws_toml) -------------------------------------------------------------------------------- /repr-stabby.md: -------------------------------------------------------------------------------- 1 | # The Repr-Stabby Specification 2 | This file details how stabby lays out types, allowing for alternative implementations of `#[repr(stabby)]`. Note that `stabby` deriving from this specification in any way is considered a bug, and I would be grateful that you report it if you found such a case. 3 | 4 | "Niches" refer to information about a type's representation. `#[repr(stabby)]` distinguishes two types of niches: 5 | - Forbidden Values (fvs) are the ordered set of values that a type isn't allowed to occupy. These values may be represented over multiple bytes. A given type may have any amount of forbidden values. The addition for fvs is the concatenation of ordered sets. A single forbidden value is an array of `(byte_offset, value)` tuples. The set of forbidden values must be ordered by ascending LSB-first order. 6 | - Unused Bits (unbits) is a bit-mask such that for any `value` of type `T`, `value ^ unbits` is strictly equivalent to `value` as long as it's treated as being of type `T`. The addtion for unbits is bitwise-or. 7 | 8 | Note that unbits can never overlap with a forbidden value. 9 | 10 | ## Primitive types 11 | ### Unit `()` 12 | Unit types have size 0, alignment 1, and no niches. 13 | 14 | ### Boolean 15 | `bool` is a single byte type, where all values that aren't `0` represents `false`, `1` represents `true`, and all other values are forbidden. 16 | 17 | ### Pointers and references 18 | Pointers are considered nullables, whereas references are non-null types (`[0; size_of_ptr]` is their only forbidden value) 19 | 20 | ## Product types (`struct`) 21 | Product type use the same representation as `#[repr(C)]` structs: fields are ordered in memory in the same order (increasing ptr offset) as in source code (increasing cursor offset at begining of field description). 22 | 23 | Their niches are fully preserved: each field's niches are shifted by the field's pointer offset to the beginning offset, and are then added together. 24 | 25 | ## Max types (`unions`) 26 | Max types have the same layout as C-unions. Max-types never have any niches, as responsibility of their memory is entirely left to the user. 27 | 28 | ## Sum types (Rust `enum`) 29 | ### `#[repr(u*/i*)]` enums 30 | Explicitly tagged unions are treated like `struct {tag: u*, union}`: the tag's potential forbidden values are not exported, nor are potential niches within the union, but the padding between tag and union is exported. 31 | 32 | ### `#[repr(stabby)]` enums 33 | Sum types are defined as a balanced binary tree of `Result`. This binary tree is constructed by the following algorithm: 34 | ```python 35 | buffer = [variant.ty for variant in enum.variants] # where variants are ordered by offset in source-code. 36 | def binary_tree(buffer): 37 | if len(buffer) > 2: 38 | pivot = len(buffer)//2; 39 | return [binary_tree(buffer[:pivot]), binary_tree(buffer[pivot:])] 40 | return buffer 41 | buffer = binary_tree(buffer) 42 | # buffer is now a binary tree 43 | ``` 44 | 45 | ## `Result` 46 | For any pair of types `Ok`, `Err` the following algorithm is applied to compute the `(determinant, ok_shift, err_shift, remaining_unbits)` tuple: 47 | ```python 48 | class Unbits: 49 | def __init__(self, mask: [int]): 50 | self.mask = mask 51 | def pad(self, target) -> Unbits: 52 | mask = copy(self.mask) 53 | while len(mask) < target: 54 | mask.append(0xff) 55 | return Unbits(mask) 56 | def shift(self, by: int) -> Unbits: 57 | mask = copy(self.mask) 58 | for i in range(by): 59 | mask.insert(0, 0xff) 60 | return Unbits(mask) 61 | def can_contain(self, fv_offset: int) -> bool: 62 | return self.mask[offset] == 0xff 63 | def extract_bit(self) -> ((int, int), Unbits) 64 | mask = copy(self.mask) 65 | for byte_offset in range(len(mask)): 66 | if mask[offset]: 67 | bit_offset = rightmost_one(mask[offset]) 68 | mask[offset] ^= 1 << bit_offset 69 | return ((byte_offset, bit_offset), Unbits(mask)) 70 | return (None, self) 71 | def determinant(Ok, Err) -> (Determinant, int, int, Unbits): 72 | if Ok.size < Err.size: 73 | det, ok_shift, err_shift, remaining_niches = determinant(Err, Ok) 74 | return Not(det), err_shift, ok_shift, remaining_niches 75 | union_size = max(next_multiple(Ok.size, Err.align), next_multiple(Err.size, Ok.align)) 76 | ok_unbits = Ok.unbits.pad(union_size) 77 | # this limit is a technical limitation of Rust's current type system, where this ABI was first defined. 78 | for i in range(8): 79 | shift = i * Err.align 80 | err_unbits = Err.unbits.shift(shift).pad(union_size) 81 | unbits = ok_unbits & err_unbits 82 | for fv in Err.fvs: 83 | if ok_unbits.can_contain(fv.offset): 84 | return ValueIsErr(fv), 0, shift, unbits 85 | for fv in Ok.fvs: 86 | if err_unbits.can_contain(fv): 87 | return Not(ValueIsErr(fv)), 0, shift, unbits 88 | if unbits: 89 | bit, unbits = unbits.extract_bit() 90 | return BitIsErr(bit), 0, shift, unbits 91 | if Err.size + shift + Err.align > union_size: 92 | break 93 | return BitDeterminant(), 0, 0, Unbits([0 for _ in union_size]) 94 | ``` 95 | 96 | `U` is defined as the union between `Ok` shifted by `ok_shift` bytes and `Err` shifted by `err_shift` bytes, with `remaining_unbits` as its unbits, and no forbidden values. 97 | 98 | `Result`'s layout depends on determinant: 99 | - `BitDeterminant()`: the Result is laid out as `struct {tag: Bit, union: U}`, where `bit == 1` signifies that `U` is `Err`. 100 | - `ValueIsErr(fv)`: the Result is laid out as `U`, where `all(self[offset] == value for (offset, value) in fv)` signifies that `U` is `Err`. 101 | - `BitIsErr((byte_offset, bit_offset))`: the Result is laid out as `U`, where `self[byte_offset] & (1<` 105 | `Option` is laid out in memory as if it was `Result`. 106 | 107 | ## Trait Objects 108 | Trait objects, usually named with `dynptr!(Ptr)` are represented as 109 | ```rust 110 | #[repr(C)] 111 | pub struct DynPtrTrait1Trait2<'lt> { 112 | ptr: Ptr<()>, // ptr is transmuted from an instance of `Ptr where T: Trait1 + Trait2 + 'lt 113 | vtable: &'static VTable, 114 | } 115 | ``` 116 | where `VTable` is a structure that contains, in order: 117 | - `T::drop` wrapped with an `extern "C"` shim, 118 | - All of `Trait1`'s methods, in the order they are declared in the trait definition, 119 | - All of `Trait2`'s methods, in the order they are declared in the trait definition. 120 | 121 | This layout implies that a trait object can be upcast into a less restrictive trait object by transmutation, 122 | on the condition that only traits to right of the `dynptr` definition are removed. 123 | 124 | # Future possibilities 125 | At the moment, `Result` never has any forbidden values left, even if `Ok` had multiple fvs that could fit in `Err`'s unbits. This means that `Option>` occupies 2 bytes, instead of 1 as it does with Rust's current layout. 126 | 127 | Since extracting only the correct FV from FVs can be complex to implement in computation contexts such as Rust's trait-solver, the choice was made not to do it. Should this become feasible, a release process will have to be designed. If you implement `#[repr(stabby)]`, please [file an issue on `stabby`'s original repository'](https://github.com/ZettaScaleLabs/stabby/issues/new) to be notified. 128 | 129 | # `stabby`'s semver policy 130 | Stabby's semver policy is built as such: 131 | - patch: `stabby` will never break your builds _or_ your ABI, whatever sections of it you are using, through a patch update. New APIs may be added, and implementations may be refined, provided those refinements don't break ABI, including implicit invariants. 132 | - minor: `stabby` reserves the right to break API _and_ ABI for a small set of types on minor releases. These breaks shall be clearly highlighted in the CHANGELOG, and will be avoided unless they provide major benefits (extreme performance increase, important new feature, or vulnerability fix). 133 | - major: these releases indicate that `stabby`'s ABI has changed in a global way: binaries that use different major releases of `stabby` are unlikely to be able to interact correctly. For examples, if `#[repr(stabby)]`'s implementation for enums was to change, this would be a major release. Note that if `stabby` is unable to detect such a change at runtime through its reflection system, this shall be explicitly stated in the changelog. Note that this applies to bugfixes: if a widely used type from `stabby` needs to have its layout or invariants changed in order to fix a bug, the fix will still be a major release. -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | stable 2 | -------------------------------------------------------------------------------- /stabby-abi/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # Pierre Avital, 13 | # 14 | 15 | [package] 16 | name = "stabby-abi" 17 | version = { workspace = true } 18 | edition = "2021" 19 | authors = { workspace = true } 20 | license = { workspace = true } 21 | categories = { workspace = true } 22 | repository = { workspace = true } 23 | readme = { workspace = true } 24 | description = "stabby's core ABI, you shouldn't add this crate to your dependencies, only `stabby`." 25 | 26 | [lints] 27 | workspace = true 28 | 29 | [features] 30 | default = ["std"] 31 | std = ["alloc-rs"] 32 | alloc-rs = [] 33 | experimental-ctypes = ["stabby-macros/experimental-ctypes"] 34 | libc = ["dep:libc"] 35 | test = [] 36 | serde = ["dep:serde"] 37 | 38 | abi_stable = ["dep:abi_stable"] 39 | abi_stable-channels = ["abi_stable", "abi_stable/channels"] 40 | 41 | # Since there currently isn't any way to construct guaranteed ABI-stable wakers from `core::task::Waker` 42 | # `stabby::future::Future::poll` may need to allocate in order to provide stable wakers. 43 | # If you're confident enough that `core::task::Waker`'s ABI will not change between your targetted versions 44 | # of rustc, you may enable this feature to pass them across the FFI boundary directly. 45 | # 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"'` 46 | 47 | [dependencies] 48 | stabby-macros.workspace = true 49 | 50 | abi_stable = { workspace = true, optional = true } 51 | libc = { workspace = true, optional = true } 52 | rustversion = { workspace = true } 53 | serde = { workspace = true, optional = true, features = ["derive"] } 54 | sha2-const-stable = { workspace = true } 55 | 56 | [dev-dependencies] 57 | rand = { workspace = true } 58 | 59 | [build-dependencies] 60 | rustc_version = "0.4.1" 61 | 62 | [package.metadata.docs.rs] 63 | rustc-args = ["--cfg", "docsrs"] 64 | -------------------------------------------------------------------------------- /stabby-abi/build.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use std::{ 16 | fmt::Write as FmtWrite, 17 | fs::File, 18 | io::{BufWriter, Write}, 19 | path::PathBuf, 20 | }; 21 | 22 | use rustc_version::{version_meta, Channel}; 23 | 24 | fn u(mut i: u128) -> String { 25 | let mut result = "UTerm".into(); 26 | let mut ids = Vec::new(); 27 | while i > 0 { 28 | let bit = i & 1; 29 | ids.push(bit as u8); 30 | i >>= 1; 31 | } 32 | for bit in ids.into_iter().rev() { 33 | result = format!("UInt<{result}, B{bit}>"); 34 | } 35 | result 36 | } 37 | 38 | fn typenum_unsigned() -> std::io::Result<()> { 39 | const SEQ_MAX: u128 = 1000; 40 | let filename = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("unsigned.rs"); 41 | let mut file = BufWriter::new(File::create(filename).unwrap()); 42 | for i in 0..=SEQ_MAX { 43 | let u = u(i); 44 | writeln!(file, "/// {i}\npub type U{i} = {u};")?; 45 | writeln!(file, "/// {i}\npub type Ux{i:X} = {u};")?; 46 | writeln!(file, "/// {i}\npub type Ub{i:b} = {u};")?; 47 | } 48 | for i in 0..39 { 49 | let ipow = 10u128.pow(i); 50 | let u = u(ipow); 51 | writeln!(file, "/// {i}\npub type U10pow{i} = {u};")?; 52 | if ipow > SEQ_MAX { 53 | writeln!(file, "/// {i}\npub type U{ipow} = {u};")?; 54 | writeln!(file, "/// {i}\npub type Ux{ipow:X} = {u};")?; 55 | writeln!(file, "/// {i}\npub type Ub{ipow:b} = {u};")?; 56 | } 57 | } 58 | for i in 0..128 { 59 | let p = 1 << i; 60 | let u = u(p); 61 | writeln!(file, "/// {i}\npub type U2pow{i} = {u};")?; 62 | if p > SEQ_MAX { 63 | writeln!(file, "/// {i}\npub type U{p} = {u};")?; 64 | } 65 | } 66 | Ok(()) 67 | } 68 | 69 | fn tuples(max_tuple: usize) -> std::io::Result<()> { 70 | let filename = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("tuples.rs"); 71 | let mut file = BufWriter::new(File::create(filename).unwrap()); 72 | for i in 0..=max_tuple { 73 | writeln!( 74 | file, 75 | r##"/// An ABI stable tuple of {i} elements. 76 | #[crate::stabby] 77 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] 78 | pub struct Tuple{i}<{generics}>({fields}); 79 | impl<{generics}> From<({generics})> for Tuple{i}<{generics}> {{ 80 | fn from(value: ({generics})) -> Self {{ 81 | let ({named_fields}) = value; 82 | Self({named_fields}) 83 | }} 84 | }} 85 | #[allow(clippy::unused_unit)] 86 | impl<{generics}> From> for ({generics}) {{ 87 | fn from(value: Tuple{i}<{generics}>) -> Self {{ 88 | let Tuple{i}({named_fields}) = value; 89 | ({named_fields}) 90 | }} 91 | }} 92 | "##, 93 | generics = (0..i).fold(String::new(), |mut acc, it| { 94 | write!(acc, "T{it}, ").unwrap(); 95 | acc 96 | }), 97 | fields = (0..i).fold(String::new(), |mut acc, it| { 98 | write!(acc, "pub T{it}, ").unwrap(); 99 | acc 100 | }), 101 | named_fields = (0..i).fold(String::new(), |mut acc, it| { 102 | write!(acc, "field{it}, ").unwrap(); 103 | acc 104 | }), 105 | )?; 106 | } 107 | Ok(()) 108 | } 109 | 110 | fn main() { 111 | typenum_unsigned().unwrap(); 112 | println!("cargo:rustc-check-cfg=cfg(stabby_max_tuple, values(any()))"); 113 | let max_tuple = std::env::var("CARGO_CFG_STABBY_MAX_TUPLE") 114 | .map_or(32, |s| s.parse().unwrap_or(32)) 115 | .max(10); 116 | tuples(max_tuple).unwrap(); 117 | println!("cargo:rustc-check-cfg=cfg(stabby_nightly, values(none()))"); 118 | println!( 119 | r#"cargo:rustc-check-cfg=cfg(stabby_default_alloc, values("RustAlloc", "LibcAlloc", "disabled"))"# 120 | ); 121 | println!( 122 | r#"cargo:rustc-check-cfg=cfg(stabby_check_unreachable, values(none(), "true", "false"))"# 123 | ); 124 | println!(r#"cargo:rustc-check-cfg=cfg(stabby_unsafe_wakers, values(none(), "true", "false"))"#); 125 | println!( 126 | r#"cargo:rustc-check-cfg=cfg(stabby_vtables, values(none(), "vec", "btree", "no_alloc"))"# 127 | ); 128 | if std::env::var("CARGO_CFG_STABBY_DEFAULT_ALLOC").is_err() { 129 | if std::env::var("CARGO_FEATURE_ALLOC_RS").is_ok() { 130 | println!(r#"cargo:rustc-cfg=stabby_default_alloc="RustAlloc""#); 131 | } else if std::env::var("CARGO_FEATURE_LIBC").is_ok() { 132 | println!(r#"cargo:rustc-cfg=stabby_default_alloc="LibcAlloc""#); 133 | } else { 134 | println!(r#"cargo:rustc-cfg=stabby_default_alloc="disabled""#); 135 | } 136 | } 137 | if let Channel::Nightly = version_meta().unwrap().channel { 138 | println!("cargo:rustc-cfg=stabby_nightly"); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /stabby-abi/src/alloc/allocators/libc_alloc.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use crate::alloc::Layout; 16 | 17 | #[cfg(not(windows))] 18 | use libc::posix_memalign; 19 | #[cfg(windows)] 20 | unsafe fn posix_memalign(this: &mut *mut core::ffi::c_void, size: usize, align: usize) -> i32 { 21 | // SAFETY: `aligned_malloc` is always safe. 22 | let ptr = unsafe { libc::aligned_malloc(size, align) }; 23 | if ptr.is_null() { 24 | return libc::ENOMEM; 25 | } 26 | *this = ptr; 27 | 0 28 | } 29 | #[cfg(windows)] 30 | use libc::aligned_free; 31 | #[cfg(not(windows))] 32 | use libc::free as aligned_free; 33 | use libc::realloc; 34 | 35 | /// An allocator based on `libc::posix_memalign` or `libc::aligned_malloc` depending on the platform. 36 | /// 37 | /// It has all of `malloc`'s usual properties. 38 | #[crate::stabby] 39 | #[derive(Clone, Copy, Default)] 40 | pub struct LibcAlloc { 41 | inner: [u8; 0], 42 | } 43 | impl core::fmt::Debug for LibcAlloc { 44 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 45 | f.write_str("LibcAlloc") 46 | } 47 | } 48 | impl LibcAlloc { 49 | /// Constructs the allocator. 50 | pub const fn new() -> Self { 51 | Self { inner: [] } 52 | } 53 | } 54 | 55 | impl crate::alloc::IAlloc for LibcAlloc { 56 | fn alloc(&mut self, layout: Layout) -> *mut () { 57 | if layout.size == 0 { 58 | return core::ptr::null_mut(); 59 | } 60 | let mut ptr = core::ptr::null_mut(); 61 | // SAFETY: `posix_memalign` is always safe. 62 | let err = unsafe { posix_memalign(&mut ptr, layout.align, layout.size) }; 63 | if err != 0 && (ptr as usize % layout.align != 0) { 64 | ptr = core::ptr::null_mut(); 65 | } 66 | ptr.cast() 67 | } 68 | unsafe fn free(&mut self, ptr: *mut ()) { 69 | // SAFETY: `aligned_free` must be called by a pointer allocated by the corresponding allocator, which is already a safety condition of `IAlloc::free` 70 | unsafe { aligned_free(ptr.cast()) } 71 | } 72 | unsafe fn realloc(&mut self, ptr: *mut (), prev: Layout, new_size: usize) -> *mut () { 73 | if new_size == 0 { 74 | return core::ptr::null_mut(); 75 | } 76 | let mut new_ptr = core::ptr::null_mut::(); 77 | if prev.align <= 8 { 78 | // SAFETY: `realloc` must be called by a pointer allocated by the corresponding allocator, which is already a safety condition of `IAlloc::free`. It may also fail if the alignment is not correct on some systems, so we avoid calling it if it's too great. 79 | new_ptr = unsafe { realloc(ptr.cast(), new_size) }; 80 | }; 81 | if new_ptr.is_null() { 82 | // SAFETY: `posix_memalign` is always safe. 83 | let err = unsafe { posix_memalign(&mut new_ptr, prev.align, new_size) }; 84 | if err == 0 { 85 | unsafe { 86 | core::ptr::copy_nonoverlapping( 87 | ptr.cast::(), 88 | new_ptr.cast::(), 89 | prev.size, 90 | ) 91 | } 92 | self.free(ptr.cast()); 93 | } 94 | } 95 | new_ptr.cast() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /stabby-abi/src/alloc/allocators/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(feature = "libc", not(target_arch = "wasm32")))] 2 | /// [`IAlloc`](crate::alloc::IAlloc) bindings for `libc::malloc` 3 | pub(crate) mod libc_alloc; 4 | #[cfg(all(feature = "libc", not(target_arch = "wasm32")))] 5 | pub use libc_alloc::LibcAlloc; 6 | 7 | #[cfg(feature = "alloc-rs")] 8 | /// Rust's GlobalAlloc, accessed through a vtable to ensure no incompatible function calls are performed 9 | mod rust_alloc; 10 | #[cfg(feature = "alloc-rs")] 11 | pub use rust_alloc::RustAlloc; 12 | 13 | #[cfg(stabby_default_alloc = "RustAlloc")] 14 | /// The default allocator, depending on which of the following is available: 15 | /// - RustAlloc: Rust's `GlobalAlloc`, through a vtable that ensures FFI-safety. 16 | /// - LibcAlloc: libc::malloc, which is 0-sized. 17 | /// - None. I _am_ working on getting a 0-dependy allocator working, but you should probably go with `feature = "alloc-rs"` anyway. 18 | /// 19 | /// You can also use the `stabby_default_alloc` cfg to override the default allocator regardless of feature flags. 20 | pub(crate) type DefaultAllocator = RustAlloc; 21 | 22 | #[cfg(stabby_default_alloc = "LibcAlloc")] 23 | /// The default allocator, depending on which of the following is available: 24 | /// - RustAlloc: Rust's `GlobalAlloc`, through a vtable that ensures FFI-safety. 25 | /// - LibcAlloc: libc::malloc, which is 0-sized. 26 | /// - None. I _am_ working on getting a 0-dependy allocator working, but you should probably go with `feature = "alloc-rs"` anyway. 27 | /// 28 | /// You can also use the `stabby_default_alloc` cfg to override the default allocator regardless of feature flags. 29 | pub(crate) type DefaultAllocator = LibcAlloc; 30 | 31 | #[cfg(stabby_default_alloc = "disabled")] 32 | /// The default allocator, depending on which of the following is available: 33 | /// - RustAlloc: Rust's `GlobalAlloc`, through a vtable that ensures FFI-safety. 34 | /// - LibcAlloc: libc::malloc, which is 0-sized. 35 | /// - None. I _am_ working on getting a 0-dependy allocator working, but you should probably go with `feature = "alloc-rs"` anyway. 36 | /// 37 | /// You can also use the `stabby_default_alloc` cfg to override the default allocator regardless of feature flags. 38 | pub(crate) type DefaultAllocator = core::convert::Infallible; 39 | -------------------------------------------------------------------------------- /stabby-abi/src/alloc/allocators/rust_alloc.rs: -------------------------------------------------------------------------------- 1 | use crate::alloc::{IAlloc, Layout}; 2 | 3 | /// Rust's GlobalAlloc, annotating its yielded pointers in such a way that the allocated pointers can be safely freed from other binaries. 4 | #[crate::stabby] 5 | #[derive(Clone, Copy)] 6 | pub struct RustAlloc { 7 | inner: [u8; 0], 8 | } 9 | #[crate::stabby] 10 | /// The VTable for [`RustAlloc`] 11 | pub struct RustAllocVt { 12 | free: extern "C" fn(*mut (), crate::alloc::Layout), 13 | realloc: extern "C" fn(*mut (), crate::alloc::Layout, usize) -> *mut (), 14 | } 15 | #[crate::stabby] 16 | /// The Prefix for [`RustAlloc`] 17 | pub struct RustAllocPrefix { 18 | layout: Layout, 19 | vtable: RustAllocVt, 20 | } 21 | 22 | extern "C" fn alloc(requested: crate::alloc::Layout) -> *mut () { 23 | let requested = Layout::of::().concat(requested); 24 | let Ok(layout) = core::alloc::Layout::from_size_align(requested.size, requested.align) else { 25 | return core::ptr::null_mut(); 26 | }; 27 | // SAFETY: The layout is always non-zero-sized 28 | let alloc_start = unsafe { alloc_rs::alloc::alloc(layout) }; 29 | let ret = // SAFETY: the addition is indeed in-bound. 30 | unsafe { alloc_start.add(layout.align().max(core::mem::size_of::())) }; 31 | // SAFETY: `ret` is allocated and _at least_ one `RustAllocPrefix` greater than the start of the allocation, so writing there is safe. 32 | unsafe { 33 | ret.cast::().sub(1).write(RustAllocPrefix { 34 | layout: requested, 35 | vtable: VTABLE, 36 | }) 37 | }; 38 | ret.cast() 39 | } 40 | extern "C" fn realloc(ptr: *mut (), prev_layout: crate::alloc::Layout, new_size: usize) -> *mut () { 41 | // SAFETY: The corresponding `alloc` returns the allocation offset by this much (see the line where `ret` is constructed in both the `alloc` and `realloc` functions) 42 | let realloc_start = unsafe { 43 | ptr.cast::().sub( 44 | prev_layout 45 | .align 46 | .max(core::mem::size_of::()), 47 | ) 48 | }; 49 | let Ok(layout) = core::alloc::Layout::from_size_align(prev_layout.size, prev_layout.align) 50 | else { 51 | return core::ptr::null_mut(); 52 | }; 53 | let requested = Layout::of::().concat(Layout { 54 | size: new_size, 55 | align: prev_layout.align, 56 | }); 57 | // SAFETY: See each line 58 | unsafe { 59 | // If `ptr` was indeed allocated on by this allocator, then `realloc_start` was indeed allocated by _our_ GlobalAlloc. 60 | let alloc_start = alloc_rs::alloc::realloc(realloc_start, layout, requested.size); 61 | // We follow the same return-value shifting as in `alloc` 62 | let ret = alloc_start.add(layout.align().max(core::mem::size_of::())); 63 | // And prepend the same prefix 64 | ret.cast::().sub(1).write(RustAllocPrefix { 65 | layout: requested, 66 | vtable: VTABLE, 67 | }); 68 | ret.cast() 69 | } 70 | } 71 | extern "C" fn free(ptr: *mut (), prev_layout: crate::alloc::Layout) { 72 | // SAFETY: The corresponding `alloc` returns the allocation offset by this much (see the line where `ret` is constructed in both the `alloc` and `realloc` functions) 73 | let dealloc_start = unsafe { 74 | ptr.cast::().sub( 75 | prev_layout 76 | .align 77 | .max(core::mem::size_of::()), 78 | ) 79 | }; 80 | // If `ptr` was indeed allocated on by this allocator, then `dealloc_start` was indeed allocated by _our_ GlobalAlloc. 81 | unsafe { 82 | alloc_rs::alloc::dealloc( 83 | dealloc_start, 84 | core::alloc::Layout::from_size_align_unchecked(prev_layout.size, prev_layout.align), 85 | ) 86 | } 87 | } 88 | const VTABLE: RustAllocVt = RustAllocVt { 89 | free: free as extern "C" fn(*mut (), crate::alloc::Layout), 90 | realloc: realloc as extern "C" fn(*mut (), crate::alloc::Layout, usize) -> *mut (), 91 | }; 92 | impl RustAlloc { 93 | /// Constructs the allocator. 94 | pub const fn new() -> Self { 95 | Self { inner: [] } 96 | } 97 | } 98 | impl Default for RustAlloc { 99 | fn default() -> Self { 100 | Self::new() 101 | } 102 | } 103 | impl IAlloc for RustAlloc { 104 | fn alloc(&mut self, layout: crate::alloc::Layout) -> *mut () { 105 | alloc(layout) 106 | } 107 | 108 | unsafe fn free(&mut self, ptr: *mut ()) { 109 | let RustAllocPrefix { layout, vtable } = // SAFETY: if called with a `ptr` allocated by an instance of `self`, this read is valid. 110 | unsafe { ptr.cast::().sub(1).read() }; 111 | (vtable.free)(ptr, layout) 112 | } 113 | 114 | unsafe fn realloc( 115 | &mut self, 116 | ptr: *mut (), 117 | _prev_layout: crate::alloc::Layout, 118 | new_size: usize, 119 | ) -> *mut () { 120 | let RustAllocPrefix { layout, vtable } = // SAFETY: if called with a `ptr` allocated by an instance of `self`, this read is valid. 121 | unsafe { ptr.cast::().sub(1).read() }; 122 | (vtable.realloc)(ptr, layout, new_size) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /stabby-abi/src/alloc/collections/mod.rs: -------------------------------------------------------------------------------- 1 | /// BTrees built for sharing 2 | /// 3 | /// ArcBTrees are build to be cheap to clone through atomic reference counts. 4 | /// 5 | /// Mutating an ArcBTree that whose ownership is shared will result in every node from the insertion spot 6 | /// to the root to be copied, while the remaining nodes will just see their reference counts increase. 7 | pub mod arc_btree; 8 | -------------------------------------------------------------------------------- /stabby-abi/src/as_mut.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use core::ops::{Deref, DerefMut}; 16 | 17 | impl super::AccessAs for Source { 18 | fn ref_as(&self) -> >::Guard<'_> 19 | where 20 | Self: IGuardRef, 21 | { 22 | self.guard_ref_inner() 23 | } 24 | fn mut_as(&mut self) -> >::GuardMut<'_> 25 | where 26 | Self: IGuardMut, 27 | { 28 | self.guard_mut_inner() 29 | } 30 | } 31 | 32 | /// Allows exposing an immutable reference to a temporary conversion. 33 | pub trait IGuardRef { 34 | /// The type of the guard which will clean up the temporary. 35 | type Guard<'a>: Deref 36 | where 37 | Self: 'a; 38 | /// Construct the temporary and guard it through an immutable reference. 39 | fn guard_ref_inner(&self) -> Self::Guard<'_>; 40 | } 41 | 42 | /// Allows exposing an mutable reference to a temporary conversion. 43 | pub trait IGuardMut: IGuardRef { 44 | /// The type of the guard which will clean up the temporary after applying its changes to the original. 45 | type GuardMut<'a>: DerefMut 46 | where 47 | Self: 'a; 48 | /// Construct the temporary and guard it through a mutable reference. 49 | fn guard_mut_inner(&mut self) -> Self::GuardMut<'_>; 50 | } 51 | 52 | /// A guard exposing an immutable reference to `T` as an immutable reference to `As` 53 | pub struct RefAs<'a, T, As> { 54 | source: core::marker::PhantomData<&'a T>, 55 | target: core::mem::ManuallyDrop, 56 | } 57 | impl Deref for RefAs<'_, T, As> { 58 | type Target = As; 59 | fn deref(&self) -> &Self::Target { 60 | &self.target 61 | } 62 | } 63 | impl, As: Into> IGuardRef for T { 64 | type Guard<'a> 65 | = RefAs<'a, T, As> 66 | where 67 | Self: 'a; 68 | 69 | fn guard_ref_inner(&self) -> Self::Guard<'_> { 70 | RefAs { 71 | source: core::marker::PhantomData, 72 | target: core::mem::ManuallyDrop::new(unsafe { core::ptr::read(self).into() }), 73 | } 74 | } 75 | } 76 | 77 | /// A guard exposing an mutable reference to `T` as an mutable reference to `As` 78 | /// 79 | /// Failing to destroy this guard will cause `T` not to receive any of the changes applied to the guard. 80 | pub struct MutAs<'a, T, As: Into> { 81 | source: &'a mut T, 82 | target: core::mem::ManuallyDrop, 83 | } 84 | impl> Deref for MutAs<'_, T, As> { 85 | type Target = As; 86 | fn deref(&self) -> &Self::Target { 87 | &self.target 88 | } 89 | } 90 | impl> DerefMut for MutAs<'_, T, As> { 91 | fn deref_mut(&mut self) -> &mut Self::Target { 92 | &mut self.target 93 | } 94 | } 95 | impl> Drop for MutAs<'_, T, As> { 96 | fn drop(&mut self) { 97 | unsafe { core::ptr::write(self.source, core::ptr::read(&*self.target).into()) } 98 | } 99 | } 100 | impl, As: Into> IGuardMut for T { 101 | type GuardMut<'a> 102 | = MutAs<'a, T, As> 103 | where 104 | Self: 'a; 105 | 106 | fn guard_mut_inner(&mut self) -> Self::GuardMut<'_> { 107 | MutAs { 108 | target: core::mem::ManuallyDrop::new(unsafe { core::ptr::read(self).into() }), 109 | source: self, 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /stabby-abi/src/checked_import.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use core::{ 16 | ops::Deref, 17 | sync::atomic::{AtomicU8, Ordering}, 18 | }; 19 | /// Used in `#[stabby::import(canaries)]` 20 | #[crate::stabby] 21 | pub struct CanariedImport { 22 | result: F, 23 | checked: AtomicU8, 24 | canary: extern "C" fn(), 25 | } 26 | // SAFETY: `CanariedImport`s always refer to `Send + Sync` things. 27 | unsafe impl Send for CanariedImport {} 28 | // SAFETY: `CanariedImport`s always refer to `Send + Sync` things. 29 | unsafe impl Sync for CanariedImport {} 30 | impl CanariedImport { 31 | /// Used in `#[stabby::import(canaries)]` 32 | pub const fn new(source: F, canary_caller: extern "C" fn()) -> Self { 33 | Self { 34 | result: source, 35 | checked: AtomicU8::new(0), 36 | canary: canary_caller, 37 | } 38 | } 39 | } 40 | impl Deref for CanariedImport { 41 | type Target = F; 42 | fn deref(&self) -> &Self::Target { 43 | if self.checked.swap(1, Ordering::Relaxed) == 0 { 44 | (self.canary)() 45 | } 46 | &self.result 47 | } 48 | } 49 | 50 | /// Used in `#[stabby::import]` 51 | #[crate::stabby] 52 | pub struct CheckedImport { 53 | result: core::cell::UnsafeCell>, 54 | checked: AtomicU8, 55 | #[allow(improper_ctypes_definitions)] 56 | checker: unsafe extern "C" fn(&crate::report::TypeReport) -> Option, 57 | get_report: unsafe extern "C" fn() -> &'static crate::report::TypeReport, 58 | local_report: &'static crate::report::TypeReport, 59 | } 60 | // SAFETY: `CheckedImport`s always refer to functions. 61 | unsafe impl Send for CheckedImport {} 62 | // SAFETY: `CheckedImport`s always refer to functions. 63 | unsafe impl Sync for CheckedImport {} 64 | 65 | /// When reports mismatch between loader and loadee, both reports are exposed to allow debuging the issue. 66 | #[crate::stabby] 67 | #[derive(Debug, Clone, Copy)] 68 | pub struct ReportMismatch { 69 | /// The report on loader side. 70 | pub local: &'static crate::report::TypeReport, 71 | /// The report on loadee side. 72 | pub loaded: &'static crate::report::TypeReport, 73 | } 74 | impl core::fmt::Display for ReportMismatch { 75 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 76 | core::fmt::Debug::fmt(&self, f) 77 | } 78 | } 79 | #[cfg(feature = "std")] 80 | impl std::error::Error for ReportMismatch {} 81 | 82 | const UNCHECKED: u8 = 0; 83 | const VALIDATED: u8 = 1; 84 | const INVALIDATED: u8 = 2; 85 | const LOCKED: u8 = 3; 86 | impl CheckedImport { 87 | /// Used by `#[stabby::import]` proc-macro 88 | #[allow(improper_ctypes_definitions)] 89 | pub const fn new( 90 | checker: unsafe extern "C" fn(&crate::report::TypeReport) -> Option, 91 | get_report: unsafe extern "C" fn() -> &'static crate::report::TypeReport, 92 | local_report: &'static crate::report::TypeReport, 93 | ) -> Self { 94 | Self { 95 | checked: AtomicU8::new(UNCHECKED), 96 | checker, 97 | get_report, 98 | local_report, 99 | result: core::cell::UnsafeCell::new(core::mem::MaybeUninit::uninit()), 100 | } 101 | } 102 | fn error_report(&self) -> ReportMismatch { 103 | ReportMismatch { 104 | local: self.local_report, 105 | loaded: unsafe { (self.get_report)() }, 106 | } 107 | } 108 | /// # Errors 109 | /// Returns a [`ReportMismatch`] if the local and loaded reports differ. 110 | pub fn as_ref(&self) -> Result<&F, ReportMismatch> { 111 | loop { 112 | match self.checked.load(Ordering::Relaxed) { 113 | UNCHECKED => match unsafe { (self.checker)(self.local_report) } { 114 | Some(result) => { 115 | if self 116 | .checked 117 | .compare_exchange_weak( 118 | UNCHECKED, 119 | LOCKED, 120 | Ordering::SeqCst, 121 | Ordering::Relaxed, 122 | ) 123 | .is_ok() 124 | { 125 | unsafe { 126 | (*self.result.get()).write(result); 127 | self.checked.store(VALIDATED, Ordering::SeqCst); 128 | return Ok((*self.result.get()).assume_init_ref()); 129 | } 130 | } 131 | } 132 | None => { 133 | self.checked.store(INVALIDATED, Ordering::Relaxed); 134 | return Err(self.error_report()); 135 | } 136 | }, 137 | VALIDATED => return Ok(unsafe { (*self.result.get()).assume_init_ref() }), 138 | INVALIDATED => return Err(self.error_report()), 139 | _ => {} 140 | } 141 | core::hint::spin_loop(); 142 | } 143 | } 144 | } 145 | impl core::ops::Deref for CheckedImport { 146 | type Target = F; 147 | fn deref(&self) -> &Self::Target { 148 | self.as_ref().unwrap() 149 | } 150 | } 151 | impl Drop for CheckedImport { 152 | fn drop(&mut self) { 153 | if self.checked.load(Ordering::Relaxed) == VALIDATED { 154 | unsafe { self.result.get_mut().assume_init_drop() } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /stabby-abi/src/closure.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | #![allow(clippy::too_many_arguments)] 16 | stabby_macros::gen_closures_impl!(); 17 | -------------------------------------------------------------------------------- /stabby-abi/src/enums/err_non_empty.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | #![allow(clippy::type_complexity)] // I mean, what did you expect? 16 | 17 | use self::err_size_0::DeterminantProviderWithUnit; 18 | 19 | pub use super::*; 20 | 21 | pub struct Layout< 22 | UnionSize: Unsigned, 23 | Budget, 24 | OkFv: IForbiddenValues, 25 | OkUb: IBitMask, 26 | ErrFv: IForbiddenValues, 27 | ErrUb: IBitMask, 28 | ErrSize: Unsigned, 29 | ErrAlign: Alignment, 30 | ErrOffset: Unsigned, 31 | >( 32 | core::marker::PhantomData<( 33 | UnionSize, 34 | Budget, 35 | OkFv, 36 | OkUb, 37 | ErrFv, 38 | ErrUb, 39 | ErrSize, 40 | ErrAlign, 41 | ErrOffset, 42 | )>, 43 | ); 44 | pub struct DeterminantProvider< 45 | Layout, 46 | ErrFvInOkUb: ISingleForbiddenValue, 47 | OkFvInErrUb: ISingleForbiddenValue, 48 | UbIntersect: IBitMask, 49 | >(core::marker::PhantomData<(Layout, ErrFvInOkUb, OkFvInErrUb, UbIntersect)>); 50 | 51 | /// Prevents the compiler from doing infinite recursion when evaluating `IDeterminantProvider` 52 | type DefaultRecursionBudget = T>>>>>>>; 53 | // ENTER LOOP ON Budget 54 | impl IDeterminantProviderInner 55 | for (Ok, Err, UInt) 56 | where 57 | Layout< 58 | UnionSize, 59 | DefaultRecursionBudget, 60 | Ok::ForbiddenValues, 61 | UnionMemberUnusedBits, 62 | Err::ForbiddenValues, 63 | Err::UnusedBits, 64 | Err::Size, 65 | Err::Align, 66 | U0, 67 | >: IDeterminantProviderInner, 68 | { 69 | same_as!( 70 | Layout< 71 | UnionSize, 72 | DefaultRecursionBudget, 73 | Ok::ForbiddenValues, 74 | UnionMemberUnusedBits, 75 | Err::ForbiddenValues, 76 | Err::UnusedBits, 77 | Err::Size, 78 | Err::Align, 79 | U0, 80 | > 81 | ); 82 | } 83 | 84 | // EXIT LOOP 85 | impl< 86 | UnionSize: Unsigned, 87 | OkFv: IForbiddenValues, 88 | OkUb: IBitMask, 89 | ErrFv: IForbiddenValues, 90 | ErrUb: IBitMask, 91 | ErrSize: Unsigned, 92 | ErrAlign: Alignment, 93 | ErrOffset: Unsigned, 94 | > IDeterminantProviderInner 95 | for Layout 96 | { 97 | same_as!(DeterminantProviderWithUnit); 98 | } 99 | 100 | type UnusedBits = <<::Padding as IStable>::UnusedBits as IBitMask>::BitOr<<::Shift as IBitMask>::BitOr<<<::Padding as IStable>::UnusedBits as IBitMask>::Shift>>; 101 | 102 | /// Branch on whether some forbidden values for Err fit inside Ok's unused bits 103 | impl< 104 | UnionSize: Unsigned, 105 | Budget, 106 | OkFv: IForbiddenValues, 107 | OkUb: IBitMask, 108 | ErrFv: IForbiddenValues, 109 | ErrUb: IBitMask, 110 | ErrSize: Unsigned, 111 | ErrAlign: Alignment, 112 | ErrOffset: Unsigned, 113 | > IDeterminantProviderInner 114 | for Layout, OkFv, OkUb, ErrFv, ErrUb, ErrSize, ErrAlign, ErrOffset> 115 | where 116 | DeterminantProvider< 117 | Self, 118 | < as IForbiddenValues>::SelectFrom< 119 | OkUb 120 | > as ISingleForbiddenValue>::Resolve, 121 | 123 | > as ISingleForbiddenValue>::Resolve, 124 | OkUb::BitAnd> 125 | >: IDeterminantProviderInner, 126 | { 127 | same_as!(DeterminantProvider< 128 | Self, 129 | < as IForbiddenValues>::SelectFrom< 130 | OkUb 131 | > as ISingleForbiddenValue>::Resolve, 132 | 134 | > as ISingleForbiddenValue>::Resolve, 135 | OkUb::BitAnd> 136 | >); 137 | } 138 | 139 | /// If some forbidden values for Err fit inside Ok's unused bits, exit the recursion 140 | impl< 141 | UnionSize: Unsigned, 142 | Budget, 143 | OkFv: IForbiddenValues, 144 | OkUb: IBitMask, 145 | ErrFv: IForbiddenValues, 146 | ErrUb: IBitMask, 147 | ErrSize: Unsigned, 148 | ErrAlign: Alignment, 149 | ErrOffset: Unsigned, 150 | Offset: Unsigned, 151 | V: Unsigned, 152 | Tail: IForbiddenValues + ISingleForbiddenValue + IntoValueIsErr, 153 | OkFvInErrUb: ISingleForbiddenValue, 154 | UbIntersect: IBitMask, 155 | > IDeterminantProviderInner 156 | for DeterminantProvider< 157 | Layout, 158 | Array, 159 | OkFvInErrUb, 160 | UbIntersect, 161 | > 162 | { 163 | type ErrShift = ErrOffset; 164 | type Determinant = Not< as IntoValueIsErr>::ValueIsErr>; 165 | type NicheExporter = NicheExporter; 166 | // type Debug = Self; 167 | } 168 | 169 | /// None of Err's forbidden values fit into Ok's unused bits, so branch on wherther 170 | /// some of Ok's forbidden values fit into Err's forbidden value 171 | /// 172 | /// If some forbidden values for Ok fit inside Err's unused bits, exit the recursion 173 | impl< 174 | UnionSize: Unsigned, 175 | Budget, 176 | OkFv: IForbiddenValues, 177 | OkUb: IBitMask, 178 | ErrFv: IForbiddenValues, 179 | ErrUb: IBitMask, 180 | ErrSize: Unsigned, 181 | ErrAlign: Alignment, 182 | ErrOffset: Unsigned, 183 | Offset: Unsigned, 184 | V: Unsigned, 185 | Tail: IForbiddenValues + ISingleForbiddenValue + IntoValueIsErr, 186 | UbIntersect: IBitMask, 187 | > IDeterminantProviderInner 188 | for DeterminantProvider< 189 | Layout, 190 | End, 191 | Array, 192 | UbIntersect, 193 | > 194 | { 195 | type ErrShift = ErrOffset; 196 | type Determinant = as IntoValueIsErr>::ValueIsErr; 197 | type NicheExporter = NicheExporter; 198 | // type Debug = Self; 199 | } 200 | 201 | /// If neither Err nor Ok's unused bits can fit any of the other's forbidden value, 202 | /// check if their unused bits have an intersection 203 | /// 204 | /// If Ok and Err's unused bits have an intersection, use it. 205 | impl< 206 | UnionSize: Unsigned, 207 | Budget, 208 | OkFv: IForbiddenValues, 209 | OkUb: IBitMask, 210 | ErrFv: IForbiddenValues, 211 | ErrUb: IBitMask, 212 | ErrSize: Unsigned, 213 | ErrAlign: Alignment, 214 | ErrOffset: Unsigned, 215 | Offset: Unsigned, 216 | V: NonZero, 217 | Tail: IBitMask, 218 | > IDeterminantProviderInner 219 | for DeterminantProvider< 220 | Layout, 221 | End, 222 | End, 223 | Array, 224 | > 225 | { 226 | type ErrShift = ErrOffset; 227 | type Determinant = BitIsErr< 228 | as IBitMask>::ExtractedBitByteOffset, 229 | as IBitMask>::ExtractedBitMask, 230 | >; 231 | type NicheExporter = 232 | NicheExporter as IBitMask>::ExtractBit, Saturator>; 233 | // type Debug = Self; 234 | } 235 | /// If no niche was found, check if Err can still be shifted to the right by its alignment. 236 | impl< 237 | UnionSize: Unsigned, 238 | Budget, 239 | OkFv: IForbiddenValues, 240 | OkUb: IBitMask, 241 | ErrFv: IForbiddenValues, 242 | ErrUb: IBitMask, 243 | ErrSize: Unsigned, 244 | ErrAlign: Alignment, 245 | ErrOffset: Unsigned, 246 | > IDeterminantProviderInner 247 | for DeterminantProvider< 248 | Layout, 249 | End, 250 | End, 251 | End, 252 | > 253 | where 254 | ( 255 | Layout, 256 | ::SmallerOrEq, 257 | ): IDeterminantProviderInner, 258 | { 259 | same_as!(( 260 | Layout, 261 | ::SmallerOrEq 262 | )); 263 | } 264 | /// If it can't be shifted 265 | impl< 266 | UnionSize: Unsigned, 267 | Budget, 268 | OkFv: IForbiddenValues, 269 | OkUb: IBitMask, 270 | ErrFv: IForbiddenValues, 271 | ErrUb: IBitMask, 272 | ErrSize: Unsigned, 273 | ErrAlign: Alignment, 274 | ErrOffset: Unsigned, 275 | > IDeterminantProviderInner 276 | for ( 277 | Layout, 278 | B0, 279 | ) 280 | { 281 | same_as!(DeterminantProviderWithUnit); 282 | } 283 | 284 | /// If it can be shifted 285 | impl< 286 | UnionSize: Unsigned, 287 | Budget, 288 | OkFv: IForbiddenValues, 289 | OkUb: IBitMask, 290 | ErrFv: IForbiddenValues, 291 | ErrUb: IBitMask, 292 | ErrSize: Unsigned, 293 | ErrAlign: Alignment, 294 | ErrOffset: Unsigned, 295 | > IDeterminantProviderInner 296 | for ( 297 | Layout, OkFv, OkUb, ErrFv, ErrUb, ErrSize, ErrAlign, ErrOffset>, 298 | B1, 299 | ) 300 | where 301 | Layout< 302 | UnionSize, 303 | Budget, 304 | OkFv, 305 | OkUb, 306 | ErrFv, 307 | ErrUb, 308 | ErrSize, 309 | ErrAlign, 310 | ErrAlign::Add, 311 | >: IDeterminantProviderInner, 312 | { 313 | same_as!(Layout< 314 | UnionSize, 315 | Budget, 316 | OkFv, 317 | OkUb, 318 | ErrFv, 319 | ErrUb, 320 | ErrSize, 321 | ErrAlign, 322 | ErrAlign::Add, 323 | >); 324 | } 325 | -------------------------------------------------------------------------------- /stabby-abi/src/enums/err_size_0.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | pub use super::*; 16 | // BRANCH Ok::ForbiddenValues 17 | impl IDeterminantProviderInner for (Ok, Err, UTerm) 18 | where 19 | DeterminantProviderWithUnit< 20 | ::SelectOne, 21 | Ok::UnusedBits, 22 | >: IDeterminantProviderInner, 23 | { 24 | same_as!(DeterminantProviderWithUnit< 25 | ::SelectOne, 26 | Ok::UnusedBits, 27 | >); 28 | } 29 | 30 | pub struct DeterminantProviderWithUnit( 31 | core::marker::PhantomData<(ForbiddenValues, UnusedBits)>, 32 | ); 33 | // IF Ok::ForbiddenValues 34 | impl< 35 | Offset: Unsigned, 36 | V: Unsigned, 37 | Tail: IForbiddenValues + IntoValueIsErr, 38 | UnusedBits: IBitMask, 39 | > IDeterminantProviderInner 40 | for DeterminantProviderWithUnit, UnusedBits> 41 | { 42 | type ErrShift = U0; 43 | type Determinant = as IntoValueIsErr>::ValueIsErr; 44 | type NicheExporter = NicheExporter; 45 | // type Debug = Self; 46 | } 47 | // ELSE IF Ok::UnusedBits 48 | impl IDeterminantProviderInner 49 | for DeterminantProviderWithUnit> 50 | { 51 | type ErrShift = U0; 52 | type Determinant = BitIsErr< 53 | as IBitMask>::ExtractedBitByteOffset, 54 | as IBitMask>::ExtractedBitMask, 55 | >; 56 | type NicheExporter = 57 | NicheExporter as IBitMask>::ExtractBit, Saturator>; 58 | // type Debug = Self; 59 | } 60 | // ELSE 61 | impl IDeterminantProviderInner for DeterminantProviderWithUnit { 62 | type Determinant = BitDeterminant; 63 | type ErrShift = U0; 64 | type NicheExporter = (); 65 | // type Debug = Self; 66 | } 67 | -------------------------------------------------------------------------------- /stabby-abi/src/enums/mod.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use core::marker::PhantomData; 16 | use stabby_macros::tyeval; 17 | 18 | use super::{ 19 | istable::{IBitMask, IForbiddenValues, ISingleForbiddenValue, NicheExporter, Saturator}, 20 | unsigned::NonZero, 21 | vtable::{H, T}, 22 | IStable, 23 | }; 24 | use crate::*; 25 | 26 | /// A type that can inspect a union to detect if it's in `ok` or `err` state. 27 | pub trait IDeterminant: IStable { 28 | /// Sets the union in `ok` state. 29 | /// # Safety 30 | /// This function MUST be called after setting `union` to a valid value for type `Ok` 31 | unsafe fn ok(union: *mut u8) -> Self; 32 | /// Sets the union in `err` state. 33 | /// # Safety 34 | /// This function MUST be called after setting `union` to a valid value for type `Err` 35 | unsafe fn err(union: *mut u8) -> Self; 36 | /// Returns the state of the union. 37 | fn is_det_ok(&self, union: *const u8) -> bool; 38 | /// Whether the determinant is explicit or implicit. 39 | type IsNicheTrick: Bit; 40 | } 41 | 42 | /// If no niche can be found, an external tag is used. 43 | #[repr(u8)] 44 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 45 | pub enum BitDeterminant { 46 | /// The union is in the `ok` state. 47 | Ok = 0, 48 | /// The union is in the `err` state. 49 | Err = 1, 50 | } 51 | // SAFETY: This is core to stabby's sum types: any sum type is a test of this. 52 | unsafe impl IStable for BitDeterminant { 53 | type Size = U1; 54 | type Align = U1; 55 | type ForbiddenValues = End; 56 | type UnusedBits = Array; 57 | type HasExactlyOneNiche = Saturator; 58 | type ContainsIndirections = B0; 59 | #[cfg(feature = "experimental-ctypes")] 60 | type CType = u8; 61 | primitive_report!("BitDeterminant"); 62 | } 63 | 64 | impl IDeterminant for BitDeterminant { 65 | unsafe fn ok(_: *mut u8) -> Self { 66 | BitDeterminant::Ok 67 | } 68 | unsafe fn err(_: *mut u8) -> Self { 69 | BitDeterminant::Err 70 | } 71 | fn is_det_ok(&self, _: *const u8) -> bool { 72 | (*self as u8 & 1) == 0 73 | } 74 | type IsNicheTrick = B0; 75 | } 76 | impl IDeterminant for End { 77 | unsafe fn ok(_: *mut u8) -> Self { 78 | End 79 | } 80 | unsafe fn err(_: *mut u8) -> Self { 81 | End 82 | } 83 | fn is_det_ok(&self, _: *const u8) -> bool { 84 | false 85 | } 86 | type IsNicheTrick = B0; 87 | } 88 | 89 | /// Indicates that if the `Offset`th byte equals `Value`, and that the `Tail` also says so, `Err` is the current variant of the inspected union. 90 | #[derive(Clone, Copy)] 91 | #[repr(C)] 92 | pub struct ValueIsErr(PhantomData<(Offset, Value)>, Tail); 93 | impl Unpin for ValueIsErr {} 94 | // SAFETY: This is core to stabby's sum types: any sum type is a test of this. 95 | unsafe impl IStable for ValueIsErr { 96 | type Size = Tail::Size; 97 | type Align = Tail::Align; 98 | type ForbiddenValues = Tail::ForbiddenValues; 99 | type UnusedBits = Tail::UnusedBits; 100 | type HasExactlyOneNiche = Tail::HasExactlyOneNiche; 101 | type ContainsIndirections = B0; 102 | #[cfg(feature = "experimental-ctypes")] 103 | type CType = (); 104 | primitive_report!("ValueIsErr"); 105 | } 106 | impl core::fmt::Debug 107 | for ValueIsErr 108 | { 109 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 110 | write!( 111 | f, 112 | "ValIsErr(ptr[{}]={}, {:?})", 113 | Offset::USIZE, 114 | Value::U8, 115 | &self.1, 116 | ) 117 | } 118 | } 119 | impl IDeterminant 120 | for ValueIsErr 121 | where 122 | ValueIsErr: IStable, 123 | { 124 | unsafe fn ok(union: *mut u8) -> Self { 125 | ValueIsErr(PhantomData, Tail::ok(union)) 126 | } 127 | unsafe fn err(union: *mut u8) -> Self { 128 | let ptr = union; 129 | *ptr.add(Offset::USIZE) = Value::U8; 130 | ValueIsErr(PhantomData, Tail::err(union)) 131 | } 132 | fn is_det_ok(&self, union: *const u8) -> bool { 133 | let ptr = union; 134 | unsafe { *ptr.add(Offset::USIZE) != Value::U8 || self.1.is_det_ok(union) } 135 | } 136 | type IsNicheTrick = B1; 137 | } 138 | /// Coerces a type into a [`ValueIsErr`]. 139 | pub trait IntoValueIsErr { 140 | /// The coerced type. 141 | type ValueIsErr: IDeterminant + IStable + Unpin; 142 | } 143 | impl IntoValueIsErr for End { 144 | type ValueIsErr = End; 145 | } 146 | impl IntoValueIsErr 147 | for Array 148 | { 149 | type ValueIsErr = ValueIsErr; 150 | } 151 | /// Indicates that if the `Offset`th byte bitanded with `Mask` is non-zero, `Err` is the current variant of the inspected union. 152 | #[crate::stabby] 153 | #[derive(Clone, Copy)] 154 | pub struct BitIsErr(PhantomData<(Offset, Mask)>); 155 | impl core::fmt::Debug for BitIsErr { 156 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 157 | write!(f, "BitIsErr(ptr[{}] & {})", Offset::USIZE, Mask::U8) 158 | } 159 | } 160 | impl Unpin for BitIsErr {} 161 | impl IDeterminant for BitIsErr { 162 | unsafe fn ok(union: *mut u8) -> Self { 163 | let ptr = union; 164 | if Mask::U8 == 1 { 165 | *ptr.add(Offset::USIZE) = 0; 166 | } 167 | *ptr.add(Offset::USIZE) &= u8::MAX ^ Mask::U8; 168 | BitIsErr(PhantomData) 169 | } 170 | unsafe fn err(union: *mut u8) -> Self { 171 | let ptr = union; 172 | if Mask::U8 == 1 { 173 | *ptr.add(Offset::USIZE) = 0; 174 | } 175 | *ptr.add(Offset::USIZE) |= Mask::U8; 176 | BitIsErr(PhantomData) 177 | } 178 | fn is_det_ok(&self, union: *const u8) -> bool { 179 | let ptr = union; 180 | unsafe { *ptr.add(Offset::USIZE) & Mask::U8 == 0 } 181 | } 182 | type IsNicheTrick = B1; 183 | } 184 | /// Inverts the return value of `Determinant`'s inspection. 185 | #[derive(Debug, Clone, Copy)] 186 | pub struct Not(Determinant); 187 | impl Unpin for Not {} 188 | // SAFETY: This is core to stabby's sum types: any sum type is a test of this. 189 | unsafe impl IStable for Not { 190 | type Size = Determinant::Size; 191 | type Align = Determinant::Align; 192 | type ForbiddenValues = Determinant::ForbiddenValues; 193 | type UnusedBits = Determinant::UnusedBits; 194 | type HasExactlyOneNiche = Determinant::HasExactlyOneNiche; 195 | type ContainsIndirections = Determinant::ContainsIndirections; 196 | #[cfg(feature = "experimental-ctypes")] 197 | type CType = Determinant::CType; 198 | primitive_report!("Not", Determinant); 199 | } 200 | impl IDeterminant for Not 201 | where 202 | Not: IStable, 203 | { 204 | unsafe fn ok(union: *mut u8) -> Self { 205 | Not(Determinant::err(union)) 206 | } 207 | unsafe fn err(union: *mut u8) -> Self { 208 | Not(Determinant::ok(union)) 209 | } 210 | fn is_det_ok(&self, union: *const u8) -> bool { 211 | !self.0.is_det_ok(union) 212 | } 213 | type IsNicheTrick = Determinant::IsNicheTrick; 214 | } 215 | 216 | // "And now for the tricky bit..." 217 | ///Proof that stabby can construct a [`crate::Result`] based on `Self` and `Other`'s niches. 218 | pub trait IDeterminantProvider: IStable { 219 | /// How much the `Ok` variant must be shifted. 220 | type OkShift: Unsigned; 221 | /// How much the `Err` variant must be shifted. 222 | type ErrShift: Unsigned; 223 | /// The discriminant. 224 | type Determinant: IDeterminant + Unpin; 225 | /// The remaining niches. 226 | type NicheExporter: IStable + Default + Copy + Unpin; 227 | } 228 | mod seal { 229 | use super::*; 230 | pub trait IDeterminantProviderInnerRev { 231 | type OkShift: Unsigned; 232 | type ErrShift: Unsigned; 233 | type Determinant: IDeterminant + Unpin; 234 | type NicheExporter: IStable + Default + Copy + Unpin; 235 | } 236 | pub trait IDeterminantProviderInner { 237 | type ErrShift: Unsigned; 238 | type Determinant: IDeterminant + Unpin; 239 | type NicheExporter: IStable + Default + Copy + Unpin; 240 | } 241 | } 242 | pub(crate) use seal::*; 243 | 244 | /// The alignment of `Union` 245 | type UnionAlign = <::Align as PowerOf2>::Max<::Align>; 246 | /// The size of `Union` 247 | type UnionSize = 248 | <::Size + OkShift) as Unsigned>::Max< 249 | tyeval!(::Size + ErrShift), 250 | > as Unsigned>::NextMultipleOf>; 251 | /// T::Size + Shift 252 | type PaddedSize = <::Size as Unsigned>::Add; 253 | /// T's unused bits, shifted by Shift bytes 254 | type ShiftedUnusedBits = <::UnusedBits as IBitMask>::Shift; 255 | 256 | /// The unused bits of the Ok variant in a Ok-Err union where the Ok is placed OkShift bytes from the left 257 | pub(crate) type UnionMemberUnusedBits = 258 | <<<::Padding as IStable>::UnusedBits as IBitMask>::BitOr< 259 | ShiftedUnusedBits, 260 | > as IBitMask>::BitOr< 261 | ShiftedUnusedBits< 262 | - PaddedSize) as Unsigned>::Padding, 263 | PaddedSize, 264 | >, 265 | >; 266 | 267 | macro_rules! same_as { 268 | ($T: ty) => { 269 | type ErrShift = <$T as IDeterminantProviderInner>::ErrShift; 270 | type Determinant = <$T as IDeterminantProviderInner>::Determinant; 271 | type NicheExporter = <$T as IDeterminantProviderInner>::NicheExporter; 272 | // type Debug = <$T as IDeterminantProviderInner>::Debug; 273 | }; 274 | ($T: ty, $Trait: ty) => { 275 | type OkShift = <$T as $Trait>::OkShift; 276 | type ErrShift = <$T as $Trait>::ErrShift; 277 | type Determinant = <$T as $Trait>::Determinant; 278 | type NicheExporter = <$T as $Trait>::NicheExporter; 279 | // type Debug = <$T as $Trait>::Debug; 280 | }; 281 | } 282 | 283 | impl IDeterminantProvider for A 284 | where 285 | (A, B, ::GreaterOrEq): IDeterminantProviderInnerRev, 286 | { 287 | same_as!( 288 | (A, B, ::GreaterOrEq), 289 | IDeterminantProviderInnerRev 290 | ); 291 | } 292 | 293 | // IF Ok::Size < Err::Size 294 | impl IDeterminantProviderInnerRev for (Ok, Err, B0) 295 | where 296 | (Err, Ok, Ok::Size): IDeterminantProviderInner, 297 | { 298 | type OkShift = <(Err, Ok, Ok::Size) as IDeterminantProviderInner>::ErrShift; 299 | type ErrShift = U0; 300 | type Determinant = Not<<(Err, Ok, Ok::Size) as IDeterminantProviderInner>::Determinant>; 301 | type NicheExporter = <(Err, Ok, Ok::Size) as IDeterminantProviderInner>::NicheExporter; 302 | // type Debug = <(Err, Ok, Ok::Size) as IDeterminantProviderInner>::Debug; 303 | } 304 | // ELSE 305 | impl IDeterminantProviderInnerRev for (Ok, Err, B1) 306 | where 307 | (Ok, Err, Err::Size): IDeterminantProviderInner, 308 | { 309 | type OkShift = U0; 310 | same_as!((Ok, Err, Err::Size)); 311 | } 312 | 313 | // IF Err::Size == 0 314 | mod err_non_empty; 315 | mod err_size_0; 316 | -------------------------------------------------------------------------------- /stabby-abi/src/iter.rs: -------------------------------------------------------------------------------- 1 | use crate::{vtable::HasDropVt, IDeterminantProvider, IPtrMut, IPtrOwned}; 2 | 3 | /// [`core::iter::Iterator`], but ABI-stable. 4 | #[crate::stabby] 5 | pub trait Iterator { 6 | /// The type of the elements of the iterator. 7 | type Item: IDeterminantProvider<()>; 8 | /// Returns the next element in the iterator if it exists. 9 | extern "C" fn next(&mut self) -> crate::Option; 10 | /// See [`core::iter::Iterator::size_hint`] 11 | extern "C" fn size_hint(&self) -> crate::Tuple>; 12 | } 13 | 14 | impl Iterator for T 15 | where 16 | T::Item: IDeterminantProvider<()>, 17 | { 18 | type Item = T::Item; 19 | extern "C" fn next(&mut self) -> crate::Option { 20 | core::iter::Iterator::next(self).into() 21 | } 22 | extern "C" fn size_hint(&self) -> crate::Tuple> { 23 | let (min, max) = core::iter::Iterator::size_hint(self); 24 | crate::Tuple(min, max.into()) 25 | } 26 | } 27 | 28 | impl> core::iter::Iterator 29 | for crate::Dyn<'_, P, crate::vtable::VTable, Vt>> 30 | { 31 | type Item = Output; 32 | fn next(&mut self) -> Option { 33 | // SAFETY: we're accessing a `StableLike` that was unsafely but properly constructed. 34 | unsafe { 35 | (self.vtable().head.next.as_ref_unchecked())( 36 | self.ptr_mut().as_mut(), 37 | core::marker::PhantomData, 38 | ) 39 | .into() 40 | } 41 | } 42 | fn size_hint(&self) -> (usize, Option) { 43 | let crate::Tuple(min, max) = // SAFETY: we're accessing a `StableLike` that was unsafely but properly constructed. 44 | unsafe { (self.vtable().head.size_hint.as_ref_unchecked())(self.ptr().as_ref(), core::marker::PhantomData) }; 45 | (min, max.into()) 46 | } 47 | } 48 | 49 | impl<'a, Output> crate::vtable::CompoundVt<'a> for dyn core::iter::Iterator 50 | where 51 | dyn Iterator: crate::vtable::CompoundVt<'a>, 52 | { 53 | type Vt = as crate::vtable::CompoundVt<'a>>::Vt; 54 | } 55 | -------------------------------------------------------------------------------- /stabby-abi/src/option.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | //! A stable option for when rust's `Option` isn't! 16 | 17 | use crate::enums::IDeterminantProvider; 18 | use crate::result::OkGuard; 19 | use crate::{unreachable_unchecked, IStable}; 20 | 21 | /// A niche optimizing equivalent of [`core::option::Option`] that's ABI-stable regardless of the inner type's niches. 22 | #[crate::stabby] 23 | #[derive(Clone, PartialEq, Eq, Hash)] 24 | pub struct Option> { 25 | inner: crate::result::Result, 26 | } 27 | impl core::fmt::Debug for Option 28 | where 29 | T: IDeterminantProvider<()>, 30 | T: core::fmt::Debug, 31 | { 32 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 33 | self.as_ref().fmt(f) 34 | } 35 | } 36 | impl From> for Option 37 | where 38 | T: IDeterminantProvider<()>, 39 | { 40 | fn from(value: core::option::Option) -> Self { 41 | match value { 42 | Some(value) => Self { 43 | inner: crate::result::Result::Ok(value), 44 | }, 45 | None => Self { 46 | inner: crate::result::Result::Err(()), 47 | }, 48 | } 49 | } 50 | } 51 | impl From> for core::option::Option 52 | where 53 | T: IDeterminantProvider<()>, 54 | { 55 | fn from(value: Option) -> Self { 56 | value.inner.ok() 57 | } 58 | } 59 | impl Default for Option 60 | where 61 | T: IDeterminantProvider<()>, 62 | { 63 | fn default() -> Self { 64 | Self::None() 65 | } 66 | } 67 | /// A guard that ensures that niche determinants are reinserted if the `Some` variant of an [`Option`] is re-established after it may have been mutated. 68 | /// 69 | /// When dropped, this guard ensures that the result's determinant is properly set. 70 | /// Failing to drop this guard may result in undefined behaviour. 71 | pub type SomeGuard<'a, T> = OkGuard<'a, T, ()>; 72 | impl Option 73 | where 74 | T: IDeterminantProvider<()>, 75 | { 76 | /// Construct the `Some` variant. 77 | #[allow(non_snake_case)] 78 | pub fn Some(value: T) -> Self { 79 | Self { 80 | inner: crate::result::Result::Ok(value), 81 | } 82 | } 83 | /// Construct the `None` variant. 84 | #[allow(non_snake_case)] 85 | pub fn None() -> Self { 86 | Self { 87 | inner: crate::result::Result::Err(()), 88 | } 89 | } 90 | /// Returns a reference to the option's contents if they exist. 91 | pub fn as_ref(&self) -> core::option::Option<&T> { 92 | self.match_ref(Some, || None) 93 | } 94 | /// Returns a mutable reference to the option's contents if they exist. 95 | pub fn as_mut(&mut self) -> core::option::Option> { 96 | self.match_mut(Some, || None) 97 | } 98 | /// Equivalent to `match &self`. If you need multiple branches to obtain mutable access or ownership 99 | /// of a local, use [`Self::match_ref_ctx`] instead. 100 | pub fn match_ref<'a, U, FnSome: FnOnce(&'a T) -> U, FnNone: FnOnce() -> U>( 101 | &'a self, 102 | some: FnSome, 103 | none: FnNone, 104 | ) -> U { 105 | self.inner.match_ref(some, |_| none()) 106 | } 107 | /// Equivalent to `match &self`. 108 | pub fn match_ref_ctx<'a, I, U, FnSome: FnOnce(I, &'a T) -> U, FnNone: FnOnce(I) -> U>( 109 | &'a self, 110 | ctx: I, 111 | some: FnSome, 112 | none: FnNone, 113 | ) -> U { 114 | self.inner.match_ref_ctx(ctx, some, move |ctx, _| none(ctx)) 115 | } 116 | /// Equivalent to `match &mut self`. If you need multiple branches to obtain mutable access or ownership 117 | /// of a local, use [`Self::match_mut_ctx`] instead. 118 | pub fn match_mut<'a, U, FnSome: FnOnce(SomeGuard<'a, T>) -> U, FnNone: FnOnce() -> U>( 119 | &'a mut self, 120 | some: FnSome, 121 | none: FnNone, 122 | ) -> U { 123 | self.inner.match_mut(some, |_| none()) 124 | } 125 | /// Equivalent to `match &mut self`. 126 | pub fn match_mut_ctx< 127 | 'a, 128 | I, 129 | U, 130 | FnSome: FnOnce(I, SomeGuard<'a, T>) -> U, 131 | FnNone: FnOnce(I) -> U, 132 | >( 133 | &'a mut self, 134 | ctx: I, 135 | some: FnSome, 136 | none: FnNone, 137 | ) -> U { 138 | self.inner.match_mut_ctx(ctx, some, move |ctx, _| none(ctx)) 139 | } 140 | /// Equivalent to `match self`. If you need multiple branches to obtain mutable access or ownership 141 | /// of a local, use [`Self::match_owned_ctx`] instead. 142 | pub fn match_owned U, FnNone: FnOnce() -> U>( 143 | self, 144 | some: FnSome, 145 | none: FnNone, 146 | ) -> U { 147 | self.inner.match_owned(some, |_| none()) 148 | } 149 | /// Equivalent to `match self`. 150 | pub fn match_owned_ctx U, FnNone: FnOnce(I) -> U>( 151 | self, 152 | ctx: I, 153 | some: FnSome, 154 | none: FnNone, 155 | ) -> U { 156 | self.inner 157 | .match_owned_ctx(ctx, some, move |ctx, _| none(ctx)) 158 | } 159 | /// Returns `true` if `self` contains a value. 160 | pub fn is_some(&self) -> bool { 161 | self.inner.is_ok() 162 | } 163 | /// Returns `true` if `self` doesn't contain a value. 164 | pub fn is_none(&self) -> bool { 165 | !self.is_some() 166 | } 167 | /// Unwraps the option, or runs `f` if no value was in it. 168 | pub fn unwrap_or_else T>(self, f: F) -> T { 169 | self.match_owned(|x| x, f) 170 | } 171 | /// # Safety 172 | /// Calling this on `Self::None()` is UB. 173 | pub unsafe fn unwrap_unchecked(self) -> T { 174 | // SAFETY: Caller-guaranteed 175 | self.unwrap_or_else(|| unsafe { unreachable_unchecked!() }) 176 | } 177 | /// # Panics 178 | /// If `!self.is_some` 179 | pub fn unwrap(self) -> T { 180 | self.unwrap_or_else(|| panic!("Option::unwrap called on None")) 181 | } 182 | } 183 | 184 | #[cfg(feature = "serde")] 185 | mod serde_impl { 186 | use super::*; 187 | use serde::{Deserialize, Serialize}; 188 | impl Serialize for Option 189 | where 190 | Ok: IDeterminantProvider<()>, 191 | { 192 | fn serialize(&self, serializer: S) -> core::result::Result 193 | where 194 | S: serde::Serializer, 195 | { 196 | let this = self.as_ref(); 197 | this.serialize(serializer) 198 | } 199 | } 200 | impl<'a, Ok: IDeterminantProvider<()>> Deserialize<'a> for Option 201 | where 202 | core::option::Option: Deserialize<'a>, 203 | { 204 | fn deserialize(deserializer: D) -> core::result::Result 205 | where 206 | D: serde::Deserializer<'a>, 207 | { 208 | Ok(core::option::Option::::deserialize(deserializer)?.into()) 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /stabby-abi/src/report.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use crate::{str::Str, StableLike}; 16 | use sha2_const_stable::Sha256; 17 | 18 | /// A type 19 | type NextField = StableLike, usize>; 20 | 21 | /// A report of a type's layout. 22 | #[crate::stabby] 23 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 24 | pub struct TypeReport { 25 | /// The type's name. 26 | pub name: Str<'static>, 27 | /// The type's parent module's path. 28 | pub module: Str<'static>, 29 | /// The fields of this type. 30 | pub fields: NextField, 31 | /// How the type was declared 32 | pub tyty: TyTy, 33 | /// The version of the type's invariants. 34 | pub version: u32, 35 | } 36 | 37 | impl core::fmt::Display for TypeReport { 38 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 39 | let Self { 40 | name, 41 | module, 42 | version, 43 | tyty, 44 | .. 45 | } = self; 46 | write!(f, "{tyty:?} {module} :: {name} (version{version}) {{")?; 47 | for FieldReport { name, ty, .. } in self.fields() { 48 | write!(f, "{name}: {ty}, ")? 49 | } 50 | write!(f, "}}") 51 | } 52 | } 53 | impl core::hash::Hash for TypeReport { 54 | fn hash(&self, state: &mut H) { 55 | self.name.hash(state); 56 | self.module.hash(state); 57 | for field in self.fields() { 58 | field.hash(state); 59 | } 60 | self.version.hash(state); 61 | self.tyty.hash(state); 62 | } 63 | } 64 | 65 | impl TypeReport { 66 | /// Whether or not two reports correspond to the same type, with the same layout and invariants. 67 | pub fn is_compatible(&self, other: &Self) -> bool { 68 | self.name == other.name 69 | && self.module == other.module 70 | && self.version == other.version 71 | && self.tyty == other.tyty 72 | && self 73 | .fields() 74 | .zip(other.fields()) 75 | .all(|(s, o)| s.name == o.name && s.ty.is_compatible(o.ty)) 76 | } 77 | } 78 | 79 | /// How a type was declared. 80 | #[crate::stabby] 81 | #[repr(u8)] 82 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] 83 | pub enum TyTy { 84 | /// As a struct 85 | Struct, 86 | /// As an enum (with which calling convention) 87 | Enum(Str<'static>), 88 | /// As a union. 89 | Union, 90 | } 91 | 92 | /// A type's field. 93 | #[crate::stabby] 94 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 95 | pub struct FieldReport { 96 | /// The field's name. 97 | pub name: Str<'static>, 98 | /// The field;s type. 99 | pub ty: &'static TypeReport, 100 | /// The next field in the [`TypeReport`] 101 | pub next_field: NextField, 102 | } 103 | impl core::hash::Hash for FieldReport { 104 | fn hash(&self, state: &mut H) { 105 | self.name.hash(state); 106 | self.ty.hash(state); 107 | } 108 | } 109 | 110 | impl TypeReport { 111 | /// Returns an iterator over the type's fields. 112 | pub const fn fields(&self) -> Fields { 113 | Fields(self.fields.value) 114 | } 115 | } 116 | /// An iterator over a type's fields. 117 | #[crate::stabby] 118 | pub struct Fields(Option<&'static FieldReport>); 119 | impl Fields { 120 | /// A `const` compatible alternative to [`Iterator::next`] 121 | pub const fn next_const(self) -> (Self, Option<&'static FieldReport>) { 122 | match self.0 { 123 | Some(field) => (Self(*field.next_field.as_ref()), Some(field)), 124 | None => (self, None), 125 | } 126 | } 127 | } 128 | impl Iterator for Fields { 129 | type Item = &'static FieldReport; 130 | fn next(&mut self) -> Option { 131 | let field = self.0.take()?; 132 | self.0 = field.next_field.value; 133 | Some(field) 134 | } 135 | } 136 | 137 | const fn hash_report(mut hash: Sha256, report: &TypeReport) -> Sha256 { 138 | hash = hash 139 | .update(report.module.as_str().as_bytes()) 140 | .update(report.name.as_str().as_bytes()); 141 | hash = match report.tyty { 142 | crate::report::TyTy::Struct => hash.update(&[0]), 143 | crate::report::TyTy::Union => hash.update(&[1]), 144 | crate::report::TyTy::Enum(s) => hash.update(s.as_str().as_bytes()), 145 | }; 146 | let mut fields = report.fields(); 147 | while let (new, Some(next)) = fields.next_const() { 148 | fields = new; 149 | hash = hash_report(hash.update(next.name.as_str().as_bytes()), next.ty) 150 | } 151 | hash 152 | } 153 | 154 | #[rustversion::attr(nightly, allow(unnecessary_transmutes))] 155 | const ARCH_INFO: [u8; 8] = [ 156 | 0, 157 | core::mem::size_of::() as u8, 158 | core::mem::align_of::() as u8, 159 | core::mem::size_of::<&()>() as u8, 160 | core::mem::align_of::<&()>() as u8, 161 | core::mem::align_of::() as u8, 162 | // SAFETY: allows us to observe the architecture's endianness 163 | unsafe { core::mem::transmute::<[u8; 2], u16>([0, 1]) } as u8, 164 | 0, 165 | ]; 166 | 167 | /// Generates an ID based on a [`TypeReport`] using [`Sha256`]. 168 | /// 169 | /// The hash also contains information about the architecture, allowing to consider a same struct distinct depending 170 | /// on the architecture's pointer size, alignment of u128, or endianness. 171 | pub const fn gen_id(report: &TypeReport) -> u64 { 172 | let [hash @ .., _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _] = 173 | hash_report(Sha256::new(), report).finalize(); 174 | u64::from_le_bytes(hash) ^ u64::from_le_bytes(ARCH_INFO) 175 | } 176 | -------------------------------------------------------------------------------- /stabby-abi/src/slice.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | //! Stable slices! 16 | 17 | use crate as stabby; 18 | use core::ops::{Deref, DerefMut}; 19 | 20 | /// An ABI stable equivalent of `&'a [T]` 21 | #[stabby::stabby] 22 | pub struct Slice<'a, T: 'a> { 23 | /// The start of the slice. 24 | pub start: core::ptr::NonNull, 25 | /// The length of the slice. 26 | pub len: usize, 27 | /// Ensures the slice has correct lifetime and variance. 28 | pub marker: core::marker::PhantomData<&'a ()>, 29 | } 30 | // SAFETY: Slices are analogous to references. 31 | unsafe impl<'a, T: 'a> Send for Slice<'a, T> where &'a T: Send {} 32 | // SAFETY: Slices are analogous to references. 33 | unsafe impl<'a, T: 'a> Sync for Slice<'a, T> where &'a T: Sync {} 34 | impl<'a, T: 'a> Clone for Slice<'a, T> { 35 | fn clone(&self) -> Self { 36 | *self 37 | } 38 | } 39 | impl<'a, T: 'a> Copy for Slice<'a, T> {} 40 | 41 | impl<'a, T: 'a> Slice<'a, T> { 42 | /// Convert `&[T]` to its ABI-stable equivalent. 43 | pub const fn new(value: &'a [T]) -> Self { 44 | Self { 45 | start: unsafe { core::ptr::NonNull::new_unchecked(value.as_ptr() as *mut T) }, 46 | len: value.len(), 47 | marker: core::marker::PhantomData, 48 | } 49 | } 50 | /// Obtain `&[T]` from its ABI-stable equivalent. 51 | pub const fn as_slice(self) -> &'a [T] { 52 | unsafe { core::slice::from_raw_parts(self.start.as_ptr(), self.len) } 53 | } 54 | } 55 | impl<'a, T> From<&'a [T]> for Slice<'a, T> { 56 | fn from(value: &'a [T]) -> Self { 57 | Self::new(value) 58 | } 59 | } 60 | impl<'a, T> From<&'a mut [T]> for Slice<'a, T> { 61 | fn from(value: &'a mut [T]) -> Self { 62 | Self { 63 | start: unsafe { core::ptr::NonNull::new_unchecked(value.as_ptr() as *mut T) }, 64 | len: value.len(), 65 | marker: core::marker::PhantomData, 66 | } 67 | } 68 | } 69 | 70 | impl<'a, T> From> for &'a [T] { 71 | fn from(value: Slice<'a, T>) -> Self { 72 | unsafe { core::slice::from_raw_parts(value.start.as_ptr(), value.len) } 73 | } 74 | } 75 | impl Deref for Slice<'_, T> { 76 | type Target = [T]; 77 | fn deref(&self) -> &Self::Target { 78 | unsafe { core::slice::from_raw_parts(self.start.as_ptr(), self.len) } 79 | } 80 | } 81 | impl<'a, T: 'a> Eq for Slice<'a, T> where for<'b> &'b [T]: Eq {} 82 | impl<'a, T: 'a> PartialEq for Slice<'a, T> 83 | where 84 | for<'b> &'b [T]: PartialEq, 85 | { 86 | fn eq(&self, other: &Self) -> bool { 87 | self.deref() == other.deref() 88 | } 89 | } 90 | impl<'a, T: 'a> core::fmt::Debug for Slice<'a, T> 91 | where 92 | for<'b> &'b [T]: core::fmt::Debug, 93 | { 94 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 95 | self.deref().fmt(f) 96 | } 97 | } 98 | impl<'a, T: 'a> core::fmt::Display for Slice<'a, T> 99 | where 100 | for<'b> &'b [T]: core::fmt::Display, 101 | { 102 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 103 | self.deref().fmt(f) 104 | } 105 | } 106 | impl<'a, T: 'a> core::hash::Hash for Slice<'a, T> 107 | where 108 | for<'b> &'b [T]: core::hash::Hash, 109 | { 110 | fn hash(&self, state: &mut H) { 111 | self.deref().hash(state); 112 | } 113 | } 114 | 115 | /// An ABI stable equivalent of `&'a mut T` 116 | #[stabby::stabby] 117 | pub struct SliceMut<'a, T: 'a> { 118 | /// The start of the slice. 119 | pub start: core::ptr::NonNull, 120 | /// The length of the slice. 121 | pub len: usize, 122 | /// Ensures the slice has correct lifetime and variance. 123 | pub marker: core::marker::PhantomData<&'a mut ()>, 124 | } 125 | // SAFETY: SliceMut is analogous to a mutable reference 126 | unsafe impl<'a, T: 'a> Send for SliceMut<'a, T> where &'a mut T: Send {} 127 | // SAFETY: SliceMut is analogous to a mutable reference 128 | unsafe impl<'a, T: 'a> Sync for SliceMut<'a, T> where &'a mut T: Sync {} 129 | impl Deref for SliceMut<'_, T> { 130 | type Target = [T]; 131 | fn deref(&self) -> &Self::Target { 132 | unsafe { core::slice::from_raw_parts(self.start.as_ref(), self.len) } 133 | } 134 | } 135 | impl DerefMut for SliceMut<'_, T> { 136 | fn deref_mut(&mut self) -> &mut Self::Target { 137 | unsafe { core::slice::from_raw_parts_mut(self.start.as_mut(), self.len) } 138 | } 139 | } 140 | impl<'a, T: 'a> Eq for SliceMut<'a, T> where for<'b> &'b [T]: Eq {} 141 | impl<'a, T: 'a> PartialEq for SliceMut<'a, T> 142 | where 143 | for<'b> &'b [T]: PartialEq, 144 | { 145 | fn eq(&self, other: &Self) -> bool { 146 | self.deref() == other.deref() 147 | } 148 | } 149 | impl<'a, T: 'a> core::fmt::Debug for SliceMut<'a, T> 150 | where 151 | for<'b> &'b [T]: core::fmt::Debug, 152 | { 153 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 154 | self.deref().fmt(f) 155 | } 156 | } 157 | impl<'a, T: 'a> core::fmt::Display for SliceMut<'a, T> 158 | where 159 | for<'b> &'b [T]: core::fmt::Display, 160 | { 161 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 162 | self.deref().fmt(f) 163 | } 164 | } 165 | impl<'a, T: 'a> core::hash::Hash for SliceMut<'a, T> 166 | where 167 | for<'b> &'b [T]: core::hash::Hash, 168 | { 169 | fn hash(&self, state: &mut H) { 170 | self.deref().hash(state); 171 | } 172 | } 173 | impl<'a, T> From<&'a mut [T]> for SliceMut<'a, T> { 174 | fn from(value: &'a mut [T]) -> Self { 175 | Self { 176 | start: unsafe { core::ptr::NonNull::new_unchecked(value.as_mut_ptr()) }, 177 | len: value.len(), 178 | marker: core::marker::PhantomData, 179 | } 180 | } 181 | } 182 | impl<'a, T> From> for Slice<'a, T> { 183 | fn from(value: SliceMut<'a, T>) -> Self { 184 | Self { 185 | start: value.start, 186 | len: value.len, 187 | marker: core::marker::PhantomData, 188 | } 189 | } 190 | } 191 | impl<'a, T> From> for &'a mut [T] { 192 | fn from(mut value: SliceMut<'a, T>) -> Self { 193 | unsafe { core::slice::from_raw_parts_mut(value.start.as_mut(), value.len) } 194 | } 195 | } 196 | 197 | impl<'a, T> From> for &'a [T] { 198 | fn from(mut value: SliceMut<'a, T>) -> Self { 199 | unsafe { core::slice::from_raw_parts(value.start.as_mut(), value.len) } 200 | } 201 | } 202 | 203 | #[cfg(feature = "serde")] 204 | mod serde_impl { 205 | use super::*; 206 | use serde::{de::Visitor, Deserialize, Serialize}; 207 | impl Serialize for Slice<'_, T> { 208 | fn serialize(&self, serializer: S) -> Result 209 | where 210 | S: serde::Serializer, 211 | { 212 | let slice: &[T] = self; 213 | slice.serialize(serializer) 214 | } 215 | } 216 | impl Serialize for SliceMut<'_, T> { 217 | fn serialize(&self, serializer: S) -> Result 218 | where 219 | S: serde::Serializer, 220 | { 221 | let slice: &[T] = self; 222 | slice.serialize(serializer) 223 | } 224 | } 225 | impl<'a> Deserialize<'a> for Slice<'a, u8> { 226 | fn deserialize(deserializer: D) -> Result 227 | where 228 | D: serde::Deserializer<'a>, 229 | { 230 | deserializer.deserialize_bytes(BytesVisitor(core::marker::PhantomData)) 231 | } 232 | } 233 | struct BytesVisitor<'a>(core::marker::PhantomData>); 234 | impl<'a> Visitor<'a> for BytesVisitor<'a> { 235 | type Value = Slice<'a, u8>; 236 | fn visit_borrowed_bytes(self, v: &'a [u8]) -> Result 237 | where 238 | E: serde::de::Error, 239 | { 240 | Ok(v.into()) 241 | } 242 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 243 | write!(formatter, "A borrowed_str") 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /stabby-abi/src/stable_impls/abi_stable.rs: -------------------------------------------------------------------------------- 1 | use core::ptr::NonNull; 2 | 3 | use crate::{typenum2::*, End, IStable, Tuple}; 4 | 5 | // SAFETY: Compile-time checks are in place. 6 | unsafe impl<'a, T: IStable> IStable for abi_stable::RRef<'a, T> { 7 | same_as!(core::ptr::NonNull); 8 | type ContainsIndirections = B1; 9 | primitive_report!("abi_stable::RRef", T); 10 | } 11 | check!(abi_stable::RRef); 12 | 13 | // SAFETY: Compile-time checks are in place. 14 | unsafe impl<'a, T: IStable> IStable for abi_stable::RMut<'a, T> { 15 | same_as!(core::ptr::NonNull); 16 | type ContainsIndirections = B1; 17 | primitive_report!("abi_stable::RMut", T); 18 | } 19 | check!(abi_stable::RMut); 20 | 21 | // SAFETY: Compile-time checks are in place. 22 | unsafe impl IStable for abi_stable::std_types::RVec { 23 | type Size = < as IStable>::Size as Unsigned>::Mul; 24 | type Align = as IStable>::Align; 25 | type ForbiddenValues = as IStable>::ForbiddenValues; 26 | type UnusedBits = End; 27 | type HasExactlyOneNiche = B1; 28 | type ContainsIndirections = B1; 29 | type CType = [*const T; 4]; 30 | primitive_report!("abi_stable::std_types::RVec", T); 31 | } 32 | check!(abi_stable::std_types::RVec); 33 | 34 | // SAFETY: Compile-time checks are in place. 35 | unsafe impl IStable for abi_stable::std_types::RString { 36 | same_as!(abi_stable::std_types::RVec); 37 | type ContainsIndirections = B1; 38 | primitive_report!("abi_stable::std_types::RString"); 39 | } 40 | check!(abi_stable::std_types::RString); 41 | 42 | // SAFETY: Compile-time checks are in place. 43 | unsafe impl<'a, T: IStable> IStable for abi_stable::std_types::RSlice<'a, T> { 44 | type Size = < as IStable>::Size as Unsigned>::Mul; 45 | type Align = as IStable>::Align; 46 | type ForbiddenValues = End; 47 | type UnusedBits = End; 48 | type HasExactlyOneNiche = B0; 49 | type ContainsIndirections = B1; 50 | type CType = [*const T; 2]; 51 | primitive_report!("abi_stable::std_types::RSlice", T); 52 | } 53 | check!(abi_stable::std_types::RSlice); 54 | 55 | // SAFETY: Compile-time checks are in place. 56 | unsafe impl<'a> IStable for abi_stable::std_types::RStr<'a> { 57 | same_as!(abi_stable::std_types::RSlice<'a, u8>); 58 | type ContainsIndirections = B1; 59 | primitive_report!("abi_stable::std_types::RStr"); 60 | } 61 | check!(abi_stable::std_types::RStr); 62 | 63 | // SAFETY: Compile-time checks are in place. 64 | unsafe impl<'a, T: IStable> IStable for abi_stable::std_types::RSliceMut<'a, T> { 65 | type Size = < as IStable>::Size as Unsigned>::Mul; 66 | type Align = as IStable>::Align; 67 | type ForbiddenValues = End; 68 | type UnusedBits = End; 69 | type HasExactlyOneNiche = B0; 70 | type ContainsIndirections = B1; 71 | type CType = [*const T; 2]; 72 | primitive_report!("abi_stable::std_types::RSliceMut", T); 73 | } 74 | check!(abi_stable::std_types::RSliceMut); 75 | 76 | // SAFETY: Compile-time checks are in place. 77 | unsafe impl IStable for abi_stable::std_types::RHashMap 78 | where 79 | Tuple: IStable, 80 | { 81 | type Size = < as IStable>::Size as Unsigned>::Mul; 82 | type Align = as IStable>::Align; 83 | type ForbiddenValues = as IStable>::ForbiddenValues; 84 | type UnusedBits = End; 85 | type HasExactlyOneNiche = B1; 86 | type ContainsIndirections = B1; 87 | type CType = [*const (); 3]; 88 | primitive_report!("abi_stable::std_types::RHashMap", Tuple); 89 | } 90 | check!(abi_stable::std_types::RHashMap); 91 | 92 | // SAFETY: Compile-time checks are in place. 93 | unsafe impl IStable for abi_stable::std_types::RDuration { 94 | same_as!(Tuple); 95 | type ContainsIndirections = B0; 96 | primitive_report!("abi_stable::std_types::RDuration"); 97 | } 98 | check!(abi_stable::std_types::RDuration); 99 | 100 | // SAFETY: Compile-time checks are in place. 101 | unsafe impl IStable for abi_stable::std_types::RBox { 102 | type Size = < as IStable>::Size as Unsigned>::Mul; 103 | type Align = as IStable>::Align; 104 | type ForbiddenValues = as IStable>::ForbiddenValues; 105 | type UnusedBits = End; 106 | type HasExactlyOneNiche = B1; 107 | type ContainsIndirections = B1; 108 | type CType = [*const T; 2]; 109 | primitive_report!("abi_stable::std_types::RBox", T); 110 | } 111 | check!(abi_stable::std_types::RBox); 112 | 113 | // SAFETY: Compile-time checks are in place. 114 | unsafe impl IStable for abi_stable::std_types::RBoxError { 115 | type Size = < as IStable>::Size as Unsigned>::Mul; 116 | type Align = as IStable>::Align; 117 | type ForbiddenValues = as IStable>::ForbiddenValues; 118 | type UnusedBits = End; 119 | type HasExactlyOneNiche = B1; 120 | type ContainsIndirections = B1; 121 | type CType = [*const (); 3]; 122 | primitive_report!("abi_stable::std_types::RBoxError"); 123 | } 124 | check!(abi_stable::std_types::RBoxError); 125 | 126 | // SAFETY: Compile-time checks are in place. 127 | unsafe impl IStable for abi_stable::std_types::SendRBoxError { 128 | same_as!(abi_stable::std_types::RBoxError); 129 | type ContainsIndirections = B1; 130 | primitive_report!("abi_stable::std_types::SendRBoxError"); 131 | } 132 | check!(abi_stable::std_types::SendRBoxError); 133 | 134 | // SAFETY: Compile-time checks are in place. 135 | unsafe impl IStable for abi_stable::std_types::UnsyncRBoxError { 136 | same_as!(abi_stable::std_types::RBoxError); 137 | type ContainsIndirections = B1; 138 | primitive_report!("abi_stable::std_types::UnsyncRBoxError"); 139 | } 140 | check!(abi_stable::std_types::UnsyncRBoxError); 141 | 142 | // SAFETY: Compile-time checks are in place. 143 | unsafe impl IStable for abi_stable::std_types::Tuple1 { 144 | same_as!(T); 145 | type ContainsIndirections = T::ContainsIndirections; 146 | primitive_report!("abi_stable::std_types::Tuple1", T); 147 | } 148 | check!(abi_stable::std_types::Tuple1); 149 | 150 | // SAFETY: Compile-time checks are in place. 151 | unsafe impl IStable for abi_stable::std_types::Tuple2 152 | where 153 | Tuple: IStable, 154 | { 155 | same_as!(Tuple); 156 | type ContainsIndirections = as IStable>::ContainsIndirections; 157 | primitive_report!("abi_stable::std_types::Tuple2", Tuple); 158 | } 159 | check!(abi_stable::std_types::Tuple2); 160 | 161 | // SAFETY: Compile-time checks are in place. 162 | unsafe impl IStable for abi_stable::std_types::RArc { 163 | same_as!(Tuple<*const (), NonNull<()>>); 164 | type ContainsIndirections = B1; 165 | primitive_report!("abi_stable::std_types::RArc", T); 166 | } 167 | check!(abi_stable::std_types::RArc); 168 | 169 | mod seal { 170 | use core::cell::UnsafeCell; 171 | 172 | #[crate::stabby] 173 | pub struct RMutex { 174 | opaque_mutex: *const (), 175 | value: UnsafeCell, 176 | vtable: &'static (), 177 | } 178 | } 179 | 180 | // SAFETY: Compile-time checks are in place. 181 | unsafe impl IStable for abi_stable::external_types::RMutex 182 | where 183 | seal::RMutex: IStable, 184 | { 185 | same_as!(seal::RMutex); 186 | type ContainsIndirections = B1; 187 | primitive_report!("abi_stable::external_types::RMutex", T); 188 | } 189 | check!(abi_stable::external_types::RMutex); 190 | 191 | // SAFETY: Compile-time checks are in place. 192 | unsafe impl IStable for abi_stable::external_types::RRwLock 193 | where 194 | seal::RMutex: IStable, 195 | { 196 | same_as!(seal::RMutex); 197 | type ContainsIndirections = B1; 198 | primitive_report!("abi_stable::external_types::RRwLock", T); 199 | } 200 | check!(abi_stable::external_types::RRwLock); 201 | 202 | // SAFETY: Compile-time checks are in place. 203 | unsafe impl IStable for abi_stable::external_types::ROnce { 204 | same_as!(Tuple<*const (), NonNull<()>>); 205 | type ContainsIndirections = B1; 206 | primitive_report!("abi_stable::external_types::ROnce"); 207 | } 208 | check!(abi_stable::external_types::ROnce); 209 | 210 | #[cfg(feature = "abi_stable-channels")] 211 | mod channels { 212 | use super::*; 213 | 214 | // SAFETY: Compile-time checks are in place. 215 | unsafe impl IStable for abi_stable::external_types::crossbeam_channel::RReceiver { 216 | same_as!(Tuple, NonNull<()>>); 217 | type ContainsIndirections = B1; 218 | primitive_report!( 219 | "abi_stable::external_types::crossbeam_channel::RReceiver", 220 | T 221 | ); 222 | } 223 | check!(abi_stable::external_types::crossbeam_channel::RReceiver); 224 | 225 | // SAFETY: Compile-time checks are in place. 226 | unsafe impl IStable for abi_stable::external_types::crossbeam_channel::RSender { 227 | same_as!(Tuple, NonNull<()>>); 228 | type ContainsIndirections = B1; 229 | primitive_report!("abi_stable::external_types::crossbeam_channel::RSender", T); 230 | } 231 | check!(abi_stable::external_types::crossbeam_channel::RSender); 232 | } 233 | -------------------------------------------------------------------------------- /stabby-abi/src/str.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | //! Stable strs! 16 | 17 | use crate as stabby; 18 | 19 | use core::ops::{Deref, DerefMut}; 20 | 21 | /// An ABI stable equivalent of `&'a str` 22 | #[stabby::stabby] 23 | #[derive(Clone, Copy, PartialEq, Eq, Hash)] 24 | pub struct Str<'a> { 25 | pub(crate) inner: crate::slice::Slice<'a, u8>, 26 | } 27 | impl<'a> Str<'a> { 28 | /// Wraps a standard `str` into its ABI-stable equivalent. 29 | pub const fn new(s: &'a str) -> Self { 30 | Self { 31 | inner: crate::slice::Slice::new(s.as_bytes()), 32 | } 33 | } 34 | /// Exposes `self` as a `&sr`. 35 | pub const fn as_str(self) -> &'a str { 36 | // Safety: the UTF8 predicate is validaetd by the type. 37 | unsafe { core::str::from_utf8_unchecked(self.inner.as_slice()) } 38 | } 39 | } 40 | impl<'a> From<&'a str> for Str<'a> { 41 | fn from(value: &'a str) -> Self { 42 | Self::new(value) 43 | } 44 | } 45 | impl<'a> From<&'a mut str> for Str<'a> { 46 | fn from(value: &'a mut str) -> Self { 47 | Self::from(&*value) 48 | } 49 | } 50 | impl<'a> From> for &'a str { 51 | fn from(value: Str<'a>) -> Self { 52 | value.as_str() 53 | } 54 | } 55 | impl AsRef for Str<'_> { 56 | fn as_ref(&self) -> &str { 57 | self 58 | } 59 | } 60 | impl Deref for Str<'_> { 61 | type Target = str; 62 | fn deref(&self) -> &Self::Target { 63 | self.as_str() 64 | } 65 | } 66 | impl core::fmt::Debug for Str<'_> { 67 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 68 | self.deref().fmt(f) 69 | } 70 | } 71 | impl core::fmt::Display for Str<'_> { 72 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 73 | self.deref().fmt(f) 74 | } 75 | } 76 | impl core::cmp::PartialOrd for Str<'_> { 77 | fn partial_cmp(&self, other: &Self) -> Option { 78 | self.deref().partial_cmp(other.deref()) 79 | } 80 | } 81 | 82 | /// An ABI stable equivalent of `&'a mut T` 83 | #[stabby::stabby] 84 | pub struct StrMut<'a> { 85 | pub(crate) inner: crate::slice::SliceMut<'a, u8>, 86 | } 87 | impl AsRef for StrMut<'_> { 88 | fn as_ref(&self) -> &str { 89 | self 90 | } 91 | } 92 | impl Deref for StrMut<'_> { 93 | type Target = str; 94 | fn deref(&self) -> &Self::Target { 95 | // Safety: the UTF8 predicate is validaetd by the type. 96 | unsafe { core::str::from_utf8_unchecked(&self.inner) } 97 | } 98 | } 99 | impl DerefMut for StrMut<'_> { 100 | fn deref_mut(&mut self) -> &mut Self::Target { 101 | // Safety: the UTF8 predicate is validaetd by the type. 102 | unsafe { core::str::from_utf8_unchecked_mut(&mut self.inner) } 103 | } 104 | } 105 | impl<'a> From<&'a mut str> for StrMut<'a> { 106 | fn from(value: &'a mut str) -> Self { 107 | Self { 108 | inner: unsafe { value.as_bytes_mut().into() }, 109 | } 110 | } 111 | } 112 | impl<'a> From> for Str<'a> { 113 | fn from(value: StrMut<'a>) -> Self { 114 | Self { 115 | inner: value.inner.into(), 116 | } 117 | } 118 | } 119 | impl<'a> From> for &'a mut str { 120 | fn from(value: StrMut<'a>) -> Self { 121 | // Safety: the UTF8 predicate is validaetd by the type. 122 | unsafe { core::str::from_utf8_unchecked_mut(value.inner.into()) } 123 | } 124 | } 125 | 126 | impl<'a> From> for &'a str { 127 | fn from(value: StrMut<'a>) -> Self { 128 | // Safety: the UTF8 predicate is validaetd by the type. 129 | unsafe { core::str::from_utf8_unchecked(value.inner.into()) } 130 | } 131 | } 132 | 133 | #[cfg(feature = "serde")] 134 | mod serde_impl { 135 | use super::*; 136 | use serde::{de::Visitor, Deserialize, Serialize}; 137 | impl<'a> Serialize for Str<'a> { 138 | fn serialize(&self, serializer: S) -> Result 139 | where 140 | S: serde::Serializer, 141 | { 142 | serializer.serialize_str(self) 143 | } 144 | } 145 | impl<'a> Serialize for StrMut<'a> { 146 | fn serialize(&self, serializer: S) -> Result 147 | where 148 | S: serde::Serializer, 149 | { 150 | serializer.serialize_str(self) 151 | } 152 | } 153 | impl<'a> Deserialize<'a> for Str<'a> { 154 | fn deserialize(deserializer: D) -> Result 155 | where 156 | D: serde::Deserializer<'a>, 157 | { 158 | deserializer.deserialize_str(StrVisitor(core::marker::PhantomData)) 159 | } 160 | } 161 | struct StrVisitor<'a>(core::marker::PhantomData>); 162 | impl<'a> Visitor<'a> for StrVisitor<'a> { 163 | type Value = Str<'a>; 164 | fn visit_borrowed_str(self, v: &'a str) -> Result 165 | where 166 | E: serde::de::Error, 167 | { 168 | Ok(v.into()) 169 | } 170 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 171 | write!(formatter, "A borrowed_str") 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /stabby-abi/src/typenum2/mod.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | /// Unsigned arithmetics in the type system 16 | pub mod unsigned; 17 | pub use unsigned::{ 18 | typenames::*, IBit as Bit, IPowerOf2 as PowerOf2, IUnsigned as Unsigned, UInt, UTerm, B0, B1, 19 | }; 20 | -------------------------------------------------------------------------------- /stabby-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # Pierre Avital, 13 | # 14 | 15 | [package] 16 | name = "stabby-macros" 17 | version = { workspace = true } 18 | edition = "2021" 19 | authors = { workspace = true } 20 | license = { workspace = true } 21 | categories = { workspace = true } 22 | repository = { workspace = true } 23 | readme = { workspace = true } 24 | description = "the macros that make working with stabby possible, you shouldn't add this crate to your dependencies, only `stabby`." 25 | 26 | [lints] 27 | workspace = true 28 | 29 | [features] 30 | experimental-ctypes = [] 31 | 32 | [dependencies] 33 | proc-macro2 = { workspace = true } 34 | proc-macro-crate = { workspace = true } 35 | quote = { workspace = true } 36 | syn = { workspace = true, features = ["full", "extra-traits"] } 37 | rand = { workspace = true } 38 | 39 | [lib] 40 | proc-macro = true 41 | -------------------------------------------------------------------------------- /stabby-macros/build.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use std::process::Command; 16 | 17 | fn encode(value: String) -> String { 18 | value 19 | } 20 | 21 | fn main() -> Result<(), std::io::Error> { 22 | use std::{ 23 | fs::File, 24 | io::{BufWriter, Write}, 25 | path::PathBuf, 26 | }; 27 | // println!("cargo:rustc-check-cfg=cfg(stabby_max_tuple, values(any()))"); 28 | let rustc = std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()); 29 | let output = String::from_utf8( 30 | Command::new(rustc) 31 | .arg("-v") 32 | .arg("-V") 33 | .output() 34 | .expect("Couldn't get rustc version") 35 | .stdout, 36 | ) 37 | .unwrap(); 38 | let env_vars = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("env_vars.rs"); 39 | let mut env_vars = BufWriter::new(File::create(env_vars).unwrap()); 40 | let mut rustc: [u16; 3] = [0; 3]; 41 | let mut llvm: [u16; 3] = [0; 3]; 42 | let mut commit = ""; 43 | for line in output.lines() { 44 | let line = line.trim(); 45 | if let Some(release) = line.strip_prefix("release: ") { 46 | for (i, s) in release.split('.').enumerate().take(3) { 47 | rustc[i] = s.parse().unwrap_or(0); 48 | } 49 | } 50 | if let Some(release) = line.strip_prefix("LLVM version: ") { 51 | for (i, s) in release.split('.').enumerate().take(3) { 52 | llvm[i] = s.parse().unwrap_or(0); 53 | } 54 | } 55 | if let Some(hash) = line.strip_prefix("commit-hash: ") { 56 | commit = hash; 57 | } 58 | } 59 | writeln!( 60 | env_vars, 61 | r#"pub (crate) const RUSTC_COMMIT: &str = "{commit}";"# 62 | )?; 63 | writeln!( 64 | env_vars, 65 | "pub (crate) const RUSTC_MAJOR: u16 = {};", 66 | rustc[0] 67 | )?; 68 | writeln!( 69 | env_vars, 70 | "pub (crate) const RUSTC_MINOR: u16 = {};", 71 | rustc[1] 72 | )?; 73 | writeln!( 74 | env_vars, 75 | "pub (crate) const RUSTC_PATCH: u16 = {};", 76 | rustc[2] 77 | )?; 78 | // writeln!(env_vars, "pub (crate) const LLVM_MAJOR: u16 = {};", llvm[0])?; 79 | // writeln!(env_vars, "pub (crate) const LLVM_MINOR: u16 = {};", llvm[1])?; 80 | // writeln!(env_vars, "pub (crate) const LLVM_PATCH: u16 = {};", llvm[2])?; 81 | for (key, value) in ["OPT_LEVEL", "DEBUG", "NUM_JOBS", "TARGET", "HOST"] 82 | .iter() 83 | .filter_map(|&name| std::env::var(name).map_or(None, |val| Some((name, val)))) 84 | { 85 | writeln!( 86 | env_vars, 87 | r#"pub (crate) const {key}: &str = "{}";"#, 88 | encode(value) 89 | )?; 90 | } 91 | Ok(()) 92 | } 93 | -------------------------------------------------------------------------------- /stabby-macros/src/gen_closures.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use quote::quote; 16 | pub fn gen_closures() -> proc_macro2::TokenStream { 17 | let st = crate::tl_mod(); 18 | let generator = (0..10).map(|i| { 19 | let c = quote::format_ident!("Call{i}"); 20 | let cm = quote::format_ident!("CallMut{i}"); 21 | let co = quote::format_ident!("CallOnce{i}"); 22 | let com = quote::format_ident!("call_once_{i}"); 23 | let cvt = quote::format_ident!("StabbyVtableCall{i}"); 24 | let cmvt = quote::format_ident!("StabbyVtableCallMut{i}"); 25 | let covt = quote::format_ident!("StabbyVtableCallOnce{i}"); 26 | let cod = quote::format_ident!("CallOnceDyn{i}"); 27 | let argtys = (0..i) 28 | .map(|i| quote::format_ident!("I{i}")) 29 | .collect::>(); 30 | let args = (0..i) 31 | .map(|i| quote::format_ident!("_{i}")) 32 | .collect::>(); 33 | quote! { 34 | #[cfg(feature = "alloc-rs")] 35 | pub use #com::*; 36 | #[cfg(feature = "alloc-rs")] 37 | mod #com { 38 | use crate::{ 39 | vtable::{HasDropVt, TransitiveDeref}, 40 | StableIf, StableLike, 41 | }; 42 | /// [`core::ops::FnOnce`], but ABI-stable 43 | pub trait #co: Sized { 44 | /// Call the function 45 | extern "C" fn call_once(this: #st::alloc::boxed::Box #(, #args: #argtys)*) -> O; 46 | } 47 | impl O + Sized> #co for F { 48 | /// Call the function 49 | extern "C" fn call_once(this: #st::alloc::boxed::Box #(, #args: #argtys)*) -> O { 50 | (#st::alloc::boxed::Box::into_inner(this))(#(#args,)*) 51 | } 52 | } 53 | 54 | /// The v-table for [`core::ops::FnOnce`] 55 | #[crate::stabby] 56 | pub struct #covt { 57 | call_once: StableIf #(, #argtys)* ) -> O, &'static ()>, O>, 58 | } 59 | impl Copy for #covt {} 60 | impl Clone for #covt { 61 | fn clone(&self) -> Self { 62 | *self 63 | } 64 | } 65 | /// The trait for calling [`core::ops::FnOnce`]. 66 | pub trait #cod { 67 | /// Call the function 68 | fn call_once(self #(, _: #argtys)* ) -> O; 69 | } 70 | impl<'a, O #(, #argtys)* , Vt: TransitiveDeref<#covt, N> + HasDropVt, N> #cod 71 | for crate::Dyn<'a, #st::alloc::boxed::Box<()>, Vt> 72 | { 73 | fn call_once(self #(, #args: #argtys)*) -> O { 74 | let this = core::mem::ManuallyDrop::new(self); 75 | let o = 76 | // SAFETY: We simply observe the internals of an unsafe `stabby::abi::StableLike` 77 | unsafe { (this.vtable().tderef().call_once.into_inner_unchecked())(core::ptr::read(this.ptr()) #(, #args)*)}; 78 | o 79 | } 80 | } 81 | 82 | impl crate::vtable::CompoundVt<'_> for dyn FnOnce(#(#argtys, )*) -> O { 83 | type Vt = crate::vtable::VTable<#covt, T>; 84 | } 85 | impl<'a, O: 'a #(, #argtys: 'a)* , F: FnOnce(#(#argtys, )*) -> O> crate::vtable::IConstConstructor<'a, F> 86 | for #covt 87 | { 88 | #st::impl_vtable_constructor!( 89 | const VTABLE_REF: &'a Self = &Self { 90 | // SAFETY: We unsafely construct `stabby::abi::StableLike` 91 | call_once: unsafe { 92 | core::mem::transmute(>::call_once as extern "C" fn(#st::alloc::boxed::Box #(, #argtys)* ) -> O) 93 | }, 94 | }; => 95 | const VTABLE: Self = Self { 96 | // SAFETY: We unsafely construct `stabby::abi::StableLike` 97 | call_once: unsafe { 98 | core::mem::transmute(>::call_once as extern "C" fn(#st::alloc::boxed::Box #(, #argtys)* ) -> O) 99 | }, 100 | }; 101 | ); 102 | } 103 | } 104 | 105 | #[cfg(feature = "alloc-rs")] 106 | #[crate::stabby] 107 | /// [`core::ops::FnMut`], but ABI-stable 108 | pub trait #cm { 109 | /// Call the function 110 | extern "C" fn call_mut(&mut self #(, #args: #argtys)*) -> O; 111 | } 112 | #[cfg(not(feature = "alloc-rs"))] 113 | #[crate::stabby] 114 | /// [`core::ops::FnMut`], but ABI-stable 115 | pub trait #cm { 116 | /// Call the function 117 | extern "C" fn call_mut(&mut self #(, #args: #argtys)*) -> O; 118 | } 119 | impl O> #cm for F { 120 | extern "C" fn call_mut(&mut self #(, #args: #argtys)*) -> O { 121 | self(#(#args,)*) 122 | } 123 | } 124 | impl<'a, O #(, #argtys)* > crate::vtable::CompoundVt<'a> for dyn FnMut(#(#argtys, )*) -> O { 125 | type Vt = crate::vtable::VTable<#cmvt<'a, O #(, #argtys)* >, T>; 126 | } 127 | /// [`core::ops::Fn`], but ABI-stable 128 | #[crate::stabby] 129 | pub trait #c: #cm { 130 | /// Call the function 131 | extern "C" fn call(&self #(, #args: #argtys)*) -> O; 132 | } 133 | impl O> #c for F { 134 | extern "C" fn call(&self #(, #args: #argtys)*) -> O { 135 | self(#(#args,)*) 136 | } 137 | } 138 | impl<'a, O #(, #argtys)* > crate::vtable::CompoundVt<'a> for dyn Fn(#(#argtys, )*) -> O { 139 | type Vt = crate::vtable::VTable<#cvt<'a, O #(, #argtys)* >, T>; 140 | } 141 | } 142 | }); 143 | quote!(#(#generator)*) 144 | } 145 | -------------------------------------------------------------------------------- /stabby-macros/src/structs.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use proc_macro2::Ident; 16 | use quote::quote; 17 | use syn::{spanned::Spanned, Attribute, DataStruct, Generics, Visibility}; 18 | 19 | use crate::Unself; 20 | 21 | struct Args { 22 | optimize: bool, 23 | version: u32, 24 | module: proc_macro2::TokenStream, 25 | } 26 | impl syn::parse::Parse for Args { 27 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 28 | let mut this = Args { 29 | optimize: true, 30 | version: 0, 31 | module: quote!(), 32 | }; 33 | while !input.is_empty() { 34 | let ident: Ident = input.parse()?; 35 | match ident.to_string().as_str() { 36 | "no_opt" => this.optimize = false, 37 | "version" => { 38 | input.parse::()?; 39 | this.version = input.parse::()?.to_string().parse().unwrap(); 40 | } 41 | "module" => { 42 | input.parse::()?; 43 | while !input.is_empty() { 44 | if input.peek(syn::Token!(,)) { 45 | break; 46 | } 47 | let token: proc_macro2::TokenTree = input.parse()?; 48 | this.module.extend(Some(token)) 49 | } 50 | } 51 | _ => return Err(input.error("Unknown stabby attribute {ident}")), 52 | } 53 | _ = input.parse::(); 54 | } 55 | Ok(this) 56 | } 57 | } 58 | #[derive(Copy, Clone)] 59 | enum AllowedRepr { 60 | C, 61 | Transparent, 62 | Align(usize), 63 | } 64 | impl syn::parse::Parse for AllowedRepr { 65 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 66 | let mut content = input.fork(); 67 | if input.peek(syn::token::Paren) { 68 | syn::parenthesized!(content in input); 69 | } 70 | let ident: Ident = content.parse()?; 71 | Ok(match ident.to_string().as_str() { 72 | "C" => AllowedRepr::C, 73 | "transparent" => AllowedRepr::Transparent, 74 | "packed" => return Err(input.error("stabby does not support packed structs, though you may implement IStable manually if you're very comfident")), 75 | "align" => { 76 | let input = content; 77 | syn::parenthesized!(content in input); 78 | let lit: syn::LitInt = content.parse()?; 79 | AllowedRepr::Align(lit.base10_parse()?) 80 | } 81 | _ => { 82 | return Err(input.error( 83 | "Only #[repr(C)] and #[repr(transparent)] are allowed for stabby structs", 84 | )) 85 | } 86 | }) 87 | } 88 | } 89 | impl quote::ToTokens for AllowedRepr { 90 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 91 | tokens.extend(match self { 92 | AllowedRepr::C => quote!(#[repr(C)]), 93 | AllowedRepr::Transparent => quote!(#[repr(transparent)]), 94 | AllowedRepr::Align(n) => { 95 | let n = syn::LitInt::new(&format!("{n}"), tokens.span()); 96 | quote!(#[repr(align(#n))]) 97 | } 98 | }) 99 | } 100 | } 101 | 102 | pub fn stabby( 103 | attrs: Vec, 104 | vis: Visibility, 105 | ident: Ident, 106 | generics: Generics, 107 | DataStruct { 108 | fields, semi_token, .. 109 | }: DataStruct, 110 | stabby_attrs: &proc_macro::TokenStream, 111 | ) -> proc_macro2::TokenStream { 112 | let Args { 113 | mut optimize, 114 | version, 115 | module, 116 | } = syn::parse(stabby_attrs.clone()).unwrap(); 117 | optimize &= generics.params.is_empty(); 118 | let st = crate::tl_mod(); 119 | let unbound_generics = crate::utils::unbound_generics(&generics.params); 120 | let generics_without_defaults = crate::utils::generics_without_defaults(&generics.params); 121 | let where_clause = &generics.where_clause; 122 | let clauses = where_clause.as_ref().map(|w| &w.predicates); 123 | let mut layout = None; 124 | let mut report = crate::Report::r#struct(ident.to_string(), version, module); 125 | let repr = attrs.iter().find_map(|attr| { 126 | if attr.path.is_ident("repr") { 127 | syn::parse2::(attr.tokens.clone()).ok() 128 | } else { 129 | None 130 | } 131 | }); 132 | let repr_attr = repr.is_none().then(|| quote! {#[repr(C)]}); 133 | optimize &= !matches!(repr, Some(AllowedRepr::Align(_))); 134 | let struct_code = match &fields { 135 | syn::Fields::Named(fields) => { 136 | let fields = &fields.named; 137 | for field in fields { 138 | let ty = field.ty.unself(&ident); 139 | layout = Some(layout.map_or_else( 140 | || quote!(#ty), 141 | |layout| quote!(#st::FieldPair<#layout, #ty>), 142 | )); 143 | report.add_field(field.ident.as_ref().unwrap().to_string(), ty); 144 | } 145 | quote! { 146 | #(#attrs)* 147 | #repr_attr 148 | #vis struct #ident #generics #where_clause { 149 | #fields 150 | } 151 | } 152 | } 153 | syn::Fields::Unnamed(fields) => { 154 | let fields = &fields.unnamed; 155 | for (i, field) in fields.iter().enumerate() { 156 | let ty = field.ty.unself(&ident); 157 | layout = Some(layout.map_or_else( 158 | || quote!(#ty), 159 | |layout| quote!(#st::FieldPair<#layout, #ty>), 160 | )); 161 | report.add_field(i.to_string(), ty); 162 | } 163 | quote! { 164 | #(#attrs)* 165 | #repr_attr 166 | #vis struct #ident #generics #where_clause (#fields); 167 | } 168 | } 169 | syn::Fields::Unit => { 170 | quote! { 171 | #(#attrs)* 172 | #repr_attr 173 | #vis struct #ident #generics #where_clause; 174 | } 175 | } 176 | }; 177 | let layout = layout.map_or_else( 178 | || quote!(()), 179 | |layout| { 180 | if let Some(AllowedRepr::Align(mut n)) = repr { 181 | let mut align = quote!(#st::U1); 182 | while n > 1 { 183 | n /= 2; 184 | align = quote!(#st::UInt<#align, #st::B0>); 185 | } 186 | quote!(#st::AlignedStruct<#layout, #align>) 187 | } else { 188 | quote!(#st::Struct<#layout>) 189 | } 190 | }, 191 | ); 192 | let opt_id = quote::format_ident!("OptimizedLayoutFor{ident}"); 193 | let size_bug = format!( 194 | "{ident}'s size was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" 195 | ); 196 | let align_bug = format!( 197 | "{ident}'s align was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" 198 | ); 199 | let reprc_bug = format!( 200 | "{ident}'s CType was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" 201 | ); 202 | let assertion = optimize.then(|| { 203 | let sub_optimal_message = format!( 204 | "{ident}'s layout is sub-optimal, reorder fields or use `#[stabby::stabby(no_opt)]`" 205 | ); 206 | quote! { 207 | const _: () = { 208 | if !<#ident>::has_optimal_layout() { 209 | panic!(#sub_optimal_message) 210 | } 211 | }; 212 | } 213 | }); 214 | let report_bounds = report.bounds(); 215 | let ctype = cfg!(feature = "experimental-ctypes").then(|| { 216 | if matches!(repr, Some(AllowedRepr::Align(_))) { 217 | return quote! {type CType = Self;}; 218 | } 219 | let ctype = report.crepr(); 220 | quote! {type CType = #ctype;} 221 | }); 222 | let ctype_assert = cfg!(feature = "experimental-ctypes").then(|| { 223 | quote! {if core::mem::size_of::() != core::mem::size_of::<::CType>() || core::mem::align_of::() != core::mem::align_of::<::CType>() { 224 | panic!(#reprc_bug) 225 | }} 226 | }); 227 | let optdoc = format!("Returns true if the layout for [`{ident}`] is smaller or equal to that Rust would have generated for it."); 228 | quote! { 229 | #struct_code 230 | 231 | #[automatically_derived] 232 | // SAFETY: This is generated by `stabby`, and checks have been added to detect potential issues. 233 | unsafe impl <#generics_without_defaults> #st::IStable for #ident <#unbound_generics> where #layout: #st::IStable, #report_bounds #clauses { 234 | type ForbiddenValues = <#layout as #st::IStable>::ForbiddenValues; 235 | type UnusedBits =<#layout as #st::IStable>::UnusedBits; 236 | type Size = <#layout as #st::IStable>::Size; 237 | type Align = <#layout as #st::IStable>::Align; 238 | type HasExactlyOneNiche = <#layout as #st::IStable>::HasExactlyOneNiche; 239 | type ContainsIndirections = <#layout as #st::IStable>::ContainsIndirections; 240 | #ctype 241 | const REPORT: &'static #st::report::TypeReport = &#report; 242 | const ID: u64 = { 243 | #ctype_assert 244 | if core::mem::size_of::() != <::Size as #st::Unsigned>::USIZE { 245 | panic!(#size_bug) 246 | } 247 | if core::mem::align_of::() != <::Align as #st::Unsigned>::USIZE { 248 | panic!(#align_bug) 249 | } 250 | #st::report::gen_id(Self::REPORT) 251 | }; 252 | } 253 | #[allow(dead_code, missing_docs)] 254 | struct #opt_id #generics #where_clause #fields #semi_token 255 | #assertion 256 | impl < #generics_without_defaults > #ident <#unbound_generics> where #layout: #st::IStable, #report_bounds #clauses { 257 | #[doc = #optdoc] 258 | pub const fn has_optimal_layout() -> bool { 259 | core::mem::size_of::() <= core::mem::size_of::<#opt_id<#unbound_generics>>() 260 | } 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /stabby-macros/src/tyops.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use proc_macro2::{Spacing, TokenStream, TokenTree}; 16 | use quote::quote; 17 | use syn::Type; 18 | 19 | #[derive(Clone)] 20 | pub enum TyExpr { 21 | Type(Type), 22 | Not(Box), 23 | Ternary(Box, Box, Box), 24 | Add(Box, Box), 25 | Sub(Box, Box), 26 | Rem(Box, Box), 27 | BitOr(Box, Box), 28 | BitAnd(Box, Box), 29 | IsEqual(Box, Box), 30 | } 31 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 32 | pub enum TyOps { 33 | Type, 34 | Not, 35 | Ternary, 36 | Add, 37 | Sub, 38 | Rem, 39 | BitOr, 40 | BitAnd, 41 | IsEqual, 42 | } 43 | impl From for TyExpr { 44 | fn from(tokens: proc_macro::TokenStream) -> Self { 45 | proc_macro2::TokenStream::from(tokens).into() 46 | } 47 | } 48 | impl From for TyExpr { 49 | fn from(tokens: proc_macro2::TokenStream) -> Self { 50 | let mut tokens = tokens.into_iter().peekable(); 51 | let mut path = TokenStream::new(); 52 | let mut accept_ident = true; 53 | let mut in_ternary = false; 54 | let mut operation = TyOps::Type; 55 | let mut set_op = |op| { 56 | if operation == TyOps::Type { 57 | operation = op 58 | } else { 59 | panic!("Operations must be surrounded by parentheses") 60 | } 61 | }; 62 | let mut types: Vec = Vec::new(); 63 | while let Some(token) = tokens.next() { 64 | match token { 65 | TokenTree::Group(group) => { 66 | types.push(group.stream().into()); 67 | accept_ident = false 68 | } 69 | TokenTree::Ident(ident) => { 70 | if accept_ident { 71 | path.extend(Some(TokenTree::Ident(ident))); 72 | accept_ident = false; 73 | } else { 74 | panic!("Identifier {ident} not accepted here") 75 | } 76 | } 77 | TokenTree::Punct(p) => { 78 | match p.as_char() { 79 | ':' => { 80 | if p.spacing() == Spacing::Joint { 81 | let next = tokens.next().unwrap(); 82 | assert!(matches!(next, TokenTree::Punct(p) if p.as_char() == ':')); 83 | path.extend(quote!(::).into_iter()); 84 | accept_ident = true; 85 | continue; 86 | } else if in_ternary { 87 | in_ternary = false 88 | } else { 89 | panic!(": is only allowed in ternaries or as path separator in ::") 90 | } 91 | } 92 | '!' => { 93 | if p.spacing() == Spacing::Joint { 94 | panic!("!= is not supported yet") 95 | } else { 96 | set_op(TyOps::Not); 97 | assert!(path.is_empty()); 98 | continue; 99 | } 100 | } 101 | '?' => { 102 | set_op(TyOps::Ternary); 103 | in_ternary = true 104 | } 105 | '+' => set_op(TyOps::Add), 106 | '-' => set_op(TyOps::Sub), 107 | '%' => set_op(TyOps::Rem), 108 | '|' => set_op(TyOps::BitOr), 109 | '&' => set_op(TyOps::BitAnd), 110 | '=' => { 111 | if p.spacing() == Spacing::Joint { 112 | let next = tokens.next().unwrap(); 113 | assert!(matches!(next, TokenTree::Punct(p) if p.as_char() == '=')); 114 | set_op(TyOps::IsEqual) 115 | } else { 116 | panic!("Did you mean == ?") 117 | } 118 | } 119 | '<' => { 120 | let mut count = 1; 121 | let braced: proc_macro2::TokenStream = tokens 122 | .by_ref() 123 | .take_while(|t| { 124 | if let TokenTree::Punct(p) = t { 125 | match p.as_char() { 126 | '<' => count += 1, 127 | '>' => count -= 1, 128 | _ => {} 129 | } 130 | } 131 | count != 0 132 | }) 133 | .collect(); 134 | path = quote!(#path < #braced >); 135 | continue; 136 | } 137 | c => panic!("{c} is not supported: {path}"), 138 | } 139 | if !path.is_empty() { 140 | types.push(Self::Type(syn::parse2(path).expect("Failed to parse type"))); 141 | path = TokenStream::new(); 142 | } 143 | accept_ident = true; 144 | } 145 | TokenTree::Literal(_) => panic!("Litterals can't be types"), 146 | } 147 | } 148 | if !path.is_empty() { 149 | types.push(Self::Type( 150 | syn::parse2(path).expect("Failed to parse final type"), 151 | )); 152 | } 153 | match operation { 154 | TyOps::Type => { 155 | assert_eq!(types.len(), 1, "Type"); 156 | types.pop().unwrap() 157 | } 158 | TyOps::Not => { 159 | assert_eq!(types.len(), 1); 160 | Self::Not(Box::new(types.pop().unwrap())) 161 | } 162 | TyOps::Ternary => { 163 | assert_eq!(types.len(), 3); 164 | let f = Box::new(types.pop().unwrap()); 165 | let t = Box::new(types.pop().unwrap()); 166 | let cond = Box::new(types.pop().unwrap()); 167 | Self::Ternary(cond, t, f) 168 | } 169 | TyOps::Add => { 170 | assert_eq!(types.len(), 2); 171 | let r = Box::new(types.pop().unwrap()); 172 | let l = Box::new(types.pop().unwrap()); 173 | Self::Add(l, r) 174 | } 175 | TyOps::Sub => { 176 | assert_eq!(types.len(), 2); 177 | let r = Box::new(types.pop().unwrap()); 178 | let l = Box::new(types.pop().unwrap()); 179 | Self::Sub(l, r) 180 | } 181 | TyOps::Rem => { 182 | assert_eq!(types.len(), 2); 183 | let r = Box::new(types.pop().unwrap()); 184 | let l = Box::new(types.pop().unwrap()); 185 | Self::Rem(l, r) 186 | } 187 | TyOps::BitOr => { 188 | assert_eq!(types.len(), 2); 189 | let r = Box::new(types.pop().unwrap()); 190 | let l = Box::new(types.pop().unwrap()); 191 | Self::BitOr(l, r) 192 | } 193 | TyOps::BitAnd => { 194 | assert_eq!(types.len(), 2); 195 | let r = Box::new(types.pop().unwrap()); 196 | let l = Box::new(types.pop().unwrap()); 197 | Self::BitAnd(l, r) 198 | } 199 | TyOps::IsEqual => { 200 | assert_eq!(types.len(), 2); 201 | let r = Box::new(types.pop().unwrap()); 202 | let l = Box::new(types.pop().unwrap()); 203 | Self::IsEqual(l, r) 204 | } 205 | } 206 | } 207 | } 208 | 209 | pub fn tyeval(tokens: &TyExpr) -> proc_macro2::TokenStream { 210 | let st = crate::tl_mod(); 211 | match tokens { 212 | TyExpr::Type(ty) => quote!(#ty), 213 | TyExpr::Add(l, r) => { 214 | let l = tyeval(l); 215 | let r = tyeval(r); 216 | quote!(<#l as #st::typenum2::Unsigned>::Add<#r>) 217 | } 218 | TyExpr::Sub(l, r) => { 219 | let l = tyeval(l); 220 | let r = tyeval(r); 221 | quote!(<#l as #st::typenum2::Unsigned>::AbsSub<#r>) 222 | } 223 | TyExpr::Rem(l, r) => { 224 | let l = tyeval(l); 225 | let r = tyeval(r); 226 | quote!(<#l as #st::typenum2::Unsigned>::Mod<#r>) 227 | } 228 | TyExpr::BitOr(l, r) => { 229 | let l = tyeval(l); 230 | let r = tyeval(r); 231 | quote!(<#l as #st::typenum2::Unsigned>::BitOr<#r>) 232 | } 233 | TyExpr::BitAnd(l, r) => { 234 | let l = tyeval(l); 235 | let r = tyeval(r); 236 | quote!(<#l as #st::typenum2::Unsigned>::BitAnd<#r>) 237 | } 238 | TyExpr::Not(ty) => { 239 | let ty = tyeval(ty); 240 | quote!(<#ty as #st::typenum2::Bit>::Not) 241 | } 242 | TyExpr::Ternary(cond, t, f) => { 243 | let cond = tyeval(cond); 244 | let t = tyeval(t); 245 | let f = tyeval(f); 246 | quote!(<#cond as #st::typenum2::Bit>::UTernary<#t, #f>) 247 | } 248 | TyExpr::IsEqual(l, r) => { 249 | let l = tyeval(l); 250 | let r = tyeval(r); 251 | quote!(<#l as #st::typenum2::Unsigned>::Equal<#r>) 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /stabby-macros/src/unions.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use proc_macro2::TokenStream; 16 | use quote::quote; 17 | use syn::{Attribute, DataUnion, Generics, Ident, Visibility}; 18 | 19 | use crate::Unself; 20 | 21 | struct Args { 22 | version: u32, 23 | module: proc_macro2::TokenStream, 24 | } 25 | impl syn::parse::Parse for Args { 26 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 27 | let mut this = Args { 28 | version: 0, 29 | module: quote!(), 30 | }; 31 | while !input.is_empty() { 32 | let ident: Ident = input.parse()?; 33 | match ident.to_string().as_str() { 34 | "version" => { 35 | input.parse::()?; 36 | this.version = input.parse::()?.to_string().parse().unwrap(); 37 | } 38 | "module" => { 39 | input.parse::()?; 40 | while !input.is_empty() { 41 | if input.peek(syn::Token!(,)) { 42 | break; 43 | } 44 | let token: proc_macro2::TokenTree = input.parse()?; 45 | this.module.extend(Some(token)) 46 | } 47 | } 48 | _ => return Err(input.error("Unknown stabby attribute {ident}")), 49 | } 50 | _ = input.parse::(); 51 | } 52 | Ok(this) 53 | } 54 | } 55 | pub fn stabby( 56 | attrs: Vec, 57 | vis: Visibility, 58 | ident: Ident, 59 | generics: Generics, 60 | data: DataUnion, 61 | stabby_attrs: &proc_macro::TokenStream, 62 | ) -> TokenStream { 63 | let st = crate::tl_mod(); 64 | let DataUnion { 65 | union_token: _, 66 | fields, 67 | } = &data; 68 | let Args { version, module } = syn::parse(stabby_attrs.clone()).unwrap(); 69 | let unbound_generics = &generics.params; 70 | let mut layout = quote!(()); 71 | let mut report = crate::Report::r#union(ident.to_string(), version, module); 72 | for field in &fields.named { 73 | let ty = field.ty.unself(&ident); 74 | layout = quote!(#st::Union<#layout, #ty>); 75 | report.add_field(field.ident.as_ref().unwrap().to_string(), ty); 76 | } 77 | let report_bounds = report.bounds(); 78 | let ctype = cfg!(feature = "experimental-ctypes").then(|| { 79 | quote! {type CType = <#layout as #st::IStable>::CType;} 80 | }); 81 | quote! { 82 | #(#attrs)* 83 | #[repr(C)] 84 | #vis union #ident #generics 85 | #fields 86 | 87 | #[automatically_derived] 88 | // SAFETY: This is generated by `stabby`, and checks have been added to detect potential issues. 89 | unsafe impl #generics #st::IStable for #ident <#unbound_generics> where #report_bounds #layout: #st::IStable { 90 | type ForbiddenValues = #st::End; 91 | type UnusedBits = #st::End; 92 | type Size = <#layout as #st::IStable>::Size; 93 | type Align = <#layout as #st::IStable>::Align; 94 | type HasExactlyOneNiche = #st::B0; 95 | type ContainsIndirections = <#layout as #st::IStable>::ContainsIndirections; 96 | #ctype 97 | const REPORT: &'static #st::report::TypeReport = & #report; 98 | const ID: u64 = #st::report::gen_id(Self::REPORT); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /stabby-macros/src/utils.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use quote::quote; 16 | use syn::{ConstParam, GenericParam, Lifetime, LifetimeDef, TypeParam}; 17 | 18 | #[derive(Clone, Default)] 19 | pub(crate) struct SeparatedGenerics { 20 | pub lifetimes: Vec, 21 | pub types: Vec, 22 | pub consts: Vec, 23 | } 24 | impl quote::ToTokens for SeparatedGenerics { 25 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 26 | for l in &self.lifetimes { 27 | tokens.extend(quote!(#l,)); 28 | } 29 | for l in &self.types { 30 | tokens.extend(quote!(#l,)); 31 | } 32 | for l in &self.consts { 33 | tokens.extend(quote!(#l,)); 34 | } 35 | } 36 | } 37 | pub(crate) fn unbound_generics<'a>( 38 | generics: impl IntoIterator, 39 | ) -> SeparatedGenerics { 40 | let mut this = SeparatedGenerics::default(); 41 | for g in generics { 42 | match g { 43 | GenericParam::Type(TypeParam { ident, .. }) => this.types.push(quote!(#ident)), 44 | GenericParam::Lifetime(LifetimeDef { lifetime, .. }) => { 45 | this.lifetimes.push(quote!(#lifetime)) 46 | } 47 | GenericParam::Const(ConstParam { ident, .. }) => this.consts.push(quote!(#ident)), 48 | } 49 | } 50 | this 51 | } 52 | pub(crate) fn generics_without_defaults<'a>( 53 | generics: impl IntoIterator, 54 | ) -> SeparatedGenerics { 55 | let mut this = SeparatedGenerics::default(); 56 | for g in generics { 57 | match g { 58 | GenericParam::Type(TypeParam { ident, bounds, .. }) => { 59 | this.types.push(quote!(#ident: #bounds)) 60 | } 61 | GenericParam::Lifetime(LifetimeDef { 62 | lifetime, bounds, .. 63 | }) => this.lifetimes.push(quote!(#lifetime: #bounds)), 64 | GenericParam::Const(ConstParam { ident, ty, .. }) => { 65 | this.consts.push(quote!(const #ident: #ty)) 66 | } 67 | } 68 | } 69 | this 70 | } 71 | 72 | pub trait IGenerics<'a> { 73 | type Lifetimes: Iterator; 74 | fn lifetimes(self) -> Self::Lifetimes; 75 | type Types: Iterator; 76 | fn types(self) -> Self::Types; 77 | type Consts: Iterator; 78 | fn consts(self) -> Self::Consts; 79 | } 80 | impl<'a, T: IntoIterator> IGenerics<'a> for T { 81 | type Lifetimes = 82 | core::iter::FilterMap Option<&'a LifetimeDef>>; 83 | fn lifetimes(self) -> Self::Lifetimes { 84 | self.into_iter().filter_map(|g| { 85 | if let GenericParam::Lifetime(l) = g { 86 | Some(l) 87 | } else { 88 | None 89 | } 90 | }) 91 | } 92 | type Types = core::iter::FilterMap Option<&'a TypeParam>>; 93 | fn types(self) -> Self::Types { 94 | self.into_iter().filter_map(|g| { 95 | if let GenericParam::Type(l) = g { 96 | Some(l) 97 | } else { 98 | None 99 | } 100 | }) 101 | } 102 | type Consts = 103 | core::iter::FilterMap Option<&'a ConstParam>>; 104 | fn consts(self) -> Self::Consts { 105 | self.into_iter().filter_map(|g| { 106 | if let GenericParam::Const(l) = g { 107 | Some(l) 108 | } else { 109 | None 110 | } 111 | }) 112 | } 113 | } 114 | pub trait Unbound { 115 | type Unbound; 116 | fn unbound(self) -> Self::Unbound; 117 | } 118 | impl<'a> Unbound for &'a LifetimeDef { 119 | type Unbound = &'a Lifetime; 120 | fn unbound(self) -> Self::Unbound { 121 | &self.lifetime 122 | } 123 | } 124 | 125 | impl<'a> Unbound for &'a TypeParam { 126 | type Unbound = &'a syn::Ident; 127 | fn unbound(self) -> Self::Unbound { 128 | &self.ident 129 | } 130 | } 131 | 132 | impl<'a> Unbound for &'a ConstParam { 133 | type Unbound = &'a syn::Ident; 134 | fn unbound(self) -> Self::Unbound { 135 | &self.ident 136 | } 137 | } 138 | impl<'a, T: Iterator, I: Unbound> Unbound 139 | for core::iter::FilterMap Option> 140 | { 141 | type Unbound = core::iter::Map< 142 | core::iter::FilterMap Option>, 143 | fn(I) -> I::Unbound, 144 | >; 145 | fn unbound(self) -> Self::Unbound { 146 | self.map(I::unbound) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /stabby/Cargo.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 ZettaScale Technology 3 | # 4 | # This program and the accompanying materials are made available under the 5 | # terms of the Eclipse Public License 2.0 which is available at 6 | # http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | # which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | # 11 | # Contributors: 12 | # Pierre Avital, 13 | # 14 | 15 | [package] 16 | name = "stabby" 17 | version = { workspace = true } 18 | edition = "2021" 19 | authors = { workspace = true } 20 | license = { workspace = true } 21 | categories = { workspace = true } 22 | repository = { workspace = true } 23 | readme = { workspace = true } 24 | description = "A Stable ABI for Rust with compact sum-types." 25 | 26 | [lints] 27 | workspace = true 28 | 29 | [features] 30 | default = ["std"] 31 | std = ["stabby-abi/std", "alloc-rs"] 32 | alloc-rs = ["stabby-abi/alloc-rs"] 33 | experimental-ctypes = ["stabby-abi/experimental-ctypes"] 34 | libloading = ["dep:libloading", "std"] 35 | libc = ["stabby-abi/libc"] 36 | serde = ["stabby-abi/serde"] 37 | 38 | [dependencies] 39 | stabby-abi = { workspace = true, default-features = false } 40 | 41 | libloading = { workspace = true, optional = true } 42 | rustversion = { workspace = true } 43 | 44 | [dev-dependencies] 45 | smol = { workspace = true } 46 | criterion = { workspace = true } 47 | rand = { workspace = true } 48 | stabby-abi = { workspace = true, features = ["test"] } 49 | 50 | [package.metadata.docs.rs] 51 | all-features = true 52 | rustc-args = ["--cfg", "docsrs"] 53 | 54 | [[bench]] 55 | name = "dynptr" 56 | harness = false 57 | 58 | [[bench]] 59 | name = "boxed_slices" 60 | harness = false 61 | 62 | [[bench]] 63 | name = "enums" 64 | harness = false 65 | 66 | 67 | [[bench]] 68 | name = "allocators" 69 | harness = false 70 | required-features = ["libc"] 71 | -------------------------------------------------------------------------------- /stabby/benches/allocators.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use rand::Rng; 3 | use stabby::alloc::{allocators, collections::arc_btree::ArcBTreeSet, vec::Vec, IAlloc}; 4 | 5 | fn bench(c: &mut Criterion, set: &[i32]) { 6 | c.bench_function(core::any::type_name::(), |b| { 7 | b.iter(|| { 8 | let mut vec = Vec::new_in(T::default()); 9 | let mut btree = ArcBTreeSet::<_, _, false, 5>::new_in(T::default()); 10 | for &i in set { 11 | vec.push(i); 12 | btree.insert(i); 13 | } 14 | black_box((vec, btree)); 15 | }) 16 | }); 17 | } 18 | 19 | fn bench_allocs(c: &mut Criterion) { 20 | let mut rng = rand::thread_rng(); 21 | for n in [10, 100, 1000, 10000].into_iter() { 22 | let set = (0..n).map(|_| rng.gen()).collect::>(); 23 | // bench::(c, &set); 24 | bench::(c, &set); 25 | bench::(c, &set); 26 | } 27 | } 28 | 29 | criterion_group!(benches, bench_allocs); 30 | criterion_main!(benches); 31 | -------------------------------------------------------------------------------- /stabby/benches/boxed_slices.rs: -------------------------------------------------------------------------------- 1 | use std::mem::MaybeUninit; 2 | 3 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 4 | 5 | fn bench_slices(c: &mut Criterion) { 6 | c.bench_function("std_box_new", |b| { 7 | b.iter(|| Box::new(black_box(15))); 8 | }); 9 | c.bench_function("stabby_box_new", |b| { 10 | b.iter(|| stabby::boxed::Box::new(black_box(15))); 11 | }); 12 | c.bench_function("stabby_box_make", |b| { 13 | b.iter(|| unsafe { stabby::boxed::Box::make(|slot| Ok(slot.write(black_box(15)))) }); 14 | }); 15 | c.bench_function("std_box_big_new", |b| { 16 | b.iter(|| Box::<[usize; 10000]>::new([15; 10000])); 17 | }); 18 | c.bench_function("stabby_box_big_new", |b| { 19 | b.iter(|| stabby::boxed::Box::<[usize; 10000]>::new([15; 10000])); 20 | }); 21 | c.bench_function("stabby_box_big_make", |b| { 22 | b.iter(|| unsafe { 23 | stabby::boxed::Box::<[usize; 10000]>::make(|slot| { 24 | for slot in core::mem::transmute::< 25 | &mut MaybeUninit<[usize; 10000]>, 26 | &mut [MaybeUninit; 10000], 27 | >(slot) 28 | { 29 | slot.write(15); 30 | } 31 | Ok(slot.assume_init_mut()) 32 | }) 33 | }); 34 | }); 35 | for n in [10, 100, 1000, 10000, 100000].into_iter() { 36 | c.bench_function(&format!("collect_std_vec_{n}"), |b| { 37 | b.iter(|| black_box(0..n).map(black_box).collect::>()) 38 | }); 39 | c.bench_function(&format!("collect_stabby_vec_{n}"), |b| { 40 | b.iter(|| { 41 | black_box(0..n) 42 | .map(black_box) 43 | .collect::>() 44 | }) 45 | }); 46 | c.bench_function(&format!("push_std_vec_{n}"), |b| { 47 | b.iter(|| { 48 | let mut v = Vec::new(); 49 | for i in 0..n { 50 | v.push(black_box(i)) 51 | } 52 | }) 53 | }); 54 | c.bench_function(&format!("push_stabby_vec_{n}"), |b| { 55 | b.iter(|| { 56 | let mut v = stabby::vec::Vec::new(); 57 | for i in 0..n { 58 | v.push(black_box(i)) 59 | } 60 | }) 61 | }); 62 | let std_vec = (0..n).collect::>(); 63 | let stabby_vec = (0..n).collect::>(); 64 | c.bench_function(&format!("arc_std_vec_{n}"), |b| { 65 | b.iter_custom(|it| { 66 | let mut t = std::time::Duration::new(0, 0); 67 | for _ in 0..it { 68 | let clone = std_vec.clone(); 69 | let start = std::time::Instant::now(); 70 | let arc: std::sync::Arc<[_]> = black_box(clone.into()); 71 | t += start.elapsed(); 72 | core::mem::drop(arc); 73 | } 74 | t 75 | }) 76 | }); 77 | c.bench_function(&format!("arc_stabby_vec_{n}"), |b| { 78 | b.iter_custom(|it| { 79 | let mut t = std::time::Duration::new(0, 0); 80 | for _ in 0..it { 81 | let clone = stabby_vec.clone(); 82 | let start = std::time::Instant::now(); 83 | let arc: stabby::sync::ArcSlice<_> = black_box(clone.into()); 84 | t += start.elapsed(); 85 | core::mem::drop(arc); 86 | } 87 | t 88 | }) 89 | }); 90 | if n == 100000 { 91 | c.bench_function(&format!("box_std_vec_{n}"), |b| { 92 | b.iter_custom(|it| { 93 | let mut t = std::time::Duration::new(0, 0); 94 | for _ in 0..it { 95 | let clone = std_vec.clone(); 96 | let start = std::time::Instant::now(); 97 | let arc: Box<[_]> = black_box(clone.into()); 98 | t += start.elapsed(); 99 | core::mem::drop(arc); 100 | } 101 | t 102 | }) 103 | }); 104 | c.bench_function(&format!("box_stabby_vec_{n}"), |b| { 105 | b.iter_custom(|it| { 106 | let mut t = std::time::Duration::new(0, 0); 107 | for _ in 0..it { 108 | let clone = stabby_vec.clone(); 109 | let start = std::time::Instant::now(); 110 | let arc: stabby::boxed::BoxedSlice<_> = black_box(clone.into()); 111 | t += start.elapsed(); 112 | core::mem::drop(arc); 113 | } 114 | t 115 | }) 116 | }); 117 | } 118 | } 119 | } 120 | 121 | criterion_group!(benches, bench_slices); 122 | criterion_main!(benches); 123 | -------------------------------------------------------------------------------- /stabby/benches/dynptr.rs: -------------------------------------------------------------------------------- 1 | use std::hint::unreachable_unchecked; 2 | 3 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 4 | use rand::{Rng, SeedableRng}; 5 | 6 | #[stabby::stabby] 7 | pub trait Op { 8 | extern "C" fn run(&self, lhs: u32, rhs: u32) -> u32; 9 | } 10 | pub trait OpNoExtern { 11 | fn run(&self, lhs: u32, rhs: u32) -> u32; 12 | } 13 | 14 | macro_rules! impl_op { 15 | ($t: ty, $lhs: ident, $rhs: ident: $e: expr) => { 16 | impl Op for $t { 17 | extern "C" fn run(&self, $lhs: u32, $rhs: u32) -> u32 { 18 | $e 19 | } 20 | } 21 | impl OpNoExtern for $t { 22 | fn run(&self, $lhs: u32, $rhs: u32) -> u32 { 23 | $e 24 | } 25 | } 26 | }; 27 | } 28 | 29 | #[allow(dead_code)] 30 | #[derive(Default)] 31 | pub struct Add(u8); 32 | impl_op!(Add, lhs, rhs: lhs.wrapping_add(rhs)); 33 | #[allow(dead_code)] 34 | #[derive(Default)] 35 | pub struct Sub(u8); 36 | impl_op!(Sub, lhs, rhs: lhs.wrapping_sub(rhs)); 37 | #[allow(dead_code)] 38 | #[derive(Default)] 39 | pub struct Mul(u8); 40 | impl_op!(Mul, lhs, rhs: lhs.wrapping_mul(rhs)); 41 | 42 | const N: usize = 1000; 43 | fn bench_dynptr(c: &mut Criterion) { 44 | let rng = rand::rngs::StdRng::seed_from_u64(0); 45 | let mut stabby_arc: Vec)> = Vec::with_capacity(N); 46 | let mut stabby_box: Vec)> = Vec::with_capacity(N); 47 | let mut std_arc: Vec> = Vec::with_capacity(N); 48 | let mut std_box: Vec> = Vec::with_capacity(N); 49 | let mut std_arc_noext: Vec> = Vec::with_capacity(N); 50 | let mut std_box_noext: Vec> = Vec::with_capacity(N); 51 | let ops = (0..N) 52 | .map({ 53 | let mut rng = rng.clone(); 54 | move |_| rng.gen_range(0..=2u8) 55 | }) 56 | .collect::>(); 57 | // Baseline (14.734 µs) for constructing 1K arc traits. 58 | c.bench_function("stabby_arc_new", |b| { 59 | b.iter(|| { 60 | stabby_arc.clear(); 61 | for i in &ops { 62 | stabby_arc.push(match i { 63 | 0 => stabby::sync::Arc::new(Add::default()).into(), 64 | 1 => stabby::sync::Arc::new(Sub::default()).into(), 65 | 2 => stabby::sync::Arc::new(Mul::default()).into(), 66 | _ => unsafe { unreachable_unchecked() }, 67 | }); 68 | } 69 | }); 70 | }); 71 | c.bench_function("stabby_box_new", |b| { 72 | b.iter(|| { 73 | stabby_box.clear(); 74 | for i in &ops { 75 | stabby_box.push(match i { 76 | 0 => stabby::boxed::Box::new(Add::default()).into(), 77 | 1 => stabby::boxed::Box::new(Sub::default()).into(), 78 | 2 => stabby::boxed::Box::new(Mul::default()).into(), 79 | _ => unsafe { unreachable_unchecked() }, 80 | }); 81 | } 82 | }); 83 | }); 84 | c.bench_function("std_arc_new", |b| { 85 | b.iter(|| { 86 | std_arc.clear(); 87 | for i in &ops { 88 | std_arc.push(match i { 89 | 0 => std::sync::Arc::new(Add::default()), 90 | 1 => std::sync::Arc::new(Sub::default()), 91 | 2 => std::sync::Arc::new(Mul::default()), 92 | _ => unsafe { unreachable_unchecked() }, 93 | }); 94 | } 95 | }); 96 | }); 97 | c.bench_function("std_box_new", |b| { 98 | b.iter(|| { 99 | std_box.clear(); 100 | for i in &ops { 101 | #[allow(clippy::box_default)] 102 | std_box.push(match i { 103 | 0 => Box::new(Add::default()), 104 | 1 => Box::new(Sub::default()), 105 | 2 => Box::new(Mul::default()), 106 | _ => unsafe { unreachable_unchecked() }, 107 | }); 108 | } 109 | }); 110 | }); 111 | c.bench_function("std_arc_noext_new", |b| { 112 | b.iter(|| { 113 | std_arc_noext.clear(); 114 | for i in &ops { 115 | std_arc_noext.push(match i { 116 | 0 => std::sync::Arc::new(Add::default()), 117 | 1 => std::sync::Arc::new(Sub::default()), 118 | 2 => std::sync::Arc::new(Mul::default()), 119 | _ => unsafe { unreachable_unchecked() }, 120 | }); 121 | } 122 | }); 123 | }); 124 | c.bench_function("std_box_noext_new", |b| { 125 | b.iter(|| { 126 | std_box_noext.clear(); 127 | for i in &ops { 128 | #[allow(clippy::box_default)] 129 | std_box_noext.push(match i { 130 | 0 => Box::new(Add::default()), 131 | 1 => Box::new(Sub::default()), 132 | 2 => Box::new(Mul::default()), 133 | _ => unsafe { unreachable_unchecked() }, 134 | }); 135 | } 136 | }); 137 | }); 138 | c.bench_function("stabby_arc_run", |b| { 139 | let mut rng = black_box(rng.clone()); 140 | b.iter(|| { 141 | black_box( 142 | stabby_arc 143 | .iter() 144 | .fold(rng.gen(), |acc, it| it.run(acc, rng.gen())), 145 | ); 146 | }); 147 | }); 148 | c.bench_function("stabby_box_run", |b| { 149 | let mut rng = black_box(rng.clone()); 150 | b.iter(|| { 151 | black_box( 152 | stabby_box 153 | .iter() 154 | .fold(rng.gen(), |acc, it| it.run(acc, rng.gen())), 155 | ); 156 | }); 157 | }); 158 | c.bench_function("std_arc_run", |b| { 159 | let mut rng = black_box(rng.clone()); 160 | b.iter(|| { 161 | black_box( 162 | std_arc 163 | .iter() 164 | .fold(rng.gen(), |acc, it| it.run(acc, rng.gen())), 165 | ); 166 | }); 167 | }); 168 | c.bench_function("std_box_run", |b| { 169 | let mut rng = black_box(rng.clone()); 170 | b.iter(|| { 171 | black_box( 172 | std_box 173 | .iter() 174 | .fold(rng.gen(), |acc, it| it.run(acc, rng.gen())), 175 | ); 176 | }); 177 | }); 178 | c.bench_function("std_arc_noext_run", |b| { 179 | let mut rng = black_box(rng.clone()); 180 | b.iter(|| { 181 | black_box( 182 | std_arc_noext 183 | .iter() 184 | .fold(rng.gen(), |acc, it| it.run(acc, rng.gen())), 185 | ); 186 | }); 187 | }); 188 | c.bench_function("std_box_noext_run", |b| { 189 | let mut rng = black_box(rng.clone()); 190 | b.iter(|| { 191 | black_box( 192 | std_box_noext 193 | .iter() 194 | .fold(rng.gen(), |acc, it| it.run(acc, rng.gen())), 195 | ); 196 | }); 197 | }); 198 | } 199 | 200 | criterion_group!(benches, bench_dynptr); 201 | criterion_main!(benches); 202 | -------------------------------------------------------------------------------- /stabby/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!(r#"cargo:rustc-check-cfg=cfg(stabby_unsafe_wakers, values(none(), "true", "false"))"#); 3 | } 4 | -------------------------------------------------------------------------------- /stabby/src/allocs/borrow.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | //! Stable Cows! 16 | 17 | use core::borrow::Borrow; 18 | 19 | use crate::{ 20 | abi::{IDeterminantProvider, IStable}, 21 | str::Str, 22 | string::String, 23 | }; 24 | 25 | #[crate::stabby] 26 | pub enum Cow<'a, Borrowed: IStable + ToOwned> 27 | where 28 | ::Owned: IStable, 29 | { 30 | Borrowed(&'a Borrowed), 31 | Owned(::Owned), 32 | } 33 | 34 | impl Cow<'_, Borrowed> 35 | where 36 | ::Owned: IStable, 37 | for<'a> &'a Borrowed: IDeterminantProvider<::Owned>, 38 | { 39 | pub fn into_owned(self) -> ::Owned { 40 | self.match_owned(|b| b.to_owned(), |o| o) 41 | } 42 | pub fn to_owned(self) -> Cow<'static, Borrowed> { 43 | Cow::Owned(self.into_owned()) 44 | } 45 | } 46 | impl Borrow for Cow<'_, Borrowed> 47 | where 48 | ::Owned: IStable, 49 | for<'a> &'a Borrowed: IDeterminantProvider<::Owned>, 50 | { 51 | fn borrow(&self) -> &Borrowed { 52 | self.match_ref(|&b| b, |o| o.borrow()) 53 | } 54 | } 55 | 56 | #[crate::stabby] 57 | pub enum CowStr<'a> { 58 | Borrowed(Str<'a>), 59 | Owned(String), 60 | } 61 | -------------------------------------------------------------------------------- /stabby/src/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | #![deny( 16 | missing_docs, 17 | clippy::missing_panics_doc, 18 | clippy::missing_const_for_fn, 19 | clippy::missing_safety_doc, 20 | clippy::missing_errors_doc 21 | )] 22 | #![cfg_attr(not(feature = "std"), no_std)] 23 | #![cfg_attr(not(doctest), doc = include_str!("../README.md"))] 24 | 25 | extern crate core; 26 | 27 | pub use stabby_abi::{ 28 | assert_unchecked, dynptr, export, import, stabby, unreachable_unchecked, vtmacro as vtable, 29 | }; 30 | 31 | pub use stabby_abi as abi; 32 | 33 | pub use stabby_abi::alloc::{self, boxed, collections, string, sync, vec}; 34 | 35 | pub use stabby_abi::{Dyn, DynRef}; 36 | 37 | /// ABI-stable tuples 38 | pub use stabby_abi::tuple; 39 | 40 | /// Futures can be ABI-stable if you wish hard enough 41 | #[cfg_attr( 42 | stabby_unsafe_wakers = "true", 43 | 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)" 44 | )] 45 | pub mod future { 46 | pub use crate::abi::future::*; 47 | use crate::boxed::Box; 48 | /// A type alias for `dynptr!(Box + Send + Sync + 'a>)` 49 | pub type DynFuture<'a, Output> = 50 | crate::dynptr!(Box + Send + Sync + 'a>); 51 | /// A type alias for `dynptr!(Box + Send + 'a>)` 52 | pub type DynFutureUnsync<'a, Output> = 53 | crate::dynptr!(Box + Send + 'a>); 54 | /// A type alias for `dynptr!(Box + 'a>)` 55 | pub type DynFutureUnsend<'a, Output> = crate::dynptr!(Box + 'a>); 56 | } 57 | 58 | /// The collection of traits that make `dynptr!(Box)` possible 59 | pub use crate::abi::closure; 60 | pub use crate::abi::{option, result, slice, str}; 61 | 62 | pub use crate::abi::{vtable::Any, AccessAs, IStable, IntoSuperTrait}; 63 | 64 | #[cfg(all(feature = "libloading", any(unix, windows, doc)))] 65 | /// Integration with [`libloading`](::libloading), allowing symbol loads to be validated thanks to either reflection or canaries. 66 | /// 67 | /// Requires the `libloading` feature to be enabled. 68 | pub mod libloading; 69 | 70 | /// ABI-stable representations of durations and instants. 71 | pub mod time; 72 | 73 | /// Like [`std::format`], but returning an ABI-stable [`String`](crate::string::String) 74 | #[macro_export] 75 | macro_rules! format { 76 | ($($t: tt)*) => {{ 77 | use ::core::fmt::Write; 78 | let mut s = $crate::string::String::default(); 79 | ::core::write!(s, $($t)*).map(move |_| s) 80 | }}; 81 | } 82 | 83 | #[cfg(doc)] 84 | #[doc = include_str!("../TUTORIAL.md")] 85 | pub mod _tutorial_ {} 86 | // #[cfg(test)] 87 | mod tests { 88 | mod enums; 89 | mod layouts; 90 | mod traits; 91 | } 92 | -------------------------------------------------------------------------------- /stabby/src/libloading.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | /// An extension trait to load symbols from libraries while checking for ABI-compatibility. 16 | pub trait StabbyLibrary { 17 | /// Gets `symbol` from the library, using stabby's reports to check for compatibility. 18 | /// 19 | /// The library must have a symbol with the appropriate type named the same way, and marked with `#[stabby::export]`. 20 | /// 21 | /// # Safety 22 | /// Since this function calls foreign code, it is inherently unsafe. 23 | /// 24 | /// # Errors 25 | /// If the symbol is not found OR reflection indicated an ABI-mismatch. 26 | /// 27 | /// The symbol missing can mean that the library was compiled with a different version of stabby, or that the symbol was not exported with `#[stabby::export]`. 28 | /// 29 | /// In case of ABI-mismatch, the error will contain a message indicating the expected and found type layouts. 30 | unsafe fn get_stabbied<'a, T: crate::IStable>( 31 | &'a self, 32 | symbol: &[u8], 33 | ) -> Result, Box>; 34 | /// Gets `symbol` from the library, using stabby's canaries to check for compatibility. 35 | /// 36 | /// The library must have a symbol with the appropriate type named the same way, and marked with `#[stabby::export(canaries)]`. 37 | /// 38 | /// Note that while canaries greatly improve the chance ABI compatibility, they don't guarantee it. 39 | /// 40 | /// # Safety 41 | /// The symbol on the other side of the FFI boundary cannot be type-checked, and may still have a different 42 | /// ABI than expected (although the canaries should greatly reduce that risk). 43 | /// 44 | /// # Errors 45 | /// If the symbol is not found OR the canaries were not found. 46 | unsafe fn get_canaried<'a, T>( 47 | &'a self, 48 | symbol: &[u8], 49 | ) -> Result, Box>; 50 | } 51 | /// A symbol bound to a library's lifetime. 52 | pub struct Symbol<'a, T> { 53 | inner: T, 54 | lt: core::marker::PhantomData<&'a ()>, 55 | } 56 | impl core::ops::Deref for Symbol<'_, T> { 57 | type Target = T; 58 | fn deref(&self) -> &Self::Target { 59 | &self.inner 60 | } 61 | } 62 | mod canaries { 63 | stabby_abi::canary_suffixes!(); 64 | } 65 | const STABBIED_SUFFIX: &[u8] = b"_stabbied_v3"; 66 | const REPORT_SUFFIX: &[u8] = b"_stabbied_v3_report"; 67 | impl StabbyLibrary for libloading::Library { 68 | /// Gets `symbol` from the library, using stabby's reports to check for compatibility. 69 | /// 70 | /// The library must have a symbol with the appropriate type named the same way, and marked with `#[stabby::export]`. 71 | /// 72 | /// # Safety 73 | /// Since this function calls foreign code, it is inherently unsafe. 74 | unsafe fn get_stabbied<'a, T: crate::IStable>( 75 | &'a self, 76 | symbol: &[u8], 77 | ) -> Result, Box> { 78 | let stabbied = self.get:: Option>( 79 | &[symbol, STABBIED_SUFFIX].concat(), 80 | )?; 81 | match stabbied(T::REPORT) { 82 | Some(f) => Ok(Symbol { 83 | inner: f, 84 | lt: core::marker::PhantomData, 85 | }), 86 | None => { 87 | let report = self 88 | .get:: &'static crate::abi::report::TypeReport>( 89 | &[symbol, REPORT_SUFFIX].concat(), 90 | )?; 91 | let report = report(); 92 | Err(format!( 93 | "Report mismatch: loader({loader}), lib({report}", 94 | loader = T::REPORT 95 | ) 96 | .into()) 97 | } 98 | } 99 | } 100 | /// Gets `symbol` from the library, using stabby's canaries to check for compatibility. 101 | /// 102 | /// The library must have a symbol with the appropriate type named the same way, and marked with `#[stabby::export(canaries)]`. 103 | /// 104 | /// Note that while canaries greatly improve the chance ABI compatibility, they don't guarantee it. 105 | /// 106 | /// # Safety 107 | /// The symbol on the other side of the FFI boundary cannot be type-checked, and may still have a different 108 | /// ABI than expected (although the canaries should greatly reduce that risk). 109 | unsafe fn get_canaried<'a, T>( 110 | &'a self, 111 | symbol: &[u8], 112 | ) -> Result, Box> { 113 | let stabbied = self.get::(symbol)?; 114 | for suffix in [ 115 | canaries::CANARY_RUSTC, 116 | canaries::CANARY_OPT_LEVEL, 117 | canaries::CANARY_DEBUG, 118 | canaries::CANARY_TARGET, 119 | canaries::CANARY_NUM_JOBS, 120 | ] { 121 | if let Err(e) = self.get::(&[symbol, suffix.as_bytes()].concat()) { 122 | return Err(format!( 123 | "Canary {symbol}{suffix} not found: {e}", 124 | symbol = std::str::from_utf8_unchecked(symbol) 125 | ) 126 | .into()); 127 | } 128 | } 129 | Ok(stabbied) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /stabby/src/map.rs: -------------------------------------------------------------------------------- 1 | use stabby_abi::{IDeterminantProvider, IStable}; 2 | 3 | #[crate::stabby] 4 | /// A trait for key-value maps, allowing to pass hashmaps and the likes accross FFI boundary. 5 | pub trait IMap> { 6 | /// Returns a reference to the value associated to `key`, or None if the key isn't present in the map. 7 | extern "C" fn get<'a>(&'a self, key: &K) -> crate::option::Option<&'a V>; 8 | /// Returns a mutable reference to the value associated to `key`, or None if the key isn't present in the map. 9 | extern "C" fn get_mut<'a>(&'a mut self, key: &K) -> crate::option::Option<&'a mut V>; 10 | /// Inserts `value`, associated to `key`, returning the previous associated value if it existed. 11 | extern "C" fn insert(&mut self, key: K, value: V) -> crate::option::Option; 12 | } 13 | 14 | #[cfg(feature = "alloc")] 15 | impl> IMap 16 | for std_alloc::collections::BTreeMap 17 | { 18 | extern "C" fn get<'a>(&'a self, key: &K) -> crate::option::Option<&'a V> { 19 | self.get(key).into() 20 | } 21 | 22 | extern "C" fn get_mut<'a>(&'a mut self, key: &K) -> crate::option::Option<&'a mut V> { 23 | self.get_mut(key).into() 24 | } 25 | 26 | extern "C" fn insert(&mut self, key: K, value: V) -> crate::option::Option { 27 | self.insert(key, value).into() 28 | } 29 | } 30 | 31 | #[cfg(feature = "std")] 32 | impl> IMap 33 | for std::collections::HashMap 34 | { 35 | extern "C" fn get<'a>(&'a self, key: &K) -> crate::option::Option<&'a V> { 36 | self.get(key).into() 37 | } 38 | 39 | extern "C" fn get_mut<'a>(&'a mut self, key: &K) -> crate::option::Option<&'a mut V> { 40 | self.get_mut(key).into() 41 | } 42 | 43 | extern "C" fn insert(&mut self, key: K, value: V) -> crate::option::Option { 44 | self.insert(key, value).into() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /stabby/src/tests/enums.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | #[test] 16 | fn enums() { 17 | use crate as stabby; 18 | use core::num::{NonZeroU16, NonZeroU8}; 19 | use stabby::{ 20 | abi::{typenum2, IDeterminantProvider, IStable}, 21 | result::Result, 22 | tuple::{Tuple2, Tuple3}, 23 | }; 24 | fn inner(a: A, b: B, expected_size: usize) 25 | where 26 | A: Clone + PartialEq + core::fmt::Debug + IStable, 27 | B: Clone + PartialEq + core::fmt::Debug + IStable, 28 | A: IDeterminantProvider, 29 | >::Determinant: core::fmt::Debug, 30 | Result: IStable, 31 | { 32 | println!( 33 | "Testing: {}({a:?}) | {}({b:?})", 34 | core::any::type_name::(), 35 | core::any::type_name::() 36 | ); 37 | let ac = a.clone(); 38 | let bc = b.clone(); 39 | let a: core::result::Result = Ok(a); 40 | let b: core::result::Result = Err(b); 41 | let a: Result<_, _> = a.into(); 42 | println!( 43 | "discriminant: {}, OkShift: {}, ErrShift: {}, Debug: {}", 44 | core::any::type_name::<>::Determinant>(), 45 | <>::OkShift as typenum2::Unsigned>::USIZE, 46 | <>::ErrShift as typenum2::Unsigned>::USIZE, 47 | 0 // core::any::type_name::<>::Debug>(), 48 | ); 49 | assert!(a.is_ok()); 50 | let b: Result<_, _> = b.into(); 51 | assert!(b.is_err()); 52 | assert_eq!(a, Result::Ok(ac.clone())); 53 | assert_eq!(a.unwrap(), ac); 54 | assert_eq!(b, Result::Err(bc.clone())); 55 | assert_eq!(b.unwrap_err(), bc); 56 | assert_eq!( as IStable>::size(), expected_size); 57 | println!() 58 | } 59 | inner(8u8, 2u8, 2); 60 | let _: typenum2::U2 = as IStable>::Size::default(); 61 | let _: typenum2::U2 = , Result> as IStable>::Size::default(); 62 | inner(Tuple2(1u8, 2u16), Tuple2(3u16, 4u16), 6); 63 | inner( 64 | Tuple2(1u8, 2u16), 65 | Tuple2(3u8, NonZeroU8::new(4).unwrap()), 66 | 4, 67 | ); 68 | inner( 69 | Tuple2(3u8, NonZeroU8::new(4).unwrap()), 70 | Tuple2(1u8, 2u16), 71 | 4, 72 | ); 73 | inner( 74 | Tuple3(3u8, NonZeroU8::new(4).unwrap(), 6u16), 75 | Tuple2(1u8, 2u16), 76 | 4, 77 | ); 78 | inner(Tuple2(3u8, 4u16), Tuple2(1u8, 2u16), 4); 79 | inner(3u16, Tuple2(1u8, 2u16), 4); 80 | inner(1u8, NonZeroU16::new(6).unwrap(), 4); 81 | let _: typenum2::U2 = as IStable>::Size::default(); 82 | let _: typenum2::U2 = as IStable>::Size::default(); 83 | let _: typenum2::U1 = as IStable>::Size::default(); 84 | inner(true, (), 1); 85 | let string: stabby::string::String = stabby::string::String::from("Hi"); 86 | inner( 87 | string, 88 | stabby::str::Str::from("there"), 89 | core::mem::size_of::(), 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /stabby/src/tests/layouts.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | use core::num::{NonZeroU16, NonZeroU32}; 16 | 17 | use crate as stabby; 18 | use stabby::tuple::{Tuple2, Tuple3}; 19 | 20 | #[stabby::stabby] 21 | pub union UnionTest { 22 | u8: u8, 23 | u64: u64, 24 | } 25 | #[stabby::stabby] 26 | pub union UTest2 { 27 | usize: usize, 28 | u32s: Tuple3, 29 | } 30 | 31 | #[stabby::stabby] 32 | #[repr(u32)] 33 | pub enum NoFields { 34 | _A, 35 | _B, 36 | } 37 | #[stabby::stabby] 38 | #[repr(C, u8)] 39 | pub enum FieldsC { 40 | _A(NonZeroU32), 41 | _B, 42 | } 43 | #[stabby::stabby] 44 | pub enum FieldsStabby { 45 | _A(NonZeroU32), 46 | _B, 47 | } 48 | #[stabby::stabby] 49 | #[repr(C, u8)] 50 | #[allow(dead_code)] 51 | pub enum MultiFieldsC { 52 | A(NonZeroU16), 53 | B, 54 | C(Tuple2), 55 | D(u8), 56 | E, 57 | } 58 | #[stabby::stabby] 59 | #[repr(C, u8)] 60 | #[allow(dead_code)] 61 | pub enum MultipleFieldsPerVariant { 62 | A(NonZeroU16, u8), 63 | B, 64 | C { c1: u8, c2: u16 }, 65 | D(u8), 66 | E, 67 | } 68 | 69 | #[stabby::stabby] 70 | #[repr(stabby)] 71 | pub enum MultiFieldsStabby { 72 | A(NonZeroU16), 73 | B, 74 | C(Tuple2), 75 | D(u32), 76 | E, 77 | } 78 | #[stabby::stabby] 79 | pub enum SameFieldsFourTimes { 80 | A(T), 81 | B(T), 82 | C(T), 83 | D(T), 84 | } 85 | 86 | #[stabby::stabby(no_opt)] 87 | pub struct WeirdStructBadLayout { 88 | fields: FieldsC, 89 | no_fields: NoFields, 90 | utest: UnionTest, 91 | u32: u32, 92 | } 93 | 94 | #[stabby::stabby] 95 | pub struct WeirdStructBadLayout2 { 96 | fields: FieldsC, 97 | no_fields: NoFields, 98 | utest: UnionTest, 99 | } 100 | 101 | #[stabby::stabby] 102 | pub struct WeirdStruct { 103 | fields: FieldsC, 104 | no_fields: NoFields, 105 | u32: u32, 106 | utest: UnionTest, 107 | } 108 | 109 | #[stabby::stabby] 110 | fn somefunc(_: u8) -> u8 { 111 | 0 112 | } 113 | #[stabby::stabby] 114 | pub struct Test { 115 | b: u8, 116 | a: u32, 117 | } 118 | 119 | #[stabby::stabby] 120 | pub struct SingleNiche { 121 | a: usize, 122 | b: &'static u8, 123 | } 124 | #[stabby::stabby] 125 | pub struct EndPadding { 126 | a: usize, 127 | b: u8, 128 | } 129 | #[allow(dead_code)] 130 | #[stabby::stabby] 131 | #[repr(transparent)] 132 | pub struct Transparent { 133 | a: usize, 134 | } 135 | 136 | #[test] 137 | fn layouts() { 138 | use core::num::NonZeroU8; 139 | use stabby::abi::istable::IForbiddenValues; 140 | use stabby::abi::{istable::Saturator, typenum2::*, Array, End, IStable, Result}; 141 | use stabby::tuple::Tuple8; 142 | 143 | let _: B1 = <::HasExactlyOneNiche>::default(); 144 | let _: Saturator = <::HasExactlyOneNiche>::default(); 145 | let _: B0 = < as IStable>::HasExactlyOneNiche>::default(); 146 | let _: Saturator = < as IStable>::HasExactlyOneNiche>::default(); 147 | let _: Saturator = <::HasExactlyOneNiche>::default(); 148 | macro_rules! test { 149 | () => {}; 150 | ($t: ty, $unused: ty, $illegal: ty) => { 151 | test!($t); 152 | let _: core::mem::MaybeUninit<$unused> = 153 | core::mem::MaybeUninit::<<$t as stabby::abi::IStable>::UnusedBits>::uninit(); 154 | let _: core::mem::MaybeUninit<$illegal> = core::mem::MaybeUninit::< 155 | <<$t as stabby::abi::IStable>::ForbiddenValues as IForbiddenValues>::SelectOne, 156 | >::uninit(); 157 | }; 158 | ($t: ty) => { 159 | dbg!(core::mem::size_of::<$t>()); 160 | assert_eq!( 161 | core::mem::size_of::<$t>(), 162 | <$t as stabby::abi::IStable>::size(), 163 | "Size mismatch for {}", 164 | std::any::type_name::<$t>() 165 | ); 166 | assert_eq!( 167 | core::mem::align_of::<$t>(), 168 | <$t as stabby::abi::IStable>::align(), 169 | "Align mismatch for {}", 170 | std::any::type_name::<$t>() 171 | ); 172 | }; 173 | } 174 | 175 | let value = MultiFieldsStabby::D(5); 176 | value.match_ref( 177 | |_| panic!(), 178 | || panic!(), 179 | |_| panic!(), 180 | |&v| assert_eq!(v, 5), 181 | || panic!(), 182 | ); 183 | value.match_owned( 184 | |_| panic!(), 185 | || panic!(), 186 | |_| panic!(), 187 | |v| assert_eq!(v, 5), 188 | || panic!(), 189 | ); 190 | 191 | test!(bool, End, Array); 192 | test!(u8, End, End); 193 | test!(u16, End, End); 194 | test!(u32, End, End); 195 | test!(u64, End, End); 196 | test!(u128, End, End); 197 | test!(usize, End, End); 198 | test!(core::num::NonZeroU8, End, Array); 199 | test!(core::num::NonZeroU16, End, Array>); 200 | test!(core::num::NonZeroU32, End, _); 201 | test!(core::num::NonZeroU64, End, _); 202 | test!(core::num::NonZeroU128, End, _); 203 | test!(core::num::NonZeroUsize, End, _); 204 | test!(i8, End, End); 205 | test!(i16, End, End); 206 | test!(i32, End, End); 207 | test!(i64, End, End); 208 | test!(i128, End, End); 209 | test!(isize, End, End); 210 | test!(core::num::NonZeroI8, End, _); 211 | test!(core::num::NonZeroI16, End, _); 212 | test!(core::num::NonZeroI32, End, _); 213 | test!(core::num::NonZeroI64, End, _); 214 | test!(core::num::NonZeroI128, End, _); 215 | test!(core::num::NonZeroIsize, End, _); 216 | test!(&'static u8, End, _); 217 | test!(&'static mut u8, End, _); 218 | test!(stabby::slice::Slice<'static, u8>, End, _); 219 | test!(stabby::tuple::Tuple2, End, End); 220 | test!(stabby::tuple::Tuple2, Array>>, End); 221 | test!(stabby::tuple::Tuple2, Array>>, End); 222 | test!(stabby::tuple::Tuple2>, Array>>>>>, End); 223 | test!(stabby::abi::Union, End, End); 224 | test!(stabby::abi::Union, End, End); 225 | test!(stabby::abi::Union<(), u8>, End, End); 226 | test!(stabby::result::Result<(), ()>, Array, End); 227 | test!(UnionTest, End, End); 228 | test!(FieldsC, Array>>, End); 229 | test!(FieldsStabby, End, End); 230 | test!(MultiFieldsC, Array, End); 231 | test!(Result, Array>>>, End); 232 | test!(Result, Result>, Array, End); 233 | test!(Result, End, End); 234 | test!(MultiFieldsStabby, Array, End); 235 | test!(stabby::tuple::Tuple2<(), usize>, End, End); 236 | test!(stabby::tuple::Tuple2, End, End); 237 | test!(NoFields); 238 | test!(WeirdStruct); 239 | test!(WeirdStructBadLayout); 240 | test!(Option<&'static u8>, End, End); 241 | test!(Option<&'static mut u8>, End, End); 242 | test!(Option, End, End); 243 | // Ensure that only 8 positions are tried before giving switching to external tags 244 | assert_eq!(core::mem::size_of::>(), 2 * 16); 245 | assert_eq!( 246 | core::mem::size_of::< 247 | Tuple2< 248 | Tuple8, 249 | Tuple8, 250 | >, 251 | >(), 252 | 16 253 | ); 254 | assert_eq!( 255 | core::mem::size_of::< 256 | Result< 257 | Tuple2, 258 | Tuple2< 259 | Tuple8, 260 | Tuple8, 261 | >, 262 | >, 263 | >(), 264 | 3 * 16 265 | ); 266 | let _ = Align1024::ID; 267 | let _: U1024 = ::Align::default(); 268 | let _: U1024 = ::Size::default(); 269 | } 270 | 271 | #[allow(dead_code)] 272 | #[stabby::stabby] 273 | #[repr(align(16))] 274 | struct Align128(u128); 275 | 276 | #[allow(dead_code)] 277 | #[stabby::stabby] 278 | #[repr(align(1024))] 279 | struct Align1024(u8); 280 | -------------------------------------------------------------------------------- /stabby/src/tests/traits.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 ZettaScale Technology 3 | // 4 | // This program and the accompanying materials are made available under the 5 | // terms of the Eclipse Public License 2.0 which is available at 6 | // http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 7 | // which is available at https://www.apache.org/licenses/LICENSE-2.0. 8 | // 9 | // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 10 | // 11 | // Contributors: 12 | // Pierre Avital, 13 | // 14 | 15 | // MYTRAIT 16 | 17 | #![cfg_attr(stabby_unsafe_wakers = "true", allow(deprecated))] 18 | 19 | pub use crate as stabby; 20 | use stabby::boxed::Box; 21 | 22 | #[stabby::stabby(checked)] 23 | pub trait MyTrait { 24 | type Output; 25 | extern "C" fn do_stuff<'a>(&'a self, with: &'a Self::Output) -> &'a u8; 26 | extern "C" fn gen_stuff(&mut self) -> Self::Output; 27 | } 28 | 29 | // IMPL 30 | 31 | impl MyTrait for u8 { 32 | type Output = u8; 33 | extern "C" fn do_stuff<'a>(&'a self, _: &'a Self::Output) -> &'a u8 { 34 | self 35 | } 36 | extern "C" fn gen_stuff(&mut self) -> Self::Output { 37 | *self 38 | } 39 | } 40 | impl MyTrait for u16 { 41 | type Output = u8; 42 | extern "C" fn do_stuff<'a>(&'a self, _: &'a Self::Output) -> &'a u8 { 43 | &0 44 | } 45 | extern "C" fn gen_stuff(&mut self) -> Self::Output { 46 | *self as u8 47 | } 48 | } 49 | 50 | // MYTRAIT2 51 | #[stabby::stabby(checked)] 52 | pub trait MyTrait2 { 53 | extern "C" fn do_stuff2(&self) -> u8; 54 | } 55 | 56 | // IMPL 57 | 58 | impl MyTrait2 for u8 { 59 | extern "C" fn do_stuff2(&self) -> u8 { 60 | *self 61 | } 62 | } 63 | impl MyTrait2 for u16 { 64 | extern "C" fn do_stuff2(&self) -> u8 { 65 | (*self) as u8 66 | } 67 | } 68 | 69 | #[stabby::stabby(checked)] 70 | pub trait MyTrait3 { 71 | type A; 72 | type B; 73 | extern "C" fn do_stuff3<'a>(&'a self, a: &'a Self::A, b: Self::B) -> Self::B; 74 | extern "C" fn gen_stuff3(&mut self, with: Hi) -> Self::A; 75 | extern "C" fn test(&mut self); 76 | extern "C" fn test2(&mut self); 77 | } 78 | 79 | impl MyTrait3> for u8 { 80 | type A = u8; 81 | type B = u8; 82 | extern "C" fn do_stuff3<'a>(&'a self, _a: &'a Self::A, _b: Self::B) -> Self::B { 83 | *self 84 | } 85 | extern "C" fn gen_stuff3(&mut self, _with: Box<()>) -> Self::A { 86 | *self 87 | } 88 | extern "C" fn test(&mut self) {} 89 | extern "C" fn test2(&mut self) {} 90 | } 91 | impl MyTrait3> for u16 { 92 | type A = u8; 93 | type B = u8; 94 | extern "C" fn do_stuff3<'a>(&'a self, _a: &'a Self::A, _b: Self::B) -> Self::B { 95 | (*self) as u8 96 | } 97 | extern "C" fn gen_stuff3(&mut self, _with: Box<()>) -> Self::A { 98 | (*self) as u8 99 | } 100 | extern "C" fn test(&mut self) {} 101 | extern "C" fn test2(&mut self) {} 102 | } 103 | 104 | #[stabby::stabby(checked)] 105 | pub trait AsyncRead { 106 | extern "C" fn read<'a>( 107 | &'a mut self, 108 | buffer: stabby::slice::SliceMut<'a, u8>, 109 | ) -> stabby::future::DynFuture<'a, usize>; 110 | } 111 | impl AsyncRead for stabby::slice::Slice<'_, u8> { 112 | extern "C" fn read<'a>( 113 | &'a mut self, 114 | mut buffer: stabby::slice::SliceMut<'a, u8>, 115 | ) -> stabby::future::DynFuture<'a, usize> { 116 | Box::new(async move { 117 | let len = self.len().min(buffer.len()); 118 | let (l, r) = self.split_at(len); 119 | let r = unsafe { core::mem::transmute::<&[u8], &[u8]>(r) }; 120 | buffer[..len].copy_from_slice(l); 121 | *self = r.into(); 122 | len 123 | }) 124 | .into() 125 | } 126 | } 127 | 128 | #[test] 129 | fn dyn_traits() { 130 | let boxed = Box::new(6u8); 131 | let dyned = , A = u8, B = u8> + Send> 133 | )>::from(boxed); 134 | let dyned: stabby::dynptr!(Box) = dyned.into_super(); 135 | assert_eq!(dyned.stable_downcast_ref::(), Some(&6)); 136 | assert!(dyned.stable_downcast_ref::().is_none()); 137 | 138 | let boxed = Box::new(6u8); 139 | let mut dyned = , A = u8, B = u8> + Sync + MyTrait> 141 | )>::from(boxed); 142 | assert_eq!(dyned.do_stuff(&0), &6); 143 | assert_eq!(dyned.gen_stuff(), 6); 144 | assert_eq!(dyned.gen_stuff3(Box::new(())), 6); 145 | // assert_eq!(unsafe { dyned.downcast_ref::() }, Some(&6)); 146 | // assert!(unsafe { dyned.downcast_ref::() }.is_none()); 147 | 148 | fn trait_assertions(_t: T) {} 149 | trait_assertions(dyned); 150 | } 151 | 152 | #[test] 153 | fn arc_traits() { 154 | use stabby::sync::Arc; 155 | let boxed = Arc::new(6u8); 156 | let dyned = 157 | >)>::from(boxed); 158 | assert_eq!(dyned.do_stuff(&0), &6); 159 | // assert_eq!(unsafe { dyned.downcast_ref::() }, Some(&6)); 160 | // assert!(unsafe { dyned.downcast_ref::() }.is_none()); 161 | fn trait_assertions(_t: T) {} 162 | trait_assertions(dyned); 163 | let boxed = Arc::new(6u8); 164 | let dyned = 165 | + Send>)>::from( 166 | boxed, 167 | ); 168 | let dyned: stabby::dynptr!(Arc) = dyned.into_super(); 169 | assert_eq!(dyned.stable_downcast_ref::(), Some(&6)); 170 | assert!(dyned.stable_downcast_ref::().is_none()); 171 | } 172 | 173 | #[cfg(not(miri))] 174 | #[test] 175 | fn async_trait() { 176 | use core::time::Duration; 177 | use stabby::future::DynFuture; 178 | const END: usize = 1; 179 | let (tx, rx) = smol::channel::bounded(5); 180 | let read_task = async move { 181 | let mut expected = 0; 182 | println!("Awaiting recv {expected}"); 183 | while let Ok(r) = rx.recv().await { 184 | assert_eq!(dbg!(r), expected); 185 | expected += 1; 186 | println!("Awaiting recv {expected}"); 187 | } 188 | assert_eq!(expected, END) 189 | }; 190 | let write_task = async move { 191 | for w in 0..END { 192 | println!("Awaiting tx.send {w}"); 193 | tx.send(w).await.unwrap(); 194 | println!("Awaiting timer"); 195 | smol::Timer::after(Duration::from_millis(30)).await; 196 | } 197 | }; 198 | fn check(read: DynFuture<'static, ()>, write: DynFuture<'static, ()>) { 199 | let rtask = smol::spawn(read); 200 | let wtask = smol::spawn(write); 201 | smol::block_on(smol::future::zip(rtask, wtask)); 202 | } 203 | check(Box::new(read_task).into(), Box::new(write_task).into()) 204 | } 205 | -------------------------------------------------------------------------------- /stabby/src/time.rs: -------------------------------------------------------------------------------- 1 | use core::sync::atomic::{AtomicI64, Ordering}; 2 | 3 | /// A stable equivalent to [`core::time::Duration`] 4 | #[crate::stabby] 5 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 6 | pub struct Duration { 7 | /// The number of seconds elapsed. 8 | pub secs: u64, 9 | /// The number of subsecond nanos elapsed. 10 | pub nanos: u32, 11 | } 12 | impl Duration { 13 | /// Construct a new [`Duration`]. 14 | pub const fn new(secs: u64, subsec_nanos: u32) -> Self { 15 | Self { 16 | secs, 17 | nanos: subsec_nanos, 18 | } 19 | } 20 | /// Construct a new [`Duration`]. 21 | pub const fn from_millis(millis: u64) -> Self { 22 | Self { 23 | secs: millis / 1000, 24 | nanos: ((millis % 1000) * 1000000) as u32, 25 | } 26 | } 27 | /// Construct a new [`Duration`]. 28 | pub const fn from_micros(micros: u64) -> Self { 29 | Self { 30 | secs: micros / 1000000, 31 | nanos: ((micros % 1000000) * 1000) as u32, 32 | } 33 | } 34 | 35 | /// Construct a new [`Duration`]. 36 | /// 37 | /// # Panics 38 | /// if `secs` is negative. 39 | #[cfg(feature = "std")] 40 | pub fn from_secs_f64(secs: f64) -> Self { 41 | assert!(secs >= 0.); 42 | Self { 43 | secs: secs.floor() as u64, 44 | nanos: ((secs % 1.) * 1_000_000_000.) as u32, 45 | } 46 | } 47 | /// Returns the number of seconds in the duration. 48 | pub const fn as_secs(&self) -> u64 { 49 | self.secs 50 | } 51 | /// Returns the total number of nanoseconds in the duration. 52 | pub const fn as_nanos(&self) -> u128 { 53 | self.secs as u128 * 1_000_000_000 + self.nanos as u128 54 | } 55 | /// Returns the number of seconds in the duration, including sub-seconds. 56 | pub fn as_secs_f64(&self) -> f64 { 57 | self.as_nanos() as f64 / 1_000_000_000. 58 | } 59 | /// Returns the number of nanoseconds after the last second of the duration. 60 | pub const fn subsec_nanos(&self) -> u32 { 61 | self.nanos 62 | } 63 | /// Returns the number of microseconds after the last second of the duration. 64 | pub const fn subsec_micros(&self) -> u32 { 65 | self.subsec_nanos() / 1000 66 | } 67 | /// Returns the number of milliseconds after the last second of the duration. 68 | pub const fn subsec_millis(&self) -> u32 { 69 | self.subsec_nanos() / 1000000 70 | } 71 | } 72 | impl core::ops::AddAssign for Duration { 73 | fn add_assign(&mut self, rhs: Self) { 74 | let nanos = self.nanos + rhs.nanos; 75 | self.secs += rhs.secs + (nanos / 1_000_000_000) as u64; 76 | self.nanos = nanos % 1_000_000_000; 77 | } 78 | } 79 | impl core::ops::Add for Duration { 80 | type Output = Self; 81 | fn add(mut self, rhs: Self) -> Self { 82 | self += rhs; 83 | self 84 | } 85 | } 86 | impl From for Duration { 87 | fn from(value: core::time::Duration) -> Self { 88 | Self { 89 | secs: value.as_secs(), 90 | nanos: value.subsec_nanos(), 91 | } 92 | } 93 | } 94 | impl From for core::time::Duration { 95 | fn from(value: Duration) -> Self { 96 | Self::new(value.secs, value.nanos) 97 | } 98 | } 99 | 100 | /// A signed [`Duration`] represented as a single [`AtomicI64`], allowing to change its value atomically. 101 | /// 102 | /// Its resolution is 1μs, and the maximum encodable duration is 278737 years. 103 | #[crate::stabby] 104 | pub struct AtomicDuration { 105 | t: AtomicI64, 106 | } 107 | const SHIFT: i64 = 20; 108 | const MASK: i64 = 0xfffff; 109 | /// A sign to be paired with a [`Duration`]. 110 | #[crate::stabby] 111 | #[repr(u8)] 112 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] 113 | pub enum Sign { 114 | #[default] 115 | /// + 116 | Positive, 117 | /// - 118 | Negative, 119 | } 120 | impl AtomicDuration { 121 | const fn i64_to_duration(mut t: i64) -> (Duration, Sign) { 122 | let sign = if t.is_negative() { 123 | t = -t; 124 | Sign::Negative 125 | } else { 126 | Sign::Positive 127 | }; 128 | let micros = (t & MASK) as u32; 129 | let secs = t >> SHIFT; 130 | (Duration::new(secs as u64, micros * 1000), sign) 131 | } 132 | const fn duration_to_i64(t: Duration, sign: Sign) -> i64 { 133 | let t = ((t.as_secs() as i64) << SHIFT) + (t.subsec_micros() as i64); 134 | match sign { 135 | Sign::Positive => t, 136 | Sign::Negative => -t, 137 | } 138 | } 139 | /// This type's time resolution. 140 | pub const RESOLUTION: Duration = Self::i64_to_duration(1).0; 141 | /// This type's maximum value. 142 | pub const MAX: Duration = Self::i64_to_duration(i64::MAX).0; 143 | /// Atomically loads the stored value, converting it to a duration-sign tuple. 144 | /// 145 | /// The [`Ordering`] is used in a single `load` operation. 146 | pub fn load(&self, ord: Ordering) -> (Duration, Sign) { 147 | Self::i64_to_duration(self.t.load(ord)) 148 | } 149 | /// Converts the duration-sign tuple into before storing it atomically. 150 | /// 151 | /// The [`Ordering`] is used in a single `store` operation. 152 | pub fn store(&self, duration: Duration, sign: Sign, ord: Ordering) { 153 | self.t.store(Self::duration_to_i64(duration, sign), ord) 154 | } 155 | /// Perform a [`AtomicDuration::load`] and [`AtomicDuration::store`] in a single atomic operation 156 | pub fn swap(&self, duration: Duration, sign: Sign, ord: Ordering) -> (Duration, Sign) { 157 | Self::i64_to_duration(self.t.swap(Self::duration_to_i64(duration, sign), ord)) 158 | } 159 | /// Construct a new [`AtomicDuration`] 160 | pub const fn new(duration: Duration, sign: Sign) -> Self { 161 | Self { 162 | t: AtomicI64::new(Self::duration_to_i64(duration, sign)), 163 | } 164 | } 165 | } 166 | 167 | #[cfg(feature = "std")] 168 | pub use impls::{AtomicInstant, Instant, SystemTime}; 169 | 170 | #[cfg(feature = "std")] 171 | mod impls { 172 | use super::{AtomicDuration, Duration}; 173 | use core::sync::atomic::Ordering; 174 | use std::time::UNIX_EPOCH; 175 | /// A stable equivalent to [`std::time::SystemTime`]. 176 | /// # Stability 177 | /// It is always represented as a duration since [`std::time::UNIX_EPOCH`]. 178 | #[crate::stabby] 179 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 180 | pub struct SystemTime(pub(crate) Duration); 181 | impl SystemTime { 182 | /// An anchor in time which can be used to create new SystemTime instances or learn about where in time a SystemTime lies. 183 | /// 184 | /// This constant is defined to be "1970-01-01 00:00:00 UTC" on all systems with respect to the system clock. Using duration_since on an existing SystemTime instance can tell how far away from this point in time a measurement lies, and using UNIX_EPOCH + duration can be used to create a SystemTime instance to represent another fixed point in time. 185 | pub const UNIX_EPOCH: Self = SystemTime(Duration { secs: 0, nanos: 0 }); 186 | /// Measure the current [`SystemTime`]. 187 | pub fn now() -> Self { 188 | std::time::SystemTime::now().into() 189 | } 190 | } 191 | impl core::ops::AddAssign for SystemTime { 192 | fn add_assign(&mut self, rhs: Duration) { 193 | self.0.add_assign(rhs) 194 | } 195 | } 196 | impl core::ops::Add for SystemTime { 197 | type Output = Self; 198 | fn add(mut self, rhs: Duration) -> Self { 199 | self += rhs; 200 | self 201 | } 202 | } 203 | 204 | impl From for SystemTime { 205 | fn from(value: std::time::SystemTime) -> Self { 206 | Self( 207 | value 208 | .duration_since(UNIX_EPOCH) 209 | .unwrap_or(core::time::Duration::new(0, 0)) 210 | .into(), 211 | ) 212 | } 213 | } 214 | impl From for std::time::SystemTime { 215 | fn from(value: SystemTime) -> Self { 216 | UNIX_EPOCH + value.0.into() 217 | } 218 | } 219 | 220 | /// A stable equivalent to [`std::time::Instant`]. 221 | /// 222 | /// # Stability 223 | /// It is always represented as a duration since a mem-zeroed [`std::time::Instant`]. 224 | /// 225 | /// ## Verified platforms 226 | /// Platforms where [`Instant`] is known to be stable accross processes: 227 | /// - Unix systems use [`libc::CLOCK_MONOTONIC`](https://docs.rs/libc/latest/libc/constant.CLOCK_MONOTONIC.html), which is system-global. 228 | /// - MacOS use [`libc::CLOCK_UPTIME_RAW`](https://docs.rs/libc/latest/libc/constant.CLOCK_UPTIME_RAW.html), which is system-global. 229 | /// - Windows uses performance counters, and [states](https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps#guidance-for-acquiring-time-stamps) that said counters are consistent accross processes, except on platforms that don't provide consistent multi-core counters on pre-Vista systems 230 | /// 231 | /// Platforms where [`Instant`] is only known to be stable within a process: 232 | /// - None to date 233 | /// 234 | /// Platforms where [`Instant`] is only known to be unstable accross dynamic linkage units: 235 | /// - None to date, if such a platform is discovered and distinguishable, its support for 236 | /// [`Instant`] will be retracted until a stable representation is found. 237 | /// 238 | /// ## Doubts 239 | /// While this representation should work on most platforms, it assumes that within a 240 | /// given process, but accross dynamic linkage units, the OS will use the same clock 241 | /// to construct [`std::time::Instant`]. 242 | /// 243 | /// While very likely to be true, this is unverified yet for niche platforms. 244 | /// Please write an issue on [stabby's official repo](https://github.com/ZettaScaleLabs/stabby) 245 | /// if you have proof either way for your system of choice, and it will be added 246 | /// to this documentation. 247 | #[crate::stabby] 248 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 249 | pub struct Instant(pub(crate) Duration); 250 | impl Instant { 251 | /// The epoch of the [`Instant`] type. 252 | pub const fn zero() -> Self { 253 | Self(Duration { secs: 0, nanos: 0 }) 254 | } 255 | /// Measure the current [`Instant`]. 256 | pub fn now() -> Self { 257 | std::time::Instant::now().into() 258 | } 259 | } 260 | #[rustversion::attr(since(1.75), const)] 261 | fn instant_epoch() -> std::time::Instant { 262 | unsafe { core::mem::MaybeUninit::zeroed().assume_init() } 263 | } 264 | impl core::ops::AddAssign for Instant { 265 | fn add_assign(&mut self, rhs: Duration) { 266 | self.0.add_assign(rhs) 267 | } 268 | } 269 | impl core::ops::Add for Instant { 270 | type Output = Self; 271 | fn add(mut self, rhs: Duration) -> Self { 272 | self += rhs; 273 | self 274 | } 275 | } 276 | impl From for Instant { 277 | fn from(value: std::time::Instant) -> Self { 278 | Self(value.duration_since(instant_epoch()).into()) 279 | } 280 | } 281 | impl From for std::time::Instant { 282 | fn from(value: Instant) -> Self { 283 | instant_epoch() + value.0.into() 284 | } 285 | } 286 | 287 | /// An [`Instant`] stored as an [`AtomicDuration`] since [`Instant::zero`] 288 | #[crate::stabby] 289 | pub struct AtomicInstant(pub(crate) AtomicDuration); 290 | impl AtomicInstant { 291 | /// Measure the current time into a new [`AtomicInstant`]. 292 | pub fn now() -> Self { 293 | Self(AtomicDuration::new( 294 | instant_epoch().elapsed().into(), 295 | super::Sign::Positive, 296 | )) 297 | } 298 | /// Construct the epoch for [`AtomicInstant`]. 299 | pub const fn epoch() -> Self { 300 | Self(AtomicDuration::new( 301 | Duration::new(0, 0), 302 | super::Sign::Positive, 303 | )) 304 | } 305 | /// Atomically update `self` to [`Instant::now`] while returning its previous value. 306 | pub fn update(&self, ordering: Ordering) -> Instant { 307 | Instant( 308 | self.0 309 | .swap( 310 | instant_epoch().elapsed().into(), 311 | super::Sign::Positive, 312 | ordering, 313 | ) 314 | .0, 315 | ) 316 | } 317 | /// Atomically update `self` to `instant` while returning its previous value. 318 | pub fn swap(&self, instant: Instant, ordering: Ordering) -> Instant { 319 | Instant(self.0.swap(instant.0, super::Sign::Positive, ordering).0) 320 | } 321 | /// Atomically read `self`. 322 | pub fn load(&self, ordering: Ordering) -> Instant { 323 | Instant(self.0.load(ordering).0) 324 | } 325 | /// Atomically write `instant` to `self`. 326 | pub fn store(&self, instant: Instant, ordering: Ordering) { 327 | self.0.store(instant.0, super::Sign::Positive, ordering) 328 | } 329 | } 330 | } 331 | --------------------------------------------------------------------------------