You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This document summarizes all the changes made to add weak map support to the mark sweep collector.
6
+
7
+
This adds `WeakMap<K, V>` to the mark sweep collector backed by ephemerons.
8
+
Each entry's key is held weakly and when the key is collected, the entry is pruned from the map automatically.
9
+
10
+
## changes made in PR #13
11
+
12
+
### `WeakGcBox` refactor
13
+
14
+
Previously `WeakGcBox<T>` allocated its own `GcBox<T>` and used an `IS_WEAK`
15
+
bit in the header flags to mark it as weak. this approach made sense as a first pass but a weak reference to an existing `Gc<K>` should point at the same allocation, not create a second one.
16
+
The GC would never see the key as dead if `WeakGcBox` held its own copy.
17
+
18
+
So I refactored `WeakGcBox` to hold an `ErasedArenaPointer` into an existing
19
+
allocation, also removed `IS_WEAK` flag, `weak_white`/`weak_black` header constructors
20
+
and the related `GcBox` code from `gc_header.rs` and `gc_box.rs`
21
+
22
+
### `Ephemeron::new_in` now uses `WeakGcBox<K>`
23
+
24
+
`Ephemeron::new_in` used to accept a value `K` and create a `WeakGcBox`
25
+
internally. Now it takes a `WeakGcBox<K>` directly. The caller creates the weak
26
+
reference from an existing `Gc<K>`. This keeps things organized by
27
+
separating how we create the weak link from how the ephemeron uses it
28
+
29
+
### new cleanup functions added to `EphemeronVTable`
30
+
31
+
Added `is_reachable_fn` and `finalize_fn` to `EphemeronVTable`. This
32
+
lets the collector's sweep phase check if a key is still alive and run any
33
+
finalizers without needing to know the specific types involved.
34
+
35
+
### `WeakMap<K, V>`
36
+
37
+
It's a `HashMap<usize, ArenaPointer<Ephemeron<K, V>>>`,
38
+
keyed by the raw pointer address of `Gc<K>`. This gives O(1) average time
39
+
for insert, lookup and remove
40
+
41
+
-`insert` removes any existing entry for the key before allocating a new ephemeron.
42
+
This prevents the old one from leaking into the collector queue when a value is
43
+
updated
44
+
45
+
-`remove` takes the entry out of the map but the backing ephemeron stays in the
46
+
collector queue, it gets swept when the key is collected.
47
+
48
+
`prune_dead_entries` only visits entries still in `self.entries` so there is no
49
+
risk of reading freed memory.
50
+
51
+
-`Trace` is handled, the ephemerons are in the collector's own queue so `WeakMap` itself doesn't need to do anything extra
52
+
53
+
## how the collector owns the map
54
+
55
+
`WeakMap::new(collector)` boxes the `WeakMapInner<K, V>`, grabs a raw pointer
56
+
from the box before erasing it to `Box<dyn ErasedWeakMap>`, then pushes that
57
+
erased box into `collector.weak_maps`. The `WeakMap` handle just holds that raw
58
+
pointer and is valid for as long as the collector lives
59
+
60
+
`ErasedWeakMap` is an internal helper with one method: `prune_dead_entries`.
61
+
during `collect()` the collector calls it on every map in `weak_maps` after the
62
+
sweep phase but before the dead arenas are freed. this ensures we can still read
63
+
the dropped flag on ephemerons before their memory is gone.
64
+
65
+
In my fisrt attempt, I made users manually register their maps with the collector, this
66
+
was awkward because it required `unsafe` code. It was also hard for the collector to safely
67
+
keep track of maps that lived outside its own memory. Worse, if a user forgot to
68
+
unregister a map before deleting it, the collector was left holding a bad pointer
69
+
which could cause the whole program to crash
70
+
71
+
By allocating `WeakMapInner` on the collector's heap and giving the collector ownership,
72
+
we eliminated the manual registration step completely. the user gets a handle that
73
+
is valid for the collector's lifetime, the aliasing
74
+
concerns are resolved because the collector owns the memory it prunes
75
+
76
+
## potential improvements
77
+
78
+
**`weak_map.rs`**
79
+
80
+
- explore using `HashTable` instead of `HashMap` to save memory.
81
+
- consider whether `insert` should take an `Ephemeron` directly instead of a key/value pair.
82
+
83
+
## conclusion
84
+
85
+
This approach gives us a map that cleans itself up automatically. It lives exactly
86
+
as long as the collector does, users don't have to fiddle with manual registration
87
+
or write `unsafe` code, and it plugs right into the collector's existing trace
88
+
and sweep phases.
89
+
90
+
In the future, the best improvement would be switching from `HashMap` to `HashTable`
91
+
to save memory. Until then, this first version works well and gives us the weak map behavior we need
0 commit comments