Skip to content

Commit 07c71ab

Browse files
authored
Automatically trigger GC in {Array,Extern,Struct}Ref allocation functions (bytecodealliance#10560)
* Automatically trigger GC in `{Array,Extern,Struct}Ref` allocation functions Rather than forcing all callers to check for `GcHeapOutOfMemory`, trigger a GC, and then try again. This does force us to define `*_async` variations for when async is enabled, however; it's ultimately worth it. * cargo fmt * review feedback and fix tests * fix +runtime -gc build * more feedback and build cfg fixes * remove copy-paste assertion that doesn't apply to this method * move assertion into retry methods
1 parent 62a502a commit 07c71ab

File tree

13 files changed

+717
-247
lines changed

13 files changed

+717
-247
lines changed

crates/wasmtime/src/runtime/gc/enabled/arrayref.rs

Lines changed: 221 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,37 @@ impl ManuallyRooted<ArrayRef> {
228228
}
229229
}
230230

231+
/// An iterator for elements in `ArrayRef::new[_async].
232+
///
233+
/// NB: We can't use `iter::repeat(elem).take(len)` because that doesn't
234+
/// implement `ExactSizeIterator`.
235+
#[derive(Clone)]
236+
struct RepeatN<'a>(&'a Val, u32);
237+
238+
impl<'a> Iterator for RepeatN<'a> {
239+
type Item = &'a Val;
240+
241+
fn next(&mut self) -> Option<Self::Item> {
242+
if self.1 == 0 {
243+
None
244+
} else {
245+
self.1 -= 1;
246+
Some(self.0)
247+
}
248+
}
249+
250+
fn size_hint(&self) -> (usize, Option<usize>) {
251+
let len = self.len();
252+
(len, Some(len))
253+
}
254+
}
255+
256+
impl ExactSizeIterator for RepeatN<'_> {
257+
fn len(&self) -> usize {
258+
usize::try_from(self.1).unwrap()
259+
}
260+
}
261+
231262
impl ArrayRef {
232263
/// Allocate a new `array` of the given length, with every element
233264
/// initialized to `elem`.
@@ -237,18 +268,28 @@ impl ArrayRef {
237268
///
238269
/// This is similar to the `array.new` instruction.
239270
///
271+
/// # Automatic Garbage Collection
272+
///
273+
/// If the GC heap is at capacity, and there isn't room for allocating this
274+
/// new array, then this method will automatically trigger a synchronous
275+
/// collection in an attempt to free up space in the GC heap.
276+
///
240277
/// # Errors
241278
///
242279
/// If the given `elem` value's type does not match the `allocator`'s array
243280
/// type's element type, an error is returned.
244281
///
245282
/// If the allocation cannot be satisfied because the GC heap is currently
246-
/// out of memory, but performing a garbage collection might free up space
247-
/// such that retrying the allocation afterwards might succeed, then a
248-
/// [`GcHeapOutOfMemory<()>`][crate::GcHeapOutOfMemory] error is returned.
283+
/// out of memory, then a [`GcHeapOutOfMemory<()>`][crate::GcHeapOutOfMemory]
284+
/// error is returned. The allocation might succeed on a second attempt if
285+
/// you drop some rooted GC references and try again.
249286
///
250287
/// # Panics
251288
///
289+
/// Panics if the `store` is configured for async; use
290+
/// [`ArrayRef::new_async`][crate::ArrayRef::new_async] to perform
291+
/// asynchronous allocation instead.
292+
///
252293
/// Panics if either the allocator or the `elem` value is not associated
253294
/// with the given store.
254295
pub fn new(
@@ -266,54 +307,106 @@ impl ArrayRef {
266307
elem: &Val,
267308
len: u32,
268309
) -> Result<Rooted<ArrayRef>> {
269-
assert_eq!(
270-
store.id(),
271-
allocator.store_id,
272-
"attempted to use a `ArrayRefPre` with the wrong store"
273-
);
310+
store.retry_after_gc((), |store, ()| {
311+
Self::new_from_iter(store, allocator, RepeatN(elem, len))
312+
})
313+
}
314+
315+
/// Asynchronously allocate a new `array` of the given length, with every
316+
/// element initialized to `elem`.
317+
///
318+
/// For example, `ArrayRef::new(ctx, pre, &Val::I64(9), 3)` allocates the
319+
/// array `[9, 9, 9]`.
320+
///
321+
/// This is similar to the `array.new` instruction.
322+
///
323+
/// # Automatic Garbage Collection
324+
///
325+
/// If the GC heap is at capacity, and there isn't room for allocating this
326+
/// new array, then this method will automatically trigger a asynchronous
327+
/// collection in an attempt to free up space in the GC heap.
328+
///
329+
/// # Errors
330+
///
331+
/// If the given `elem` value's type does not match the `allocator`'s array
332+
/// type's element type, an error is returned.
333+
///
334+
/// If the allocation cannot be satisfied because the GC heap is currently
335+
/// out of memory, then a [`GcHeapOutOfMemory<()>`][crate::GcHeapOutOfMemory]
336+
/// error is returned. The allocation might succeed on a second attempt if
337+
/// you drop some rooted GC references and try again.
338+
///
339+
/// # Panics
340+
///
341+
/// Panics if your engine is not configured for async; use
342+
/// [`ArrayRef::new_async`][crate::ArrayRef::new_async] to perform
343+
/// synchronous allocation instead.
344+
///
345+
/// Panics if either the allocator or the `elem` value is not associated
346+
/// with the given store.
347+
#[cfg(feature = "async")]
348+
pub async fn new_async(
349+
mut store: impl AsContextMut,
350+
allocator: &ArrayRefPre,
351+
elem: &Val,
352+
len: u32,
353+
) -> Result<Rooted<ArrayRef>> {
354+
Self::_new_async(store.as_context_mut().0, allocator, elem, len).await
355+
}
356+
357+
#[cfg(feature = "async")]
358+
pub(crate) async fn _new_async(
359+
store: &mut StoreOpaque,
360+
allocator: &ArrayRefPre,
361+
elem: &Val,
362+
len: u32,
363+
) -> Result<Rooted<ArrayRef>> {
364+
store
365+
.retry_after_gc_async((), |store, ()| {
366+
Self::new_from_iter(store, allocator, RepeatN(elem, len))
367+
})
368+
.await
369+
}
274370

371+
/// Like `ArrayRef::new` but when async is configured must only ever be
372+
/// called from on a fiber stack.
373+
pub(crate) unsafe fn new_maybe_async(
374+
store: &mut StoreOpaque,
375+
allocator: &ArrayRefPre,
376+
elem: &Val,
377+
len: u32,
378+
) -> Result<Rooted<ArrayRef>> {
275379
// Type check the initial element value against the element type.
276380
elem.ensure_matches_ty(store, allocator.ty.element_type().unpack())
277381
.context("element type mismatch")?;
278382

279-
return Self::_new_unchecked(store, allocator, RepeatN(elem, len));
280-
281-
// NB: Can't use `iter::repeat(elem).take(len)` above because that
282-
// doesn't implement `ExactSizeIterator`.
283-
struct RepeatN<'a>(&'a Val, u32);
284-
285-
impl<'a> Iterator for RepeatN<'a> {
286-
type Item = &'a Val;
287-
288-
fn next(&mut self) -> Option<Self::Item> {
289-
if self.1 == 0 {
290-
None
291-
} else {
292-
self.1 -= 1;
293-
Some(self.0)
294-
}
295-
}
296-
297-
fn size_hint(&self) -> (usize, Option<usize>) {
298-
let len = self.len();
299-
(len, Some(len))
300-
}
301-
}
302-
303-
impl ExactSizeIterator for RepeatN<'_> {
304-
fn len(&self) -> usize {
305-
usize::try_from(self.1).unwrap()
306-
}
383+
unsafe {
384+
store.retry_after_gc_maybe_async((), |store, ()| {
385+
Self::new_from_iter(store, allocator, RepeatN(elem, len))
386+
})
307387
}
308388
}
309389

310-
/// Allocate a new array of the given elements, without checking that the
311-
/// elements' types match the array's element type.
312-
fn _new_unchecked<'a>(
390+
/// Allocate a new array of the given elements.
391+
///
392+
/// Does not attempt a GC on OOM; leaves that to callers.
393+
fn new_from_iter<'a>(
313394
store: &mut StoreOpaque,
314395
allocator: &ArrayRefPre,
315-
elems: impl ExactSizeIterator<Item = &'a Val>,
396+
elems: impl Clone + ExactSizeIterator<Item = &'a Val>,
316397
) -> Result<Rooted<ArrayRef>> {
398+
assert_eq!(
399+
store.id(),
400+
allocator.store_id,
401+
"attempted to use a `ArrayRefPre` with the wrong store"
402+
);
403+
404+
// Type check the elements against the element type.
405+
for elem in elems.clone() {
406+
elem.ensure_matches_ty(store, allocator.ty.element_type().unpack())
407+
.context("element type mismatch")?;
408+
}
409+
317410
let len = u32::try_from(elems.len()).unwrap();
318411

319412
// Allocate the array and write each field value into the appropriate
@@ -346,25 +439,35 @@ impl ArrayRef {
346439
}
347440
}
348441

349-
/// Allocate a new `array` containing the given elements.
442+
/// Synchronously allocate a new `array` containing the given elements.
350443
///
351444
/// For example, `ArrayRef::new_fixed(ctx, pre, &[Val::I64(4), Val::I64(5),
352445
/// Val::I64(6)])` allocates the array `[4, 5, 6]`.
353446
///
354447
/// This is similar to the `array.new_fixed` instruction.
355448
///
449+
/// # Automatic Garbage Collection
450+
///
451+
/// If the GC heap is at capacity, and there isn't room for allocating this
452+
/// new array, then this method will automatically trigger a synchronous
453+
/// collection in an attempt to free up space in the GC heap.
454+
///
356455
/// # Errors
357456
///
358457
/// If any of the `elems` values' type does not match the `allocator`'s
359458
/// array type's element type, an error is returned.
360459
///
361460
/// If the allocation cannot be satisfied because the GC heap is currently
362-
/// out of memory, but performing a garbage collection might free up space
363-
/// such that retrying the allocation afterwards might succeed, then a
364-
/// [`GcHeapOutOfMemory<()>`][crate::GcHeapOutOfMemory] error is returned.
461+
/// out of memory, then a [`GcHeapOutOfMemory<()>`][crate::GcHeapOutOfMemory]
462+
/// error is returned. The allocation might succeed on a second attempt if
463+
/// you drop some rooted GC references and try again.
365464
///
366465
/// # Panics
367466
///
467+
/// Panics if the `store` is configured for async; use
468+
/// [`ArrayRef::new_fixed_async`][crate::ArrayRef::new_fixed_async] to
469+
/// perform asynchronous allocation instead.
470+
///
368471
/// Panics if the allocator or any of the `elems` values are not associated
369472
/// with the given store.
370473
pub fn new_fixed(
@@ -380,19 +483,81 @@ impl ArrayRef {
380483
allocator: &ArrayRefPre,
381484
elems: &[Val],
382485
) -> Result<Rooted<ArrayRef>> {
383-
assert_eq!(
384-
store.id(),
385-
allocator.store_id,
386-
"attempted to use a `ArrayRefPre` with the wrong store"
387-
);
486+
store.retry_after_gc((), |store, ()| {
487+
Self::new_from_iter(store, allocator, elems.iter())
488+
})
489+
}
388490

389-
// Type check the elements against the element type.
390-
for elem in elems {
391-
elem.ensure_matches_ty(store, allocator.ty.element_type().unpack())
392-
.context("element type mismatch")?;
393-
}
491+
/// Asynchronously allocate a new `array` containing the given elements.
492+
///
493+
/// For example, `ArrayRef::new_fixed_async(ctx, pre, &[Val::I64(4),
494+
/// Val::I64(5), Val::I64(6)])` allocates the array `[4, 5, 6]`.
495+
///
496+
/// This is similar to the `array.new_fixed` instruction.
497+
///
498+
/// If your engine is not configured for async, use
499+
/// [`ArrayRef::new_fixed`][crate::ArrayRef::new_fixed] to perform
500+
/// synchronous allocation.
501+
///
502+
/// # Automatic Garbage Collection
503+
///
504+
/// If the GC heap is at capacity, and there isn't room for allocating this
505+
/// new array, then this method will automatically trigger a synchronous
506+
/// collection in an attempt to free up space in the GC heap.
507+
///
508+
/// # Errors
509+
///
510+
/// If any of the `elems` values' type does not match the `allocator`'s
511+
/// array type's element type, an error is returned.
512+
///
513+
/// If the allocation cannot be satisfied because the GC heap is currently
514+
/// out of memory, then a [`GcHeapOutOfMemory<()>`][crate::GcHeapOutOfMemory]
515+
/// error is returned. The allocation might succeed on a second attempt if
516+
/// you drop some rooted GC references and try again.
517+
///
518+
/// # Panics
519+
///
520+
/// Panics if the `store` is not configured for async; use
521+
/// [`ArrayRef::new_fixed`][crate::ArrayRef::new_fixed] to perform
522+
/// synchronous allocation instead.
523+
///
524+
/// Panics if the allocator or any of the `elems` values are not associated
525+
/// with the given store.
526+
#[cfg(feature = "async")]
527+
pub async fn new_fixed_async(
528+
mut store: impl AsContextMut,
529+
allocator: &ArrayRefPre,
530+
elems: &[Val],
531+
) -> Result<Rooted<ArrayRef>> {
532+
Self::_new_fixed_async(store.as_context_mut().0, allocator, elems).await
533+
}
394534

395-
return Self::_new_unchecked(store, allocator, elems.iter());
535+
#[cfg(feature = "async")]
536+
pub(crate) async fn _new_fixed_async(
537+
store: &mut StoreOpaque,
538+
allocator: &ArrayRefPre,
539+
elems: &[Val],
540+
) -> Result<Rooted<ArrayRef>> {
541+
store
542+
.retry_after_gc_async((), |store, ()| {
543+
Self::new_from_iter(store, allocator, elems.iter())
544+
})
545+
.await
546+
}
547+
548+
/// Like `ArrayRef::new_fixed[_async]` but it is the caller's responsibility
549+
/// to ensure that when async is enabled, this is only called from on a
550+
/// fiber stack.
551+
pub(crate) unsafe fn new_fixed_maybe_async(
552+
store: &mut StoreOpaque,
553+
allocator: &ArrayRefPre,
554+
elems: &[Val],
555+
) -> Result<Rooted<ArrayRef>> {
556+
unsafe {
557+
store.retry_after_gc_maybe_async((), |store, ()| {
558+
Self::new_from_iter(store, allocator, elems.iter())
559+
})
560+
}
396561
}
397562

398563
#[inline]
@@ -478,7 +643,7 @@ impl ArrayRef {
478643

479644
/// Get the values of this array's elements.
480645
///
481-
/// Note that `i8` and `i16` field values are zero-extended into
646+
/// Note that `i8` and `i16` element values are zero-extended into
482647
/// `Val::I32(_)`s.
483648
///
484649
/// # Errors

0 commit comments

Comments
 (0)