├── .gitignore
├── .travis.yml
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── TODO.md
├── doc
├── Implementation-Notes.md
└── Project-RFC.md
├── examples
├── balloon_animals.rs
├── correctnesstest.rs
├── low_allocation_rate.rs
└── small_objects_stress.rs
└── src
├── appthread.rs
├── constants.rs
├── gcthread.rs
├── heap.rs
├── journal.rs
├── lib.rs
├── parheap.rs
├── statistics.rs
├── trace.rs
└── youngheap.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | Cargo.lock
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: rust
4 | rust: nightly
5 |
6 | after_success: |
7 | [ $TRAVIS_BRANCH = master ] &&
8 | [ $TRAVIS_PULL_REQUEST = false ] &&
9 | cargo doc &&
10 | echo '' > target/doc/index.html &&
11 | pip install --user ghp-import &&
12 | ghp-import -n target/doc &&
13 | git push -qf https://${TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
14 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "mo-gc"
3 | description = "MO, a pauseless, concurrent, generational, parallel mark-and-sweep garbage collector"
4 | keywords = ["mo", "gc", "garbage", "collector"]
5 | homepage = "https://github.com/pliniker/mo-gc"
6 | repository = "https://github.com/pliniker/mo-gc"
7 | documentation = "https://crates.fyi/crates/mo-gc"
8 | version = "0.1.0"
9 | license = "MIT/Apache-2.0"
10 | authors = ["Peter Liniker "]
11 |
12 | [dependencies]
13 | bitmaptrie = { git = "https://github.com/pliniker/bitmaptrie-rs" }
14 | scoped-pool = "0.1"
15 | num_cpus = "0.2"
16 | time = "0.1"
17 |
18 | [dev-dependencies]
19 | stopwatch = "0.0.6"
20 |
--------------------------------------------------------------------------------
/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 |
203 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Peter Liniker
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 | ## An experimental garbage collector in Rust
2 |
3 | This is a very experimental garbage collector primarily built to research the viability of a
4 | write barrier mechanism that does not depend on compiler GC support.
5 |
6 |
7 | * [](https://travis-ci.org/pliniker/mo-gc)
8 |
9 | ### Further information
10 |
11 | Please read the [Introduction to mo-gc](http://pliniker.github.io/mo-gc-intro.html) first.
12 |
13 | * [Ideas](http://pliniker.github.io/mo-gc-ideas.html) expands on the further direction in the introduction.
14 | * [API Documentation](https://pliniker.github.io/mo-gc/), but also see the examples.
15 | * [Implementation Notes](https://github.com/pliniker/mo-gc/blob/master/doc/Implementation-Notes.md)
16 | * [Original draft design outline](https://github.com/pliniker/mo-gc/blob/master/doc/Project-RFC.md)
17 | * [Original discussion issue](https://github.com/pliniker/mo-gc/issues/1) on the original design.
18 |
19 | ### See also
20 |
21 | * [rust-gc](https://github.com/manishearth/rust-gc)
22 | * [crossbeam](https://github.com/aturon/crossbeam/)
23 | * [bacon-rajan-cc](https://github.com/fitzgen/bacon-rajan-cc)
24 |
25 | ### About this Project
26 |
27 | * Copyright © 2015 Peter Liniker
28 | * Licensed under dual MIT/Apache-2.0
29 | * Named after [M-O](http://pixar.wikia.com/wiki/M-O).
30 |
31 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # Testing
2 |
3 | * integration tests
4 | * benchmarks
5 |
6 | # Examples
7 |
8 | * build some data structures, esp concurrent data structures
9 | * see crossbeam for treiber stack example
10 |
11 | # Issues
12 |
13 | ## Race condition
14 |
15 | There is currently a race condition where a pointer is read from the heap, rooted and then that
16 | pointer value on the heap is overwritten during the mark/sweep phase of collection. The
17 | rooting should ensure that the referenced object is marked, but the journal is not being
18 | read at this point and the reference count increment is too late to stop the object from being
19 | swept.
20 |
21 | This race condition means that the mutator threads cannot currently use this GC as fully general
22 | purpose, or rather that data structures must be persistent.
23 |
24 | The sequence of events causing the race condition is:
25 |
26 | * GC stops reading journal, enters mark phase
27 | * mutator reads pointer to object A from heap, roots A, writing to journal
28 | * mutator overwrites pointer on heap with new object B reference
29 | * GC traces heap, marking new object B but not previously referenced object A
30 | * GC sweeps, dropping A even though A was rooted
31 |
32 | The benefit of fixing this issue is that this GC design becomes general purpose.
33 |
34 | ### Additional write barrier
35 |
36 | This race condition might be avoided by an additional synchronous write barrier: if a pointer A
37 | on the heap is going to be replaced by pointer B, the object A might be marked as "pinned"
38 | to prevent the sweep phase from dropping it. The sweep phase would unpin the object, after
39 | which if it has been rooted, the reference count increment will be picked up from the journal
40 | before the next mark phase.
41 |
42 | This solution has the downside of adding a word to the size of every object,
43 | the cost of an atomic store on the app-thread side and the cost of an atomic load and store
44 | on the sweep phase. It would also make programs that use this GC less fork-friendly, as
45 | pinning objects would incur copy-on-write costs for memory pages that might otherwise remain
46 | read-only.
47 |
48 | Question: just how atomic would the pinning operation need to be? It only needs to take effect
49 | during the mark phase but the pin flag would need to be readable by the sweep phase.
50 |
51 | Experimentation will determine if this mechanism is worth the cost. There may be alternative
52 | implementation options that are more efficient: perhaps using a shared data structure to
53 | write pinned object pointers to that is consumed by a phase between mark and sweep that
54 | sets the marked flag on those objects?
55 |
56 | ### Use the journal
57 |
58 | The journal contains the rooting information needed to avoid this problem. Another possible
59 | solution may be to read the journal in the mark phase, _after_ marking any new roots, before
60 | moving on to the sweep phase.
61 |
62 | This needs further thought.
63 |
64 | ## Performance Bottlenecks
65 |
66 | ### Journal processing
67 |
68 | `Trie::set()` is the bottleneck in `YoungHeap::read_journals()`. This is a single-threaded
69 | function and consumes most of the GC linear time. It is the single greatest throughput limiter.
70 | If insertion into `bitmaptrie::Trie` could be parallelized, throughput would improve.
71 |
72 | One option is to process each mutator journal on a separate thread but defer new-object
73 | insertion to a single thread. This way some parallelism is gained for processing reference
74 | count increments. This is still not optimal though.
75 |
76 | ### The Allocator
77 |
78 | Building on the generic allocator: jemalloc maintains a radix trie for allocation so there
79 | are two tries, increasing CPU and memory requirements. A custom allocator would
80 | solve this problem, but would introduce the problem of writing a scalable, fragmentation-
81 | minimizing allocator.
82 |
83 | ## Collection Scheduling
84 |
85 | This is currently very simple and has not been tuned at all.
86 | See `gcthread::gc_thread()` and `constants::*` for tuning.
87 |
--------------------------------------------------------------------------------
/doc/Implementation-Notes.md:
--------------------------------------------------------------------------------
1 |
2 | * Date: 2016-03-13
3 |
4 | # Implementation Notes
5 |
6 | The current implementation has been tested on x86 and x86_64. It has not bee tested on
7 | ARM, though the ARM weaker memory model may highlight some flaws.
8 |
9 | ## The journal
10 |
11 | The journal is designed to never block the mutator. Each mutator thread allocates a buffer to
12 | write reference count adjustments to. When the buffer is full, a new buffer is allocated.
13 | The GC thread consumes the buffers. Thus the journal behaves like an infinitely sized
14 | SPSC queue. Each mutator gets its own journal.
15 |
16 | The values written by the mutator to a buffer are essentially `TraitObject`s that describe
17 | a pointer to an object and the `Trace` trait virtual table. The virtual table pointer is
18 | required to provide the `drop()` and `Trace::trace()` methods, as the GC thread does not
19 | know concrete types at runtime.
20 |
21 | Because heap allocations are word aligned, a pointer's two least significant bits can be used
22 | as bit flags.
23 |
24 | The object address has four possible values in it's LSBs:
25 |
26 | * 0: reference count decrement
27 | * 1: reference count increment
28 | * 2: new object allocated, no reference count adjustment
29 | * 3: new object allocated, reference count increment
30 |
31 | The object vtable has one flag value that can be set:
32 |
33 | * 2: the object is a container of other GC-managed objects and must be traced. This flag saves
34 | the mark phase from making virtual function calls for scalar objects.
35 |
36 | ### Advantages
37 |
38 | The mutator thread will never be blocked on writing to the journal unless the application hits
39 | out-of-memory, thus providing a basic pauselessness guarantee.
40 |
41 | The journal is very fast, not requiring atomics on the x86/64 TSO-memory-model architecture.
42 |
43 | ### Disadvantages
44 |
45 | If the GC thread cannot keep up with the mutator(s), the journal will continue to allocate
46 | new buffers faster than the GC thread can consume them, contributing to the OOM death march.
47 |
48 | ## Young generation heap and root reference counts
49 |
50 | A young-generation heap map is implemented using a bitmapped vector trie, whose indeces are
51 | word-sized: keys are object addresses, values are a composition of root reference count, the object
52 | vtable and a word for flags for marking and sweeping.
53 |
54 | The addresses used as keys are right-shifted to eliminate the least significant bits that are
55 | always zero because heap allocations are word aligned.
56 |
57 | The flags set on the object address have been processed at this point and the heap updated
58 | accordingly. Reference count decrements are written to a deferred buffer for processing later.
59 |
60 | For new objects, the heap map flags for the object are marked as `NEW`. These are the young
61 | generation objects. Other entries in the map not marked as `NEW` are stack roots only.
62 |
63 | Thus the young generation heap map combines pure stack-root references and new object references.
64 |
65 | A typical generational GC keeps a data structure such a as a card table to discover pointers from
66 | the mature object heap into the young generation heap. Write barriers are required to update the
67 | card table when mature objects are written to. In our case, the non-`NEW` stack-root
68 | references act as the set of mature objects that may have references to young generation objects.
69 | Essentially, the journal is a type of write barrier.
70 |
71 | When the young generation heap enters a mark phase, all objects that have a non-zero reference
72 | count are considered potential roots. Only `NEW` objects are considered during sweeping.
73 |
74 | Both marking and sweeping are done in parallel: during the mark phase, the heap map is sharded across
75 | multiple threads for scanning for roots while each thread can look up entries in the whole map for
76 | marking; during the sweep phase, the heap map is sharded across multiple threads for sweeping.
77 |
78 | ### Advantages
79 |
80 | This combined roots and new-objects map makes for a straightforwardly parallelizable mark and
81 | sweep implementation. The trie can be sharded into sub-tries and each sub-trie can be processed
82 | independently and mutated independently of the others while remaining thread safe without
83 | requiring locks or atomic access.
84 |
85 | ### Disadvantages
86 |
87 | Inserting into the trie is currently not parallelizable, making reading the journal into the trie
88 | a single-threaded affair, impacting GC throughput.
89 |
90 | On high rates of new object allocation, the GC thread currently cannot keep up with the
91 | mutators rate of writing to the journal. The cause of this is not the journal itself: reading
92 | and writing the journal can be done very fast. However, inserting and updating the heap map
93 | causes the GC thread to process the journal at half the rate at which a single mutator thread
94 | can allocate new objects.
95 |
96 | If journal processing (trie insertion) can be parallelized, the GC throughput will hugely improve.
97 |
98 | One part-way step may be to parallelize reference count updates while still processing new
99 | objects in sequence.
100 |
101 | ## The mature object heap
102 |
103 | This heap map is similar to the young generation heap but does not consider reference counts
104 | or new objects. Marking and sweeping is parallelized similarly.
105 |
106 | A mature heap collection is triggered when the young generation heap reaches a threshold count of
107 | `NEW` objects that it is managing. `NEW` object data is copied to the mature heap trie and
108 | the original entries in the young generation are unmarked as `NEW`. They become plain stack
109 | root entries.
110 |
--------------------------------------------------------------------------------
/doc/Project-RFC.md:
--------------------------------------------------------------------------------
1 |
2 | * Date: 2015-08-24
3 | * Discussion issue: [pliniker/mo-gc#1](https://github.com/pliniker/mo-gc/issues/1)
4 |
5 | # Summary
6 |
7 | Mutator threads maintain precise-rooted GC-managed objects through smart
8 | pointers on the stack that write reference-count increments and decrements to a
9 | journal.
10 |
11 | The reference-count journal is read by a GC thread that
12 | maintains the actual reference count numbers in a cache of roots. When a
13 | reference count reaches zero, the GC thread moves the pointer to a heap cache
14 | data structure that is used by a tracing collector.
15 |
16 | Because the GC thread runs concurrently with the mutator threads without
17 | stopping them to scan stacks or trace, all GC-managed data structures that refer to
18 | other GC-managed objects must provide a safe concurrent trace function.
19 |
20 | Data structures' trace functions can implement any transactional
21 | mechanism that provides the GC a snapshot of the data structure's
22 | nested pointers for the duration of the trace function call.
23 |
24 | # Why
25 |
26 | Many languages and runtimes are hosted in the inherently unsafe languages
27 | C and/or C++, from Python to GHC.
28 |
29 | My interest in this project is in building a foundation, written in Rust, for
30 | language runtimes on top of Rust. Since Rust is a modern
31 | language for expressing low-level interactions with hardware, it is an
32 | ideal alternative to C/C++ while providing the opportunity to avoid classes
33 | of bugs common to C/C++ by default.
34 |
35 | With the brilliant, notable exception of Rust, a garbage collector is an
36 | essential luxury for most styles of programming. But how memory is managed in
37 | a language can be an asset or a liability that becomes so intertwined with
38 | the language semantics itself that it can become a huge undertaking to
39 | modernize years later.
40 |
41 | With that in mind, this GC is designed from the ground up to be concurrent
42 | and never stop the world. The caveat is that data structures
43 | need to be designed for concurrent reads and writes. In this world,
44 | the GC is just another thread, reading data structures and freeing any that
45 | are no longer live.
46 |
47 | That seems a reasonable tradeoff in a time when scaling out by adding
48 | processors rather than up through increased clock speed is now the status quo.
49 |
50 | # What this is not
51 |
52 | This is not particularly intended to be a general purpose GC, providing
53 | a near drop-in replacement for `Rc`, though it may be possible.
54 | For that, I recommend looking at
55 | [rust-gc](https://github.com/manishearth/rust-gc) or
56 | [bacon-rajan-cc](https://github.com/fitzgen/bacon-rajan-cc).
57 |
58 | This is also not primarily intended to be an ergonomic, native GC for all
59 | concurrent data structures in Rust. For that, I recommend a first look at
60 | [crossbeam](https://github.com/aturon/crossbeam/).
61 |
62 | # Assumptions
63 |
64 | This RFC assumes the use of the default Rust allocator, jemalloc, throughout
65 | the GC. No custom allocator is described here at this time. Correspondingly,
66 | the performance characteristics of jemalloc should be assumed.
67 |
68 | # Journal Implementation
69 |
70 | ## Mutator Threads
71 |
72 | The purpose of using a journal is to minimize the burden on the mutator
73 | threads as much as possible, pushing as much workload as possible over to the
74 | GC thread, while avoiding pauses if that is possible.
75 |
76 | In the most straightforward implementation, the journal can simply be a
77 | MPSC channel shared between mutator threads and sending
78 | reference count adjustments to the GC thread, that is, +1 and -1 for pointer
79 | clone and drop respectively.
80 |
81 | Performance for multiple mutator threads writing to an MPSC, with each
82 | write causing an allocation, can be improved on based on the
83 | [single writer principle][9] by 1) giving each mutator thread its own
84 | channel and 2) buffering journal entries and passing a reference to the buffer
85 | through the channel.
86 |
87 | Buffering journal entries should reduce the number of extra allocations per
88 | object created compared with a non-blocking MPSC channel.
89 |
90 | A typical problem of reference counted objects is locality: every reference
91 | count update requires a write to the object itself, making very inefficient
92 | spatial memory access. The journal, being a series of buffers, each
93 | of which is a contiguous block of memory, should give an efficiency gain
94 | for the mutator threads.
95 |
96 | It should be noted that the root smart-pointers shouldn't necessarily
97 | be churning out reference count adjustments. This is Rust: prefer to borrow
98 | a root smart-pointer before cloning it. This is one of the main features that
99 | makes implementing this in Rust so attractive.
100 |
101 | ### Implementation Notes
102 |
103 | When newly rooting a pointer to the stack, the current buffer must be accessed.
104 | One solution is to use Thread Local Storage so that each thread will be able
105 | to access its own buffer at any time. The overhead of looking up the TLS
106 | pointer is a couple of extra instructions in a release build to check that
107 | the buffer data has been initialized
108 |
109 | A journal buffer maintains a count at offset 0 to indicate how many words of
110 | adjustment data have been written. This count might be written to using
111 | [release](https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html) ordering
112 | while the GC thread might read the count using acquire ordering.
113 |
114 | ## Garbage Collection Thread
115 |
116 | In the basic MPSC use case, the GC thread reads reference count adjustments
117 | from the channel. For each inc/dec adjustment, it must look up the
118 | associated pointer in the cache of root pointers and update the total reference
119 | count for that pointer.
120 |
121 | In the case of multiple channels, each sending a buffer of adjustments at a
122 | time, there will naturally be an ordering problem:
123 |
124 | Thread A may, for a pointer, write the following to its journal:
125 |
126 | |Action|adjustment| |
127 | | --- | --- | --- |
128 | |new pointer|+1||
129 | |clone pointer|+1|(move cloned pointer to Thread B)|
130 | |drop pointer|-1||
131 |
132 | Thread B may do the following a brief moment later after receiving the
133 | cloned pointer:
134 |
135 | |Action|adjustment| |
136 | | --- | --- | --- |
137 | |drop pointer|-1|(drop cloned pointer)|
138 |
139 | The order in which these adjustments are processed by the GC thread may well
140 | be out of order, and there is no information available to restore the correct
141 | order. The decrement from Thread B might be processed first, followed by the
142 | first increment from Thread A, giving a momentary reference count of 0. The
143 | collector may kick in at that point, freeing the object and resulting in a
144 | possible use-after-free and possibly a double-free.
145 |
146 | Here, learning from [Bacon2003][1], decrement adjustments should be
147 | buffered by an amount of time sufficient to clear all increment adjustments
148 | that occurred prior to those decrements. An appropriate amount of time might
149 | be provided by scanning the mutator threads'
150 | buffers one further iteration before applying the buffered decrements.
151 |
152 | Increment adjustments can be applied immediately, always.
153 |
154 | # Tracing
155 |
156 | While more advanced or efficient algorithms might be applied here, this section
157 | will describe how two-colour mark and sweep can be applied.
158 |
159 | As in [rust-gc][4], all types participating in GC must implement
160 | a trait that allows that type to be traced. (This is an inconvenience that
161 | a compiler plugin may be able to alleviate for many cases.)
162 |
163 | The GC thread maintains two trie structures: one to map from roots to
164 | reference counts; a second to map from heap objects to any metadata needed to
165 | run `drop()` against them, and bits for marking objects as live.
166 |
167 | The roots trie is traversed, calling the trace function for each. Every visited
168 | object is marked in the heap trie.
169 |
170 | Then the heap trie is traversed and every unmarked entry is `drop()`ped and
171 | the live objects unmarked.
172 |
173 | It is worth noting that by using a separate data structure for the heap and
174 | root caches that this GC scheme remains `fork()` memory friendly: the act
175 | of updating reference counts and marking heap objects does not force a
176 | page copy-on-write for every counted and marked object location.
177 |
178 | # Concurrent Data Structures
179 |
180 | To prevent data races between the mutator threads and the GC thread, all
181 | GC-managed data structures that contain pointers to other GC-managed objects
182 | must be transactional in updates to those relationships. That is, a
183 | `GcRoot>` can contain mutable data where the mutability follows only
184 | the Rust static analysis rules, but a `GcRoot>>` must be
185 | reimplemented additionally with a transactional runtime nature.
186 |
187 | The `Vec::trace()` method has to be able to provide a readonly
188 | snapshot of its contents to the GC thread and atomic updates to its
189 | contents.
190 |
191 | Applying a compile-time distinction between these may be possible using the
192 | type system. Indeed, presenting a safe API is one of the challenges in
193 | implementing this.
194 |
195 | As the `trace()` method is part of the data structure code itself, data
196 | structures should be free to implement any method of atomic update without the
197 | GC code or thread needing to be aware of transactions or their mechanism.
198 |
199 | The `trace()` method may, depending on the data structure characteristics,
200 | opt to return immediately with an "defer" status, meaning that at the time
201 | of calling, it isn't expedient to obtain a readonly snapshot of the data
202 | structure for tracing. In that case, the GC thread will requeue the object
203 | for a later attempt.
204 |
205 | Fortunately, concurrent data structures are fairly widely researched and
206 | in use by 2015 and I will not go into implementation details here.
207 |
208 | # Tradeoffs
209 |
210 | How throughput compares to other GC algorithms is left to
211 | readers more experienced in the field to say. My guess is that with the overhead
212 | of the journal while doing mostly new-generation collections that this
213 | algorithm should be competitive for multiple threads on multiprocessing
214 | machines. The single-threaded case will suffer from the concurrent data
215 | structure overhead.
216 |
217 | Non-atomic objects must be transactional, adding the runtime and complexity
218 | cost associated with concurrent data structures: the garbage generated. In some
219 | circumstances there could be enormous amounts of garbage generated, raising the
220 | overall overhead of using the GC to where the GC thread affects throughput.
221 |
222 | Jemalloc is said to give low fragmentation rates compared to other malloc
223 | implementations, but fragmentation is likely nonetheless.
224 |
225 | At least this one language/compiler safety issue remains: referencing
226 | GC-managed pointers in a `drop()` is currently considered safe by the compiler
227 | as it has no awareness of the GC, but doing so is of course unsafe as the order
228 | of collection is non-deterministic leading to possible use-after-free in custom
229 | `drop()` functions.
230 |
231 | # Rust Library Compatibility
232 |
233 | As the GC takes over the lifetime management of any objects put under its
234 | control - and that transfer of control is completely under the control of
235 | the programmer - any Rust libraries should work with it, including low-level
236 | libraries such as [coroutine-rs](https://github.com/rustcc/coroutine-rs) and
237 | by extension [mioco](https://github.com/dpc/mioco).
238 |
239 | This GC will never interfere with any code that uses only the native Rust
240 | memory management.
241 |
242 | # Improvements
243 |
244 | ## Compiler Plugin
245 |
246 | It is possible to give the compiler some degree of awareness of GC requirements
247 | through custom plugins, as implemented in [rust-gc][4] and [servo][13]. The same
248 | may be applicable here.
249 |
250 | In the future, this implementation would surely benefit from aspects of the
251 | planned [tracing hooks][5].
252 |
253 | ## Generational Optimization
254 |
255 | Since the mutator threads write a journal of all root pointers, all
256 | pointers that the mutator uses will be recorded. It may be possible
257 | for the GC thread to use that fact to process batches of journal changes
258 | in a generational manner, rather than having to trace the entire heap
259 | on every iteration. This needs further investigation.
260 |
261 | ## Parallel Collection
262 |
263 | The tries used in the GC should be amenable to parallelizing tracing which
264 | may be particularly beneficial in conjunction with tracing the whole heap.
265 |
266 | ## Copying Collector
267 |
268 | Any form of copying or moving collector would require a custom allocator and
269 | probably a Baker-style read barrier. The barrier could be implemented on the
270 | root smart pointers with the added expense of the mutator threads having to
271 | check whether the pointer must be updated on every dereference. There are
272 | pitfalls here though as the Rust compiler may optimize dereferences with
273 | pointers taking temporary but hard-to-discover root in CPU registers. It may
274 | be necessary to use the future tracing hooks to discover all roots to avoid
275 | Bad Things happening.
276 |
277 | # Patent Issues
278 |
279 | I have read through the patents granted to IBM and David F. Bacon that cover
280 | reference counting and have come to the conclusion that nothing described here
281 | infringes.
282 |
283 | I have not read further afield though. My assumption has been that there is
284 | prior art for most garbage collection methods at this point.
285 |
286 | # References
287 |
288 | * [Bacon2003][1] Bacon et al, A Pure Reference Counting Garbage Collector
289 | * [Bacon2004][2] Bacon et al, A Unified Theory of Garbage Collection
290 | * [Oxischeme][3] Nick Fitzgerald, Memory Management in Oxischeme
291 | * [Manishearth/rust-gc][4] Manish Goregaokar, rust-gc project
292 | * [Rust blog][5] Rust in 2016
293 | * [rust-lang/rust#11399][6] Add garbage collector to std::gc
294 | * [rust-lang/rfcs#415][7] Garbage collection
295 | * [rust-lang/rust#2997][8] Tracing GC in rust
296 | * [Mechanical Sympathy][9] Martin Thompson, Single Writer Principle
297 | * [michaelwoerister/rs-persistent-datastructures][10] Michael Woerister, HAMT in Rust
298 | * [crossbeam][11] Aaron Turon, Lock-freedom without garbage collection
299 | * [Shenandoah][12] Shenandoah, a low-pause GC for the JVM
300 | * [Servo][13] Servo blog, JavaScript: Servo’s only garbage collector
301 |
302 | [1]: http://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf
303 | [2]: http://www.cs.virginia.edu/~cs415/reading/bacon-garbage.pdf
304 | [3]: http://fitzgeraldnick.com/weblog/60/
305 | [4]: https://github.com/Manishearth/rust-gc
306 | [5]: http://blog.rust-lang.org/2015/08/14/Next-year.html
307 | [6]: https://github.com/rust-lang/rust/pull/11399
308 | [7]: https://github.com/rust-lang/rfcs/issues/415
309 | [8]: https://github.com/rust-lang/rust/issues/2997
310 | [9]: http://mechanical-sympathy.blogspot.co.uk/2011/09/single-writer-principle.html
311 | [10]: https://github.com/michaelwoerister/rs-persistent-datastructures
312 | [11]: http://aturon.github.io/blog/2015/08/27/epoch/
313 | [12]: https://www.youtube.com/watch?v=QcwyKLlmXeY
314 | [13]: https://blog.mozilla.org/research/2014/08/26/javascript-servos-only-garbage-collector/
315 |
--------------------------------------------------------------------------------
/examples/balloon_animals.rs:
--------------------------------------------------------------------------------
1 |
2 |
3 | extern crate mo_gc;
4 | use mo_gc::{Gc, GcRoot, GcThread, StatsLogger, Trace, TraceOps, TraceStack};
5 |
6 |
7 | struct Segment {
8 | next: Gc,
9 | }
10 |
11 |
12 | impl Segment {
13 | fn new() -> Segment {
14 | Segment {
15 | next: Gc::null()
16 | }
17 | }
18 |
19 | fn join_to(&mut self, to: Gc) {
20 | self.next = to;
21 | }
22 | }
23 |
24 |
25 | unsafe impl Trace for Segment {
26 | fn traversible(&self) -> bool {
27 | true
28 | }
29 |
30 | unsafe fn trace(&self, heap: &mut TraceStack) {
31 | if let Some(ptr) = self.next.as_raw() {
32 | heap.push_to_trace(&*ptr);
33 | }
34 | }
35 | }
36 |
37 |
38 | struct Balloon {
39 | head: Gc,
40 | tail: Gc,
41 | }
42 |
43 |
44 | impl Balloon {
45 | fn inflate() -> Balloon {
46 | let body = Gc::new(Segment::new());
47 | Balloon {
48 | head: body,
49 | tail: body,
50 | }
51 | }
52 |
53 | fn twist(&mut self) {
54 | let mut new_seg = Gc::new(Segment::new());
55 | new_seg.join_to(self.head);
56 | self.head = new_seg;
57 | }
58 |
59 | fn complete(&mut self) {
60 | self.tail.next = self.head;
61 | }
62 |
63 | fn count(&mut self) {
64 | let mut count = 0;
65 | let mut current = self.head;
66 |
67 | loop {
68 | current = current.next;
69 | count += 1;
70 |
71 | if current.is(self.tail) {
72 | break;
73 | }
74 | }
75 |
76 | if count != 1000 {
77 | println!("snake is short - only {} segments", count);
78 | }
79 | }
80 | }
81 |
82 |
83 | unsafe impl Trace for Balloon {
84 | fn traversible(&self) -> bool {
85 | true
86 | }
87 |
88 | unsafe fn trace(&self, heap: &mut TraceStack) {
89 | heap.push_to_trace(&*self.head as &Trace);
90 | }
91 | }
92 |
93 |
94 | fn snake() {
95 | // this many snake balloons
96 | for _snake in 0..5000 {
97 | let mut balloon = GcRoot::new(Balloon::inflate());
98 |
99 | // with this many segments each
100 | for _segment in 0..1000 {
101 | balloon.twist();
102 | }
103 |
104 | balloon.complete();
105 | balloon.count();
106 | }
107 | }
108 |
109 |
110 | fn main() {
111 | let gc = GcThread::spawn_gc();
112 |
113 | let snake_handle = gc.spawn(|| snake());
114 |
115 | let logger = gc.join().expect("gc failed");
116 | logger.dump_to_stdout();
117 |
118 | snake_handle.join().expect("snake failed");
119 | }
120 |
--------------------------------------------------------------------------------
/examples/correctnesstest.rs:
--------------------------------------------------------------------------------
1 | #![feature(alloc_system)]
2 | extern crate alloc_system;
3 |
4 |
5 | extern crate mo_gc;
6 |
7 | use mo_gc::{GcThread, GcRoot, StatsLogger, Trace};
8 |
9 |
10 | struct Thing {
11 | value: [usize; 4]
12 | }
13 |
14 |
15 | unsafe impl Trace for Thing {}
16 |
17 |
18 | impl Thing {
19 | fn new() -> Thing {
20 | Thing {
21 | value: [42; 4]
22 | }
23 | }
24 | }
25 |
26 |
27 | impl Drop for Thing {
28 | fn drop(&mut self) {
29 | // any heap corruption might be evident here
30 | assert!(self.value[0] == 42);
31 | assert!(self.value[1] == 42);
32 | assert!(self.value[2] == 42);
33 | assert!(self.value[3] == 42);
34 | }
35 | }
36 |
37 |
38 | fn app() {
39 | for _ in 0..10000000 {
40 | let _new = GcRoot::new(Thing::new());
41 | }
42 | }
43 |
44 |
45 | fn main() {
46 | let gc = GcThread::spawn_gc();
47 |
48 | let app_handle = gc.spawn(|| app());
49 |
50 | let logger = gc.join().expect("gc failed");
51 | logger.dump_to_stdout();
52 |
53 | app_handle.join().expect("app failed");
54 | }
55 |
--------------------------------------------------------------------------------
/examples/low_allocation_rate.rs:
--------------------------------------------------------------------------------
1 |
2 | extern crate stopwatch;
3 | use stopwatch::Stopwatch;
4 |
5 | extern crate mo_gc;
6 |
7 | use std::thread;
8 | use std::time::Duration;
9 |
10 | use mo_gc::{GcThread, GcRoot, Trace, StatsLogger};
11 |
12 |
13 | const THING_SIZE: usize = 8;
14 | const THING_COUNT: i64 = 2500000;
15 |
16 |
17 | struct Thing {
18 | _data: [u64; THING_SIZE],
19 | }
20 |
21 |
22 | impl Thing {
23 | fn new() -> Thing {
24 | Thing { _data: [0; THING_SIZE] }
25 | }
26 | }
27 |
28 |
29 | unsafe impl Trace for Thing {}
30 |
31 |
32 | fn app() {
33 | let sw = Stopwatch::start_new();
34 |
35 | thread::sleep(Duration::from_millis(100));
36 |
37 | for count in 0..THING_COUNT {
38 | let _new = GcRoot::new(Thing::new());
39 |
40 | if count & 0xfff == 0 {
41 | thread::sleep(Duration::from_millis(50));
42 | }
43 | }
44 |
45 | let per_second = (THING_COUNT * 1000) / sw.elapsed_ms();
46 | println!("app allocated {} objects at {} objects per second", THING_COUNT, per_second);
47 | println!("app finished in {}ms", sw.elapsed_ms());
48 | }
49 |
50 |
51 | fn main() {
52 | let gc = GcThread::spawn_gc();
53 |
54 | let app_handle = gc.spawn(|| app());
55 |
56 | let logger = gc.join().expect("gc failed");
57 | logger.dump_to_stdout();
58 |
59 | app_handle.join().expect("app failed");
60 | }
61 |
--------------------------------------------------------------------------------
/examples/small_objects_stress.rs:
--------------------------------------------------------------------------------
1 |
2 | extern crate stopwatch;
3 | use stopwatch::Stopwatch;
4 |
5 | extern crate mo_gc;
6 |
7 | use mo_gc::{GcThread, GcRoot, Trace, StatsLogger};
8 |
9 |
10 | const THING_SIZE: usize = 8;
11 | const THING_COUNT: i64 = 2500000;
12 |
13 |
14 | struct Thing {
15 | _data: [u64; THING_SIZE],
16 | }
17 |
18 |
19 | impl Thing {
20 | fn new() -> Thing {
21 | Thing { _data: [0; THING_SIZE] }
22 | }
23 | }
24 |
25 |
26 | unsafe impl Trace for Thing {}
27 |
28 |
29 | fn app() {
30 | let sw = Stopwatch::start_new();
31 |
32 | for _ in 0..THING_COUNT {
33 | let _new = GcRoot::new(Thing::new());
34 | }
35 |
36 | let per_second = (THING_COUNT * 1000) / sw.elapsed_ms();
37 | println!("app allocated {} objects at {} objects per second", THING_COUNT, per_second);
38 | println!("app finished in {}ms", sw.elapsed_ms());
39 | }
40 |
41 |
42 | fn main() {
43 | let gc = GcThread::spawn_gc();
44 |
45 | let app_handle1 = gc.spawn(|| app());
46 | let app_handle2 = gc.spawn(|| app());
47 |
48 | let logger = gc.join().expect("gc failed");
49 | logger.dump_to_stdout();
50 |
51 | app_handle1.join().expect("app failed");
52 | app_handle2.join().expect("app failed");
53 | }
54 |
--------------------------------------------------------------------------------
/src/appthread.rs:
--------------------------------------------------------------------------------
1 | //! Types for the mutator to use to build data structures
2 |
3 |
4 | use std::cell::Cell;
5 | use std::mem::transmute;
6 | use std::ops::{Deref, DerefMut};
7 | use std::ptr::{null, null_mut};
8 | use std::raw::TraitObject;
9 | use std::sync::atomic::{AtomicPtr, Ordering};
10 | use std::thread;
11 |
12 | use constants::{INC_BIT, JOURNAL_BUFFER_SIZE, NEW_BIT, TRAVERSE_BIT};
13 | use gcthread::{JournalSender, EntrySender};
14 | use heap::{Object, TraceStack};
15 | use journal;
16 | use trace::Trace;
17 |
18 |
19 | /// Each thread gets it's own EntrySender
20 | thread_local!(
21 | static GC_JOURNAL: Cell<*const EntrySender> = Cell::new(null())
22 | );
23 |
24 |
25 | /// GcBox struct and traits: a boxed object that is GC managed
26 | pub struct GcBox {
27 | value: T,
28 | }
29 |
30 |
31 | /// Root smart pointer, sends reference count changes to the journal.
32 | ///
33 | /// Whenever a reference to an object on the heap must be retained on the stack, this type must be
34 | /// used. It's use will ensure that the object will be seen as a root.
35 | pub struct GcRoot {
36 | ptr: *mut GcBox,
37 | }
38 |
39 |
40 | /// Non-atomic pointer type. This type is `!Sync` and thus is useful for presenting a Rust-ish
41 | /// API to a data structure where aliasing and mutability must follow the standard rules: there
42 | /// can be only one mutator.
43 | ///
44 | /// *Important note:* even though this type is `!Sync`, any data structures that are composed of
45 | /// `Gc` pointers must still be designed with the awareness that the GC thread will call `trace()`
46 | /// at any point and so, must still be thread safe!
47 | ///
48 | /// This is not a root pointer type. It should be used inside data structures to reference other
49 | /// GC-managed objects.
50 | pub struct Gc {
51 | ptr: *mut GcBox,
52 | }
53 |
54 |
55 | /// Atomic pointer type that points at a traceable object. This type is `Sync` and can be used to
56 | /// build concurrent data structures.
57 | ///
58 | /// This type should be used inside data structures to reference other GC-managed objects, but
59 | /// provides interior mutability and atomic methods.
60 | ///
61 | /// TODO: cas, swap etc for GcRoot and Gc
62 | pub struct GcAtomic {
63 | ptr: AtomicPtr>,
64 | }
65 |
66 |
67 | /// An Application Thread, manages a thread-local reference to a tx channel
68 | ///
69 | /// TODO: a version of `spawn()` is required that can be called from an existing mutator thread.
70 | pub struct AppThread;
71 |
72 |
73 | impl AppThread {
74 | /// As thread::spawn but takes a journal Sender to initialize the thread_local instance with.
75 | pub fn spawn_from_gc(tx: JournalSender, f: F) -> thread::JoinHandle
76 | where F: FnOnce() -> T,
77 | F: Send + 'static,
78 | T: Send + 'static
79 | {
80 | thread::spawn(move || {
81 | let (jtx, jrx) = journal::make_journal(JOURNAL_BUFFER_SIZE);
82 |
83 | tx.send(jrx).expect("Failed to send a new Journal to the GC thread!");
84 |
85 | GC_JOURNAL.with(|j| {
86 | j.set(&jtx);
87 | });
88 |
89 | f()
90 | })
91 | }
92 | }
93 |
94 | // Reference count functions. Only new-objects need to specify the traverse bit.
95 |
96 | #[inline]
97 | fn as_traitobject(object: &T) -> TraitObject {
98 | let trace: &Trace = object;
99 | unsafe { transmute(trace) }
100 | }
101 |
102 |
103 | /// Write a reference count increment to the journal for a newly allocated object
104 | #[inline]
105 | fn write(object: &T, is_new: bool, flags: usize) {
106 | GC_JOURNAL.with(|j| {
107 | let tx = unsafe { &*j.get() };
108 |
109 | let tobj = as_traitobject(object);
110 |
111 | // set the refcount-increment bit
112 | let ptr = (tobj.data as usize) | flags;
113 |
114 | // set the traversible bit
115 | let mut vtable = tobj.vtable as usize;
116 | if is_new && object.traversible() {
117 | vtable |= TRAVERSE_BIT;
118 | }
119 |
120 | tx.send(Object {
121 | ptr: ptr,
122 | vtable: vtable,
123 | });
124 | });
125 | }
126 |
127 | // GcBox implementation
128 |
129 | impl GcBox {
130 | fn new(value: T) -> GcBox {
131 | GcBox {
132 | value: value,
133 | }
134 | }
135 | }
136 |
137 |
138 | unsafe impl Trace for GcBox {
139 | #[inline]
140 | fn traversible(&self) -> bool {
141 | self.value.traversible()
142 | }
143 |
144 | #[inline]
145 | unsafe fn trace(&self, heap: &mut TraceStack) {
146 | self.value.trace(heap);
147 | }
148 | }
149 |
150 | // GcRoot implementation
151 |
152 | impl GcRoot {
153 | /// Put a new object on the heap and hand ownership to the GC, writing a reference count
154 | /// increment to the journal.
155 | pub fn new(value: T) -> GcRoot {
156 | let boxed = Box::new(GcBox::new(value));
157 | write(&*boxed, true, NEW_BIT | INC_BIT);
158 |
159 | GcRoot {
160 | ptr: Box::into_raw(boxed)
161 | }
162 | }
163 |
164 | fn from_raw(ptr: *mut GcBox) -> GcRoot {
165 | let root = GcRoot { ptr: ptr };
166 | write(&*root, false, INC_BIT);
167 | root
168 | }
169 |
170 | fn ptr(&self) -> *mut GcBox {
171 | self.ptr
172 | }
173 |
174 | fn value(&self) -> &T {
175 | unsafe { &(*self.ptr).value }
176 | }
177 |
178 | fn value_mut(&mut self) -> &mut T {
179 | unsafe { &mut (*self.ptr).value }
180 | }
181 | }
182 |
183 |
184 | impl Drop for GcRoot {
185 | fn drop(&mut self) {
186 | write(&**self, false, 0);
187 | }
188 | }
189 |
190 |
191 | impl Deref for GcRoot {
192 | type Target = T;
193 |
194 | fn deref(&self) -> &T {
195 | self.value()
196 | }
197 | }
198 |
199 |
200 | impl DerefMut for GcRoot {
201 | fn deref_mut(&mut self) -> &mut T {
202 | self.value_mut()
203 | }
204 | }
205 |
206 |
207 | impl Clone for GcRoot {
208 | fn clone(&self) -> Self {
209 | GcRoot::from_raw(self.ptr())
210 | }
211 | }
212 |
213 | // Gc implementation
214 |
215 | impl Gc {
216 | /// Creates a new null pointer.
217 | pub fn null() -> Gc {
218 | Gc {
219 | ptr: null_mut(),
220 | }
221 | }
222 |
223 | /// Move a value to the heap and create a pointer to it.
224 | pub fn new(value: T) -> Gc {
225 | let boxed = Box::new(GcBox::new(value));
226 | write(&*boxed, true, NEW_BIT);
227 |
228 | Gc {
229 | ptr: Box::into_raw(boxed)
230 | }
231 | }
232 |
233 | /// Return the raw pointer value, or None if it is a null pointer.
234 | pub fn as_raw(&self) -> Option<*mut GcBox> {
235 | if self.ptr.is_null() {
236 | None
237 | } else {
238 | Some(self.ptr)
239 | }
240 | }
241 |
242 | /// Pointer equality comparison.
243 | pub fn is(&self, other: Gc) -> bool {
244 | self.ptr == other.ptr
245 | }
246 |
247 | fn from_raw(ptr: *mut GcBox) -> Gc {
248 | Gc {
249 | ptr: ptr,
250 | }
251 | }
252 |
253 | fn ptr(&self) -> *mut GcBox {
254 | self.ptr
255 | }
256 |
257 | fn value(&self) -> &T {
258 | unsafe { &(*self.ptr).value }
259 | }
260 |
261 | fn value_mut(&mut self) -> &mut T {
262 | unsafe { &mut (*self.ptr).value }
263 | }
264 | }
265 |
266 |
267 | impl Deref for Gc {
268 | type Target = T;
269 |
270 | fn deref(&self) -> &T {
271 | self.value()
272 | }
273 | }
274 |
275 |
276 | impl DerefMut for Gc {
277 | fn deref_mut(&mut self) -> &mut T {
278 | self.value_mut()
279 | }
280 | }
281 |
282 |
283 | impl Clone for Gc {
284 | fn clone(&self) -> Self {
285 | Gc {
286 | ptr: self.ptr,
287 | }
288 | }
289 | }
290 |
291 |
292 | impl Copy for Gc {}
293 |
294 | // GcAtomic implementation
295 |
296 | impl GcAtomic {
297 | /// Instantiate a new null pointer
298 | pub fn null() -> GcAtomic {
299 | GcAtomic {
300 | ptr: AtomicPtr::new(null_mut())
301 | }
302 | }
303 |
304 | /// Instantiate a new pointer, moving `value` to the heap. Writes to the journal.
305 | pub fn new(value: T) -> GcAtomic {
306 | let boxed = Box::new(GcBox::new(value));
307 | write(&*boxed, true, NEW_BIT);
308 |
309 | GcAtomic {
310 | ptr: AtomicPtr::new(Box::into_raw(boxed)),
311 | }
312 | }
313 |
314 | /// Root the pointer by loading it into a `GcRoot`
315 | ///
316 | /// Panics if `order` is `Release` or `AcqRel`.
317 | pub fn load_into_root(&self, order: Ordering) -> GcRoot {
318 | let root = GcRoot {
319 | ptr: self.ptr.load(order),
320 | };
321 |
322 | write(&*root, false, INC_BIT);
323 | root
324 | }
325 |
326 | /// Copy the pointer into a new `Gc` instance.
327 | ///
328 | /// Panics if `order` is `Release` or `AcqRel`.
329 | pub fn load_into_gc(&self, order: Ordering) -> Gc {
330 | Gc::from_raw(self.ptr.load(order))
331 | }
332 |
333 | /// Fetch the current raw pointer value
334 | ///
335 | /// Panics if `order` is `Release` or `AcqRel`.
336 | pub fn load_raw(&self, order: Ordering) -> *mut GcBox {
337 | self.ptr.load(order)
338 | }
339 |
340 | /// Replace the current pointer value with the pointer from the given `GcRoot`.
341 | ///
342 | /// Panics if `order` is `Acquire` or `AcqRel`.
343 | pub fn store_from_root(&self, root: GcRoot, order: Ordering) {
344 | self.ptr.store(root.ptr(), order);
345 | }
346 |
347 | /// Replace the current pointer value with the pointer from the given `Gc`.
348 | ///
349 | /// Panics of `order` is `Acquire` or `AcqRel`.
350 | pub fn store_from_gc(&self, gc: Gc, order: Ordering) {
351 | self.ptr.store(gc.ptr(), order);
352 | }
353 |
354 | /// Replace the current pointer value with the given raw pointer
355 | ///
356 | /// Panics if `order` is `Acquire` or `AcqRel`.
357 | pub fn store_raw(&self, ptr: *mut GcBox, order: Ordering) {
358 | self.ptr.store(ptr, order);
359 | }
360 | }
361 |
--------------------------------------------------------------------------------
/src/constants.rs:
--------------------------------------------------------------------------------
1 | //! Numerous constants used as parameters to GC behavior
2 | //!
3 | //! The journal and GC parameters of these should become runtime rather than compile time.
4 |
5 |
6 | // Journal and GC parameters
7 | pub const JOURNAL_BUFFER_SIZE: usize = 32768;
8 | pub const BUFFER_RUN: usize = 1024;
9 | pub const JOURNAL_RUN: usize = 32;
10 | pub const MAX_SLEEP_DUR: usize = 100; // milliseconds
11 | pub const MIN_SLEEP_DUR: usize = 1; // milliseconds
12 | pub const MAJOR_COLLECT_THRESHOLD: usize = 1 << 20;
13 |
14 | // Cache line in bytes
15 | pub const CACHE_LINE: usize = 64;
16 |
17 | // Bits and masks
18 | pub const PTR_MASK: usize = !3;
19 | pub const MARK_BIT: usize = 1;
20 | pub const MARK_MASK: usize = !1;
21 | pub const TRAVERSE_BIT: usize = 2;
22 |
23 | // mask for low bits of address of object through journal
24 | pub const FLAGS_MASK: usize = 3;
25 |
26 | // bit number that indicates whether a reference count is being incremented
27 | pub const INC_BIT: usize = 1;
28 | // // bit number that indicates whether or not an object is newly allocated or not
29 | pub const NEW_BIT: usize = 2;
30 | pub const NEW_MASK: usize = !2;
31 |
32 | // Values found in the 2 bits masked by FLAGS_MASK
33 | // new object, increment refcount value
34 | pub const NEW_INC: usize = 3;
35 | // new object not rooted value
36 | pub const NEW: usize = 2;
37 | // old object, increment refcount value
38 | pub const INC: usize = 1;
39 | // decrement refcount value
40 | pub const DEC: usize = 0;
41 |
--------------------------------------------------------------------------------
/src/gcthread.rs:
--------------------------------------------------------------------------------
1 | //! Garbage collection thread
2 |
3 |
4 | use std::any::Any;
5 | use std::cmp::min;
6 | use std::mem::size_of;
7 | use std::sync::mpsc;
8 | use std::thread;
9 | use std::time::Duration;
10 |
11 | use num_cpus;
12 | use scoped_pool::Pool;
13 |
14 | use appthread::AppThread;
15 | use constants::{MAJOR_COLLECT_THRESHOLD, MAX_SLEEP_DUR, MIN_SLEEP_DUR};
16 | use heap::{CollectOps, Object};
17 | use journal;
18 | use parheap::ParHeap;
19 | use statistics::{StatsLogger, DefaultLogger};
20 | use youngheap::YoungHeap;
21 |
22 |
23 | pub type EntryReceiver = journal::Receiver