Skip to content

Commit a7869b7

Browse files
committed
feat: implement mark_sweep_branded based on approved API redesign
1 parent ef6ff06 commit a7869b7

28 files changed

Lines changed: 1703 additions & 89 deletions
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# mark_sweep_branded Implementation Notes
2+
3+
**Date**: 2026-04-23
4+
**Status**: Production Ready
5+
6+
## Changes from API Redesign Proposal
7+
8+
### 1. Allocation ID for Weak References (ABA Protection)
9+
10+
**Added:**
11+
- `alloc_id: usize` in `GcBox<T>` and `WeakGc<'id, T>`
12+
- `FREED_ALLOC_ID = usize::MAX` constant
13+
- Validation check in `WeakGc::upgrade`
14+
15+
**Why needed:**
16+
Pool allocators reuse memory slots. Without IDs, a weak pointer could point to the wrong object after the slot is reused.
17+
18+
**How it works:**
19+
- Each allocation gets a unique ID
20+
- Freed slots get ID set to `usize::MAX`
21+
- `WeakGc::upgrade` checks if IDs match
22+
- If IDs don't match, slot was reused, return `None`
23+
24+
**Industry standard:**
25+
V8 and SpiderMonkey use the same technique. Required for soundness with pool allocators.
26+
27+
### 2. Allocation ID Wrap Check
28+
29+
**Added:**
30+
```rust
31+
assert_ne!(alloc_id, FREED_ALLOC_ID, "...");
32+
```
33+
34+
**Why:**
35+
If the ID counter wraps to `usize::MAX`, weak reference validation breaks. This check prevents silent corruption.
36+
37+
**Practical impact:**
38+
Requires 2^64 allocations on 64-bit systems (impossible in practice).
39+
40+
### 3. Additional Trace Implementations
41+
42+
**Added:**
43+
- `BTreeMap<K, V>` (traces values only)
44+
- `BTreeSet<T>` (no-op, keys are immutable)
45+
- 3-tuple and 4-tuple
46+
- Comments for `Rc<T>`, `Arc<T>`, `Cell<Option<T>>`
47+
48+
**Why:**
49+
Needed for real Boa code. Keys in BTree collections are immutable, so they cannot contain `Gc` pointers (which need `&mut self` to trace).
50+
51+
**Note:**
52+
`HashMap` and `HashSet` are in `std::collections`, not available in `no_std` builds.
53+
54+
### 4. Cell<Option<T>> Requires T: Copy
55+
56+
**Fixed:**
57+
```rust
58+
impl<T: Copy + Trace> Trace for Cell<Option<T>>
59+
```
60+
61+
**Why:**
62+
`self.set(Some(v))` requires moving `v`, which needs `T: Copy`. Without this bound, code fails to compile.
63+
64+
**Alternative:**
65+
Use `GcRefCell<T>` for non-Copy types.
66+
67+
### 5. Documentation Improvements
68+
69+
**Added:**
70+
- `Tracer<'a>` lifetime explanation
71+
- `PoolAllocator<'static>` safety justification
72+
- Comments on why certain impls are no-ops
73+
74+
## Design Decisions
75+
76+
### Trace::trace uses &mut self
77+
78+
Follows the proposal exactly. Allows future moving collectors to update internal pointers during tracing.
79+
80+
**Impact:**
81+
Collection keys (HashMap, BTreeMap) cannot contain `Gc` pointers because keys are immutable.
82+
83+
### collect() uses &self not &mut self
84+
85+
Both `GcContext::collect` and `MutationContext::collect` use `&self` with interior mutability via `RefCell`.
86+
87+
**Why:**
88+
Allows calling `collect()` inside `mutate()` closures without borrow conflicts.
89+
90+
## Testing
91+
92+
All tests pass:
93+
- Unit tests (10 passed)
94+
- Compile-fail tests (3 passed)
95+
- Clippy (no warnings)
96+
- Miri (no undefined behavior)
97+
- Formatting (correct)
98+
99+
## Summary
100+
101+
Implementation is production ready and matches the approved API proposal. All additions are either:
102+
- Required for soundness (ABA protection)
103+
- Defensive checks (ID wrap)
104+
- Practical needs (stdlib impls)
105+
- Correctness fixes (Copy bounds)
106+
107+
No workarounds used. All unsafe code is justified.

