1818//! Implementation of StarlarkSerializeContext.
1919
2020use std:: collections:: BTreeMap ;
21+ use std:: mem;
2122use std:: sync:: Arc ;
2223use std:: sync:: RwLock ;
2324
2425use allocative:: Allocative ;
25- use dashmap:: DashSet ;
26+ use dashmap:: DashMap ;
27+ use dashmap:: mapref:: entry:: Entry ;
28+ use dupe:: Dupe ;
2629use pagable:: PagableSerialize ;
2730use pagable:: PagableSerializer ;
2831
@@ -33,6 +36,8 @@ use crate::pagable::static_value::get_static_value_id;
3336use crate :: values:: FrozenValue ;
3437use crate :: values:: layout:: heap:: arena:: ChunkInfo ;
3538use crate :: values:: layout:: heap:: heap_type:: FrozenHeapRef ;
39+ use crate :: values:: layout:: heap:: heap_type:: WeakFrozenHeapRef ;
40+ use crate :: values:: layout:: heap:: repr:: AValueHeader ;
3641use 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 ) ]
5870pub ( 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
6677impl 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