mmtk/vm/slot.rs
1//! This module provides the trait [`Slot`] and related traits and types which allow VMs to
2//! customize the layout of slots and the behavior of loading and updating object references in
3//! slots.
4
5use std::hash::Hash;
6use std::marker::PhantomData;
7use std::{fmt::Debug, ops::Range};
8
9use atomic::Atomic;
10
11use crate::util::constants::{BYTES_IN_ADDRESS, LOG_BYTES_IN_ADDRESS};
12use crate::util::{Address, ObjectReference};
13
14/// `Slot` is an abstraction for MMTk to load and update object references in memory.
15///
16/// # Slots and the `Slot` trait
17///
18/// In a VM, a slot can contain an object reference or a non-reference value. It can be in an
19/// object (a.k.a. a field), on the stack (i.e. a local variable) or in any other places (such as
20/// global variables). It may have different representations in different VMs. Some VMs put a
21/// direct pointer to an object into a slot, while others may use compressed pointers, tagged
22/// pointers, offsetted pointers, etc. Some VMs (such as JVM) have null references, and others
23/// (such as CRuby and JavaScript engines) can also use tagged bits to represent non-reference
24/// values such as small integers, `true`, `false`, `null` (a.k.a. "none", "nil", etc.),
25/// `undefined`, etc.
26///
27/// In MMTk, the `Slot` trait is intended to abstract out such different representations of
28/// reference fields (compressed, tagged, offsetted, etc.) among different VMs. From MMTk's point
29/// of view, **MMTk only cares about the object reference held inside the slot, but not
30/// non-reference values**, such as `null`, `true`, etc. When the slot is holding an object
31/// reference, we can load the object reference from it, and we can update the object reference in
32/// it after the GC moves the object.
33///
34/// # The `Slot` trait has pointer semantics
35///
36/// A `Slot` value *points to* a slot, and is not the slot itself. In fact, the simplest
37/// implementation of the `Slot` trait ([`SimpleSlot`], see below) can simply contain the address of
38/// the slot.
39///
40/// A `Slot` can be [copied](std::marker::Copy), and the copied `Slot` instance points to the same
41/// slot.
42///
43/// # How to implement `Slot`?
44///
45/// If a reference field of a VM is word-sized and holds the raw pointer to an object, and uses the
46/// 0 word as the null pointer, it can use the default [`SimpleSlot`] we provide. It simply
47/// contains a pointer to a memory location that holds an address.
48///
49/// ```rust
50/// pub struct SimpleSlot {
51/// slot_addr: *mut Atomic<Address>,
52/// }
53/// ```
54///
55/// In other cases, the VM need to implement its own `Slot` instances.
56///
57/// For example:
58/// - The VM uses **compressed pointers** (Compressed OOPs in OpenJDK's terminology), where the
59/// heap size is limited, and a 64-bit pointer is stored in a 32-bit slot.
60/// - The VM uses **tagged pointers**, where some bits of a word are used as metadata while the
61/// rest are used as pointer.
62/// - The VM uses **offsetted pointers**, i.e. the value of the field is an address at an offset
63/// from the [`ObjectReference`] of the target object. Such offsetted pointers are usually used
64/// to represent **interior pointers**, i.e. pointers to an object field, an array element, etc.
65///
66/// If needed, the implementation of `Slot` can contain not only the pointer, but also additional
67/// information. The `OffsetSlot` example below also contains an offset which can be used when
68/// decoding the pointer. See `src/vm/tests/mock_tests/mock_test_slots.rs` for more concrete
69/// examples, such as `CompressedOopSlot` and `TaggedSlot`.
70///
71/// ```rust
72/// pub struct OffsetSlot {
73/// slot_addr: *mut Atomic<Address>,
74/// offset: usize,
75/// }
76/// ```
77///
78/// When loading, `Slot::load` shall load the value from the slot and decode the value into a
79/// regular `ObjectReference` (note that MMTk has specific requirements for `ObjectReference`, such
80/// as being aligned, pointing inside an object, and cannot be null. Please read the doc comments
81/// of [`ObjectReference`] for details). The decoding is VM-specific, but usually involves removing
82/// tag bits and/or adding an offset to the word, and (in the case of compressed pointers) extending
83/// the word size. By doing this conversion, MMTk can implement GC algorithms in a VM-neutral way,
84/// knowing only `ObjectReference`.
85///
86/// When GC moves object, `Slot::store` shall convert the updated `ObjectReference` back to the
87/// slot-specific representation. Compressed pointers remain compressed; tagged pointers preserve
88/// their tag bits; and offsetted pointers keep their offsets.
89///
90/// # Performance notes
91///
92/// The methods of this trait are called on hot paths. Please ensure they have high performance.
93///
94/// The size of the data structure of the `Slot` implementation may affect the performance as well.
95/// During GC, MMTk enqueues `Slot` instances, and its size affects the overhead of copying. If
96/// your `Slot` implementation has multiple fields or uses `enum` for multiple kinds of slots, it
97/// may have extra cost when copying or decoding. You should measure it. If the cost is too much,
98/// you can implement `Slot` with a tagged word. For example, the [mmtk-openjdk] binding uses the
99/// low order bit to encode whether the slot is compressed or not.
100///
101/// [mmtk-openjdk]: https://github.com/mmtk/mmtk-openjdk/blob/master/mmtk/src/slots.rs
102///
103/// # About weak references
104///
105/// This trait only concerns the representation (i.e. the shape) of the slot, not its semantics,
106/// such as whether it holds strong or weak references. Therefore, one `Slot` implementation can be
107/// used for both slots that hold strong references and slots that hold weak references.
108pub trait Slot: Copy + Send + Sync + Debug + PartialEq + Eq + Hash {
109 /// Load object reference from the slot.
110 ///
111 /// If the slot is not holding an object reference (For example, if it is holding NULL or a
112 /// tagged non-reference value. See trait-level doc comment.), this method should return
113 /// `None`.
114 ///
115 /// If the slot holds an object reference with tag bits, the returned value shall be the object
116 /// reference with the tag bits removed.
117 fn load(&self) -> Option<ObjectReference>;
118
119 /// Store the object reference `object` into the slot.
120 ///
121 /// This method is used during a GC to update a slot so that it holds the updated
122 /// `ObjectReference` which points to the new address of the target object during a moving GC.
123 /// MMTk core may conservatively call this method even if the target object is not moved.
124 ///
125 /// Note that if [`crate::plan::PlanConstraints::may_trace_duplicate_edges`] is true, multiple
126 /// GC worker threads may visit the same slot during tracing, and update it concurrently. In
127 /// this case, the implementation of [`Slot::store`] must be benign with respect to such a race,
128 /// but doesn't need to be an atomic read-modify-write operation. Because the new address of a
129 /// moved object is unique during a GC, if such a race occurs, all invocations of `store` will
130 /// receive the same `object` argument. Storing the same value to the same address is usually
131 /// idempotent.
132 ///
133 /// If the slot holds an object reference with tag bits, this method must preserve the tag
134 /// bits while updating the object reference so that it points to the forwarded object given by
135 /// the parameter `object`.
136 ///
137 /// FIXME: This design is inefficient for handling object references with tag bits. Consider
138 /// introducing a new updating function to do the load, trace and store in one function.
139 /// See: <https://github.com/mmtk/mmtk-core/issues/1033>
140 ///
141 /// FIXME: This method is currently used by both moving GC algorithms and the subsuming write
142 /// barrier ([`crate::memory_manager::object_reference_write`]). The two reference writing
143 /// operations have different semantics, and need to be implemented differently if the VM
144 /// supports offsetted or tagged references.
145 /// See: <https://github.com/mmtk/mmtk-core/issues/1038>
146 fn store(&self, object: ObjectReference);
147
148 /// Prefetch the slot so that a subsequent `load` will be faster.
149 fn prefetch_load(&self) {
150 // no-op by default
151 }
152
153 /// Prefetch the slot so that a subsequent `store` will be faster.
154 fn prefetch_store(&self) {
155 // no-op by default
156 }
157}
158
159/// A simple slot implementation that represents a word-sized slot which holds the raw address of
160/// an `ObjectReference`, or 0 if it is holding a null reference.
161///
162/// It is the default slot type, and should be suitable for most VMs.
163#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
164#[repr(transparent)]
165pub struct SimpleSlot {
166 slot_addr: *mut Atomic<Address>,
167}
168
169impl SimpleSlot {
170 /// Create a simple slot from an address.
171 ///
172 /// Arguments:
173 /// * `address`: The address in memory where an `ObjectReference` is stored.
174 pub fn from_address(address: Address) -> Self {
175 Self {
176 slot_addr: address.to_mut_ptr(),
177 }
178 }
179
180 /// Get the address of the slot.
181 ///
182 /// Return the address at which the `ObjectReference` is stored.
183 pub fn as_address(&self) -> Address {
184 Address::from_mut_ptr(self.slot_addr)
185 }
186}
187
188unsafe impl Send for SimpleSlot {}
189unsafe impl Sync for SimpleSlot {}
190
191impl Slot for SimpleSlot {
192 fn load(&self) -> Option<ObjectReference> {
193 let addr = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) };
194 ObjectReference::from_raw_address(addr)
195 }
196
197 fn store(&self, object: ObjectReference) {
198 unsafe { (*self.slot_addr).store(object.to_raw_address(), atomic::Ordering::Relaxed) }
199 }
200}
201
202/// For backword compatibility, we let `Address` implement `Slot` with the same semantics as
203/// [`SimpleSlot`] so that existing bindings that use `Address` as `Slot` can continue to work.
204///
205/// However, we should use `SimpleSlot` directly instead of using `Address`. The purpose of the
206/// `Address` type is to represent an address in memory. It is not directly related to fields
207/// that hold references to other objects. Calling `load()` and `store()` on an `Address` does
208/// not indicate how many bytes to load or store, or how to interpret those bytes. On the other
209/// hand, `SimpleSlot` is all about how to access a field that holds a reference represented
210/// simply as an `ObjectReference`. The intention and the semantics are clearer with
211/// `SimpleSlot`.
212impl Slot for Address {
213 fn load(&self) -> Option<ObjectReference> {
214 let addr = unsafe { Address::load(*self) };
215 ObjectReference::from_raw_address(addr)
216 }
217
218 fn store(&self, object: ObjectReference) {
219 unsafe { Address::store(*self, object) }
220 }
221}
222
223#[test]
224fn a_simple_slot_should_have_the_same_size_as_a_pointer() {
225 assert_eq!(
226 std::mem::size_of::<SimpleSlot>(),
227 std::mem::size_of::<*mut libc::c_void>()
228 );
229}
230
231/// A abstract memory slice represents a piece of **heap** memory which may contains many slots.
232pub trait MemorySlice: Send + Debug + PartialEq + Eq + Clone + Hash {
233 /// The associate type to define how to access slots from a memory slice.
234 type SlotType: Slot;
235 /// The associate type to define how to iterate slots in a memory slice.
236 type SlotIterator: Iterator<Item = Self::SlotType>;
237 /// Iterate object slots within the slice. If there are non-reference values in the slice, the iterator should skip them.
238 fn iter_slots(&self) -> Self::SlotIterator;
239 /// The object which this slice belongs to. If we know the object for the slice, we will check the object state (e.g. mature or not), rather than the slice address.
240 /// Normally checking the object and checking the slice does not make a difference, as the slice is part of the object (in terms of memory range). However,
241 /// if a slice is in a different location from the object, the object state and the slice can be hugely different, and providing a proper implementation
242 /// of this method for the owner object is important.
243 fn object(&self) -> Option<ObjectReference>;
244 /// Start address of the memory slice
245 fn start(&self) -> Address;
246 /// Size of the memory slice
247 fn bytes(&self) -> usize;
248 /// Memory copy support
249 fn copy(src: &Self, tgt: &Self);
250}
251
252/// Iterate slots within `Range<Address>`.
253pub struct AddressRangeIterator {
254 cursor: Address,
255 limit: Address,
256}
257
258impl Iterator for AddressRangeIterator {
259 type Item = Address;
260
261 fn next(&mut self) -> Option<Self::Item> {
262 if self.cursor >= self.limit {
263 None
264 } else {
265 let slot = self.cursor;
266 self.cursor += BYTES_IN_ADDRESS;
267 Some(slot)
268 }
269 }
270}
271
272impl MemorySlice for Range<Address> {
273 type SlotType = Address;
274 type SlotIterator = AddressRangeIterator;
275
276 fn iter_slots(&self) -> Self::SlotIterator {
277 AddressRangeIterator {
278 cursor: self.start,
279 limit: self.end,
280 }
281 }
282
283 fn object(&self) -> Option<ObjectReference> {
284 None
285 }
286
287 fn start(&self) -> Address {
288 self.start
289 }
290
291 fn bytes(&self) -> usize {
292 self.end - self.start
293 }
294
295 fn copy(src: &Self, tgt: &Self) {
296 debug_assert_eq!(src.bytes(), tgt.bytes());
297 debug_assert_eq!(
298 src.bytes() & ((1 << LOG_BYTES_IN_ADDRESS) - 1),
299 0,
300 "bytes are not a multiple of words"
301 );
302 // Raw memory copy
303 unsafe {
304 let words = tgt.bytes() >> LOG_BYTES_IN_ADDRESS;
305 let src = src.start().to_ptr::<usize>();
306 let tgt = tgt.start().to_mut_ptr::<usize>();
307 std::ptr::copy(src, tgt, words)
308 }
309 }
310}
311
312/// Memory slice type with empty implementations.
313/// For VMs that do not use the memory slice type.
314#[derive(Debug, PartialEq, Eq, Clone, Hash)]
315pub struct UnimplementedMemorySlice<SL: Slot = SimpleSlot>(PhantomData<SL>);
316
317/// Slot iterator for `UnimplementedMemorySlice`.
318pub struct UnimplementedMemorySliceSlotIterator<SL: Slot>(PhantomData<SL>);
319
320impl<SL: Slot> Iterator for UnimplementedMemorySliceSlotIterator<SL> {
321 type Item = SL;
322
323 fn next(&mut self) -> Option<Self::Item> {
324 unimplemented!()
325 }
326}
327
328impl<SL: Slot> MemorySlice for UnimplementedMemorySlice<SL> {
329 type SlotType = SL;
330 type SlotIterator = UnimplementedMemorySliceSlotIterator<SL>;
331
332 fn iter_slots(&self) -> Self::SlotIterator {
333 unimplemented!()
334 }
335
336 fn object(&self) -> Option<ObjectReference> {
337 unimplemented!()
338 }
339
340 fn start(&self) -> Address {
341 unimplemented!()
342 }
343
344 fn bytes(&self) -> usize {
345 unimplemented!()
346 }
347
348 fn copy(_src: &Self, _tgt: &Self) {
349 unimplemented!()
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn address_range_iteration() {
359 let src: Vec<usize> = (0..32).collect();
360 let src_slice = Address::from_ptr(&src[0])..Address::from_ptr(&src[0]) + src.len();
361 for (i, v) in src_slice.iter_slots().enumerate() {
362 assert_eq!(i, unsafe { v.load::<usize>() })
363 }
364 }
365
366 #[test]
367 fn memory_copy_on_address_ranges() {
368 let src = [1u8; 32];
369 let mut dst = [0u8; 32];
370 let src_slice = Address::from_ptr(&src[0])..Address::from_ptr(&src[0]) + src.len();
371 let dst_slice =
372 Address::from_mut_ptr(&mut dst[0])..Address::from_mut_ptr(&mut dst[0]) + src.len();
373 MemorySlice::copy(&src_slice, &dst_slice);
374 assert_eq!(dst.iter().sum::<u8>(), src.len() as u8);
375 }
376}