├── .buildbot.config.toml ├── .buildbot.sh ├── .gitignore ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── allocator ├── Cargo.toml ├── build.rs └── src │ ├── boehm.rs │ └── lib.rs ├── bors.toml ├── gc_tests ├── run_tests.rs └── tests │ └── multithreaded.rs └── src ├── gc.rs ├── lib.rs └── stats.rs /.buildbot.config.toml: -------------------------------------------------------------------------------- 1 | # Config file for continuous integration. 2 | 3 | [rust] 4 | codegen-units = 0 # Use many compilation units. 5 | debug-assertions = true # Turn on assertions in rustc. 6 | 7 | [build] 8 | docs = false 9 | extended = false 10 | 11 | [llvm] 12 | assertions = true # Turn on assertions in LLVM. 13 | use-linker = "gold" # Uses less memory than ld. 14 | 15 | [install] 16 | prefix = "build/rustc_boehm" 17 | sysconfdir = "etc" 18 | -------------------------------------------------------------------------------- /.buildbot.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | 5 | export CARGO_HOME="`pwd`/.cargo" 6 | export RUSTUP_HOME="`pwd`/.rustup" 7 | 8 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh 9 | sh rustup.sh --default-host x86_64-unknown-linux-gnu \ 10 | --default-toolchain nightly \ 11 | --no-modify-path \ 12 | --profile minimal \ 13 | -y 14 | export PATH=`pwd`/.cargo/bin/:$PATH 15 | cargo check --features "standalone" 16 | 17 | rustup toolchain install nightly --allow-downgrade --component rustfmt 18 | cargo +nightly fmt --all -- --check 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /bdwgc 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Except as otherwise noted (below and/or in individual files), this project is 2 | licensed under the Apache License, Version 2.0 3 | or the MIT license 4 | , at your option. 5 | 6 | Copyright is retained by contributors and/or the organisations they 7 | represent(ed) -- this project does not require copyright assignment. Please see 8 | version control history for a full list of contributors. Note that some files 9 | may include explicit copyright and/or licensing notices. 10 | 11 | The following contributors wish to explicitly make it known that the copyright 12 | of their contributions is retained by an organisation: 13 | 14 | Edd Barrett : copyright retained by 15 | King's College London 16 | Jacob Hughes : copyright retained by 17 | King's College London 18 | Laurence Tratt : copyright retained by 19 | King's College London 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libgc" 3 | version = "0.1.0" 4 | authors = ["Jacob Hughes "] 5 | edition = "2018" 6 | 7 | [features] 8 | # Enable this feature to turn on additional GC optimizations that are only 9 | # possible with the rustgc fork of the compiler. 10 | standalone = ["allocator"] 11 | 12 | # Enable various GC based statistics. Stats are disabled by default as they have 13 | # a run-time cost and are expected to only be used for profiling purposes. 14 | gc_stats = [] 15 | 16 | [dependencies] 17 | libc = "*" 18 | allocator = { path = "allocator", optional = true } 19 | 20 | [dev-dependencies] 21 | lang_tester = "0.3" 22 | tempfile = "3.2" 23 | 24 | 25 | [[test]] 26 | name = "gc_tests" 27 | path = "gc_tests/run_tests.rs" 28 | harness = false 29 | 30 | [build-dependencies] 31 | rerun_except = "0.1" 32 | num_cpus = "1.12" 33 | which = "4.0" 34 | 35 | [workspace] 36 | members = ["allocator"] 37 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 2 | this file except in compliance with the License. You may obtain a copy of the 3 | License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software distributed 8 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 10 | specific language governing permissions and limitations under the License. 11 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of 2 | this software and associated documentation files (the "Software"), to deal in 3 | the Software without restriction, including without limitation the rights to 4 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 5 | of the Software, and to permit persons to whom the Software is furnished to do 6 | so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libgc 2 | 3 | [![Bors enabled](https://bors.tech/images/badge_small.svg)](https://bors.tech) 4 | 5 | _libgc_ is a garbage collection library for Rust. It can be used as a standalone 6 | library, but it is highly recommended that programs are compiled with the 7 | companion [rustc fork](https://github.com/softdevteam/rustgc), which offers 8 | language support for much better performance. 9 | 10 | _libgc_ is in active development - there will be bugs! 11 | 12 | ## Example 13 | 14 | _libgc_ provides a smart pointer, `Gc`, which can be used to make garbage 15 | collected values. An example, with the necessary global allocator setup, looks 16 | as follows: 17 | 18 | ```rust 19 | use libgc::{Gc, GcAllocator}; 20 | 21 | #[global_allocator] 22 | static ALLOCATOR: GcAllocator = GcAllocator; 23 | 24 | 25 | fn foo() -> Gc> { 26 | let foo = Gc::new(vec![1,2,3]); 27 | let a = foo; // GC pointers are copyable 28 | let b = foo; 29 | 30 | foo 31 | } 32 | 33 | fn main() { 34 | let gc = foo(); 35 | } 36 | ``` 37 | 38 | ## Overview 39 | 40 | If you want to write code with shared ownership in Rust, `Rc` makes this 41 | possible. Unfortunately, managing cyclic data structures with reference counting 42 | is hard: weak pointers are needed to break strong cycles and thus prevent memory 43 | leaks. In programs where these sorts of structures are common, 44 | garbage collection is a natural fit. 45 | 46 | _libgc_ is not a replacement to the single ownership model - it is intended to 47 | complement it by providing a garbage collected alternative for values which 48 | might be too difficult to manage with `Rc`. Values must opt-in to using 49 | garbage collection with the `Gc::new(x)` constructor. This tells _libgc_ to heap 50 | allocate `x`, and GC it for you when you're done with it. `Gc` can be thought of 51 | as a special `Box` type, where `x`'s lifetime is unknown at compile-time. 52 | Periodically, the garbage collector will interrupt the program (known as 53 | "stopping the world") to see which `Gc` values are still in use, and drop those 54 | which aren't. 55 | 56 | Garbage collection involves scanning parts of the stack and heap to look for 57 | live references to `Gc` values. This means that _libgc_ must be aware of all heap 58 | allocated values, even those which aren't `Gc`. To do this, _libgc_ has its own 59 | allocator, `GcAllocator`, which must be set as the global allocator when using 60 | _libgc_. 61 | 62 | ```rust 63 | use libgc::GcAllocator; 64 | 65 | #[global_allocator] 66 | static ALLOCATOR: GcAllocator = GcAllocator; 67 | ``` 68 | 69 | ### Finalization 70 | 71 | A `Gc` can be used to manage values which have a `drop` method. Like all tracing 72 | garbage collectors, _libgc_ can not provide any guarantees about exactly when a 73 | 'dead' value is dropped. Instead, once _libgc_ has determined that a value is 74 | unreachable, its `drop` method is added to a drop queue, which is ran on a 75 | parallel finalization thread at some point in the future. The order of 76 | finalization is intentionally undefined to allow _libgc_ to run `drop` methods on 77 | values which contain cycles of `Gc`. 78 | 79 | :warning: You must not dereference a field of type `Gc` inside `Drop::drop`. 80 | Doing so is unsound and can lead to dangling pointers. TODO: Add a lint for this 81 | and explain why in further details. 82 | 83 | ## Implementation 84 | 85 | __libgc__ is implemented using the [Boehm-Demers-Weiser 86 | collector](https://github.com/https://github.com/ivmai/bdwgc). It is a 87 | conservative, stop-the-world, parallel, mark-sweep collector. 88 | 89 | TODO: Expand 90 | 91 | ## Known Issues 92 | 93 | * Single-threaded support only. 94 | * No Drop Lint to prevent unsound dereferencing of `Gc` typed fields. 95 | 96 | ## Using libgc with rustgc 97 | 98 | There are two repositories which make up the gc infrastructure: 99 | 100 | * **libgc** the main library which provides the `Gc` smart pointer and its 101 | API. 102 | * **rustgc** a fork of rustc with GC-aware optimisations. This can be used to 103 | compile user programs which use libgc, giving them better GC 104 | performance. Use of rustgc is not mandated, but it enables further 105 | optimisations for programs which use libgc. 106 | 107 | This seperation between libgc and rustgc exists so that a stripped-down form of 108 | garbage collection can be used without compiler support. 109 | 110 | TODO: Explain rustgc and it's optimizations. 111 | -------------------------------------------------------------------------------- /allocator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "allocator" 3 | version = "0.1.0" 4 | authors = ["Jake Hughes "] 5 | edition = "2018" 6 | license = "Apache-2.0 OR MIT" 7 | 8 | [features] 9 | # Enable this feature to turn on additional GC optimizations that are only 10 | # possible with the rustgc fork of the compiler. 11 | rustgc = ["core", "compiler_builtins"] 12 | 13 | [dependencies] 14 | core = { version = "1.0.0", optional = true, package = 'rustc-std-workspace-core' } 15 | compiler_builtins = { version = "0.1.10", optional = true, features = ['rustc-dep-of-std'] } 16 | -------------------------------------------------------------------------------- /allocator/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | use std::process::Command; 4 | 5 | const BOEHM_REPO: &str = "https://github.com/ivmai/bdwgc.git"; 6 | const BOEHM_ATOMICS_REPO: &str = "https://github.com/ivmai/libatomic_ops.git"; 7 | const BOEHM_DIR: &str = "bdwgc"; 8 | const BUILD_DIR: &str = ".libs"; 9 | 10 | #[cfg(not(all(target_pointer_width = "64", target_arch = "x86_64")))] 11 | compile_error!("Requires x86_64 with 64 bit pointer width."); 12 | static POINTER_MASK: &str = "-DPOINTER_MASK=0xFFFFFFFFFFFFFFF8"; 13 | static FPIC: &str = "-fPIC"; 14 | static MULTITHREADED: &str = "-DGC_ALWAYS_MULTITHREADED"; 15 | 16 | fn run(name: &str, mut configure: F) 17 | where 18 | F: FnMut(&mut Command) -> &mut Command, 19 | { 20 | let mut command = Command::new(name); 21 | let configured = configure(&mut command); 22 | if !configured.status().is_ok() { 23 | panic!("failed to execute {:?}", configured); 24 | } 25 | } 26 | 27 | fn main() { 28 | let out_dir = env::var("OUT_DIR").unwrap(); 29 | let mut boehm_src = PathBuf::from(out_dir); 30 | boehm_src.push(BOEHM_DIR); 31 | 32 | if !boehm_src.exists() { 33 | run("git", |cmd| { 34 | cmd.arg("clone").arg(BOEHM_REPO).arg(&boehm_src) 35 | }); 36 | 37 | run("git", |cmd| { 38 | cmd.arg("clone") 39 | .arg(BOEHM_ATOMICS_REPO) 40 | .current_dir(&boehm_src) 41 | }); 42 | 43 | env::set_current_dir(&boehm_src).unwrap(); 44 | 45 | run("./autogen.sh", |cmd| cmd); 46 | run("./configure", |cmd| { 47 | cmd.arg("--enable-static").arg("--disable-shared").env( 48 | "CFLAGS", 49 | format!("{} {} {}", POINTER_MASK, FPIC, MULTITHREADED), 50 | ) 51 | }); 52 | 53 | run("make", |cmd| cmd.arg("-j")); 54 | } 55 | 56 | let mut libpath = PathBuf::from(&boehm_src); 57 | libpath.push(BUILD_DIR); 58 | 59 | println!( 60 | "cargo:rustc-link-search=native={}", 61 | &libpath.as_path().to_str().unwrap() 62 | ); 63 | println!("cargo:rustc-link-lib=static=gc"); 64 | } 65 | -------------------------------------------------------------------------------- /allocator/src/boehm.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | #[derive(Default)] 3 | pub struct ProfileStats { 4 | /// Heap size in bytes (including area unmapped to OS). 5 | pub(crate) heapsize_full: usize, 6 | /// Total bytes contained in free and unmapped blocks. 7 | pub(crate) free_bytes_full: usize, 8 | /// Amount of memory unmapped to OS. 9 | pub(crate) unmapped_bytes: usize, 10 | /// Number of bytes allocated since the recent collection. 11 | pub(crate) bytes_allocd_since_gc: usize, 12 | /// Number of bytes allocated before the recent collection. 13 | /// The value may wrap. 14 | pub(crate) allocd_bytes_before_gc: usize, 15 | /// Number of bytes not considered candidates for garbage collection. 16 | pub(crate) non_gc_bytes: usize, 17 | /// Garbage collection cycle number. 18 | /// The value may wrap. 19 | pub(crate) gc_no: usize, 20 | /// Number of marker threads (excluding the initiating one). 21 | pub(crate) markers_m1: usize, 22 | /// Approximate number of reclaimed bytes after recent collection. 23 | pub(crate) bytes_reclaimed_since_gc: usize, 24 | /// Approximate number of bytes reclaimed before the recent collection. 25 | /// The value may wrap. 26 | pub(crate) reclaimed_bytes_before_gc: usize, 27 | /// Number of bytes freed explicitly since the recent GC. 28 | pub(crate) expl_freed_bytes_since_gc: usize, 29 | } 30 | 31 | #[link(name = "gc")] 32 | extern "C" { 33 | pub(crate) fn GC_malloc(nbytes: usize) -> *mut u8; 34 | 35 | #[cfg(not(feature = "rustgc"))] 36 | pub(crate) fn GC_malloc_uncollectable(nbytes: usize) -> *mut u8; 37 | 38 | pub(crate) fn GC_realloc(old: *mut u8, new_size: usize) -> *mut u8; 39 | 40 | pub(crate) fn GC_free(dead: *mut u8); 41 | 42 | pub(crate) fn GC_register_finalizer( 43 | ptr: *mut u8, 44 | finalizer: Option, 45 | client_data: *mut u8, 46 | old_finalizer: *mut extern "C" fn(*mut u8, *mut u8), 47 | old_client_data: *mut *mut u8, 48 | ); 49 | 50 | pub(crate) fn GC_register_finalizer_no_order( 51 | ptr: *mut u8, 52 | finalizer: Option, 53 | client_data: *mut u8, 54 | old_finalizer: *mut extern "C" fn(*mut u8, *mut u8), 55 | old_client_data: *mut *mut u8, 56 | ); 57 | 58 | pub(crate) fn GC_gcollect(); 59 | 60 | pub(crate) fn GC_get_full_gc_total_time() -> usize; 61 | 62 | pub(crate) fn GC_get_prof_stats(prof_stats: *mut ProfileStats, stats_size: usize) -> usize; 63 | 64 | #[cfg(feature = "rustgc")] 65 | pub(crate) fn GC_malloc_explicitly_typed(size: usize, descriptor: usize) -> *mut u8; 66 | 67 | #[cfg(feature = "rustgc")] 68 | pub(crate) fn GC_make_descriptor(bitmap: *const usize, len: usize) -> usize; 69 | 70 | #[cfg(feature = "rustgc")] 71 | pub(crate) fn GC_malloc_atomic(nbytes: usize) -> *mut u8; 72 | 73 | #[cfg(feature = "rustgc")] 74 | pub(crate) fn GC_malloc_atomic_uncollectable(nbytes: usize) -> *mut u8; 75 | 76 | pub(crate) fn GC_thread_is_registered() -> u32; 77 | 78 | pub(crate) fn GC_register_my_thread(stack_base: *mut u8) -> i32; 79 | 80 | pub(crate) fn GC_unregister_my_thread() -> i32; 81 | 82 | pub(crate) fn GC_allow_register_threads(); 83 | 84 | pub(crate) fn GC_init(); 85 | } 86 | -------------------------------------------------------------------------------- /allocator/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(allocator_api)] 3 | #![feature(nonnull_slice_from_raw_parts)] 4 | 5 | use core::{ 6 | alloc::{AllocError, Allocator, GlobalAlloc, Layout}, 7 | ptr::NonNull, 8 | }; 9 | 10 | mod boehm; 11 | 12 | pub struct GcAllocator; 13 | 14 | unsafe impl GlobalAlloc for GcAllocator { 15 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 16 | return boehm::GC_malloc_uncollectable(layout.size()) as *mut u8; 17 | } 18 | 19 | unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) { 20 | boehm::GC_free(ptr); 21 | } 22 | 23 | unsafe fn realloc(&self, ptr: *mut u8, _: Layout, new_size: usize) -> *mut u8 { 24 | boehm::GC_realloc(ptr, new_size) as *mut u8 25 | } 26 | 27 | #[cfg(feature = "rustgc")] 28 | #[inline] 29 | unsafe fn alloc_precise(&self, layout: Layout, bitmap: usize, bitmap_size: usize) -> *mut u8 { 30 | unimplemented!("Boehm does not provide an uncollectable version of this call") 31 | } 32 | 33 | #[cfg(feature = "rustgc")] 34 | #[inline] 35 | fn alloc_conservative(&self, layout: Layout) -> *mut u8 { 36 | unsafe { boehm::GC_malloc_uncollectable(layout.size()) as *mut u8 } 37 | } 38 | 39 | #[cfg(feature = "rustgc")] 40 | #[inline] 41 | unsafe fn alloc_untraceable(&self, layout: Layout) -> *mut u8 { 42 | boehm::GC_malloc_atomic_uncollectable(layout.size()) as *mut u8 43 | } 44 | } 45 | 46 | unsafe impl Allocator for GcAllocator { 47 | fn allocate(&self, layout: Layout) -> Result, AllocError> { 48 | unsafe { 49 | let ptr = boehm::GC_malloc(layout.size()) as *mut u8; 50 | let ptr = NonNull::new_unchecked(ptr); 51 | Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) 52 | } 53 | } 54 | 55 | unsafe fn deallocate(&self, _: NonNull, _: Layout) {} 56 | 57 | #[cfg(feature = "rustgc")] 58 | #[inline] 59 | fn alloc_untraceable(&self, layout: Layout) -> Result, AllocError> { 60 | unsafe { 61 | let ptr = boehm::GC_malloc_atomic(layout.size()) as *mut u8; 62 | let ptr = NonNull::new_unchecked(ptr); 63 | Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) 64 | } 65 | } 66 | 67 | #[cfg(feature = "rustgc")] 68 | #[inline] 69 | fn alloc_conservative(&self, layout: Layout) -> Result, AllocError> { 70 | unsafe { 71 | let ptr = boehm::GC_malloc(layout.size()) as *mut u8; 72 | let ptr = NonNull::new_unchecked(ptr); 73 | Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) 74 | } 75 | } 76 | 77 | #[cfg(feature = "rustgc")] 78 | #[inline] 79 | fn alloc_precise( 80 | &self, 81 | layout: Layout, 82 | bitmap: usize, 83 | bitmap_size: usize, 84 | ) -> Result, AllocError> { 85 | unsafe { 86 | let gc_descr = boehm::GC_make_descriptor(&bitmap as *const usize, bitmap_size); 87 | let ptr = boehm::GC_malloc_explicitly_typed(layout.size(), gc_descr); 88 | let ptr = NonNull::new_unchecked(ptr); 89 | Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) 90 | } 91 | } 92 | } 93 | 94 | impl GcAllocator { 95 | pub fn force_gc() { 96 | unsafe { boehm::GC_gcollect() } 97 | } 98 | 99 | pub unsafe fn register_finalizer( 100 | &self, 101 | obj: *mut u8, 102 | finalizer: Option, 103 | client_data: *mut u8, 104 | old_finalizer: *mut extern "C" fn(*mut u8, *mut u8), 105 | old_client_data: *mut *mut u8, 106 | ) { 107 | boehm::GC_register_finalizer_no_order( 108 | obj, 109 | finalizer, 110 | client_data, 111 | old_finalizer, 112 | old_client_data, 113 | ) 114 | } 115 | 116 | pub fn unregister_finalizer(&self, gcbox: *mut u8) { 117 | unsafe { 118 | boehm::GC_register_finalizer( 119 | gcbox, 120 | None, 121 | ::core::ptr::null_mut(), 122 | ::core::ptr::null_mut(), 123 | ::core::ptr::null_mut(), 124 | ); 125 | } 126 | } 127 | 128 | pub fn get_stats() -> GcStats { 129 | let mut ps = boehm::ProfileStats::default(); 130 | unsafe { 131 | boehm::GC_get_prof_stats( 132 | &mut ps as *mut boehm::ProfileStats, 133 | core::mem::size_of::(), 134 | ); 135 | } 136 | let total_gc_time = unsafe { boehm::GC_get_full_gc_total_time() }; 137 | 138 | GcStats { 139 | total_gc_time, 140 | num_collections: ps.gc_no, 141 | total_freed: ps.bytes_reclaimed_since_gc, 142 | total_alloced: ps.bytes_allocd_since_gc, 143 | } 144 | } 145 | 146 | pub fn init() { 147 | unsafe { boehm::GC_init() } 148 | } 149 | 150 | /// Returns true if thread was successfully registered. 151 | pub unsafe fn register_thread(stack_base: *mut u8) -> bool { 152 | boehm::GC_register_my_thread(stack_base) == 0 153 | } 154 | 155 | /// Returns true if thread was successfully unregistered. 156 | pub unsafe fn unregister_thread() -> bool { 157 | boehm::GC_unregister_my_thread() == 0 158 | } 159 | 160 | pub fn thread_registered() -> bool { 161 | unsafe { boehm::GC_thread_is_registered() != 0 } 162 | } 163 | 164 | pub fn allow_register_threads() { 165 | unsafe { boehm::GC_allow_register_threads() } 166 | } 167 | } 168 | 169 | #[derive(Debug)] 170 | pub struct GcStats { 171 | total_gc_time: usize, // In milliseconds. 172 | num_collections: usize, 173 | total_freed: usize, // In bytes 174 | total_alloced: usize, // In bytes 175 | } 176 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = ["buildbot/buildbot-build-script"] 2 | 3 | timeout_sec = 3600 # 1 hour 4 | 5 | # Have bors delete auto-merged branches 6 | delete_merged_branches = true 7 | -------------------------------------------------------------------------------- /gc_tests/run_tests.rs: -------------------------------------------------------------------------------- 1 | use std::{env, path::PathBuf, process::Command}; 2 | 3 | use lang_tester::LangTester; 4 | use tempfile::TempDir; 5 | 6 | fn deps_path() -> String { 7 | let mut path = PathBuf::new(); 8 | path.push(env::var("CARGO_MANIFEST_DIR").unwrap()); 9 | path.push("target"); 10 | #[cfg(debug_assertions)] 11 | path.push("debug"); 12 | #[cfg(not(debug_assertions))] 13 | path.push("release"); 14 | path.push("deps"); 15 | 16 | path.to_str().unwrap().to_owned() 17 | } 18 | 19 | fn main() { 20 | let rustc = env::var("RUSTGC").expect("RUSTGC environment var not specified"); 21 | // We grab the rlibs from `target//` but in order 22 | // for them to exist here, they must have been moved from `deps/`. 23 | // Simply running `cargo test` will not do this, instead, we must 24 | // ensure that `cargo build` has been run before running the tests. 25 | 26 | #[cfg(debug_assertions)] 27 | let build_mode = "--debug"; 28 | #[cfg(not(debug_assertions))] 29 | let build_mode = "--release"; 30 | 31 | Command::new("cargo") 32 | .args(&["build", build_mode]) 33 | .env("RUSTC", &rustc.as_str()) 34 | .output() 35 | .expect("Failed to build libs"); 36 | 37 | let tempdir = TempDir::new().unwrap(); 38 | LangTester::new() 39 | .test_dir("gc_tests/tests") 40 | .test_file_filter(|p| p.extension().unwrap().to_str().unwrap() == "rs") 41 | .test_extract(|s| { 42 | Some( 43 | s.lines() 44 | .take_while(|l| l.starts_with("//")) 45 | .map(|l| &l[2..]) 46 | .collect::>() 47 | .join("\n"), 48 | ) 49 | }) 50 | .test_cmds(move |p| { 51 | let mut exe = PathBuf::new(); 52 | exe.push(&tempdir); 53 | exe.push(p.file_stem().unwrap()); 54 | 55 | let mut compiler = Command::new(&rustc); 56 | compiler.args(&[ 57 | "-L", 58 | deps_path().as_str(), 59 | p.to_str().unwrap(), 60 | "-o", 61 | exe.to_str().unwrap(), 62 | ]); 63 | 64 | vec![("Compiler", compiler), ("Run-time", Command::new(exe))] 65 | }) 66 | .run(); 67 | } 68 | -------------------------------------------------------------------------------- /gc_tests/tests/multithreaded.rs: -------------------------------------------------------------------------------- 1 | // Run-time: 2 | // status: success 3 | #![feature(rustc_private)] 4 | 5 | extern crate libgc; 6 | 7 | use std::alloc::GcAllocator; 8 | use std::{thread, time}; 9 | use std::sync::atomic::{AtomicBool, Ordering}; 10 | use libgc::Gc; 11 | 12 | #[global_allocator] 13 | static ALLOCATOR: GcAllocator = GcAllocator; 14 | 15 | struct PanicOnDrop(String); 16 | 17 | impl Drop for PanicOnDrop { 18 | fn drop(&mut self) { 19 | eprintln!("Finalizer called. Object erroneously collected"); 20 | } 21 | 22 | } 23 | 24 | static mut NO_CHILD_EXISTS: AtomicBool = AtomicBool::new(true); 25 | 26 | fn main() { 27 | for _ in 1..10 { 28 | thread::spawn(child); 29 | } 30 | 31 | while(unsafe { NO_CHILD_EXISTS.load(Ordering::SeqCst) }) {}; 32 | 33 | // This should collect no garbage, because the call stacks of each child 34 | // thread should be scanned for roots. 35 | GcAllocator::force_gc(); 36 | 37 | // If there's a problem, a finalizer will print to stderr. Lets wait an 38 | // appropriate amount of time for this to happen. 39 | thread::sleep(time::Duration::from_millis(10)); 40 | } 41 | 42 | fn child() { 43 | unsafe { NO_CHILD_EXISTS.store(false, Ordering::SeqCst)}; 44 | let gc = Gc::new(String::from("Hello world!")); 45 | 46 | // Wait a bit before dying, ensuring that the thread stays alive long enough 47 | // cross the force_gc call. 48 | thread::sleep(time::Duration::from_millis(10)); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/gc.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | alloc::{Allocator, Layout}, 3 | any::Any, 4 | fmt, 5 | hash::{Hash, Hasher}, 6 | marker::{PhantomData, Unsize}, 7 | mem::{forget, ManuallyDrop, MaybeUninit}, 8 | ops::{CoerceUnsized, Deref, DispatchFromDyn}, 9 | ptr::NonNull, 10 | }; 11 | 12 | use crate::ALLOCATOR; 13 | 14 | /// This is usually a no-op, but if `gc_stats` is enabled it will setup the GC 15 | /// for profiliing. 16 | pub fn gc_init() { 17 | #[cfg(all(feature = "gc_stats", feature = "boehm"))] 18 | boehm::init(); 19 | } 20 | 21 | /// A garbage collected pointer. 22 | /// 23 | /// The type `Gc` provides shared ownership of a value of type `T`, 24 | /// allocted in the heap. `Gc` pointers are `Copyable`, so new pointers to 25 | /// the same value in the heap can be produced trivially. The lifetime of 26 | /// `T` is tracked automatically: it is freed when the application 27 | /// determines that no references to `T` are in scope. This does not happen 28 | /// deterministically, and no guarantees are given about when a value 29 | /// managed by `Gc` is freed. 30 | /// 31 | /// Shared references in Rust disallow mutation by default, and `Gc` is no 32 | /// exception: you cannot generally obtain a mutable reference to something 33 | /// inside an `Gc`. If you need mutability, put a `Cell` or `RefCell` inside 34 | /// the `Gc`. 35 | /// 36 | /// Unlike `Rc`, cycles between `Gc` pointers are allowed and can be 37 | /// deallocated without issue. 38 | /// 39 | /// `Gc` automatically dereferences to `T` (via the `Deref` trait), so 40 | /// you can call `T`'s methods on a value of type `Gc`. 41 | /// 42 | /// `Gc` will implement `Sync` as long as `T` implements `Sync`. `Gc` 43 | /// will always implement `Send` because it requires `T` to implement `Send`. 44 | /// This is because if `T` has a finalizer, it will be run on a seperate thread. 45 | #[derive(PartialEq, Eq)] 46 | pub struct Gc { 47 | ptr: GcPointer, 48 | _phantom: PhantomData, 49 | } 50 | 51 | /// This zero-sized wrapper struct is needed to allow `Gc` to have the same 52 | /// `Send` + `Sync` semantics as `T`. Without it, the inner `NonNull` type would 53 | /// mean that a `Gc` never implements `Send` or `Sync`. 54 | #[derive(PartialEq, Eq)] 55 | struct GcPointer(NonNull>); 56 | 57 | unsafe impl Send for GcPointer {} 58 | unsafe impl Sync for GcPointer {} 59 | 60 | impl + Send, U: ?Sized + Send> CoerceUnsized> for Gc {} 61 | impl + Send, U: ?Sized + Send> DispatchFromDyn> for Gc {} 62 | 63 | impl + Send, U: ?Sized + Send> CoerceUnsized> for GcPointer {} 64 | impl + Send, U: ?Sized + Send> DispatchFromDyn> 65 | for GcPointer 66 | { 67 | } 68 | 69 | impl Gc { 70 | /// Constructs a new `Gc`. 71 | pub fn new(v: T) -> Self { 72 | Gc { 73 | ptr: unsafe { GcPointer(NonNull::new_unchecked(GcBox::new(v))) }, 74 | _phantom: PhantomData, 75 | } 76 | } 77 | 78 | /// Constructs a new `Gc>` which is capable of storing data 79 | /// up-to the size permissible by `layout`. 80 | /// 81 | /// This can be useful if you want to store a value with a custom layout, 82 | /// but have the collector treat the value as if it were T. 83 | /// 84 | /// # Panics 85 | /// 86 | /// If `layout` is smaller than that required by `T` and/or has an alignment 87 | /// which is smaller than that required by `T`. 88 | pub fn new_from_layout(layout: Layout) -> Gc> { 89 | let tl = Layout::new::(); 90 | if layout.size() < tl.size() || layout.align() < tl.align() { 91 | panic!( 92 | "Requested layout {:?} is either smaller than size {} and/or not aligned to {}", 93 | layout, 94 | tl.size(), 95 | tl.align() 96 | ); 97 | } 98 | unsafe { Gc::new_from_layout_unchecked(layout) } 99 | } 100 | 101 | /// Constructs a new `Gc>` which is capable of storing data 102 | /// up-to the size permissible by `layout`. 103 | /// 104 | /// This can be useful if you want to store a value with a custom layout, 105 | /// but have the collector treat the value as if it were T. 106 | /// 107 | /// # Safety 108 | /// 109 | /// The caller is responsible for ensuring that both `layout`'s size and 110 | /// alignment must match or exceed that required to store `T`. 111 | pub unsafe fn new_from_layout_unchecked(layout: Layout) -> Gc> { 112 | Gc::from_inner(GcBox::new_from_layout(layout)) 113 | } 114 | 115 | pub fn unregister_finalizer(&mut self) { 116 | let ptr = self.ptr.0.as_ptr() as *mut GcBox; 117 | unsafe { 118 | GcBox::unregister_finalizer(&mut *ptr); 119 | } 120 | } 121 | } 122 | 123 | impl Gc { 124 | pub fn downcast(&self) -> Result, Gc> { 125 | if (*self).is::() { 126 | let ptr = self.ptr.0.cast::>(); 127 | Ok(Gc::from_inner(ptr)) 128 | } else { 129 | Err(Gc::from_inner(self.ptr.0)) 130 | } 131 | } 132 | } 133 | 134 | #[cfg(feature = "standalone")] 135 | pub fn needs_finalizer() -> bool { 136 | std::mem::needs_drop::() 137 | } 138 | 139 | #[cfg(not(feature = "standalone"))] 140 | pub fn needs_finalizer() -> bool { 141 | std::mem::needs_finalizer::() 142 | } 143 | 144 | impl Gc { 145 | /// Get a raw pointer to the underlying value `T`. 146 | pub fn into_raw(this: Self) -> *const T { 147 | this.ptr.0.as_ptr() as *const T 148 | } 149 | 150 | pub fn ptr_eq(this: &Self, other: &Self) -> bool { 151 | this.ptr.0.as_ptr() == other.ptr.0.as_ptr() 152 | } 153 | 154 | /// Get a `Gc` from a raw pointer. 155 | /// 156 | /// # Safety 157 | /// 158 | /// The caller must guarantee that `raw` was allocated with `Gc::new()` or 159 | /// u8 `Gc::new_from_layout()`. 160 | /// 161 | /// It is legal for `raw` to be an interior pointer if `T` is valid for the 162 | /// size and alignment of the originally allocated block. 163 | pub fn from_raw(raw: *const T) -> Gc { 164 | Gc { 165 | ptr: unsafe { GcPointer(NonNull::new_unchecked(raw as *mut GcBox)) }, 166 | _phantom: PhantomData, 167 | } 168 | } 169 | 170 | fn from_inner(ptr: NonNull>) -> Self { 171 | Self { 172 | ptr: GcPointer(ptr), 173 | _phantom: PhantomData, 174 | } 175 | } 176 | } 177 | 178 | impl Gc> { 179 | /// As with `MaybeUninit::assume_init`, it is up to the caller to guarantee 180 | /// that the inner value really is in an initialized state. Calling this 181 | /// when the content is not yet fully initialized causes immediate undefined 182 | /// behaviour. 183 | pub unsafe fn assume_init(self) -> Gc { 184 | let ptr = self.ptr.0.as_ptr() as *mut GcBox>; 185 | Gc::from_inner((&mut *ptr).assume_init()) 186 | } 187 | } 188 | 189 | impl fmt::Display for Gc { 190 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 191 | fmt::Display::fmt(&**self, f) 192 | } 193 | } 194 | 195 | impl fmt::Debug for Gc { 196 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 197 | fmt::Debug::fmt(&**self, f) 198 | } 199 | } 200 | 201 | impl fmt::Pointer for Gc { 202 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 203 | fmt::Pointer::fmt(&(&**self as *const T), f) 204 | } 205 | } 206 | 207 | /// A `GcBox` is a 0-cost wrapper which allows a single `Drop` implementation 208 | /// while also permitting multiple, copyable `Gc` references. The `drop` method 209 | /// on `GcBox` acts as a guard, preventing the destructors on its contents from 210 | /// running unless the object is really dead. 211 | struct GcBox(ManuallyDrop); 212 | 213 | impl GcBox { 214 | fn new(value: T) -> *mut GcBox { 215 | let layout = Layout::new::(); 216 | let ptr = ALLOCATOR.allocate(layout).unwrap().as_ptr() as *mut GcBox; 217 | let gcbox = GcBox(ManuallyDrop::new(value)); 218 | 219 | unsafe { 220 | ptr.copy_from_nonoverlapping(&gcbox, 1); 221 | GcBox::register_finalizer(&mut *ptr); 222 | } 223 | 224 | forget(gcbox); 225 | ptr 226 | } 227 | 228 | fn new_from_layout(layout: Layout) -> NonNull>> { 229 | unsafe { 230 | let base_ptr = ALLOCATOR.allocate(layout).unwrap().as_ptr() as *mut usize; 231 | NonNull::new_unchecked(base_ptr as *mut GcBox>) 232 | } 233 | } 234 | 235 | fn register_finalizer(&mut self) { 236 | #[cfg(feature = "gc_stats")] 237 | crate::stats::NUM_REGISTERED_FINALIZERS.fetch_add(1, std::sync::atomic::Ordering::Relaxed); 238 | 239 | if !needs_finalizer::() { 240 | return; 241 | } 242 | 243 | unsafe extern "C" fn fshim(obj: *mut u8, _meta: *mut u8) { 244 | ManuallyDrop::drop(&mut *(obj as *mut ManuallyDrop)); 245 | } 246 | 247 | unsafe { 248 | ALLOCATOR.register_finalizer( 249 | self as *mut _ as *mut u8, 250 | Some(fshim::), 251 | ::std::ptr::null_mut(), 252 | ::std::ptr::null_mut(), 253 | ::std::ptr::null_mut(), 254 | ) 255 | } 256 | } 257 | 258 | fn unregister_finalizer(&mut self) { 259 | ALLOCATOR.unregister_finalizer(self as *mut _ as *mut u8); 260 | } 261 | } 262 | 263 | impl GcBox> { 264 | unsafe fn assume_init(&mut self) -> NonNull> { 265 | // Now that T is initialized, we must make sure that it's dropped when 266 | // `GcBox` is freed. 267 | let init = self as *mut _ as *mut GcBox; 268 | GcBox::register_finalizer(&mut *init); 269 | NonNull::new_unchecked(init) 270 | } 271 | } 272 | 273 | impl Deref for Gc { 274 | type Target = T; 275 | 276 | fn deref(&self) -> &Self::Target { 277 | unsafe { &*(self.ptr.0.as_ptr() as *const T) } 278 | } 279 | } 280 | 281 | /// `Copy` and `Clone` are implemented manually because a reference to `Gc` 282 | /// should be copyable regardless of `T`. It differs subtly from `#[derive(Copy, 283 | /// Clone)]` in that the latter only makes `Gc` copyable if `T` is. 284 | impl Copy for Gc {} 285 | 286 | impl Clone for Gc { 287 | fn clone(&self) -> Self { 288 | *self 289 | } 290 | } 291 | 292 | impl Copy for GcPointer {} 293 | 294 | impl Clone for GcPointer { 295 | fn clone(&self) -> Self { 296 | *self 297 | } 298 | } 299 | impl Hash for Gc { 300 | fn hash(&self, state: &mut H) { 301 | (**self).hash(state); 302 | } 303 | } 304 | 305 | #[cfg(test)] 306 | mod test { 307 | use super::*; 308 | use std::mem::size_of; 309 | 310 | #[test] 311 | #[should_panic] 312 | fn test_too_small() { 313 | Gc::<[u8; 256]>::new_from_layout(Layout::from_size_align(1, 1).unwrap()); 314 | } 315 | 316 | #[test] 317 | #[should_panic] 318 | fn test_unaligned() { 319 | #[repr(align(1024))] 320 | struct S { 321 | _x: usize, 322 | } 323 | Gc::::new_from_layout(Layout::from_size_align(size_of::(), 1).unwrap()); 324 | } 325 | 326 | #[test] 327 | fn test_dispatchable() { 328 | struct S1 { 329 | x: u64, 330 | } 331 | struct S2 { 332 | y: u64, 333 | } 334 | trait T: Send { 335 | fn f(self: Gc) -> u64 336 | where 337 | Self: Send; 338 | } 339 | impl T for S1 { 340 | fn f(self: Gc) -> u64 341 | where 342 | Self: Send, 343 | { 344 | self.x 345 | } 346 | } 347 | impl T for S2 { 348 | fn f(self: Gc) -> u64 349 | where 350 | Self: Send, 351 | { 352 | self.y 353 | } 354 | } 355 | 356 | let s1 = S1 { x: 1 }; 357 | let s2 = S2 { y: 2 }; 358 | let s1gc: Gc = Gc::new(s1); 359 | let s2gc: Gc = Gc::new(s2); 360 | assert_eq!(s1gc.f(), 1); 361 | assert_eq!(s2gc.f(), 2); 362 | 363 | let s1gcd: Gc = s1gc; 364 | let s2gcd: Gc = s2gc; 365 | assert_eq!(s1gcd.f(), 1); 366 | assert_eq!(s2gcd.f(), 2); 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "standalone"), feature(gc))] 2 | #![cfg_attr(not(feature = "standalone"), feature(rustc_private))] 3 | #![feature(core_intrinsics)] 4 | #![feature(allocator_api)] 5 | #![feature(alloc_layout_extra)] 6 | #![feature(arbitrary_self_types)] 7 | #![feature(dispatch_from_dyn)] 8 | #![feature(nonnull_slice_from_raw_parts)] 9 | #![feature(raw_vec_internals)] 10 | #![feature(const_fn)] 11 | #![feature(coerce_unsized)] 12 | #![feature(unsize)] 13 | #![feature(maybe_uninit_ref)] 14 | #![feature(negative_impls)] 15 | #![allow(incomplete_features)] 16 | #![allow(where_clauses_object_safety)] 17 | #[cfg(not(all(target_pointer_width = "64", target_arch = "x86_64")))] 18 | compile_error!("Requires x86_64 with 64 bit pointer width."); 19 | 20 | pub mod gc; 21 | #[cfg(feature = "gc_stats")] 22 | pub mod stats; 23 | 24 | #[cfg(feature = "standalone")] 25 | pub use allocator::GcAllocator; 26 | 27 | #[cfg(not(feature = "standalone"))] 28 | pub use std::alloc::GcAllocator; 29 | 30 | pub use gc::Gc; 31 | 32 | pub static ALLOCATOR: GcAllocator = GcAllocator; 33 | -------------------------------------------------------------------------------- /src/stats.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicUsize, Ordering}; 2 | 3 | pub static NUM_REGISTERED_FINALIZERS: AtomicUsize = AtomicUsize::new(0); 4 | --------------------------------------------------------------------------------