├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── crates └── mutator │ ├── .gitignore │ ├── Cargo.toml │ └── src │ └── lib.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── mutator.rs └── src ├── free_list.rs ├── lib.rs └── mark_bits.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | /target 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald "] 3 | categories = ["memory-management", "rust-patterns"] 4 | description = "A garbage collection library with zero `unsafe` code and zero dependencies." 5 | documentation = "https://docs.rs/safe-gc" 6 | edition = "2021" 7 | license = "MIT OR Apache-2.0" 8 | name = "safe-gc" 9 | readme = "README.md" 10 | repository = "https://github.com/fitzgen/safe-gc" 11 | version = "1.1.1" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | 17 | [workspace] 18 | members = [ 19 | './fuzz', 20 | './crates/mutator', 21 | ] 22 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Nick Fitzgerald 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

safe-gc

3 |

A garbage collection library for Rust without any unsafe code

4 |

5 | build status 6 | Documentation Status 7 |

8 |
9 | 10 | ## About 11 | 12 | `safe-gc` implements a garbage collection library for Rust with zero `unsafe` 13 | code and zero dependencies. It even has a `forbid(unsafe_code)` directive at the 14 | top! 15 | 16 | Additional features: 17 | 18 | * Allows constructing and collecting arbitrary heap graphs, including cycles. It 19 | doesn't impose any ownership hierarchy, or anything like that, to the shapes 20 | of references between GC-managed objects within the heap. 21 | 22 | * Leverages Rust's ownership and borrowing in its API: if you have an `&mut 23 | Heap`, you can get mutable access to objects in the heap. It doesn't, for 24 | example, force everything in the heap into `RefCell`s, or only give out shared 25 | references to GC-managed objects, or similar. 26 | 27 | * Allows constructing multiple, separate GC heaps that can be independently 28 | collected. 29 | 30 | * Allows allocating any number of heterogeneous types within the heap. For 31 | example, you can allocate both `Cons` and `Tree` objects within the 32 | heap. Heaps are *not* constrained to only a single, uniform `T` type of GC 33 | objects. 34 | 35 | * Footgun-free GC object finalization with Rust's regular, old `Drop` trait. No 36 | worries about accidentally deref'ing pointers to GC objects the collector has 37 | already reclaimed or resurrecting objects it was about to reclaim. 38 | 39 | `safe-gc` is not, however, a particularly high-performance garbage collector. 40 | 41 | ## Usage 42 | 43 | * Define types managed by the GC. 44 | 45 | * Define references from within one GC type to another GC type with `Gc`. 46 | 47 | * Implement `Trace` for your GC-managed types. 48 | 49 | * Create one or more `Heap`s. 50 | 51 | * Allocate objects in your `Heap`s. 52 | 53 | * Hold onto GC roots with `Root`. 54 | 55 | * Let the garbage collector reclaim unreachable objects! 56 | 57 | ## Example 58 | 59 | ```rust 60 | use safe_gc::{Collector, Gc, Heap, Trace}; 61 | 62 | // Define a GC-managed tree of `T` values. 63 | struct Tree { 64 | value: Gc, 65 | 66 | // A cyclic parent pointer. 67 | parent: Option>>, 68 | 69 | // Left and right subtrees. 70 | left: Option>>, 71 | right: Option>>, 72 | } 73 | 74 | // Report each of the GC references within a `Tree` to the 75 | // collector. 76 | // 77 | // See the `Trace` docs for more details. 78 | impl Trace for Tree { 79 | fn trace(&self, collector: &mut Collector) { 80 | collector.edge(self.value); 81 | if let Some(parent) = self.parent { 82 | collector.edge(parent); 83 | } 84 | if let Some(left) = self.left { 85 | collector.edge(left); 86 | } 87 | if let Some(right) = self.right { 88 | collector.edge(right); 89 | } 90 | } 91 | } 92 | 93 | // Another GC type! 94 | struct Cat { 95 | cuteness: u32, 96 | cat_tree: Option>>, 97 | } 98 | 99 | impl Trace for Cat { 100 | fn trace(&self, collector: &mut Collector) { 101 | if let Some(tree) = self.cat_tree { 102 | collector.edge(tree); 103 | } 104 | } 105 | } 106 | 107 | // Create a new GC heap! 108 | let mut heap = Heap::new(); 109 | 110 | // Allocate some objects in the heap! 111 | let momo = heap.alloc(Cat { 112 | cuteness: u32::MAX, 113 | cat_tree: None, 114 | }); 115 | let tree = heap.alloc(Tree { 116 | value: momo.unrooted(), 117 | parent: None, 118 | left: None, 119 | right: None, 120 | }); 121 | 122 | // Create a bunch of garbage! Who cares! 123 | for _ in 0..100 { 124 | let _ = heap.alloc(Tree { 125 | value: momo.unrooted(), 126 | parent: None, 127 | left: None, 128 | right: None, 129 | }); 130 | } 131 | 132 | // Read data from objects in the heap! 133 | let cuteness = heap[&momo].cuteness; 134 | assert_eq!(cuteness, u32::MAX); 135 | 136 | // Mutate objects in the heap! 137 | heap[&momo].cat_tree = Some(tree.into()); 138 | 139 | // Garbage collections will happen automatically, as necessary, but you can also 140 | // force a collection, if you want! 141 | heap.gc(); 142 | ``` 143 | 144 | ## Why? 145 | 146 | `safe-gc` is certainly a point in the design space of garbage-collection 147 | libraries in Rust. One could even argue it is an interesting -- and maybe even 148 | useful? -- point in the design space! 149 | 150 | Also, it was fun! 151 | 152 | At the very least, you don't have to wonder about the correctness of any 153 | `unsafe` code in here, because there isn't any. As long as the Rust language and 154 | its standard library are sound, this crate is too. 155 | -------------------------------------------------------------------------------- /crates/mutator/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/mutator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "safe-gc-mutator" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | arbitrary = "1.3.2" 11 | safe-gc = { path = "../.." } 12 | -------------------------------------------------------------------------------- /crates/mutator/src/lib.rs: -------------------------------------------------------------------------------- 1 | use arbitrary::{Arbitrary, Result, Unstructured}; 2 | use safe_gc::{Collector, Gc, Heap, Root, Trace}; 3 | 4 | #[derive(Debug)] 5 | pub struct Mutator(Vec); 6 | 7 | struct A; 8 | 9 | impl Trace for A { 10 | fn trace(&self, _: &mut Collector) {} 11 | } 12 | 13 | struct B(Option>); 14 | 15 | impl Trace for B { 16 | fn trace(&self, collector: &mut Collector) { 17 | if let Some(x) = self.0 { 18 | collector.edge(x); 19 | } 20 | } 21 | } 22 | 23 | struct C { 24 | x: Option>, 25 | y: Option>, 26 | } 27 | 28 | impl Trace for C { 29 | fn trace(&self, collector: &mut Collector) { 30 | if let Some(x) = self.x { 31 | collector.edge(x); 32 | } 33 | if let Some(y) = self.y { 34 | collector.edge(y); 35 | } 36 | } 37 | } 38 | 39 | struct D(Vec>); 40 | 41 | impl Trace for D { 42 | fn trace(&self, collector: &mut Collector) { 43 | for c in &self.0 { 44 | collector.edge(*c); 45 | } 46 | } 47 | } 48 | 49 | #[derive(Debug)] 50 | enum Op { 51 | Gc, 52 | 53 | AllocA, 54 | AllocB(Option), 55 | AllocC { x: Option, y: Option }, 56 | AllocD(Vec), 57 | 58 | GetA(usize), 59 | GetB(usize), 60 | GetCX(usize), 61 | GetCY(usize), 62 | GetD(usize, usize), 63 | 64 | SetB(usize, Option), 65 | SetCX(usize, Option), 66 | SetCY(usize, Option), 67 | SetD(usize, usize, usize), 68 | 69 | UnrootA(usize), 70 | UnrootB(usize), 71 | UnrootC(usize), 72 | UnrootD(usize), 73 | } 74 | 75 | impl<'a> Arbitrary<'a> for Mutator { 76 | fn arbitrary(u: &mut Unstructured<'a>) -> Result { 77 | let mut ops = vec![]; 78 | 79 | #[derive(Default)] 80 | struct State { 81 | a_count: usize, 82 | b_count: usize, 83 | c_count: usize, 84 | d_count: usize, 85 | } 86 | 87 | let mut state = State::default(); 88 | let mut choices: Vec Result> = vec![]; 89 | while !u.is_empty() { 90 | choices.clear(); 91 | 92 | choices.push(|_, _| Ok(Op::Gc)); 93 | 94 | choices.push(|_, state| { 95 | state.a_count += 1; 96 | Ok(Op::AllocA) 97 | }); 98 | 99 | choices.push(|u, state| { 100 | let op = Op::AllocB(if state.b_count > 0 && u.arbitrary()? { 101 | Some(u.int_in_range(0..=state.b_count - 1)?) 102 | } else { 103 | None 104 | }); 105 | state.b_count += 1; 106 | Ok(op) 107 | }); 108 | 109 | choices.push(|u, state| { 110 | let x = if state.c_count > 0 && u.arbitrary()? { 111 | Some(u.int_in_range(0..=state.c_count - 1)?) 112 | } else { 113 | None 114 | }; 115 | let y = if state.d_count > 0 && u.arbitrary()? { 116 | Some(u.int_in_range(0..=state.d_count - 1)?) 117 | } else { 118 | None 119 | }; 120 | state.c_count += 1; 121 | Ok(Op::AllocC { x, y }) 122 | }); 123 | 124 | choices.push(|u, state| { 125 | state.d_count += 1; 126 | let mut cs = vec![]; 127 | if state.c_count > 0 { 128 | for _ in 0..u.int_in_range::(0..=8)? { 129 | cs.push(u.int_in_range(0..=state.c_count - 1)?); 130 | } 131 | } 132 | Ok(Op::AllocD(cs)) 133 | }); 134 | 135 | if state.a_count > 0 { 136 | choices.push(|u, state| { 137 | let op = Op::GetA(u.int_in_range(0..=state.a_count - 1)?); 138 | state.a_count += 1; 139 | Ok(op) 140 | }); 141 | choices.push(|u, state| { 142 | let op = Op::UnrootA(u.int_in_range(0..=state.a_count - 1)?); 143 | state.a_count -= 1; 144 | Ok(op) 145 | }); 146 | } 147 | 148 | if state.b_count > 0 { 149 | choices.push(|u, state| { 150 | let op = Op::GetB(u.int_in_range(0..=state.b_count - 1)?); 151 | state.b_count += 1; 152 | Ok(op) 153 | }); 154 | choices.push(|u, state| { 155 | Ok(Op::SetB( 156 | u.int_in_range(0..=state.b_count - 1)?, 157 | if u.arbitrary()? { 158 | Some(u.int_in_range(0..=state.b_count - 1)?) 159 | } else { 160 | None 161 | }, 162 | )) 163 | }); 164 | choices.push(|u, state| { 165 | let op = Op::UnrootB(u.int_in_range(0..=state.b_count - 1)?); 166 | state.b_count -= 1; 167 | Ok(op) 168 | }); 169 | } 170 | 171 | if state.c_count > 0 { 172 | choices.push(|u, state| { 173 | let op = Op::GetCX(u.int_in_range(0..=state.c_count - 1)?); 174 | state.c_count += 1; 175 | Ok(op) 176 | }); 177 | choices.push(|u, state| { 178 | let op = Op::GetCY(u.int_in_range(0..=state.c_count - 1)?); 179 | state.d_count += 1; 180 | Ok(op) 181 | }); 182 | choices.push(|u, state| { 183 | Ok(Op::SetCX( 184 | u.int_in_range(0..=state.c_count - 1)?, 185 | if u.arbitrary()? { 186 | Some(u.int_in_range(0..=state.c_count - 1)?) 187 | } else { 188 | None 189 | }, 190 | )) 191 | }); 192 | choices.push(|u, state| { 193 | Ok(Op::SetCY( 194 | u.int_in_range(0..=state.c_count - 1)?, 195 | if state.d_count > 0 && u.arbitrary()? { 196 | Some(u.int_in_range(0..=state.d_count - 1)?) 197 | } else { 198 | None 199 | }, 200 | )) 201 | }); 202 | choices.push(|u, state| { 203 | let op = Op::UnrootC(u.int_in_range(0..=state.c_count - 1)?); 204 | state.c_count -= 1; 205 | Ok(op) 206 | }); 207 | } 208 | 209 | if state.d_count > 0 { 210 | choices.push(|u, state| { 211 | let op = Op::GetD(u.int_in_range(0..=state.d_count - 1)?, u.arbitrary()?); 212 | state.c_count += 1; 213 | Ok(op) 214 | }); 215 | if state.c_count > 0 { 216 | choices.push(|u, state| { 217 | Ok(Op::SetD( 218 | u.int_in_range(0..=state.d_count - 1)?, 219 | u.arbitrary()?, 220 | u.int_in_range(0..=state.c_count - 1)?, 221 | )) 222 | }); 223 | } 224 | choices.push(|u, state| { 225 | let op = Op::UnrootD(u.int_in_range(0..=state.d_count - 1)?); 226 | state.d_count -= 1; 227 | Ok(op) 228 | }); 229 | } 230 | 231 | let f = u.choose(&choices)?; 232 | let op = f(u, &mut state)?; 233 | ops.push(op); 234 | } 235 | 236 | Ok(Mutator(ops)) 237 | } 238 | } 239 | 240 | impl Mutator { 241 | pub fn run(&self) { 242 | self.run_in(&mut Heap::new()); 243 | } 244 | 245 | pub fn run_in(&self, heap: &mut Heap) { 246 | let mut a_roots: Vec> = vec![]; 247 | let mut b_roots: Vec> = vec![]; 248 | let mut c_roots: Vec> = vec![]; 249 | let mut d_roots: Vec> = vec![]; 250 | 251 | for op in &self.0 { 252 | match op { 253 | Op::Gc => heap.gc(), 254 | 255 | Op::AllocA => a_roots.push(heap.alloc(A)), 256 | Op::AllocB(b) => b_roots.push(heap.alloc(B(b.map(|b| b_roots[b].unrooted())))), 257 | Op::AllocC { x, y } => c_roots.push(heap.alloc(C { 258 | x: x.map(|x| c_roots[x].unrooted()), 259 | y: y.map(|y| d_roots[y].unrooted()), 260 | })), 261 | Op::AllocD(cs) => { 262 | d_roots.push(heap.alloc(D(cs.iter().map(|c| c_roots[*c].unrooted()).collect()))) 263 | } 264 | 265 | Op::GetA(a) => { 266 | let a = a_roots[*a].clone(); 267 | a_roots.push(a); 268 | } 269 | Op::GetB(b) => { 270 | let b = &b_roots[*b]; 271 | let b = match heap[b].0 { 272 | Some(b) => heap.root(b), 273 | None => b.clone(), 274 | }; 275 | b_roots.push(b); 276 | } 277 | Op::GetCX(c) => { 278 | let c = &c_roots[*c]; 279 | let x = match heap[c].x { 280 | Some(x) => heap.root(x), 281 | None => c.clone(), 282 | }; 283 | c_roots.push(x); 284 | } 285 | Op::GetCY(c) => { 286 | let c = &c_roots[*c]; 287 | let y = match heap[c].y { 288 | Some(y) => heap.root(y), 289 | None => heap.alloc(D(vec![])), 290 | }; 291 | d_roots.push(y); 292 | } 293 | Op::GetD(d, i) => { 294 | let d = &d_roots[*d]; 295 | if heap[d].0.len() == 0 { 296 | let c = heap.alloc(C { x: None, y: None }); 297 | c_roots.push(c); 298 | } else { 299 | let i = i % heap[d].0.len(); 300 | let c = heap[d].0[i]; 301 | let c = heap.root(c); 302 | c_roots.push(c); 303 | } 304 | } 305 | 306 | Op::SetB(b, c) => { 307 | let c = c.map(|c| b_roots[c].unrooted()); 308 | heap[&b_roots[*b]].0 = c; 309 | } 310 | Op::SetCX(c, x) => { 311 | let x = x.map(|x| c_roots[x].unrooted()); 312 | heap[&c_roots[*c]].x = x; 313 | } 314 | Op::SetCY(c, y) => { 315 | let y = y.map(|y| d_roots[y].unrooted()); 316 | heap[&c_roots[*c]].y = y; 317 | } 318 | Op::SetD(d, i, c) => { 319 | let c = c_roots[*c].unrooted(); 320 | let d = &mut heap[&d_roots[*d]]; 321 | if d.0.len() == 0 { 322 | d.0.push(c); 323 | } else { 324 | let i = i % d.0.len(); 325 | d.0[i] = c; 326 | } 327 | } 328 | 329 | Op::UnrootA(a) => { 330 | a_roots.swap_remove(*a); 331 | } 332 | Op::UnrootB(b) => { 333 | b_roots.swap_remove(*b); 334 | } 335 | Op::UnrootC(c) => { 336 | c_roots.swap_remove(*c); 337 | } 338 | Op::UnrootD(d) => { 339 | d_roots.swap_remove(*d); 340 | } 341 | } 342 | } 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "safe-gc-fuzz" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | libfuzzer-sys = "0.4" 12 | safe-gc = { path = ".." } 13 | safe-gc-mutator = { path = "../crates/mutator" } 14 | 15 | [profile.release] 16 | debug = 1 17 | 18 | [[bin]] 19 | name = "mutator" 20 | path = "./mutator.rs" 21 | test = false 22 | doc = false 23 | -------------------------------------------------------------------------------- /fuzz/mutator.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use safe_gc_mutator::Mutator; 5 | 6 | fuzz_target!(|mutator: Mutator| { 7 | mutator.run(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/free_list.rs: -------------------------------------------------------------------------------- 1 | enum Entry { 2 | /// An occupied entry holding a `T`. 3 | Occupied(T), 4 | 5 | /// A free entry that is also part of a linked list pointing to the next 6 | /// free entry, if any. 7 | Free(Option), 8 | } 9 | 10 | pub struct FreeList { 11 | entries: Vec>, 12 | 13 | /// The index of the first free entry in the free list. 14 | free: Option, 15 | 16 | len: usize, 17 | } 18 | 19 | impl Default for FreeList { 20 | fn default() -> Self { 21 | Self { 22 | entries: Vec::default(), 23 | free: None, 24 | len: 0, 25 | } 26 | } 27 | } 28 | 29 | impl FreeList { 30 | pub fn new() -> Self { 31 | FreeList::default() 32 | } 33 | 34 | pub fn with_capacity(capacity: usize) -> Self { 35 | let mut list = FreeList::new(); 36 | list.reserve(capacity); 37 | list 38 | } 39 | 40 | pub fn reserve(&mut self, additional: usize) { 41 | self.entries.reserve(additional); 42 | 43 | // Note that we didn't call `reserve_exact`, so the vector and/or 44 | // allocator may have given us a little extra capacity if it was 45 | // convenient for them. So make sure to use the actual new capacity, 46 | // rather than the requested new capacity. 47 | while self.entries.len() < self.entries.capacity() { 48 | let index = u32::try_from(self.entries.len()).unwrap(); 49 | let next_free = std::mem::replace(&mut self.free, Some(index)); 50 | self.entries.push(Entry::Free(next_free)); 51 | } 52 | } 53 | 54 | pub fn double_capacity(&mut self) { 55 | // Double our capacity to amortize the cost of resizing. But make sure 56 | // we add some amount of additional capacity, since doubling zero 57 | // capacity isn't useful. 58 | const MIN_CAPACITY: usize = 16; 59 | let additional = std::cmp::max(self.entries.capacity(), MIN_CAPACITY); 60 | self.reserve(additional); 61 | } 62 | 63 | pub fn capacity(&self) -> usize { 64 | self.entries.capacity() 65 | } 66 | 67 | pub fn len(&self) -> usize { 68 | self.len 69 | } 70 | 71 | pub fn try_alloc(&mut self, value: T) -> Result { 72 | if let Some(index) = self.free { 73 | let next_free = match self.entries[usize::try_from(index).unwrap()] { 74 | Entry::Free(next_free) => next_free, 75 | Entry::Occupied { .. } => unreachable!(), 76 | }; 77 | self.free = next_free; 78 | self.entries[usize::try_from(index).unwrap()] = Entry::Occupied(value); 79 | self.len += 1; 80 | Ok(index) 81 | } else { 82 | Err(value) 83 | } 84 | } 85 | 86 | pub fn alloc(&mut self, value: T) -> u32 { 87 | self.try_alloc(value).unwrap_or_else(|value| { 88 | // Reserve additional capacity, since we didn't have space for the 89 | // allocation. 90 | self.double_capacity(); 91 | // After which the allocation will succeed. 92 | self.try_alloc(value).ok().unwrap() 93 | }) 94 | } 95 | 96 | pub fn get(&self, index: u32) -> &T { 97 | match &self.entries[usize::try_from(index).unwrap()] { 98 | Entry::Occupied(x) => x, 99 | Entry::Free(_) => unreachable!(), 100 | } 101 | } 102 | 103 | pub fn get_mut(&mut self, index: u32) -> &mut T { 104 | match &mut self.entries[usize::try_from(index).unwrap()] { 105 | Entry::Occupied(x) => x, 106 | Entry::Free(_) => unreachable!(), 107 | } 108 | } 109 | 110 | pub fn iter(&self) -> impl Iterator + '_ { 111 | self.entries 112 | .iter() 113 | .enumerate() 114 | .filter_map(|(i, e)| match e { 115 | Entry::Occupied(x) => Some((u32::try_from(i).unwrap(), x)), 116 | Entry::Free(_) => None, 117 | }) 118 | } 119 | 120 | pub fn dealloc(&mut self, index: u32) { 121 | match &mut self.entries[usize::try_from(index).unwrap()] { 122 | Entry::Free(_) => {} 123 | e @ Entry::Occupied(_) => { 124 | let next_free = std::mem::replace(&mut self.free, Some(index)); 125 | *e = Entry::Free(next_free); 126 | self.len -= 1; 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![forbid(unsafe_code)] 3 | #![deny(missing_docs)] 4 | 5 | mod free_list; 6 | mod mark_bits; 7 | 8 | use free_list::FreeList; 9 | use mark_bits::MarkBits; 10 | use std::{ 11 | any::{Any, TypeId}, 12 | cell::RefCell, 13 | collections::HashMap, 14 | fmt::Debug, 15 | hash::Hash, 16 | rc::Rc, 17 | sync::atomic, 18 | }; 19 | 20 | /// Report references to other GC-managed objects to the collector. 21 | /// 22 | /// This trait must be implemented by all types that are allocated within a 23 | /// [`Heap`]. 24 | /// 25 | /// Failure to enumerate all edges in an instance will result in wacky -- but 26 | /// still safe! -- behavior: panics when attempting to access a collected 27 | /// object, accesses to a "wrong" object that's been allocated in place of the 28 | /// old object, etc... 29 | /// 30 | /// # Example 31 | /// 32 | /// ``` 33 | /// use safe_gc::{Collector, Gc, Trace}; 34 | /// 35 | /// struct List { 36 | /// value: Gc, 37 | /// prev: Option>>, 38 | /// next: Option>>, 39 | /// } 40 | /// 41 | /// impl Trace for List { 42 | /// fn trace(&self, collector: &mut Collector) { 43 | /// collector.edge(self.value); 44 | /// if let Some(prev) = self.prev { 45 | /// collector.edge(prev); 46 | /// } 47 | /// if let Some(next) = self.next { 48 | /// collector.edge(next); 49 | /// } 50 | /// } 51 | /// } 52 | /// ``` 53 | pub trait Trace: Any { 54 | /// Call `collector.edge(gc)` for each `Gc` reference within `self`. 55 | fn trace(&self, collector: &mut Collector); 56 | } 57 | 58 | /// A reference to a garbage-collected `T`. 59 | /// 60 | /// `Gc` should be used when: 61 | /// 62 | /// * Referencing other GC-managed objects from within a GC-managed object's 63 | /// type definition. 64 | /// 65 | /// * Traversing or mutating GC-managed objects when you know a garbage 66 | /// collection cannot happen. 67 | /// 68 | /// A `Gc` does *not* root the referenced `T` or keep it alive across garbage 69 | /// collections. (The [`Root`][crate::Root] type does that.) Therefore, 70 | /// `Gc` should *not* be used to hold onto GC references across any operation 71 | /// that could trigger a garbage collection. 72 | /// 73 | /// # Example: Referencing Other GC-Managed Objects Within a GC-Managed Object 74 | /// 75 | /// ``` 76 | /// use safe_gc::{Collector, Gc, Trace}; 77 | /// 78 | /// struct Tree { 79 | /// // A non-nullable reference to a GC-managed `T`. 80 | /// value: Gc, 81 | /// 82 | /// // Nullable references to parent, left, and right tree nodes. 83 | /// parent: Option>>, 84 | /// left: Option>>, 85 | /// right: Option>>, 86 | /// } 87 | /// 88 | /// impl Trace for Tree { 89 | /// fn trace(&self, collector: &mut Collector) { 90 | /// // Report each of the `Gc`s referenced from within `self` to the 91 | /// // collector. See the `Trace` docs for more details. 92 | /// collector.edge(self.value); 93 | /// if let Some(parent) = self.parent { 94 | /// collector.edge(parent); 95 | /// } 96 | /// if let Some(left) = self.left { 97 | /// collector.edge(left); 98 | /// } 99 | /// if let Some(right) = self.right { 100 | /// collector.edge(right); 101 | /// } 102 | /// } 103 | /// } 104 | /// ``` 105 | /// 106 | /// # Example: Accessing a `Gc`'s referenced `T` 107 | /// 108 | /// ``` 109 | /// use safe_gc::{Gc, Heap, Trace}; 110 | /// 111 | /// struct Node { 112 | /// value: u32, 113 | /// tail: Option>, 114 | /// } 115 | /// 116 | /// impl Trace for Node { 117 | /// // ... 118 | /// # fn trace(&self, _: &mut safe_gc::Collector) {} 119 | /// } 120 | /// 121 | /// let mut heap = Heap::new(); 122 | /// 123 | /// let a = heap.alloc(Node { value: 36, tail: None }); 124 | /// let b = heap.alloc(Node { value: 42, tail: Some(a.into()) }); 125 | /// let c = heap.alloc(Node { value: 99, tail: Some(b.clone().into()) }); 126 | /// 127 | /// // Read `(*c).tail`. 128 | /// let c_tail = heap[&c].tail; 129 | /// assert_eq!(c_tail, Some(b.into())); 130 | /// 131 | /// // Write `(*c).tail = None`. 132 | /// heap[&c].tail = None; 133 | /// ``` 134 | /// 135 | /// # Example: Downgrading a `Root` into a `Gc` 136 | /// 137 | /// The [`Heap::alloc`] method returns rooted references, but to store those 138 | /// references into the field of a GC-managed object, you'll need to unroot the 139 | /// reference with [`Root::unrooted`][crate::Root::unrooted]. (You can also 140 | /// use `root.into()` or `Gc::from(root)`.) 141 | /// 142 | /// ``` 143 | /// use safe_gc::{Gc, Heap, Root, Trace}; 144 | /// 145 | /// struct Cat { 146 | /// siblings: Vec>, 147 | /// } 148 | /// 149 | /// impl Trace for Cat { 150 | /// // ... 151 | /// # fn trace(&self, _: &mut safe_gc::Collector) {} 152 | /// } 153 | /// 154 | /// let mut heap = Heap::new(); 155 | /// 156 | /// let momo: Root = heap.alloc(Cat { siblings: vec![] }); 157 | /// let juno: Root = heap.alloc(Cat { siblings: vec![] }); 158 | /// 159 | /// // Add `momo` and `juno` to each other's siblings vectors. This requires 160 | /// // downgrading the `Root`s to `Gc`s via the `unrooted` method. 161 | /// heap[&momo].siblings.push(juno.unrooted()); 162 | /// heap[&juno].siblings.push(momo.unrooted()); 163 | /// ``` 164 | /// 165 | /// # Example: Upgrading a `Gc` into a `Root` 166 | /// 167 | /// You can upgrade a `Gc` into a [`Root`][crate::Root] via the 168 | /// [`Heap::root`] method, so that you can hold references to GC-objects across 169 | /// operations that can potentially trigger garbage collections. 170 | /// 171 | /// See the docs for [`Heap::root`] for more details and an example. 172 | pub struct Gc { 173 | heap_id: u32, 174 | index: u32, 175 | _phantom: std::marker::PhantomData<*mut T>, 176 | } 177 | 178 | impl Gc { 179 | /// Create a new `Gc` from a raw heap ID and index. 180 | pub fn from_raw_parts(heap_id: u32, index: u32) -> Self { 181 | Self { 182 | heap_id, 183 | index, 184 | _phantom: std::marker::PhantomData, 185 | } 186 | } 187 | 188 | /// Get the raw heap ID and index from a `Gc`. 189 | pub fn into_raw_parts(self) -> (u32, u32) { 190 | (self.heap_id, self.index) 191 | } 192 | } 193 | 194 | impl Clone for Gc { 195 | fn clone(&self) -> Self { 196 | *self 197 | } 198 | } 199 | 200 | impl Copy for Gc {} 201 | 202 | impl Debug for Gc { 203 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 204 | f.debug_struct(&format!("Gc<{}>", std::any::type_name::())) 205 | .field("heap_id", &self.heap_id) 206 | .field("index", &self.index) 207 | .finish() 208 | } 209 | } 210 | 211 | impl Hash for Gc { 212 | fn hash(&self, state: &mut H) { 213 | self.heap_id.hash(state); 214 | self.index.hash(state); 215 | } 216 | } 217 | 218 | impl PartialEq for Gc { 219 | fn eq(&self, other: &Self) -> bool { 220 | self.heap_id == other.heap_id && self.index == other.index 221 | } 222 | } 223 | 224 | impl PartialEq> for Gc 225 | where 226 | T: Trace, 227 | { 228 | fn eq(&self, other: &Root) -> bool { 229 | *self == other.unrooted() 230 | } 231 | } 232 | 233 | impl Eq for Gc {} 234 | 235 | impl From> for Gc 236 | where 237 | T: Trace, 238 | { 239 | fn from(root: Root) -> Self { 240 | root.unrooted() 241 | } 242 | } 243 | 244 | impl<'a, T> From<&'a Root> for Gc 245 | where 246 | T: Trace, 247 | { 248 | fn from(root: &'a Root) -> Self { 249 | root.unrooted() 250 | } 251 | } 252 | 253 | struct RootSet { 254 | inner: Rc>>>, 255 | } 256 | 257 | impl Clone for RootSet { 258 | fn clone(&self) -> Self { 259 | Self { 260 | inner: Rc::clone(&self.inner), 261 | } 262 | } 263 | } 264 | 265 | impl Default for RootSet { 266 | fn default() -> Self { 267 | Self { 268 | inner: Rc::new(RefCell::new(FreeList::>::default())), 269 | } 270 | } 271 | } 272 | 273 | impl RootSet 274 | where 275 | T: Trace, 276 | { 277 | fn insert(&self, gc: Gc) -> Root { 278 | let mut inner = self.inner.borrow_mut(); 279 | let index = inner.alloc(gc); 280 | Root { 281 | roots: self.clone(), 282 | index, 283 | } 284 | } 285 | 286 | fn remove(&self, index: u32) { 287 | let mut inner = self.inner.borrow_mut(); 288 | inner.dealloc(index); 289 | } 290 | 291 | fn trace(&self, collector: &mut Collector) { 292 | let inner = self.inner.borrow(); 293 | for (_, gc) in inner.iter() { 294 | collector.edge(*gc); 295 | } 296 | } 297 | } 298 | 299 | /// A rooted reference to a GC-managed `T`. 300 | /// 301 | /// `Root`s prevent their referenced `T` from being reclaimed during garbage 302 | /// collections. This makes them suitable for holding references to GC-managed 303 | /// objects across operations that can trigger GCs. 304 | /// 305 | /// `Root`s are *not* suitable for referencing other GC-manged objects within 306 | /// the definition of a GC-managed object. Doing this will effectively leak 307 | /// everything transitively referenced from the `Root`. Instead, use 308 | /// [`Gc`][crate::Gc] for references to other GC-managed objects from within 309 | /// a GC-managed object. 310 | /// 311 | /// See also the docs for [`Gc`][crate::Gc] for more examples of converting 312 | /// between `Root` and `Gc` and when you want to use which type. 313 | /// 314 | /// # Example: Creating a `Root` via Allocation 315 | /// 316 | /// ``` 317 | /// use safe_gc::{Gc, Heap, Root, Trace}; 318 | /// 319 | /// struct Node { 320 | /// value: u32, 321 | /// tail: Option>, 322 | /// } 323 | /// 324 | /// impl Trace for Node { 325 | /// // ... 326 | /// # fn trace(&self, _: &mut safe_gc::Collector) {} 327 | /// } 328 | /// 329 | /// let mut heap = Heap::new(); 330 | /// 331 | /// // Allocating a new GC object in a heap returns a `Root` reference. 332 | /// let node: Root = heap.alloc(Node { value: 1234, tail: None }); 333 | /// ``` 334 | pub struct Root 335 | where 336 | T: Trace, 337 | { 338 | roots: RootSet, 339 | index: u32, 340 | } 341 | 342 | impl Clone for Root 343 | where 344 | T: Trace, 345 | { 346 | fn clone(&self) -> Self { 347 | self.roots.insert(self.unrooted()) 348 | } 349 | } 350 | 351 | impl PartialEq> for Root 352 | where 353 | T: Trace, 354 | { 355 | fn eq(&self, other: &Root) -> bool { 356 | self.unrooted() == other.unrooted() 357 | } 358 | } 359 | 360 | impl PartialEq> for Root 361 | where 362 | T: Trace, 363 | { 364 | fn eq(&self, other: &Gc) -> bool { 365 | self.unrooted() == *other 366 | } 367 | } 368 | 369 | impl Drop for Root 370 | where 371 | T: Trace, 372 | { 373 | fn drop(&mut self) { 374 | self.roots.remove(self.index); 375 | } 376 | } 377 | 378 | impl Root 379 | where 380 | T: Trace, 381 | { 382 | /// Get an unrooted [`Gc`][crate::Gc] reference pointing to the same `T` 383 | /// that this `Root` points to. 384 | /// 385 | /// See also the docs for [`Gc`][crate::Gc] for more examples of 386 | /// converting between `Root` and `Gc` and when you want to use which 387 | /// type. 388 | pub fn unrooted(&self) -> Gc { 389 | let inner = (*self.roots.inner).borrow(); 390 | *inner.get(self.index) 391 | } 392 | } 393 | 394 | struct Arena { 395 | roots: RootSet, 396 | elements: FreeList, 397 | } 398 | 399 | // We don't default to 0-capacity arenas because the arenas themselves are 400 | // lazily constructed, and so by the time we are constructing an arena, we will 401 | // always immediately push onto it. 402 | const DEFAULT_ARENA_CAPACITY: usize = 32; 403 | 404 | impl Default for Arena { 405 | fn default() -> Self { 406 | Arena { 407 | roots: RootSet::::default(), 408 | elements: FreeList::with_capacity(DEFAULT_ARENA_CAPACITY), 409 | } 410 | } 411 | } 412 | 413 | impl Arena 414 | where 415 | T: Trace, 416 | { 417 | #[inline] 418 | fn try_alloc(&mut self, heap_id: u32, value: T) -> Result, T> { 419 | let index = self.elements.try_alloc(value)?; 420 | Ok(self.root(Gc { 421 | heap_id, 422 | index, 423 | _phantom: std::marker::PhantomData, 424 | })) 425 | } 426 | 427 | fn alloc_slow(&mut self, heap_id: u32, value: T) -> Root { 428 | if self.elements.len() == self.elements.capacity() { 429 | self.elements.double_capacity(); 430 | } 431 | let index = self.elements.try_alloc(value).ok().unwrap(); 432 | self.root(Gc { 433 | heap_id, 434 | index, 435 | _phantom: std::marker::PhantomData, 436 | }) 437 | } 438 | 439 | #[inline] 440 | fn root(&self, gc: Gc) -> Root { 441 | self.roots.insert(gc) 442 | } 443 | } 444 | 445 | trait ArenaObject: Any { 446 | fn as_any(&self) -> &dyn Any; 447 | 448 | fn as_any_mut(&mut self) -> &mut dyn Any; 449 | 450 | fn trace_roots(&self, collector: &mut Collector); 451 | 452 | fn trace_one(&mut self, index: u32, collector: &mut Collector); 453 | 454 | fn capacity(&self) -> usize; 455 | 456 | fn sweep(&mut self, mark_bits: &MarkBits); 457 | } 458 | 459 | impl ArenaObject for Arena 460 | where 461 | T: Trace, 462 | { 463 | fn as_any(&self) -> &dyn Any { 464 | self 465 | } 466 | 467 | fn as_any_mut(&mut self) -> &mut dyn Any { 468 | self 469 | } 470 | 471 | fn trace_roots(&self, collector: &mut Collector) { 472 | self.roots.trace(collector); 473 | } 474 | 475 | fn trace_one(&mut self, index: u32, collector: &mut Collector) { 476 | self.elements.get(index).trace(collector); 477 | } 478 | 479 | fn capacity(&self) -> usize { 480 | self.elements.capacity() 481 | } 482 | 483 | fn sweep(&mut self, mark_bits: &MarkBits) { 484 | let capacity = self.elements.capacity(); 485 | for index in 0..u32::try_from(capacity).unwrap() { 486 | if !mark_bits.get(index) { 487 | self.elements.dealloc(index); 488 | } 489 | } 490 | 491 | // If we are close to running out of capacity, reserve additional 492 | // space. This amortizes the cost of collections and avoids the failure 493 | // mode where we do a full GC on every object allocation: 494 | // 495 | // * The arena has zero available capacity. 496 | // * The mutator tries to allocate, triggering a GC. 497 | // * The GC is able to free up only one (or only a small handfull) of 498 | // slots. 499 | // * The mutator's pending allocation fills the newly reclaimed slot. 500 | // * Now we are out of capacity again, and the process repeats from the 501 | // top. 502 | let len = self.elements.len(); 503 | let available = capacity - len; 504 | if available < capacity / 4 { 505 | self.elements.double_capacity(); 506 | } 507 | } 508 | } 509 | 510 | /// The garbage collector for a heap. 511 | /// 512 | /// GC-managed objects should report all of their references to other GC-managed 513 | /// objects (aka "edges") to the collector in their [`Trace`] implementations. 514 | /// 515 | /// See the docs for [`Trace`] for more information. 516 | // 517 | // This type is only exposed to users so they can report edges, but internally 518 | // this does a bit more than that: 519 | // 520 | // * It maintains the mark stack work lists that contain all the GC objects 521 | // we've seen but have not yet finished processing. 522 | // 523 | // * It maintains the mark bits for all GC objects in the heap, which keep track 524 | // of which GC objects we have and have not seen while tracing the live set. 525 | pub struct Collector { 526 | heap_id: u32, 527 | mark_stacks: HashMap>, 528 | mark_bits: HashMap, 529 | } 530 | 531 | impl Collector { 532 | fn new(heap_id: u32) -> Self { 533 | Self { 534 | heap_id, 535 | mark_stacks: HashMap::default(), 536 | mark_bits: HashMap::default(), 537 | } 538 | } 539 | 540 | /// Report a reference to another GC-managed object (aka an "edge" in the 541 | /// heap graph). 542 | /// 543 | /// See the docs for [`Trace`] for more information. 544 | /// 545 | /// Panics when given cross-heap edges. See the "Cross-`Heap` GC References" 546 | /// section of [`Heap`]'s documentation for details on cross-heap edges. 547 | pub fn edge(&mut self, to: Gc) 548 | where 549 | T: Trace, 550 | { 551 | assert_eq!(to.heap_id, self.heap_id); 552 | let ty = TypeId::of::(); 553 | let mark_bits = self.mark_bits.get_mut(&ty).unwrap(); 554 | if mark_bits.set(to.index) { 555 | return; 556 | } 557 | let mark_stack = self.mark_stacks.entry(ty).or_default(); 558 | mark_stack.push(to.index); 559 | } 560 | 561 | fn next_non_empty_mark_stack(&self) -> Option { 562 | self.mark_stacks.iter().find_map( 563 | |(ty, stack)| { 564 | if stack.is_empty() { 565 | None 566 | } else { 567 | Some(*ty) 568 | } 569 | }, 570 | ) 571 | } 572 | 573 | fn pop_mark_stack(&mut self, type_id: TypeId) -> Option { 574 | self.mark_stacks.get_mut(&type_id).unwrap().pop() 575 | } 576 | } 577 | 578 | /// A collection of GC-managed objects. 579 | /// 580 | /// A `Heap` is a collection of GC-managed objects that can all reference each 581 | /// other, and garbage collection is performed at the `Heap` granularity. The 582 | /// smaller the `Heap`, the less overhead imposed by garbage collecting it. The 583 | /// larger the `Heap`, the more overhead is imposed. 584 | /// 585 | /// There are no restrictions on the shape of references between GC-managed 586 | /// objects within a `Heap`: references may form arbitrary cycles and there is 587 | /// no imposed ownership hierarchy. 588 | /// 589 | /// # Allocating Objects 590 | /// 591 | /// You can allocate objects with the [`Heap::alloc`] method. 592 | /// 593 | /// You can allocate instances of any number of heterogeneous types of 594 | /// GC-managed objects within a `Heap`: they may, for example, contain both 595 | /// `Cons` objects and `Tree` objects. `Heap`s are *not* constrained to only 596 | /// instances of a single, uniform `T` type of GC objects. 597 | /// 598 | /// All types allocated within a heap must, however, implement the [`Trace`] 599 | /// trait. See the [`Trace`] trait's docs for more details. 600 | /// 601 | /// ``` 602 | /// use safe_gc::{Gc, Heap, Trace}; 603 | /// 604 | /// struct Tree { 605 | /// value: Gc, 606 | /// parent: Option>>, 607 | /// left: Option>>, 608 | /// right: Option>>, 609 | /// } 610 | /// 611 | /// impl Trace for Tree { 612 | /// // See the docs for `Trace` for details... 613 | /// # fn trace(&self, _: &mut safe_gc::Collector) {} 614 | /// } 615 | /// 616 | /// struct Cat { 617 | /// cuteness: u32, 618 | /// cat_tree: Option>>, 619 | /// } 620 | /// 621 | /// impl Trace for Cat { 622 | /// // See the docs for `Trace` for details... 623 | /// # fn trace(&self, _: &mut safe_gc::Collector) {} 624 | /// } 625 | /// 626 | /// let mut heap = Heap::new(); 627 | /// 628 | /// // Allocate a cat within the heap! 629 | /// let goomba = heap.alloc(Cat { 630 | /// cuteness: u32::MAX, 631 | /// cat_tree: None, 632 | /// }); 633 | /// 634 | /// // Also allocate a tree within the heap! 635 | /// let tree = heap.alloc(Tree { 636 | /// value: goomba.unrooted(), 637 | /// parent: None, 638 | /// left: None, 639 | /// right: None, 640 | /// }); 641 | /// 642 | /// // Create a cycle: the `tree` already references `goomba`, but now 643 | /// // `goomba` references the `tree` as well. 644 | /// heap[goomba].cat_tree = Some(tree.into()); 645 | /// ``` 646 | /// 647 | /// # Accessing Allocating Objects 648 | /// 649 | /// Rather than dereferencing pointers to allocated GC objects directly, you 650 | /// must use one of two types ([`Gc`][crate::Gc] or [`Root`][crate::Root]) 651 | /// to index into the `Heap` to access the referenced `T` object. This enables 652 | /// `safe-gc`'s lack of `unsafe` code and allows the implementation to follow 653 | /// Rust's ownership and borrowing discipline. 654 | /// 655 | /// Given a [`Gc`][crate::Gc] or [`Root`][crate::Root], you can use the 656 | /// [`Heap::get`] method to get an `&T`. Similarly, you can use the 657 | /// [`Heap::get_mut`] method to get an `&mut T`. As convenient syntactic sugar, 658 | /// `Heap` also implements [`std::ops::Index`] and [`std::ops::IndexMut`] as 659 | /// aliases for `get` and `get_mut` respectively. 660 | /// 661 | /// The [`Gc`][crate::Gc] index type is an unrooted reference, suitable for 662 | /// defining references to other GC-managed types within a GC-managed type's 663 | /// definition. 664 | /// 665 | /// The [`Root`][crate::Root] index type is a rooted reference, prevents its 666 | /// referent from being reclaimed during garbage collections, and is suitable 667 | /// for holding GC-managed objects alive from outside of the `Heap`. 668 | /// 669 | /// See the docs for [`Gc`][crate::Gc] and [`Root`][crate::Root] for more 670 | /// details, how to convert between them, and other examples. 671 | /// 672 | /// ``` 673 | /// use safe_gc::{Heap, Trace}; 674 | /// 675 | /// struct Point(u32, u32); 676 | /// 677 | /// impl Trace for Point { 678 | /// // ... 679 | /// # fn trace(&self, _: &mut safe_gc::Collector) {} 680 | /// } 681 | /// 682 | /// let mut heap = Heap::new(); 683 | /// 684 | /// let p = heap.alloc(Point(42, 36)); 685 | /// 686 | /// // Read data from an object in the heap. 687 | /// let p0 = heap[&p].0; 688 | /// assert_eq!(p0, 42); 689 | /// 690 | /// // Write data to an object in the heap. 691 | /// heap[&p].1 = 5; 692 | /// ``` 693 | /// 694 | /// # When Can Garbage Collections Happen? 695 | /// 696 | /// There are two ways to trigger garbage collection: 697 | /// 698 | /// 1. When the [`Heap::gc`] method is explicitly invoked. 699 | /// 700 | /// 2. When allocating an object in the heap with [`Heap::alloc`]. 701 | /// 702 | /// Note that both of those methods require an `&mut self`, so they cannot be 703 | /// invoked when there are shared `&Heap` borrows. Therefore, when there are 704 | /// shared `&Heap` borrows, you may freely use [`Gc`][crate::Gc] instead of 705 | /// [`Root`][crate::Root] to hold references to objects in the heap without 706 | /// fear of the GC collecting the objects out from under your feet. Traversing 707 | /// deeply nested structures will have more overhead when using 708 | /// [`Root`][crate::Root] than when using [`Gc`][crate::Gc] because 709 | /// [`Root`][crate::Root] must maintain its associated entry in the heap's 710 | /// root set. 711 | /// 712 | /// # Finalization and `Drop` 713 | /// 714 | /// Unlike many GC libraries for Rust, it is perfectly safe and footgun-free to 715 | /// put `Drop` types in the GC heap, even if they have references to other GC 716 | /// objects. Finalization of and `Drop` implementations for GC objects is 717 | /// usually tricky because of the risks of accessing objects that have already 718 | /// been reclaimed by the collector or accidentally entrenching objects and 719 | /// making them live again. 720 | /// 721 | /// The reason this is fine with `safe-gc` is because the `Drop` implementation 722 | /// doesn't have access to a `Heap`, so it statically *cannot* suffer from those 723 | /// kinds of bugs. 724 | /// 725 | /// # Cross-`Heap` GC References 726 | /// 727 | /// Typically, GC objects will only reference other GC objects that are within 728 | /// the same `Heap`. In fact, edges reported to the [`Collector`] *must* point 729 | /// to objects within the same `Heap` as the object being traced or else 730 | /// [`Collector::edge`] will raise a panic. 731 | /// 732 | /// However, you can create cross-heap edges with [`Root`][crate::Root], but 733 | /// it requires some care. While a [`Root`][crate::Root] pointing to an 734 | /// object in the same `Heap` will make all transitively referenced objects 735 | /// unreclaimable, a [`Root`][crate::Root] pointing to an object in another 736 | /// heap will not indefinitely leak memory, provided you do not create *cycles* 737 | /// across `Heap`s. To fully collect all garbage across all `Heap`s, you will 738 | /// need to run GC on each `Heap` either 739 | /// 740 | /// * in topological order of cross-`Heap` edges, if you statically know that 741 | /// order in your application, or 742 | /// 743 | /// * in a fixed point loop, if you do not statically know that order. 744 | /// 745 | /// However, if you don't statically know that topological order, it is 746 | /// recommended that you don't create cross-`Heap` edges; you will likely 747 | /// accidentally create cycles and leak memory. Instead, simply put everything 748 | /// in the same `Heap`. 749 | /// 750 | /// Collecting cycles across `Heap`s would require a global, cross-`Heap` 751 | /// collector, and is not a goal of this crate. If you do choose to create 752 | /// cross-`Heap` references, the responsibility of avoiding cross-`Heap` cycles 753 | /// is yours. 754 | pub struct Heap { 755 | // The unique ID for this heap. Used to ensure that `Gc`s are only used 756 | // with their associated arena. Could use branded lifetimes to avoid these 757 | // IDs and checks statically, but the API is gross and pushes lifetimes into 758 | // everything. 759 | id: u32, 760 | 761 | // A map from `type_id(T)` to `Arena`. 762 | arenas: HashMap>, 763 | 764 | collector: Collector, 765 | } 766 | 767 | impl Default for Heap { 768 | fn default() -> Self { 769 | Heap::new() 770 | } 771 | } 772 | 773 | impl std::ops::Index> for Heap 774 | where 775 | T: Trace, 776 | { 777 | type Output = T; 778 | fn index(&self, root: Root) -> &Self::Output { 779 | &self[root.unrooted()] 780 | } 781 | } 782 | 783 | impl std::ops::IndexMut> for Heap 784 | where 785 | T: Trace, 786 | { 787 | fn index_mut(&mut self, root: Root) -> &mut Self::Output { 788 | &mut self[root.unrooted()] 789 | } 790 | } 791 | 792 | impl<'a, T> std::ops::Index<&'a Root> for Heap 793 | where 794 | T: Trace, 795 | { 796 | type Output = T; 797 | fn index(&self, root: &'a Root) -> &Self::Output { 798 | &self[root.unrooted()] 799 | } 800 | } 801 | 802 | impl<'a, T> std::ops::IndexMut<&'a Root> for Heap 803 | where 804 | T: Trace, 805 | { 806 | fn index_mut(&mut self, root: &'a Root) -> &mut Self::Output { 807 | &mut self[root.unrooted()] 808 | } 809 | } 810 | 811 | impl std::ops::Index> for Heap 812 | where 813 | T: Trace, 814 | { 815 | type Output = T; 816 | fn index(&self, index: Gc) -> &Self::Output { 817 | self.get(index) 818 | } 819 | } 820 | 821 | impl std::ops::IndexMut> for Heap 822 | where 823 | T: Trace, 824 | { 825 | fn index_mut(&mut self, gc: Gc) -> &mut Self::Output { 826 | self.get_mut(gc) 827 | } 828 | } 829 | 830 | impl Heap { 831 | /// Construct a new `Heap`. 832 | /// 833 | /// # Example 834 | /// 835 | /// ``` 836 | /// use safe_gc::Heap; 837 | /// 838 | /// let heap = Heap::new(); 839 | /// ``` 840 | #[inline] 841 | pub fn new() -> Self { 842 | let id = Self::next_id(); 843 | Self { 844 | id, 845 | arenas: HashMap::default(), 846 | collector: Collector::new(id), 847 | } 848 | } 849 | 850 | #[inline] 851 | fn next_id() -> u32 { 852 | static ID_COUNTER: atomic::AtomicU32 = atomic::AtomicU32::new(0); 853 | ID_COUNTER.fetch_add(1, atomic::Ordering::AcqRel) 854 | } 855 | 856 | #[inline] 857 | fn arena(&self) -> Option<&Arena> 858 | where 859 | T: Trace, 860 | { 861 | let arena = self.arenas.get(&TypeId::of::())?; 862 | Some(arena.as_any().downcast_ref().unwrap()) 863 | } 864 | 865 | #[inline] 866 | fn arena_mut(&mut self) -> Option<&mut Arena> 867 | where 868 | T: Trace, 869 | { 870 | let arena = self.arenas.get_mut(&TypeId::of::())?; 871 | Some(arena.as_any_mut().downcast_mut().unwrap()) 872 | } 873 | 874 | #[inline] 875 | fn ensure_arena(&mut self) -> &mut Arena 876 | where 877 | T: Trace, 878 | { 879 | self.arenas 880 | .entry(TypeId::of::()) 881 | .or_insert_with(|| Box::new(Arena::::default()) as _) 882 | .as_any_mut() 883 | .downcast_mut() 884 | .unwrap() 885 | } 886 | 887 | /// Allocate an object in the heap. 888 | /// 889 | /// # Example 890 | /// 891 | /// ``` 892 | /// use safe_gc::{Gc, Heap, Trace}; 893 | /// 894 | /// struct List { 895 | /// value: u32, 896 | /// prev: Option>, 897 | /// next: Option>, 898 | /// } 899 | /// 900 | /// impl Trace for List { 901 | /// // See the docs for `Trace` for details... 902 | /// # fn trace(&self, _: &mut safe_gc::Collector) {} 903 | /// } 904 | /// 905 | /// let mut heap = Heap::new(); 906 | /// 907 | /// // Allocate an object in the heap. 908 | /// let list = heap.alloc(List { 909 | /// value: 10, 910 | /// prev: None, 911 | /// next: None, 912 | /// }); 913 | /// ``` 914 | #[inline] 915 | pub fn alloc(&mut self, value: T) -> Root 916 | where 917 | T: Trace, 918 | { 919 | let heap_id = self.id; 920 | let arena = self.ensure_arena::(); 921 | match arena.try_alloc(heap_id, value) { 922 | Ok(root) => root, 923 | Err(value) => self.alloc_slow(value), 924 | } 925 | } 926 | 927 | #[inline(never)] 928 | fn alloc_slow(&mut self, value: T) -> Root 929 | where 930 | T: Trace, 931 | { 932 | // TODO: need to temporarily root `value` across this GC so that its 933 | // edges don't get collected. 934 | self.gc(); 935 | let heap_id = self.id; 936 | let arena = self.ensure_arena::(); 937 | arena.alloc_slow(heap_id, value) 938 | } 939 | 940 | /// Get a shared reference to an allocated object in the heap. 941 | /// 942 | /// You can also use [`std::ops::Index`] to access objects in the heap. 943 | /// 944 | /// # Example 945 | /// 946 | /// ``` 947 | /// use safe_gc::{Gc, Heap, Trace}; 948 | /// 949 | /// struct List { 950 | /// value: u32, 951 | /// prev: Option>, 952 | /// next: Option>, 953 | /// } 954 | /// 955 | /// impl Trace for List { 956 | /// // See the docs for `Trace` for details... 957 | /// # fn trace(&self, _: &mut safe_gc::Collector) {} 958 | /// } 959 | /// 960 | /// let mut heap = Heap::new(); 961 | /// 962 | /// // Allocate an object in the heap. 963 | /// let list = heap.alloc(List { 964 | /// value: 10, 965 | /// prev: None, 966 | /// next: None, 967 | /// }); 968 | /// 969 | /// // Access an allocated object in the heap. 970 | /// let value = heap.get(list).value; 971 | /// assert_eq!(value, 10); 972 | /// ``` 973 | #[inline] 974 | pub fn get(&self, gc: impl Into>) -> &T 975 | where 976 | T: Trace, 977 | { 978 | let gc = gc.into(); 979 | assert_eq!(self.id, gc.heap_id); 980 | let arena = self.arena::().unwrap(); 981 | arena.elements.get(gc.index) 982 | } 983 | 984 | /// Get a shared reference to an allocated object in the heap. 985 | /// 986 | /// You can also use [`std::ops::Index`] to access objects in the heap. 987 | /// 988 | /// # Example 989 | /// 990 | /// ``` 991 | /// use safe_gc::{Gc, Heap, Trace}; 992 | /// 993 | /// struct List { 994 | /// value: u32, 995 | /// prev: Option>, 996 | /// next: Option>, 997 | /// } 998 | /// 999 | /// impl Trace for List { 1000 | /// // See the docs for `Trace` for details... 1001 | /// # fn trace(&self, _: &mut safe_gc::Collector) {} 1002 | /// } 1003 | /// 1004 | /// let mut heap = Heap::new(); 1005 | /// 1006 | /// // Allocate an object in the heap. 1007 | /// let list = heap.alloc(List { 1008 | /// value: 10, 1009 | /// prev: None, 1010 | /// next: None, 1011 | /// }); 1012 | /// 1013 | /// // Mutate an allocated object in the heap. 1014 | /// heap.get_mut(&list).value += 1; 1015 | /// assert_eq!(heap[list].value, 11); 1016 | /// ``` 1017 | #[inline] 1018 | pub fn get_mut(&mut self, gc: impl Into>) -> &mut T 1019 | where 1020 | T: Trace, 1021 | { 1022 | let gc = gc.into(); 1023 | assert_eq!(self.id, gc.heap_id); 1024 | let arena = self.arena_mut::().unwrap(); 1025 | arena.elements.get_mut(gc.index) 1026 | } 1027 | 1028 | /// Root a reference to a GC object. 1029 | /// 1030 | /// This method upgrades a [`Gc`][crate::Gc] into a 1031 | /// [`Root`][crate::Root]. This is useful for holding references to 1032 | /// GC-managed objects across operations that can potentially trigger 1033 | /// garbage collections, making sure that the collector doesn't reclaim them 1034 | /// from under your feed. 1035 | /// 1036 | /// # Example 1037 | /// 1038 | /// ``` 1039 | /// use safe_gc::{Gc, Heap, Root, Trace}; 1040 | /// 1041 | /// struct Node { 1042 | /// value: u32, 1043 | /// tail: Option>, 1044 | /// } 1045 | /// 1046 | /// impl Trace for Node { 1047 | /// // See the docs for `Trace` for details... 1048 | /// # fn trace(&self, _: &mut safe_gc::Collector) {} 1049 | /// } 1050 | /// 1051 | /// let mut heap = Heap::new(); 1052 | /// 1053 | /// let a = heap.alloc(Node { value: 0, tail: None }); 1054 | /// let b = heap.alloc(Node { value: 1, tail: Some(a.into()) }); 1055 | /// 1056 | /// // Get a reference to a `Gc` from the heap. 1057 | /// let b_tail: Gc = heap[b].tail.unwrap(); 1058 | /// 1059 | /// // Upgrade the `Gc` to a `Root` via the `Heap::root` method so it is 1060 | /// // suitable for holding across garbage collections. 1061 | /// let b_tail: Root = heap.root(b_tail); 1062 | /// 1063 | /// // Now we can perform operations, like heap allocation, that might GC 1064 | /// // without worrying about `b_tail`'s referenced GC object from being 1065 | /// // collected out from under us. 1066 | /// heap.alloc(Node { value: 123, tail: None }); 1067 | /// 1068 | /// // And we can access `b_tail`'s referenced GC object again, after GC may 1069 | /// // have happened. 1070 | /// let b_tail_value = heap[&b_tail].value; 1071 | /// assert_eq!(b_tail_value, 0); 1072 | /// ``` 1073 | #[inline] 1074 | pub fn root(&self, gc: Gc) -> Root 1075 | where 1076 | T: Trace, 1077 | { 1078 | assert_eq!(self.id, gc.heap_id); 1079 | let arena = self.arena::().unwrap(); 1080 | arena.root(gc) 1081 | } 1082 | 1083 | /// Collect garbage. 1084 | /// 1085 | /// Any object in the heap that is not transitively referenced by a 1086 | /// [`Root`][crate::Root] will be reclaimed. 1087 | /// 1088 | /// # Example 1089 | /// 1090 | /// ``` 1091 | /// use safe_gc::Heap; 1092 | /// 1093 | /// let mut heap = Heap::new(); 1094 | /// 1095 | /// # let allocate_a_bunch_of_stuff = |_| {}; 1096 | /// allocate_a_bunch_of_stuff(&mut heap); 1097 | /// 1098 | /// // Collect garbage! 1099 | /// heap.gc(); 1100 | /// ``` 1101 | #[inline(never)] 1102 | pub fn gc(&mut self) { 1103 | debug_assert!(self.collector.mark_stacks.values().all(|s| s.is_empty())); 1104 | 1105 | // Reset/pre-allocate the mark bits. 1106 | for (ty, arena) in &self.arenas { 1107 | self.collector 1108 | .mark_bits 1109 | .entry(*ty) 1110 | .or_default() 1111 | .reset(arena.capacity()); 1112 | } 1113 | 1114 | // Mark all roots. 1115 | for arena in self.arenas.values() { 1116 | arena.trace_roots(&mut self.collector); 1117 | } 1118 | 1119 | // Mark everything transitively reachable from the roots. 1120 | // 1121 | // NB: We have a two-level fixed-point loop to avoid checking if every 1122 | // mark stack is non-empty on every iteration of the hottest, inner-most 1123 | // loop. 1124 | while let Some(type_id) = self.collector.next_non_empty_mark_stack() { 1125 | while let Some(index) = self.collector.pop_mark_stack(type_id) { 1126 | self.arenas 1127 | .get_mut(&type_id) 1128 | .unwrap() 1129 | .trace_one(index, &mut self.collector); 1130 | } 1131 | } 1132 | 1133 | // Sweep. 1134 | for (ty, arena) in &mut self.arenas { 1135 | let mark_bits = &self.collector.mark_bits[ty]; 1136 | arena.sweep(mark_bits); 1137 | } 1138 | } 1139 | } 1140 | 1141 | #[cfg(test)] 1142 | mod tests { 1143 | use super::*; 1144 | 1145 | #[test] 1146 | fn object_safety() { 1147 | fn _trace(_: &dyn Trace) {} 1148 | fn _arena_object(_: &dyn ArenaObject) {} 1149 | } 1150 | } 1151 | -------------------------------------------------------------------------------- /src/mark_bits.rs: -------------------------------------------------------------------------------- 1 | //! Simple bit set implementation for marking. 2 | 3 | #[derive(Default)] 4 | pub struct MarkBits(Vec); 5 | 6 | impl MarkBits { 7 | /// Reset these mark bits, and ensure there are enough bits for the given 8 | /// capacity. 9 | pub fn reset(&mut self, capacity: usize) { 10 | self.0.clear(); 11 | self.0.resize(capacity, 0); 12 | } 13 | 14 | /// Get the mark bit for the given index. 15 | /// 16 | /// Panics if the index is out of bounds. 17 | pub fn get(&self, index: u32) -> bool { 18 | let index = usize::try_from(index).unwrap(); 19 | let byte_index = index / 8; 20 | let bit_index = index % 8; 21 | let mask = 1 << bit_index; 22 | self.0[byte_index] & mask != 0 23 | } 24 | 25 | /// Sets the mark bit for the given index, and returns the old mark bit 26 | /// state. 27 | /// 28 | /// Panics if the index is out of bounds. 29 | pub fn set(&mut self, index: u32) -> bool { 30 | let index = usize::try_from(index).unwrap(); 31 | let byte_index = index / 8; 32 | let bit_index = index % 8; 33 | let mask = 1 << bit_index; 34 | let old_byte = self.0[byte_index]; 35 | self.0[byte_index] = old_byte | mask; 36 | old_byte & mask != 0 37 | } 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use super::*; 43 | 44 | #[test] 45 | fn mark_bits() { 46 | let mut mark_bits = MarkBits::default(); 47 | 48 | for i in 0..32 { 49 | mark_bits.reset(32); 50 | 51 | for j in 0..32 { 52 | assert_eq!(mark_bits.get(j), false); 53 | } 54 | 55 | let was_already_set = mark_bits.set(i); 56 | assert!(!was_already_set); 57 | let was_already_set = mark_bits.set(i); 58 | assert!(was_already_set); 59 | 60 | if i > 0 { 61 | let was_already_set = mark_bits.set(i - 1); 62 | assert!(!was_already_set); 63 | let was_already_set = mark_bits.set(i - 1); 64 | assert!(was_already_set); 65 | assert!(mark_bits.get(i)); 66 | } 67 | 68 | if i < 31 { 69 | let was_already_set = mark_bits.set(i + 1); 70 | assert!(!was_already_set); 71 | let was_already_set = mark_bits.set(i + 1); 72 | assert!(was_already_set); 73 | assert!(mark_bits.get(i)); 74 | } 75 | } 76 | } 77 | } 78 | --------------------------------------------------------------------------------