@@ -81,72 +81,93 @@ impl StoreOpaque {
8181 log:: trace!( "Attempting to grow the GC heap by {bytes_needed} bytes" ) ;
8282 assert ! ( bytes_needed > 0 ) ;
8383
84+ let page_size = self . engine ( ) . tunables ( ) . gc_heap_memory_type ( ) . page_size ( ) ;
85+
8486 // Take the GC heap's underlying memory out of the GC heap, attempt to
8587 // grow it, then replace it.
86- let mut memory = self . unwrap_gc_store_mut ( ) . gc_heap . take_memory ( ) ;
87- let mut delta_bytes_grown = 0 ;
88- let grow_result: Result < ( ) > = ( || {
89- let page_size = self . engine ( ) . tunables ( ) . gc_heap_memory_type ( ) . page_size ( ) ;
88+ let mut heap = TakenGcHeap :: new ( self ) ;
9089
91- let current_size_in_bytes = u64:: try_from ( memory. byte_size ( ) ) . unwrap ( ) ;
92- let current_size_in_pages = current_size_in_bytes / page_size;
90+ let current_size_in_bytes = u64:: try_from ( heap . memory . byte_size ( ) ) . unwrap ( ) ;
91+ let current_size_in_pages = current_size_in_bytes / page_size;
9392
94- // Aim to double the heap size, amortizing the cost of growth.
95- let doubled_size_in_pages = current_size_in_pages. saturating_mul ( 2 ) ;
96- assert ! ( doubled_size_in_pages >= current_size_in_pages) ;
97- let delta_pages_for_doubling = doubled_size_in_pages - current_size_in_pages;
93+ // Aim to double the heap size, amortizing the cost of growth.
94+ let doubled_size_in_pages = current_size_in_pages. saturating_mul ( 2 ) ;
95+ assert ! ( doubled_size_in_pages >= current_size_in_pages) ;
96+ let delta_pages_for_doubling = doubled_size_in_pages - current_size_in_pages;
9897
99- // When doubling our size, saturate at the maximum memory size in pages.
100- //
101- // TODO: we should consult the instance allocator for its configured
102- // maximum memory size, if any, rather than assuming the index
103- // type's maximum size.
104- let max_size_in_bytes = 1 << 32 ;
105- let max_size_in_pages = max_size_in_bytes / page_size;
106- let delta_to_max_size_in_pages = max_size_in_pages - current_size_in_pages;
107- let delta_pages_for_alloc = delta_pages_for_doubling. min ( delta_to_max_size_in_pages) ;
98+ // When doubling our size, saturate at the maximum memory size in pages.
99+ //
100+ // TODO: we should consult the instance allocator for its configured
101+ // maximum memory size, if any, rather than assuming the index
102+ // type's maximum size.
103+ let max_size_in_bytes = 1 << 32 ;
104+ let max_size_in_pages = max_size_in_bytes / page_size;
105+ let delta_to_max_size_in_pages = max_size_in_pages - current_size_in_pages;
106+ let delta_pages_for_alloc = delta_pages_for_doubling. min ( delta_to_max_size_in_pages) ;
108107
109- // But always make sure we are attempting to grow at least as many pages
110- // as needed by the requested allocation. This must happen *after* the
111- // max-size saturation, so that if we are at the max already, we do not
112- // succeed in growing by zero delta pages, and then return successfully
113- // to our caller, who would be assuming that there is now capacity for
114- // their allocation.
115- let pages_needed = bytes_needed. div_ceil ( page_size) ;
116- assert ! ( pages_needed > 0 ) ;
117- let delta_pages_for_alloc = delta_pages_for_alloc. max ( pages_needed) ;
118- assert ! ( delta_pages_for_alloc > 0 ) ;
108+ // But always make sure we are attempting to grow at least as many pages
109+ // as needed by the requested allocation. This must happen *after* the
110+ // max-size saturation, so that if we are at the max already, we do not
111+ // succeed in growing by zero delta pages, and then return successfully
112+ // to our caller, who would be assuming that there is now capacity for
113+ // their allocation.
114+ let pages_needed = bytes_needed. div_ceil ( page_size) ;
115+ assert ! ( pages_needed > 0 ) ;
116+ let delta_pages_for_alloc = delta_pages_for_alloc. max ( pages_needed) ;
117+ assert ! ( delta_pages_for_alloc > 0 ) ;
119118
120- // Safety: we pair growing the GC heap with updating its associated
121- // `VMMemoryDefinition` in the `VMStoreContext` immediately
122- // afterwards.
123- unsafe {
124- memory
125- . grow ( delta_pages_for_alloc, Some ( self . traitobj ( ) . as_mut ( ) ) ) ?
126- . ok_or_else ( || anyhow ! ( "failed to grow GC heap" ) ) ?;
127- }
128- self . vm_store_context . gc_heap = memory. vmmemory ( ) ;
119+ // Safety: we pair growing the GC heap with updating its associated
120+ // `VMMemoryDefinition` in the `VMStoreContext` immediately
121+ // afterwards.
122+ unsafe {
123+ heap . memory
124+ . grow ( delta_pages_for_alloc, Some ( heap . store . traitobj ( ) . as_mut ( ) ) ) ?
125+ . ok_or_else ( || anyhow ! ( "failed to grow GC heap" ) ) ?;
126+ }
127+ heap . store . vm_store_context . gc_heap = heap . memory . vmmemory ( ) ;
129128
130- let new_size_in_bytes = u64:: try_from ( memory. byte_size ( ) ) . unwrap ( ) ;
131- assert ! ( new_size_in_bytes > current_size_in_bytes) ;
132- delta_bytes_grown = new_size_in_bytes - current_size_in_bytes;
133- let delta_bytes_for_alloc = delta_pages_for_alloc. checked_mul ( page_size) . unwrap ( ) ;
134- assert ! (
135- delta_bytes_grown >= delta_bytes_for_alloc,
136- "{delta_bytes_grown } should be greater than or equal to {delta_bytes_for_alloc}"
137- ) ;
138- Ok ( ( ) )
139- } ) ( ) ;
129+ let new_size_in_bytes = u64:: try_from ( heap . memory . byte_size ( ) ) . unwrap ( ) ;
130+ assert ! ( new_size_in_bytes > current_size_in_bytes) ;
131+ heap . delta_bytes_grown = new_size_in_bytes - current_size_in_bytes;
132+ let delta_bytes_for_alloc = delta_pages_for_alloc. checked_mul ( page_size) . unwrap ( ) ;
133+ assert ! (
134+ heap . delta_bytes_grown >= delta_bytes_for_alloc,
135+ "{ } should be greater than or equal to {delta_bytes_for_alloc}",
136+ heap . delta_bytes_grown ,
137+ ) ;
138+ return Ok ( ( ) ) ;
140139
141- // Regardless whether growing succeeded or failed, place the memory back
142- // inside the GC heap.
143- unsafe {
144- self . unwrap_gc_store_mut ( )
145- . gc_heap
146- . replace_memory ( memory, delta_bytes_grown) ;
140+ struct TakenGcHeap < ' a > {
141+ store : & ' a mut StoreOpaque ,
142+ memory : ManuallyDrop < vm:: Memory > ,
143+ delta_bytes_grown : u64 ,
147144 }
148145
149- grow_result
146+ impl < ' a > TakenGcHeap < ' a > {
147+ fn new ( store : & ' a mut StoreOpaque ) -> TakenGcHeap < ' a > {
148+ TakenGcHeap {
149+ memory : ManuallyDrop :: new ( store. unwrap_gc_store_mut ( ) . gc_heap . take_memory ( ) ) ,
150+ store,
151+ delta_bytes_grown : 0 ,
152+ }
153+ }
154+ }
155+
156+ impl Drop for TakenGcHeap < ' _ > {
157+ fn drop ( & mut self ) {
158+ // SAFETY: this `Drop` guard ensures that this has exclusive
159+ // ownership of fields and is thus safe to take `self.memory`.
160+ // Additionally for `replace_memory` the memory was previously
161+ // taken when this was created so it should be safe to place
162+ // back inside the GC heap.
163+ unsafe {
164+ self . store . unwrap_gc_store_mut ( ) . gc_heap . replace_memory (
165+ ManuallyDrop :: take ( & mut self . memory ) ,
166+ self . delta_bytes_grown ,
167+ ) ;
168+ }
169+ }
170+ }
150171 }
151172
152173 /// Attempt an allocation, if it fails due to GC OOM, then do a GC and
0 commit comments