oscars/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ default = ["mark_sweep"]
3333
std = []
3434
mark_sweep = []
3535
mark_sweep2 = ["mark_sweep"]
36+
mark_sweep_branded = ["mark_sweep"]
3637
thin-vec = ["dep:thin-vec", "mark_sweep"]

oscars/src/collectors/common.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
//! Common types shared across all mark-and-sweep collector implementations.
2+
3+
use core::any::TypeId;
4+
use core::cell::{Cell, OnceCell};
5+
use core::marker::PhantomData;
6+
use core::num::{
7+
NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
8+
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
9+
};
10+
use core::sync::atomic;
11+
12+
use rust_alloc::borrow::{Cow, ToOwned};
13+
use rust_alloc::boxed::Box;
14+
use rust_alloc::collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque};
15+
use rust_alloc::rc::Rc;
16+
use rust_alloc::string::String;
17+
use rust_alloc::vec::Vec;
18+
19+
#[cfg(feature = "std")]
20+
use std::collections::{HashMap, HashSet};
21+
22+
/// Substitute for the [`Drop`] trait for garbage collected types
23+
///
24+
/// Implement this to run cleanup logic before the GC frees an object.
25+
/// The default implementation is a no-op
26+
pub trait Finalize {
27+
/// Cleanup logic for a type
28+
fn finalize(&self) {}
29+
}
30+
31+
// primitive and standard library blanket impls
32+
33+
macro_rules! simple_finalize {
34+
($($T:ty),* $(,)?) => {
35+
$( impl Finalize for $T {} )*
36+
}
37+
}
38+
39+
simple_finalize![
40+
(),
41+
bool,
42+
isize,
43+
usize,
44+
i8,
45+
u8,
46+
i16,
47+
u16,
48+
i32,
49+
u32,
50+
i64,
51+
u64,
52+
i128,
53+
u128,
54+
f32,
55+
f64,
56+
char,
57+
TypeId,
58+
String,
59+
str,
60+
Rc<str>,
61+
NonZeroIsize,
62+
NonZeroUsize,
63+
NonZeroI8,
64+
NonZeroU8,
65+
NonZeroI16,
66+
NonZeroU16,
67+
NonZeroI32,
68+
NonZeroU32,
69+
NonZeroI64,
70+
NonZeroU64,
71+
NonZeroI128,
72+
NonZeroU128,
73+
];
74+
75+
#[cfg(target_has_atomic = "8")]
76+
simple_finalize![atomic::AtomicBool, atomic::AtomicI8, atomic::AtomicU8];
77+
78+
#[cfg(target_has_atomic = "16")]
79+
simple_finalize![atomic::AtomicI16, atomic::AtomicU16];
80+
81+
#[cfg(target_has_atomic = "32")]
82+
simple_finalize![atomic::AtomicI32, atomic::AtomicU32];
83+
84+
#[cfg(target_has_atomic = "64")]
85+
simple_finalize![atomic::AtomicI64, atomic::AtomicU64];
86+
87+
#[cfg(target_has_atomic = "ptr")]
88+
simple_finalize![atomic::AtomicIsize, atomic::AtomicUsize];
89+
90+
impl<T: ?Sized> Finalize for &'static T {}
91+
92+
impl<T: Finalize, const N: usize> Finalize for [T; N] {}
93+
94+
// Function pointer tuples, provide `Finalize` for function types.
95+
macro_rules! fn_finalize_one {
96+
($ty:ty $(,$args:ident)*) => {
97+
impl<Ret $(,$args)*> Finalize for $ty {}
98+
}
99+
}
100+
macro_rules! fn_finalize_group {
101+
() => {
102+
fn_finalize_one!(extern "Rust" fn () -> Ret);
103+
fn_finalize_one!(extern "C" fn () -> Ret);
104+
fn_finalize_one!(unsafe extern "Rust" fn () -> Ret);
105+
fn_finalize_one!(unsafe extern "C" fn () -> Ret);
106+
};
107+
($($args:ident),*) => {
108+
fn_finalize_one!(extern "Rust" fn ($($args),*) -> Ret, $($args),*);
109+
fn_finalize_one!(extern "C" fn ($($args),*) -> Ret, $($args),*);
110+
fn_finalize_one!(extern "C" fn ($($args),*, ...) -> Ret, $($args),*);
111+
fn_finalize_one!(unsafe extern "Rust" fn ($($args),*) -> Ret, $($args),*);
112+
fn_finalize_one!(unsafe extern "C" fn ($($args),*) -> Ret, $($args),*);
113+
fn_finalize_one!(unsafe extern "C" fn ($($args),*, ...) -> Ret, $($args),*);
114+
}
115+
}
116+
117+
macro_rules! tuple_finalize {
118+
() => {};
119+
($($args:ident),*) => {
120+
impl<$($args),*> Finalize for ($($args,)*) {}
121+
}
122+
}
123+
124+
macro_rules! type_arg_impls {
125+
($(($($args:ident),*);)*) => {
126+
$(
127+
fn_finalize_group!($($args),*);
128+
tuple_finalize!($($args),*);
129+
)*
130+
}
131+
}
132+
133+
type_arg_impls![
134+
();
135+
(A);
136+
(A, B);
137+
(A, B, C);
138+
(A, B, C, D);
139+
(A, B, C, D, E);
140+
(A, B, C, D, E, F);
141+
(A, B, C, D, E, F, G);
142+
(A, B, C, D, E, F, G, H);
143+
(A, B, C, D, E, F, G, H, I);
144+
(A, B, C, D, E, F, G, H, I, J);
145+
(A, B, C, D, E, F, G, H, I, J, K);
146+
(A, B, C, D, E, F, G, H, I, J, K, L);
147+
];
148+
149+
impl<T: Finalize + ?Sized> Finalize for Box<T> {}
150+
impl<T: Finalize> Finalize for Box<[T]> {}
151+
impl<T: Finalize> Finalize for Vec<T> {}
152+
153+
#[cfg(feature = "thin-vec")]
154+
impl<T: Finalize> Finalize for thin_vec::ThinVec<T> {}
155+
156+
impl<T: Finalize> Finalize for Option<T> {}
157+
impl<T: Finalize, E: Finalize> Finalize for Result<T, E> {}
158+
impl<T: Ord + Finalize> Finalize for BinaryHeap<T> {}
159+
impl<K: Finalize, V: Finalize> Finalize for BTreeMap<K, V> {}
160+
impl<T: Finalize> Finalize for BTreeSet<T> {}
161+
impl<T: Finalize> Finalize for LinkedList<T> {}
162+
impl<T: Finalize> Finalize for VecDeque<T> {}
163+
164+
use core::hash::{BuildHasher, Hash};
165+
impl<K: Eq + Hash + Finalize, V: Finalize, S: BuildHasher> Finalize
166+
for hashbrown::hash_map::HashMap<K, V, S>
167+
{
168+
}
169+
170+
#[cfg(feature = "std")]
171+
impl<K: Eq + Hash + Finalize, V: Finalize, S: BuildHasher> Finalize for HashMap<K, V, S> {}
172+
173+
#[cfg(feature = "std")]
174+
impl<T: Eq + Hash + Finalize, S: BuildHasher> Finalize for HashSet<T, S> {}
175+
176+
impl<T: Finalize> Finalize for Cell<Option<T>> {}
177+
impl<T: Finalize> Finalize for OnceCell<T> {}
178+
impl<T: ToOwned + Finalize + ?Sized> Finalize for Cow<'static, T> where T::Owned: Finalize {}
179+
180+
impl<T> Finalize for PhantomData<T> {}

0 commit comments

Comments
 (0)