Skip to content

Commit b4fa010

Browse files
Nero5023meta-codesync[bot]
authored andcommitted
Reuse resident heaps during page-in
Summary: This completes the shared-heap paging fix started by the previous diff. In the X/Y example, after X is paged out and evicted, Y may still keep heap A alive. When X is paged back in, reconstructing a second allocation of A would give both allocations the same `HeapRefId`. A subsequent page-out can register the chunks of one allocation and then hit the pointer-not-found panic when it serializes a value from the other. Track each registered heap as a weak `HeapRefId` to `FrozenFrozenHeap` association. Page-in reuses A when Y still keeps it alive and resolves the serialized `value_index` for X through the existing chunk index for A, while still consuming the serialized heap body to keep the deserializer cursor aligned. If A is no longer resident, the existing lazy heap reconstruction path remains unchanged. Pointer-identity-checked cleanup prevents an older allocation from unregistering a newer resident entry with the same ID. Reviewed By: christolliday Differential Revision: D113883882 fbshipit-source-id: 4b4345dbbe733ae68e7addc8bd1d5dba991db45a
1 parent 92f6cba commit b4fa010

5 files changed

Lines changed: 270 additions & 98 deletions

File tree

starlark-rust/starlark/src/pagable/serialized_frozen_value.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ use crate::pagable::static_value::StaticValueId;
3030
/// is currently being (de)serialized.
3131
///
3232
/// `value_index` is the value's position in the heap's serialization order
33-
/// (drop bump first, then non-drop bump). Resolved via the heap's per-value
34-
/// address table registered in `StarlarkDeserState.heap_value_addrs`.
33+
/// (drop bump first, then non-drop bump). It resolves either through the
34+
/// resident heap's chunk index or the heap's lazy-deserialization state.
3535
#[derive(Debug)]
3636
pub(super) enum SerializedFrozenValue {
3737
HeapPtr {

starlark-rust/starlark/src/pagable/starlark_deserialize_context.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ use crate::pagable::heap_ref_id::HeapRefId;
4343
use crate::pagable::lookup_vtable;
4444
use crate::pagable::serialized_frozen_value::SerializedFrozenValue;
4545
use crate::pagable::starlark_deserialize::StarlarkDeserializeContext;
46+
use crate::pagable::starlark_serialize_context::StarlarkSerState;
4647
use crate::pagable::static_value::get_frozen_value_by_static_id;
4748
use crate::values::FrozenValue;
4849
use crate::values::layout::heap::allocator::alloc::allocator::ChunkAllocator;
@@ -704,6 +705,15 @@ impl<'a, 'de> StarlarkDeserializerImpl<'a, 'de> {
704705
value_index: u32,
705706
is_str: bool,
706707
) -> crate::Result<FrozenValue> {
708+
if let Some(value) = self
709+
.pagable
710+
.session_context()
711+
.get::<Arc<StarlarkSerState>>()
712+
.and_then(|state| state.lookup_resident_value(heap_id, value_index, is_str))
713+
{
714+
return Ok(value);
715+
}
716+
707717
let target_state = self
708718
.state
709719
.get_heap(&heap_id)

starlark-rust/starlark/src/pagable/starlark_serialize_context.rs

Lines changed: 99 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@
1818
//! Implementation of StarlarkSerializeContext.
1919
2020
use std::collections::BTreeMap;
21+
use std::mem;
2122
use std::sync::Arc;
2223
use std::sync::RwLock;
2324

2425
use allocative::Allocative;
25-
use dashmap::DashSet;
26+
use dashmap::DashMap;
27+
use dashmap::mapref::entry::Entry;
28+
use dupe::Dupe;
2629
use pagable::PagableSerialize;
2730
use pagable::PagableSerializer;
2831

@@ -33,6 +36,8 @@ use crate::pagable::static_value::get_static_value_id;
3336
use crate::values::FrozenValue;
3437
use crate::values::layout::heap::arena::ChunkInfo;
3538
use crate::values::layout::heap::heap_type::FrozenHeapRef;
39+
use crate::values::layout::heap::heap_type::WeakFrozenHeapRef;
40+
use crate::values::layout::heap::repr::AValueHeader;
3641
use crate::values::layout::pointer::PointerTags;
3742

3843
/// Per-chunk entry in [`StarlarkSerState::chunks`]. The chunk's base
@@ -52,68 +57,67 @@ pub(crate) struct ChunkEntry {
5257
payload_offsets: Box<[u32]>,
5358
}
5459

55-
/// Shared serialization state across all heaps in a session, stored in
56-
/// `SessionContext` as `Arc<StarlarkSerState>`.
60+
#[derive(Allocative)]
61+
struct ResidentHeapEntry {
62+
heap: WeakFrozenHeapRef,
63+
/// Chunks ordered by `values_before` for value-index lookup.
64+
chunks_by_value_index: Box<[(usize, Arc<ChunkEntry>)]>,
65+
}
66+
67+
/// Shared resident-heap state across serialization and deserialization in a
68+
/// session, stored in `SessionContext` as `Arc<StarlarkSerState>`.
5769
#[derive(Allocative)]
5870
pub(crate) struct StarlarkSerState {
5971
/// Per-chunk index keyed by chunk base address.
60-
chunks: RwLock<BTreeMap<usize, ChunkEntry>>,
61-
/// Heaps whose chunk entries have already been folded into `chunks`.
62-
/// Used to skip duplicate registrations on transitive ref walks.
63-
registered_heaps: DashSet<HeapRefId>,
72+
chunks: RwLock<BTreeMap<usize, Arc<ChunkEntry>>>,
73+
/// Resident heaps and their value-index-ordered chunks.
74+
resident_heaps: DashMap<HeapRefId, ResidentHeapEntry>,
6475
}
6576

6677
impl StarlarkSerState {
6778
pub(crate) fn new() -> Self {
6879
Self {
6980
chunks: RwLock::new(BTreeMap::new()),
70-
registered_heaps: DashSet::new(),
81+
resident_heaps: DashMap::new(),
7182
}
7283
}
7384

74-
fn register_heap(&self, heap_id: HeapRefId, entries: Vec<ChunkInfo>) {
85+
fn register_heap(&self, heap_id: HeapRefId, heap: WeakFrozenHeapRef, entries: Vec<ChunkInfo>) {
86+
let chunks_by_value_index: Vec<_> = entries
87+
.into_iter()
88+
.map(|info| {
89+
let base = info.base;
90+
let entry = Arc::new(ChunkEntry {
91+
size: info.size,
92+
heap_id,
93+
values_before: info.values_before,
94+
payload_offsets: info.payload_offsets.into_boxed_slice(),
95+
});
96+
(base, entry)
97+
})
98+
.collect();
99+
75100
{
76101
let mut chunks = self.chunks.write().expect("chunks lock poisoned");
77-
for info in entries {
78-
chunks.insert(
79-
info.base,
80-
ChunkEntry {
81-
size: info.size,
82-
heap_id,
83-
values_before: info.values_before,
84-
payload_offsets: info.payload_offsets.into_boxed_slice(),
85-
},
86-
);
102+
for (base, entry) in &chunks_by_value_index {
103+
chunks.insert(*base, entry.dupe());
87104
}
88105
}
89-
// Mark registered AFTER inserts so any observer of
90-
// `has_heap(heap_id) == true` is guaranteed to see all entries.
91-
self.registered_heaps.insert(heap_id);
106+
// Publish the heap after its chunks so a successful weak upgrade is
107+
// guaranteed to have a complete pointer index.
108+
self.resident_heaps.insert(
109+
heap_id,
110+
ResidentHeapEntry {
111+
heap,
112+
chunks_by_value_index: chunks_by_value_index.into_boxed_slice(),
113+
},
114+
);
92115
}
93116

94-
/// Check if a heap's offset map has already been registered.
95-
fn has_heap(&self, heap_id: HeapRefId) -> bool {
96-
self.registered_heaps.contains(&heap_id)
97-
}
98-
99-
/// Recursively ensure that chunk indices are registered for a heap
100-
/// (identified by `heap_id`, with given `refs`) and all of its transitive dependencies.
101-
pub(crate) fn ensure_chunk_index_registered_inner(
102-
self: &Arc<Self>,
103-
heap_id: HeapRefId,
104-
refs: &[FrozenHeapRef],
105-
build_chunks: impl FnOnce() -> Vec<ChunkInfo>,
106-
) -> pagable::Result<()> {
107-
if self.has_heap(heap_id) {
108-
return Ok(());
109-
}
110-
111-
for dep in refs {
112-
self.ensure_chunk_index_registered(dep)?;
113-
}
114-
115-
self.register_heap(heap_id, build_chunks());
116-
Ok(())
117+
pub(crate) fn resident_heap(&self, heap_id: HeapRefId) -> Option<FrozenHeapRef> {
118+
self.resident_heaps
119+
.get(&heap_id)
120+
.and_then(|entry| entry.heap.upgrade())
117121
}
118122

119123
/// Recursively ensure that chunk indices are registered for a heap
@@ -130,21 +134,36 @@ impl StarlarkSerState {
130134
let Some(name) = heap_ref.name() else {
131135
return Ok(());
132136
};
133-
heap_ref.register_ser_state(self)?;
134137
let heap_id = HeapRefId::from_heap_name(name);
135-
self.ensure_chunk_index_registered_inner(heap_id, heap_ref.refs_slice(), || {
136-
heap_ref.build_chunk_index()
137-
})
138+
if self.resident_heap(heap_id).is_some() {
139+
return Ok(());
140+
}
141+
142+
for dep in heap_ref.refs_slice() {
143+
self.ensure_chunk_index_registered(dep)?;
144+
}
145+
146+
heap_ref.register_ser_state(self)?;
147+
let heap = heap_ref
148+
.downgrade()
149+
.expect("named FrozenHeapRef should have an inner heap");
150+
self.register_heap(heap_id, heap, heap_ref.build_chunk_index());
151+
Ok(())
138152
}
139153

140154
pub(crate) fn unregister_heap(
141155
&self,
142156
heap_id: HeapRefId,
157+
heap_ptr: *const (),
143158
chunk_bases: impl IntoIterator<Item = usize>,
144159
) {
145160
// Let future registrations proceed before removing this still-live
146161
// arena's addresses, which cannot be reused until Drop completes.
147-
self.registered_heaps.remove(&heap_id);
162+
if let Entry::Occupied(entry) = self.resident_heaps.entry(heap_id)
163+
&& entry.get().heap.points_to(heap_ptr)
164+
{
165+
entry.remove();
166+
}
148167
let mut chunks = self.chunks.write().expect("chunks lock poisoned");
149168
for base in chunk_bases {
150169
if chunks
@@ -156,6 +175,36 @@ impl StarlarkSerState {
156175
}
157176
}
158177

178+
/// Resolve a value index directly into a heap that is still resident.
179+
pub(crate) fn lookup_resident_value(
180+
&self,
181+
heap_id: HeapRefId,
182+
value_index: u32,
183+
is_str: bool,
184+
) -> Option<FrozenValue> {
185+
// Keep the arena alive while converting its indexed payload address
186+
// back to an AValueHeader pointer.
187+
let (heap, base, entry) = {
188+
let resident = self.resident_heaps.get(&heap_id)?;
189+
let chunk_index = resident
190+
.chunks_by_value_index
191+
.partition_point(|(_, entry)| entry.values_before <= value_index)
192+
.checked_sub(1)?;
193+
let (base, entry) = resident.chunks_by_value_index.get(chunk_index)?;
194+
(resident.heap.dupe(), *base, entry.dupe())
195+
};
196+
let _heap = heap.upgrade()?;
197+
let within_chunk_index = value_index.checked_sub(entry.values_before)?;
198+
let payload_offset = entry.payload_offsets.get(within_chunk_index as usize)?;
199+
let payload_ptr = base.checked_add(*payload_offset as usize)?;
200+
let header_ptr =
201+
payload_ptr.checked_sub(mem::size_of::<AValueHeader>())? as *const AValueHeader;
202+
// SAFETY: `_heap` keeps the arena alive, and this chunk entry belongs
203+
// to that resident heap.
204+
let header = unsafe { &*header_ptr };
205+
Some(FrozenValue::new_ptr(header, is_str))
206+
}
207+
159208
/// Resolve a raw payload pointer to its `(heap_id, value_index)` by
160209
/// looking up the containing chunk in `chunks` and `binary_search`ing
161210
/// the chunk's sorted `payload_offsets` for the within-chunk index.

0 commit comments

Comments
 (0)