diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9dc84fc..88162d3 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -13,8 +13,6 @@ jobs: run: cargo build --verbose - name: Run tests run: cargo test --workspace - - name: Run tests without default features - run: cargo test --no-default-features --lib miri: name: Run tests under miri @@ -29,5 +27,5 @@ jobs: override: true - name: Run tests under miri run: cargo +nightly miri test - - name: Run tests under miri without default features - run: cargo +nightly miri test --no-default-features --lib + env: + MIRIFLAGS: "-Zmiri-tree-borrows" diff --git a/.gitignore b/.gitignore index 4470988..1626893 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -target/ -Cargo.lock \ No newline at end of file +/.direnv +/target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..133aa6f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,330 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "cc" +version = "1.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "gcmodule-fuzz" +version = "0.0.0" +dependencies = [ + "jrsonnet-gcmodule", + "libfuzzer-sys", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "jrsonnet-gcmodule" +version = "0.3.10" +dependencies = [ + "jrsonnet-gcmodule-derive", + "parking_lot", + "quickcheck", +] + +[[package]] +name = "jrsonnet-gcmodule-derive" +version = "0.3.10" +dependencies = [ + "jrsonnet-gcmodule", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "rand", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] diff --git a/Cargo.toml b/Cargo.toml index 2b6c983..dfa2d87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [workspace.package] -version = "0.3.7" +version = "0.3.10" authors = ["Jun Wu ", "Yaroslav Bolyukin "] -edition = "2021" +edition = "2024" license = "MIT" repository = "https://github.com/CertainLach/gcmodule" [package] name = "jrsonnet-gcmodule" version.workspace = true -authors.workspace = true +authors.workspace = true edition.workspace = true license.workspace = true repository.workspace = true @@ -21,11 +21,11 @@ readme = "README.md" quickcheck = { version = "1.0", default-features = false } [dependencies] -jrsonnet-gcmodule-derive = { version = "0.3.7", optional = true, path = "gcmodule_derive" } +jrsonnet-gcmodule-derive = { version = "0.3.10", optional = true, path = "gcmodule_derive" } parking_lot = { version = "0.12.3", optional = true } [features] -default = ["derive", "sync"] +default = ["derive"] debug = [] derive = ["jrsonnet-gcmodule-derive"] nightly = [] @@ -33,5 +33,4 @@ sync = ["parking_lot"] testutil = [] [workspace] -members = ["gcmodule_derive"] - +members = ["gcmodule_derive", "fuzz"] diff --git a/flake.lock b/flake.lock index e4f9236..d541e19 100644 --- a/flake.lock +++ b/flake.lock @@ -20,26 +20,27 @@ }, "nixpkgs": { "locked": { - "lastModified": 1732544120, - "narHash": "sha256-zf7ralxx0k+w5smp1n3ZGp9qsHwPH4t/m/dzWs3eHks=", + "lastModified": 1746862638, + "narHash": "sha256-AH5ailVsZF6uyT7Y0eYKMPdaMbSAWIjHI1/cUejCLqg=", "owner": "nixos", "repo": "nixpkgs", - "rev": "ed44a65978a6aad175f972ecc96b965619e97407", + "rev": "82d7645c4fce80f261fe02cd1e66658b061b518f", "type": "github" }, "original": { "owner": "nixos", + "ref": "release-24.11", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1728538411, - "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", + "lastModified": 1744536153, + "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", + "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", "type": "github" }, "original": { @@ -61,11 +62,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1732328983, - "narHash": "sha256-RHt12f/slrzDpSL7SSkydh8wUE4Nr4r23HlpWywed9E=", + "lastModified": 1746844454, + "narHash": "sha256-GcUWDQUDRYrD34ol90KGUpjbVcOfUNbv0s955jPecko=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "ed8aa5b64f7d36d9338eb1d0a3bb60cf52069a72", + "rev": "be092436d4c0c303b654e4007453b69c0e33009e", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 0e26778..a1c1946 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Gcmodule"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs"; + nixpkgs.url = "github:nixos/nixpkgs/release-24.11"; flake-utils.url = "github:numtide/flake-utils"; rust-overlay.url = "github:oxalica/rust-overlay"; }; @@ -22,6 +22,7 @@ devShell = pkgs.mkShell { nativeBuildInputs = with pkgs; [ rust + cargo-fuzz cargo-edit valgrind ]; diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 06e2660..8eebea2 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -4,20 +4,18 @@ name = "gcmodule-fuzz" version = "0.0.0" authors = ["Jun Wu "] publish = false -edition = "2018" +edition.workspace = true [package.metadata] cargo-fuzz = true [dependencies] -libfuzzer-sys = "0.3" +libfuzzer-sys = "0.4.9" -[dependencies.gcmodule] +[dependencies.jrsonnet-gcmodule] path = ".." features = ["testutil"] [[bin]] name = "graph16" path = "fuzz_targets/graph16.rs" - -[workspace] diff --git a/fuzz/fuzz_targets/graph16.rs b/fuzz/fuzz_targets/graph16.rs index d0517c9..34f93c2 100644 --- a/fuzz/fuzz_targets/graph16.rs +++ b/fuzz/fuzz_targets/graph16.rs @@ -1,5 +1,5 @@ #![no_main] -use gcmodule::testutil::test_small_graph; +use jrsonnet_gcmodule::testutil::test_small_graph; use libfuzzer_sys::fuzz_target; fuzz_target!(|data: (u8, u16, u16, Vec)| { diff --git a/gcmodule_derive/tests/trace.rs b/gcmodule_derive/tests/trace.rs index f647252..0a6a3fe 100644 --- a/gcmodule_derive/tests/trace.rs +++ b/gcmodule_derive/tests/trace.rs @@ -1,4 +1,4 @@ -use jrsonnet_gcmodule::{Cc, Trace, Tracer}; +use jrsonnet_gcmodule::{Cc, Trace, TraceBox, Tracer}; use jrsonnet_gcmodule_derive::Trace as DeriveTrace; use std::cell::RefCell; use std::rc::Rc; @@ -29,7 +29,7 @@ fn test_named_struct() { #[derive(DeriveTrace)] struct S1 { - a: Option>, + a: Option>, b: (u32, u64), } assert!(S1::is_type_tracked()); @@ -42,14 +42,14 @@ fn test_type_parameters() { a: Option, } assert!(!S0::::is_type_tracked()); - assert!(S0::>::is_type_tracked()); + assert!(S0::>::is_type_tracked()); #[derive(DeriveTrace)] struct S1 { a: Option>, } assert!(!S1::::is_type_tracked()); - assert!(!S1::>::is_type_tracked()); + assert!(!S1::>::is_type_tracked()); } #[test] @@ -93,7 +93,7 @@ fn test_container_skip() { fn test_recursive_struct() { #[derive(DeriveTrace)] struct A { - b: Box, + b: TraceBox, #[trace(tracking(ignore))] a: Box, } @@ -109,7 +109,7 @@ fn test_recursive_struct() { #[derive(DeriveTrace)] #[trace(tracking(force))] struct C { - c: (Box, Box), + c: (Box, TraceBox), } assert!(C::is_type_tracked()); } @@ -121,21 +121,21 @@ fn test_unnamed_struct() { assert!(!S0::is_type_tracked()); #[derive(DeriveTrace)] - struct S1(u8, Box); + struct S1(u8, TraceBox); assert!(S1::is_type_tracked()); } #[test] fn test_real_cycles() { #[derive(DeriveTrace, Default)] - struct S(RefCell>>); + struct S(RefCell>>); { let s1: Cc = Default::default(); let s2: Cc = Default::default(); let s3: Cc = Default::default(); - *(s1.0.borrow_mut()) = Some(Box::new(s2.clone())); - *(s2.0.borrow_mut()) = Some(Box::new(s3.clone())); - *(s3.0.borrow_mut()) = Some(Box::new(s1.clone())); + *(s1.0.borrow_mut()) = Some(TraceBox(Box::new(s2.clone()))); + *(s2.0.borrow_mut()) = Some(TraceBox(Box::new(s3.clone()))); + *(s3.0.borrow_mut()) = Some(TraceBox(Box::new(s1.clone()))); } assert_eq!(jrsonnet_gcmodule::collect_thread_cycles(), 3); } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 1dec8ac..812375d 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2024-11-20" +channel = "nightly-2025-05-08" components = ["rustfmt", "clippy", "rust-analyzer", "rust-src", "miri"] diff --git a/src/cc.rs b/src/cc.rs index 2a9a3e1..80e1786 100644 --- a/src/cc.rs +++ b/src/cc.rs @@ -7,6 +7,7 @@ use crate::trace::Trace; use crate::trace::Tracer; use std::cell::UnsafeCell; use std::mem; +use std::mem::offset_of; use std::mem::ManuallyDrop; use std::ops::Deref; use std::ops::DerefMut; @@ -78,6 +79,7 @@ pub type Cc = RawCc; pub type Weak = RawWeak; /// Low-level type for [`Cc`](type.Cc.html). +#[repr(transparent)] pub struct RawCc(NonNull>); /// Low-level type for [`Weak`](type.Weak.html). @@ -178,21 +180,22 @@ impl RawCc { // Create a GcHeader before the CcBox. This is similar to cpython. let header = space.empty_header(); let cc_box_with_header = RawCcBoxWithGcHeader { header, cc_box }; - let mut boxed = Box::new(cc_box_with_header); + let boxed = Box::new(cc_box_with_header); // Fix-up fields in GcHeader. This is done after the creation of the // Box so the memory addresses are stable. - space.insert(&mut boxed.header, &boxed.cc_box); assert!(mem::align_of::() >= mem::align_of::>()); + let header_space = offset_of!(RawCcBoxWithGcHeader<(), O>, cc_box); debug_assert_eq!( - mem::size_of::() + header_space + aligned_size( mem::size_of::>(), mem::align_of::>().max(mem::align_of::()) ), mem::size_of::>() ); - let ptr: *mut RawCcBox = &mut boxed.cc_box; - Box::leak(boxed); + let leaked = Box::leak(boxed); + space.insert(&leaked.header, &leaked.cc_box); + let ptr: *mut RawCcBox = &raw mut leaked.cc_box; ptr } else { Box::into_raw(Box::new(cc_box)) @@ -225,9 +228,8 @@ impl RawCc { // Cc has 2 usize values: The first one is the same // as Cc. The second one is the vtable. The vtable pointer // is the same as the second pointer of `&dyn Trace`. - let mut fat_ptr: [usize; 2] = mem::transmute(self.inner().deref() as &dyn Trace); - let self_ptr: usize = mem::transmute(self); - fat_ptr[0] = self_ptr; + let fat_ptr: *const dyn Trace = &**self.inner(); + let fat_ptr = fat_ptr.with_addr(mem::transmute::, usize>(self)); mem::transmute(fat_ptr) } } @@ -236,11 +238,15 @@ impl RawCc { /// Create Cc from Cc where T: impl Trait, Trait is trait object #[macro_export] macro_rules! cc_dyn { - ($(#[$($meta:meta)+])* $conv:ident, $t:path $(, $new_vis:vis fn new() {...})?) => { + ( + $(#[$($meta:meta)+])* + $conv:ident $(<$($gen:ident $(: $($gen_bound:path),+)?),+>)?, + $t:path $(, $new_vis:vis fn new() {...})? + ) => { $(#[$($meta)+])* #[repr(transparent)] - pub struct $conv($crate::Cc); - impl $crate::Trace for $conv { + pub struct $conv$(<$($gen),+>)?($crate::Cc); + impl$(<$($gen: 'static),+>)? $crate::Trace for $conv$(<$($gen),+>)? { fn trace(&self, tracer: &mut $crate::Tracer) { $crate::Cc::::trace(&self.0, tracer) } @@ -250,17 +256,15 @@ macro_rules! cc_dyn { true } } - impl $conv { + impl$(<$($gen $(: $($gen_bound),+)?),+>)? $conv$(<$($gen),+>)? { $($new_vis)? fn new(input: T) -> Self { - use std::ops::Deref; - unsafe { - let cc: $crate::RawCc<_, _> = $crate::Cc::new(input); - let mut fat_ptr: [usize; 2] = - core::mem::transmute(cc.inner().deref() as &dyn $t); - let self_ptr: usize = core::mem::transmute(cc); - fat_ptr[0] = self_ptr; - $conv(core::mem::transmute::<[usize; 2], $crate::RawCc>(fat_ptr)) - } + let cc: $crate::RawCc<_, _> = $crate::Cc::new(input); + let cc_ptr = unsafe { $crate::Cc::inner_box(&raw const cc) }; + let ptr: *const dyn $t = cc_ptr; + let ptr = unsafe { + ptr.with_addr(core::mem::transmute::<$crate::RawCc, usize>(cc)) + }; + $conv(unsafe {core::mem::transmute::<*const dyn $t, $crate::RawCc>(ptr)}) } } }; @@ -292,10 +296,19 @@ impl RawCcBox { } #[inline] - fn header(&self) -> &O::Header { + fn header(&self) -> *const O::Header { debug_assert!(self.is_tracked()); + + // using () instead of T should work here. + // `offset_of!` macro has no ability to lookup unsized field offset, + // however we want access to the RawCcBox sized prelude, and it should work + // fine here. + let box_offset = offset_of!(RawCcBoxWithGcHeader<(), O>, cc_box); + // memoffset::offset_of!(RawCcBoxWithGcHeader, cc_box); + let ptr: *const Self = self; // safety: See `Cc::new`. GcHeader is before CcBox for tracked objects. - unsafe { cast_ref(self, -(mem::size_of::() as isize)) } + let ptr: *const O::Header = unsafe { ptr.cast::().byte_sub(box_offset) }.cast(); + ptr } #[inline] @@ -477,6 +490,12 @@ impl RawCc { unsafe { self.0.as_ref() } } + #[doc(hidden)] + pub unsafe fn inner_box(this: *const Self) -> *const T { + let ptr: *const ManuallyDrop = unsafe { (*(*this).0.as_ptr()).value.get() }.cast_const(); + ptr as _ + } + /// `trace` without `T: Trace` bound. /// /// Useful for structures with `Cc` fields where `T` does not implement @@ -721,18 +740,10 @@ impl, U: ?Sized, O: AbstractObjectSpace> { } -#[inline] -unsafe fn cast_ref(value: &T, offset_bytes: isize) -> &R { - let ptr: *const T = value; - let ptr: *const u8 = ptr as _; - let ptr = ptr.offset(offset_bytes); - &*(ptr as *const R) -} - #[inline] unsafe fn cast_box( value: Box>, -) -> Box> { +) -> Box> { unsafe { let mut ptr: *const RawCcBox = Box::into_raw(value); // ptr can be "thin" (1 pointer) or "fat" (2 pointers). @@ -742,17 +753,20 @@ unsafe fn cast_box( *pptr = (*pptr).offset(-1); let ptr: *mut RawCcBoxWithGcHeader = mem::transmute(ptr); Box::from_raw(ptr) -} +}} #[cfg(test)] mod tests { + use self::collect::GcHeader; + use super::*; use crate::collect::Linked; + use crate::TraceBox; /// Check that `GcHeader::value()` returns a working trait object. #[test] fn test_gc_header_value() { - let v1: Cc> = Cc::new(Box::new(1)); + let v1: Cc> = Cc::new(TraceBox(Box::new(1))); assert_eq!(v1.ref_count(), 1); let v2 = v1.clone(); @@ -762,7 +776,7 @@ mod tests { let v3: &dyn CcDyn = v1.inner() as &dyn CcDyn; assert_eq!(v3.gc_ref_count(), 2); - let v4: &dyn CcDyn = v2.inner().header().value(); + let v4: &dyn CcDyn = unsafe{&*GcHeader::value_ptr(v2.inner().header())}; assert_eq!(v4.gc_ref_count(), 2); } diff --git a/src/collect.rs b/src/collect.rs index d2a9237..6349e71 100644 --- a/src/collect.rs +++ b/src/collect.rs @@ -14,10 +14,10 @@ use crate::Cc; use crate::Trace; use std::cell::Cell; use std::cell::RefCell; +use std::cell::UnsafeCell; use std::marker::PhantomData; use std::mem; -use std::ops::Deref; -use std::pin::Pin; +use std::ptr::without_provenance; /// Provides advanced explicit control about where to store [`Cc`](type.Cc.html) /// objects. @@ -38,18 +38,18 @@ use std::pin::Pin; /// # Example /// /// ``` -/// use jrsonnet_gcmodule::{Cc, ObjectSpace, Trace}; +/// use jrsonnet_gcmodule::{Cc, ObjectSpace, Trace, TraceBox}; /// use std::cell::RefCell; /// /// let mut space = ObjectSpace::default(); /// assert_eq!(space.count_tracked(), 0); /// /// { -/// type List = Cc>>>; +/// type List = Cc>>>; /// let a: List = space.create(Default::default()); /// let b: List = space.create(Default::default()); -/// a.borrow_mut().push(Box::new(b.clone())); -/// b.borrow_mut().push(Box::new(a.clone())); +/// a.borrow_mut().push(TraceBox(Box::new(b.clone()))); +/// b.borrow_mut().push(TraceBox(Box::new(a.clone()))); /// } /// /// assert_eq!(space.count_tracked(), 2); @@ -57,7 +57,7 @@ use std::pin::Pin; /// ``` pub struct ObjectSpace { /// Linked list to the tracked objects. - pub(crate) list: RefCell>>, + pub(crate) list: RefCell, /// Mark `ObjectSpace` as `!Send` and `!Sync`. This enforces thread-exclusive /// access to the linked list so methods can use `&self` instead of @@ -71,7 +71,7 @@ pub trait AbstractObjectSpace: 'static + Sized { type Header; /// Insert "header" and "value" to the linked list. - fn insert(&self, header: &mut Self::Header, value: &dyn CcDyn); + fn insert(&self, header: *const Self::Header, value: &dyn CcDyn); /// Remove from linked list. fn remove(header: &Self::Header); @@ -86,18 +86,19 @@ impl AbstractObjectSpace for ObjectSpace { type RefCount = SingleThreadRefCount; type Header = GcHeader; - fn insert(&self, header: &mut Self::Header, value: &dyn CcDyn) { - let prev: &GcHeader = &self.list.borrow(); - debug_assert!(header.next.get().is_null()); + fn insert(&self, header: *const Self::Header, value: &dyn CcDyn) { + let list = self.list.borrow(); + let prev: &GcHeader = list.inner(); + debug_assert!(unsafe { (*header).next.get() }.is_null()); let next = prev.next.get(); - header.prev.set(prev); - header.next.set(next); + unsafe { (*header).prev.set(prev) }; + unsafe { (*header).next.set(next) }; unsafe { // safety: The linked list is maintained, and pointers are valid. (*next).prev.set(header); // safety: To access vtable pointer. Test by test_gc_header_value. let fat_ptr: [*mut (); 2] = mem::transmute(value); - header.ccdyn_vptr = fat_ptr[1]; + (*header).ccdyn_vptr.set(fat_ptr[1]); } prev.next.set(header); } @@ -142,7 +143,8 @@ impl Default for ObjectSpace { impl ObjectSpace { /// Count objects tracked by this [`ObjectSpace`](struct.ObjectSpace.html). pub fn count_tracked(&self) -> usize { - let list: &GcHeader = &self.list.borrow(); + let list = self.list.borrow(); + let list: &GcHeader = list.inner(); let mut count = 0; visit_list(list, |_| count += 1); count @@ -151,7 +153,8 @@ impl ObjectSpace { /// Collect cyclic garbage tracked by this [`ObjectSpace`](struct.ObjectSpace.html). /// Return the number of objects collected. pub fn collect_cycles(&self) -> usize { - let list: &GcHeader = &self.list.borrow(); + let list = self.list.borrow(); + let list: &GcHeader = list.inner(); collect_list(list, ()) } @@ -185,19 +188,22 @@ pub trait Linked { fn prev(&self) -> *const Self; fn set_prev(&self, other: *const Self); + fn value_ptr(this: *const Self) -> *const dyn CcDyn; /// Get the trait object to operate on the actual `CcBox`. fn value(&self) -> &dyn CcDyn; } /// Internal metadata used by the cycle collector. -#[cfg_attr(target_pointer_width = "32", repr(C, align(8)))] -#[cfg_attr(not(target_pointer_width = "32"), repr(C))] +#[repr(C)] pub struct GcHeader { pub(crate) next: Cell<*const GcHeader>, pub(crate) prev: Cell<*const GcHeader>, /// Vtable of (`&CcBox as &dyn CcDyn`) - pub(crate) ccdyn_vptr: *const (), + pub(crate) ccdyn_vptr: Cell<*const ()>, + + /// https://github.com/rust-lang/unsafe-code-guidelines/issues/256#issuecomment-2506767812 + pub(crate) _marker: UnsafeCell<()>, } impl Linked for GcHeader { @@ -213,16 +219,18 @@ impl Linked for GcHeader { fn set_prev(&self, other: *const Self) { self.prev.set(other) } - #[inline] - fn value(&self) -> &dyn CcDyn { + fn value_ptr(this: *const Self) -> *const dyn CcDyn { // safety: To build trait object from self and vtable pointer. // Test by test_gc_header_value_consistency(). unsafe { - let fat_ptr: (*const (), *const ()) = - ((self as *const Self).offset(1) as _, self.ccdyn_vptr); + let fat_ptr: (*const (), *const ()) = (this.offset(1) as _, (*this).ccdyn_vptr.get()); mem::transmute(fat_ptr) } } + #[inline] + fn value(&self) -> &dyn CcDyn { + unsafe { mem::transmute(Self::value_ptr(self)) } + } } impl GcHeader { @@ -231,7 +239,8 @@ impl GcHeader { Self { next: Cell::new(std::ptr::null()), prev: Cell::new(std::ptr::null()), - ccdyn_vptr: CcDummy::ccdyn_vptr(), + ccdyn_vptr: Cell::new(CcDummy::ccdyn_vptr()), + _marker: UnsafeCell::new(()), } } } @@ -258,13 +267,27 @@ pub fn with_thread_object_space(handler: impl FnOnce(&ObjectSpace) -> R) -> R THREAD_OBJECT_SPACE.with(handler) } +pub(crate) struct OwnedGcHeader { + raw: *mut GcHeader, +} +impl OwnedGcHeader { + fn inner(&self) -> &GcHeader { + unsafe { &*self.raw } + } +} +impl Drop for OwnedGcHeader { + fn drop(&mut self) { + drop(unsafe { Box::from_raw(self.raw) }); + } +} + /// Create an empty linked list with a dummy GcHeader. -pub(crate) fn new_gc_list() -> Pin> { - let pinned = Box::pin(GcHeader::empty()); - let header: &GcHeader = pinned.deref(); - header.prev.set(header); - header.next.set(header); - pinned +pub(crate) fn new_gc_list() -> OwnedGcHeader { + let header = Box::into_raw(Box::new(GcHeader::empty())); + unsafe { (*header).prev.set(header) }; + unsafe { (*header).next.set(header) }; + + OwnedGcHeader { raw: header } } /// Scan the specified linked list. Collect cycles. @@ -293,7 +316,7 @@ const PREV_SHIFT: u32 = 2; #[inline] fn unmask_ptr(ptr: *const T) -> *const T { - ((ptr as usize) & PTR_MASK) as *const T + ptr.map_addr(|ptr| ptr & PTR_MASK) } /// Temporarily use `GcHeader.prev` as `gc_ref_count`. @@ -310,7 +333,7 @@ fn update_refs(list: &L) { // In such case just ignore the object by not marking it as COLLECTING. if ref_count > 0 { let shifted = (ref_count << PREV_SHIFT) | PREV_MASK_COLLECTING; - header.set_prev(shifted as _); + header.set_prev(without_provenance(shifted)); } else { debug_assert!(header.prev() as usize & PREV_MASK_COLLECTING == 0); } @@ -451,38 +474,38 @@ fn restore_prev(list: &L) { } fn is_unreachable(header: &L) -> bool { - let prev = header.prev() as usize; + let prev = header.prev().addr(); is_collecting(header) && (prev >> PREV_SHIFT) == 0 } pub(crate) fn is_collecting(header: &L) -> bool { - let prev = header.prev() as usize; + let prev = header.prev().addr(); (prev & PREV_MASK_COLLECTING) != 0 } fn set_visited(header: &L) -> bool { - let prev = header.prev() as usize; - let visited = (prev & PREV_MASK_VISITED) != 0; + let prev = header.prev(); + let visited = (prev.addr() & PREV_MASK_VISITED) != 0; debug_assert!( !visited, "bug: double visit: {} (is Trace impl correct?)", debug_name(header) ); - let new_prev = prev | PREV_MASK_VISITED; - header.set_prev(new_prev as _); + let new_prev = prev.map_addr(|prev| prev | PREV_MASK_VISITED); + header.set_prev(new_prev); visited } fn unset_collecting(header: &L) { - let prev = header.prev() as usize; - let new_prev = (prev & PREV_MASK_COLLECTING) ^ prev; - header.set_prev(new_prev as _); + let prev = header.prev(); + let new_prev = prev.map_addr(|prev| (prev & PREV_MASK_COLLECTING) ^ prev); + header.set_prev(new_prev); } fn edit_gc_ref_count(header: &L, delta: isize) { let prev = header.prev() as isize; let new_prev = prev + (1 << PREV_SHIFT) * delta; - header.set_prev(new_prev as _); + header.set_prev(without_provenance(new_prev as usize)); } #[allow(unused_variables)] diff --git a/src/lib.rs b/src/lib.rs index 5c73856..154c45f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,14 +33,14 @@ //! many objects are tracked by the collector. //! //! ``` -//! use jrsonnet_gcmodule::{Cc, Trace}; +//! use jrsonnet_gcmodule::{Cc, Trace, TraceBox}; //! use std::cell::RefCell; //! { -//! type List = Cc>>>; +//! type List = Cc>>>; //! let a: List = Default::default(); //! let b: List = Default::default(); -//! a.borrow_mut().push(Box::new(b.clone())); -//! b.borrow_mut().push(Box::new(a.clone())); +//! a.borrow_mut().push(TraceBox(Box::new(b.clone()))); +//! b.borrow_mut().push(TraceBox(Box::new(a.clone()))); //! } //! //! // a and b form circular references. The objects they point to are not @@ -50,34 +50,36 @@ //! assert_eq!(jrsonnet_gcmodule::collect_thread_cycles(), 2); // This will drop a and b. //! assert_eq!(jrsonnet_gcmodule::count_thread_tracked(), 0); // no values are tracked. //! ``` -//! -//! ## Multi-thread support -//! -//! The main type [`Cc`](type.cc.html) works fine in a single-thread environment. -//! -//! There are also [`ThreadedObjectSpace`](struct.ThreadedObjectSpace.html) -//! and [`ThreadedCc`](type.ThreadedCc.html) for multi-thread usecases. Beware -//! they take more memory, are slower, and a bit harder to use. -//! -//! ``` -//! use jrsonnet_gcmodule::{ThreadedObjectSpace, ThreadedCc, Trace}; -//! use std::sync::Mutex; -//! -//! type List = ThreadedCc>>>; -//! let space = ThreadedObjectSpace::default(); -//! { -//! let list1: List = space.create(Mutex::new(Default::default())); -//! let list2: List = space.create(Mutex::new(Default::default())); -//! let thread = std::thread::spawn(move || { -//! list1.borrow().lock().unwrap().push(Box::new(list2.clone())); -//! list2.borrow().lock().unwrap().push(Box::new(list1.clone())); -//! }); -//! thread.join().unwrap(); -//! } -//! assert_eq!(space.count_tracked(), 2); -//! assert_eq!(space.collect_cycles(), 2); -//! assert_eq!(space.count_tracked(), 0); -//! ``` +#![cfg_attr(feature = "sync", doc = r##" + +## Multi-thread support + +The main type [`Cc`](type.cc.html) works fine in a single-thread environment. + +There are also [`ThreadedObjectSpace`](struct.ThreadedObjectSpace.html) +and [`ThreadedCc`](type.ThreadedCc.html) for multi-thread usecases. Beware +they take more memory, are slower, and a bit harder to use. + +``` +use jrsonnet_gcmodule::{ThreadedObjectSpace, ThreadedCc, Trace, TraceBox}; +use std::sync::Mutex; + +type List = ThreadedCc>>>; +let space = ThreadedObjectSpace::default(); +{ + let list1: List = space.create(Mutex::new(Default::default())); + let list2: List = space.create(Mutex::new(Default::default())); + let thread = std::thread::spawn(move || { + list1.borrow().lock().unwrap().push(TraceBox(Box::new(list2.clone()))); + list2.borrow().lock().unwrap().push(TraceBox(Box::new(list1.clone()))); + }); + thread.join().unwrap(); +} +assert_eq!(space.count_tracked(), 2); +assert_eq!(space.collect_cycles(), 2); +assert_eq!(space.count_tracked(), 0); +``` +"##)] //! //! ## Defining new types //! @@ -117,7 +119,7 @@ //! types without referring to trait objects or itself are considered acyclic. //! //! ``` -//! use jrsonnet_gcmodule::{Cc, Trace}; +//! use jrsonnet_gcmodule::{Cc, Trace, TraceBox}; //! //! #[derive(Trace)] //! struct Foo(T1, T2, u8); @@ -127,7 +129,7 @@ //! assert_eq!(jrsonnet_gcmodule::count_thread_tracked(), 0); //! //! // `b` is tracked because it contains a trait object. -//! let b = Cc::new(Foo(Box::new(1) as Box, 2, 3)); +//! let b = Cc::new(Foo(TraceBox(Box::new(1) as Box), 2, 3)); //! assert_eq!(jrsonnet_gcmodule::count_thread_tracked(), 1); //! ``` //! @@ -276,6 +278,8 @@ pub mod testutil; mod trace; mod trace_impls; +pub use trace_impls::TraceBox; + pub use cc::{Cc, RawCc, RawWeak, Weak}; pub use collect::{ collect_thread_cycles, count_thread_tracked, with_thread_object_space, ObjectSpace, @@ -314,7 +318,7 @@ pub use jrsonnet_gcmodule_derive::Trace; #[cfg(not(test))] mod debug { - #[cfg(any(feature = "debug", test))] + #[cfg(any(feature = "debug", feature = "testutil", test))] thread_local!(pub(crate) static NEXT_DEBUG_NAME: std::cell::Cell = Default::default()); #[cfg(any(feature = "debug", test))] thread_local!(pub(crate) static GC_DROPPING: std::cell::Cell = Cell::new(false)); @@ -338,14 +342,13 @@ pub const DEBUG_ENABLED: bool = cfg!(feature = "debug"); #[cfg(not(any(test, feature = "debug")))] pub mod interop { use std::mem; - use std::pin::Pin; - use crate::collect::{new_gc_list, GcHeader, THREAD_OBJECT_SPACE}; + use crate::collect::{new_gc_list, OwnedGcHeader, THREAD_OBJECT_SPACE}; /// Type-erased gc object list pub enum GcState {} - type UnerasedState = Pin>; + type UnerasedState = OwnedGcHeader; /// Dump current interned string pool, to be restored by /// `reenter_thread` @@ -381,9 +384,8 @@ pub mod interop { mod dyn_cc { use crate::Trace; use std::fmt::Debug; - use std::ops::Deref; - use crate::{cc_dyn, Cc}; + use crate::cc_dyn; #[derive(Debug, Trace)] struct Test { @@ -392,7 +394,30 @@ mod dyn_cc { trait DebugAndTrace: Debug + Trace {} impl DebugAndTrace for T where T: Debug + Trace {} - cc_dyn!(CcDebugAndTrace, DebugAndTrace); + cc_dyn!( + #[derive(Debug)] + CcDebugAndTrace, + DebugAndTrace + ); + + trait BorrowTy { + fn borrow_ty(&self) -> &O; + } + cc_dyn!(CcBorrowTy, BorrowTy); + cc_dyn!( + CcBorrowTyAlsoDebug, + BorrowTy + ); + + #[derive(Trace)] + struct Borrowable { + v: T, + } + impl BorrowTy for Borrowable { + fn borrow_ty(&self) -> &T { + &self.v + } + } #[test] fn test_dyn() { @@ -400,17 +425,28 @@ mod dyn_cc { a: "hello".to_owned(), }; + let dynccgeneric = CcBorrowTy::new(Borrowable { v: 1u32 }); + let v = dynccgeneric.0.borrow_ty(); + assert_eq!(*v, 1); + + let dynccgeneric = CcBorrowTyAlsoDebug::new(Borrowable { v: 1u32 }); + let v = dynccgeneric.0.borrow_ty(); + assert_eq!(*v, 1); + let dyncc = CcDebugAndTrace::new(test); - assert_eq!(format!("{dyncc:?}"), "Cc(Test { a: \"hello\" })"); + assert_eq!( + format!("{dyncc:?}"), + "CcDebugAndTrace(Cc(Test { a: \"hello\" }))" + ); let dyncc_is_trace = dyncc; assert_eq!( format!("{dyncc_is_trace:?}"), - "Cc(Cc(Test { a: \"hello\" }))" + "CcDebugAndTrace(Cc(Test { a: \"hello\" }))" ); let dyncc_is_trace_as_dyn = CcDebugAndTrace::new(dyncc_is_trace); assert_eq!( format!("{dyncc_is_trace_as_dyn:?}"), - "Cc(Cc(Test { a: \"hello\" }))" + "CcDebugAndTrace(Cc(CcDebugAndTrace(Cc(Test { a: \"hello\" }))))" ); } } diff --git a/src/ref_count.rs b/src/ref_count.rs index 9dc12c3..1ef78a5 100644 --- a/src/ref_count.rs +++ b/src/ref_count.rs @@ -40,6 +40,7 @@ pub trait RefCount: 'static { fn weak_count(&self) -> usize; } +#[repr(C)] pub struct SingleThreadRefCount(Cell, Cell); impl SingleThreadRefCount { diff --git a/src/sync/collect.rs b/src/sync/collect.rs index fa67193..4159008 100644 --- a/src/sync/collect.rs +++ b/src/sync/collect.rs @@ -1,12 +1,12 @@ -use super::ref_count::ThreadedRefCount; use super::ThreadedCc; +use super::ref_count::ThreadedRefCount; +use crate::Trace; use crate::cc::CcDummy; use crate::cc::CcDyn; use crate::collect; use crate::collect::AbstractObjectSpace; use crate::collect::Linked; use crate::debug; -use crate::Trace; use parking_lot::Mutex; use parking_lot::RwLock; use std::cell::Cell; @@ -20,7 +20,7 @@ pub struct Header { prev: Cell<*const Header>, /// Vtable of (`&CcBox as &dyn CcDyn`) - ccdyn_vptr: *const (), + ccdyn_vptr: Cell<*const ()>, /// Lock for mutating the linked list. linked_list_lock: Arc>, @@ -47,25 +47,25 @@ impl AbstractObjectSpace for ThreadedObjectSpace { type RefCount = ThreadedRefCount; type Header = Header; - fn insert(&self, header: &mut Self::Header, value: &dyn CcDyn) { + fn insert(&self, header: *const Self::Header, value: &dyn CcDyn) { debug_assert!(Arc::ptr_eq( - &header.linked_list_lock, + unsafe { &(*header).linked_list_lock }, &self.list.linked_list_lock )); // Should be locked by `create()` already. debug_assert!(self.list.linked_list_lock.try_lock().is_none()); let prev: &Header = &self.list; debug_assert!(!collect::is_collecting(prev)); - debug_assert!(header.next.get().is_null()); + debug_assert!(unsafe { (*header).next.get() }.is_null()); let next = prev.next.get(); - header.prev.set(prev); - header.next.set(next); + unsafe { (*header).prev.set(prev) }; + unsafe { (*header).next.set(next) }; unsafe { // safety: The linked list is maintained, and pointers are valid. (*next).prev.set(header); // safety: To access vtable pointer. Test by test_gc_header_value. let fat_ptr: [*mut (); 2] = mem::transmute(value); - header.ccdyn_vptr = fat_ptr[1]; + (*header).ccdyn_vptr.set(fat_ptr[1]); } prev.next.set(header); } @@ -98,7 +98,7 @@ impl AbstractObjectSpace for ThreadedObjectSpace { linked_list_lock, next: Cell::new(std::ptr::null()), prev: Cell::new(std::ptr::null()), - ccdyn_vptr: CcDummy::ccdyn_vptr(), + ccdyn_vptr: Cell::new(CcDummy::ccdyn_vptr()), } } } @@ -110,7 +110,7 @@ impl Default for ThreadedObjectSpace { let pinned = Box::pin(Header { prev: Cell::new(std::ptr::null()), next: Cell::new(std::ptr::null()), - ccdyn_vptr: CcDummy::ccdyn_vptr(), + ccdyn_vptr: Cell::new(CcDummy::ccdyn_vptr()), linked_list_lock, }); let header: &Header = &pinned; @@ -189,14 +189,16 @@ impl Linked for Header { fn set_prev(&self, other: *const Self) { self.prev.set(other) } - #[inline] - fn value(&self) -> &dyn CcDyn { + fn value_ptr(this: *const Self) -> *const dyn CcDyn { // safety: To build trait object from self and vtable pointer. // Test by test_gc_header_value_consistency(). unsafe { - let fat_ptr: (*const (), *const ()) = - ((self as *const Self).offset(1) as _, self.ccdyn_vptr); + let fat_ptr: (*const (), *const ()) = (this.offset(1) as _, (*this).ccdyn_vptr.get()); mem::transmute(fat_ptr) } } + #[inline] + fn value(&self) -> &dyn CcDyn { + unsafe { mem::transmute(Self::value_ptr(self)) } + } } diff --git a/src/sync/tests.rs b/src/sync/tests.rs index fafbd1c..42e6a12 100644 --- a/src/sync/tests.rs +++ b/src/sync/tests.rs @@ -1,12 +1,13 @@ use super::*; use crate::debug; use crate::Trace; +use crate::TraceBox; use std::sync::mpsc::channel; use std::sync::Arc; use std::sync::Mutex; use std::thread::spawn; -type List = ThreadedCc>>>; +type List = ThreadedCc>>>; fn test_cross_thread_cycle(n: usize) { let list: Arc>> = Arc::new(Mutex::new(Vec::with_capacity(n))); @@ -25,9 +26,15 @@ fn test_cross_thread_cycle(n: usize) { let cloned_other = other.clone(); let cloned_this = this.clone(); let this_ref = this.borrow(); - this_ref.lock().unwrap().push(Box::new(cloned_other)); + this_ref + .lock() + .unwrap() + .push(TraceBox(Box::new(cloned_other))); let other_ref = other.borrow(); - other_ref.lock().unwrap().push(Box::new(cloned_this)); + other_ref + .lock() + .unwrap() + .push(TraceBox(Box::new(cloned_this))); } list.push(this); }) @@ -99,7 +106,7 @@ fn test_racy_threads( if (create_cycles_bits >> i) & 1 == 1 { for j in 0..thread_count { if j % (i + 1) == 0 { - let _ = tx_list[j].send(Box::new(acc.clone())); + let _ = tx_list[j].send(TraceBox(Box::new(acc.clone()))); } } } diff --git a/src/tests.rs b/src/tests.rs index 90da316..790b88e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,6 +1,6 @@ use crate::testutil::test_small_graph; -use crate::{collect, collect_thread_cycles, Cc, Trace, Tracer}; -use crate::{debug, with_thread_object_space, Weak}; +use crate::{Cc, Trace, Tracer, collect, collect_thread_cycles}; +use crate::{Weak, debug, with_thread_object_space}; use std::cell::Cell; use std::cell::RefCell; use std::ops::Deref; @@ -63,16 +63,16 @@ fn test_simple_tracked() { fn test_simple_cycles() { assert_eq!(collect::collect_thread_cycles(), 0); { - let a: Cc>>> = Cc::new(RefCell::new(Vec::new())); - let b: Cc>>> = Cc::new(RefCell::new(Vec::new())); + let a: Cc>>> = Cc::new(RefCell::new(Vec::new())); + let b: Cc>>> = Cc::new(RefCell::new(Vec::new())); assert_eq!(collect::collect_thread_cycles(), 0); { let mut a = a.borrow_mut(); - a.push(Box::new(b.clone())); + a.push(TraceBox(Box::new(b.clone()))); } { let mut b = b.borrow_mut(); - b.push(Box::new(a.clone())); + b.push(TraceBox(Box::new(a.clone()))); } assert_eq!(collect::collect_thread_cycles(), 0); assert_eq!(collect::count_thread_tracked(), 2); @@ -140,12 +140,12 @@ fn test_weakref_without_cycles() { fn test_weakref_with_cycles() { let log = debug::capture_log(|| { debug::NEXT_DEBUG_NAME.with(|n| n.set(1)); - let a: Cc>>> = Cc::new(RefCell::new(Vec::new())); + let a: Cc>>> = Cc::new(RefCell::new(Vec::new())); assert_eq!(a.strong_count(), 1); debug::NEXT_DEBUG_NAME.with(|n| n.set(2)); - let b: Cc>>> = Cc::new(RefCell::new(Vec::new())); - a.borrow_mut().push(Box::new(b.clone())); - b.borrow_mut().push(Box::new(a.clone())); + let b: Cc>>> = Cc::new(RefCell::new(Vec::new())); + a.borrow_mut().push(TraceBox(Box::new(b.clone()))); + b.borrow_mut().push(TraceBox(Box::new(a.clone()))); assert_eq!(a.strong_count(), 2); assert_eq!(a.weak_count(), 0); let wa = a.downgrade(); @@ -400,7 +400,7 @@ fn test_update_with() { // Update on a unique value. let log = debug::capture_log(|| { let mut cc = Cc::new(30); - cc.update_with(|i| *i = *i + 1); + cc.update_with(|i| *i += 1); assert_eq!(cc.deref(), &31); }); assert_eq!(log, "\n0: new (CcBox), drop (0), drop (T), drop (CcBox)"); @@ -411,7 +411,7 @@ fn test_update_with() { let cc1 = Cc::new(30); let mut cc2 = cc1.clone(); debug::NEXT_DEBUG_NAME.with(|n| n.set(3)); - cc2.update_with(|i| *i = *i + 1); + cc2.update_with(|i| *i += 1); assert_eq!(cc1.deref(), &30); assert_eq!(cc2.deref(), &31); }); @@ -439,7 +439,7 @@ fn test_update_with() { let cc1: Cc = Cc::new(V(30)); let mut cc2 = cc1.clone(); debug::NEXT_DEBUG_NAME.with(|n| n.set(3)); - cc2.update_with(|i| i.0 = i.0 + 1); + cc2.update_with(|i| i.0 += 1); assert_eq!(cc1.deref().0, 30); assert_eq!(cc2.deref().0, 31); }); @@ -456,7 +456,7 @@ fn test_update_with() { #[derive(Default)] struct DuplicatedVisits { - a: RefCell>>, + a: RefCell>>, extra_times: Cell, } impl Trace for DuplicatedVisits { @@ -476,15 +476,13 @@ impl panic::UnwindSafe for DuplicatedVisits {} fn capture_panic_message R + panic::UnwindSafe>(func: F) -> String { match panic::catch_unwind(func) { Ok(_) => "(no panic happened)".to_string(), - Err(e) => { - if let Some(s) = e.downcast_ref::() { - return s.clone(); - } else if let Some(s) = e.downcast_ref::<&'static str>() { - return s.to_string(); - } else { - "(panic information is not a string)".to_string() - } - } + Err(e) => match e.downcast_ref::() { + Some(s) => s.clone(), + _ => match e.downcast_ref::<&'static str>() { + Some(s) => s.to_string(), + _ => "(panic information is not a string)".to_string(), + }, + }, } } @@ -492,9 +490,9 @@ fn capture_panic_message R + panic::UnwindSafe>(func: F) -> String fn test_trace_impl_double_visits() { let v: Cc = Default::default(); v.extra_times.set(1); - *(v.a.borrow_mut()) = Some(Box::new(v.clone())); + *(v.a.borrow_mut()) = Some(TraceBox(Box::new(v.clone()))); - let message = capture_panic_message(|| collect::collect_thread_cycles()); + let message = capture_panic_message(collect::collect_thread_cycles); assert!(message.contains("bug: unexpected ref-count after dropping cycles")); // The `CcBox<_>` was "forced dropped" as a side effect. @@ -561,6 +559,7 @@ impl Trace for TraceBox { } } struct Tracked { + #[allow(dead_code)] a: Vec, } impl Trace for Tracked { diff --git a/src/testutil.rs b/src/testutil.rs index d3ae711..e221d30 100644 --- a/src/testutil.rs +++ b/src/testutil.rs @@ -1,5 +1,6 @@ //! Test utilities. +use crate::TraceBox; use crate::{collect, debug, Cc, Trace, Tracer}; use std::cell::Cell; use std::cell::RefCell; @@ -29,7 +30,7 @@ pub(crate) fn create_objects( n: usize, atomic_bits: u16, drop_count: Arc, -) -> Vec>>>>> { +) -> Vec>>>>> { assert!(n <= 16); let is_tracked = |n| -> bool { (atomic_bits >> n) & 1 == 0 }; (0..n) @@ -73,7 +74,7 @@ pub fn test_small_graph(n: usize, edges: &[u8], atomic_bits: u16, collect_bits: (true, false) => continue, } let mut to = values[to_index].0.borrow_mut(); - to.push(Box::new(values[from_index].clone())); + to.push(TraceBox(Box::new(values[from_index].clone()))); edge_descs[to_index].push(from_index); } for (i, _value) in values.into_iter().enumerate() { diff --git a/src/trace_impls.rs b/src/trace_impls.rs index 085f803..f1c0823 100644 --- a/src/trace_impls.rs +++ b/src/trace_impls.rs @@ -1,3 +1,6 @@ +use std::borrow::{Borrow, BorrowMut}; +use std::ops::{Deref, DerefMut}; + use crate::trace::{Trace, Tracer}; /// Mark types as acyclic. Opt-out the cycle collector. @@ -108,7 +111,6 @@ mod borrow { mod boxed { use super::*; - impl Trace for Box { fn trace(&self, tracer: &mut Tracer) { self.as_ref().trace(tracer); @@ -119,40 +121,6 @@ mod boxed { T::is_type_tracked() } } - - impl Trace for Box { - fn trace(&self, tracer: &mut Tracer) { - self.as_ref().trace(tracer); - } - - #[inline] - fn is_type_tracked() -> bool { - // Trait objects can have complex non-atomic structure. - true - } - } - - impl Trace for Box { - fn trace(&self, tracer: &mut Tracer) { - self.as_ref().trace(tracer); - } - - #[inline] - fn is_type_tracked() -> bool { - true - } - } - - impl Trace for Box { - fn trace(&self, tracer: &mut Tracer) { - self.as_ref().trace(tracer); - } - - #[inline] - fn is_type_tracked() -> bool { - true - } - } } mod cell { @@ -486,10 +454,10 @@ mod tests { assert!(!<(bool, f64)>::is_type_tracked()); assert!(!Cell::::is_type_tracked()); assert!(!RefCell::::is_type_tracked()); - assert!(Box::::is_type_tracked()); - assert!(RefCell::>::is_type_tracked()); - assert!(RefCell::>>::is_type_tracked()); - assert!(Vec::>>::is_type_tracked()); + assert!(TraceBox::::is_type_tracked()); + assert!(RefCell::>::is_type_tracked()); + assert!(RefCell::>>::is_type_tracked()); + assert!(Vec::>>::is_type_tracked()); assert!(!Cc::::is_type_tracked()); assert!(!Vec::>::is_type_tracked()); @@ -527,3 +495,62 @@ mod tests { assert!(S2::is_type_tracked()); } } + +/// Box, that assumes inner type is tracked +/// +/// Exists because plain `Box` only accepts sized `T` +#[derive(Debug, Clone)] +pub struct TraceBox(pub Box); + +impl Trace for TraceBox { + fn trace(&self, tracer: &mut Tracer<'_>) { + self.0.trace(tracer); + } + + fn is_type_tracked() -> bool { + true + } +} + +impl From> for TraceBox { + fn from(inner: Box) -> Self { + Self(inner) + } +} + +impl Deref for TraceBox { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for TraceBox { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Borrow for TraceBox { + fn borrow(&self) -> &T { + &self.0 + } +} + +impl BorrowMut for TraceBox { + fn borrow_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl AsRef for TraceBox { + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl AsMut for TraceBox { + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +}