-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Reuse Wasm linear memories code for GC heaps #10503
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
f18aed4
to
087345c
Compare
Subscribe to Label Actioncc @fitzgen
This issue or pull request has been labeled: "cranelift", "cranelift:area:x64", "wasmtime:api", "wasmtime:ref-types"
Thus the following users have been cc'd because of the following labels:
To subscribe or unsubscribe from this label, edit the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've gotten through a chunk of this, but if possible I do really think it would be nice to split this apart. I realize it's extra work over what's already here to split this apart but it's pretty hard to review this all-at-once even though I was helping out for at least a chunk of it. If it's too hard it doesn't have to be split but I tend to get nervous when big changes to core runtime bits happen all in one large PR
@@ -92,7 +92,14 @@ pub struct InstanceAllocationRequest<'a> { | |||
/// InstanceAllocationRequest, rather than on a &mut InstanceAllocationRequest | |||
/// itself, because several use-sites require a split mut borrow on the | |||
/// InstanceAllocationRequest. | |||
pub struct StorePtr(Option<NonNull<dyn VMStore>>); | |||
pub struct StorePtr(Option<crate::vm::SendSyncPtr<dyn VMStore>>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mind leaving this as NonNull
to avoid the cognitive overhead of using SendSyncPtr
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(un-resolving this since this is still SendSyncPtr
?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh huh, I guess I lost this in a rebase or something, because I definitely undid this change at one point
assert!(self.async_support()); | ||
if let Some(bytes_needed) = bytes_needed { | ||
if self | ||
.on_fiber(|store| store.maybe_async_grow_gc_heap(bytes_needed).is_ok()) | ||
.await | ||
.unwrap_or(false) | ||
{ | ||
return; | ||
} | ||
} | ||
|
||
self.gc_async().await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be inverted? First GC then maybe grow if necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want to grow first because
-
it is usually cheaper (just updating the accessible region of a mapping; even copying bytes to a new region is also cheaper) and
-
if we swapped the order, then we could hit the following pathological case:
// Assume: the GC heap has capacity for N objects, and we have N-1 // objects allocated and held alive. // Allocate an object, now the GC heap is at capacity. We do not keep the // object live, however. drop(allocate()); loop { // Upon entry of this loop, the GC heap is at capacity. // Allocate another object. This triggers a collect-or-else-grow, the // collection reclaims the previously allocated object, and then this // allocation succeeds without needing to grow the heap. drop(allocate()); // At the end of this loop, the GC heap is still at capacity. }
This scenario means we will trigger a GC on every allocation inside the loop which is super pathological. We should really only enter into this kind of behavior when there really is no more memory we can give the GC heap (and even then, the embedder probably wants to cancel the execution via async epochs/fuel at some point; may eventually make sense to add a GC limiter style API).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, but isn't this current behavior pathological too? GC heaps will, by default, balloon to 4G before a GC ever runs?
This makes me think that there's not an ideal answer one way or the other, but picking either extreme seems bad?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think growing to the limit before doing GC is actually what you want: you don't want to do GC if you can avoid it. Growing the heap is faster than doing a GC. There are knobs for limiting the max size of memories, including the GC heap, so unbounded growth isn't really a concern (also there is a hard bound of 4 GiB since the GC heap is a 32-bit memory). And we are likely going to throw away the whole store, and the GC heap along with it, before we ever need to collect, and doing that is also much faster than doing a collection.
`VMRuntimeLimits` was renamed to `VMStoreContext` a little while back. Split off from bytecodealliance#10503
`VMRuntimeLimits` was renamed to `VMStoreContext` a little while back. Split off from bytecodealliance#10503
`VMRuntimeLimits` was renamed to `VMStoreContext` a little while back. Split off from #10503
…k` module to the top level of the crate Split off from bytecodealliance#10503
…k` module to the top level of the crate Split off from bytecodealliance#10503
And let alias analysis dedupe and clean up multiple uses, instead of trying to do that manually in our Wasm-to-CLIF frontend by loading the pointer once in the entry block and trying to keep track of whether it is actually necessary to pre-declare the pointer and all that. Split off from bytecodealliance#10503
…k` module to the top level of the crate (bytecodealliance#10517) Split off from bytecodealliance#10503
* Support adding additional capacity to `FreeList`s Split out from #10503 * Add a test for `FreeList::add_capacity` * Also test when old capacity is not a multiple of our alignment * Add comments about alignment-rounding and free list capacity * Don't run little example as a doc test
…nt` (#10526) And let alias analysis dedupe and clean up multiple uses, instead of trying to do that manually in our Wasm-to-CLIF frontend by loading the pointer once in the entry block and trying to keep track of whether it is actually necessary to pre-declare the pointer and all that. Split off from #10503
f9470dc
to
bff10fe
Compare
Instead of bespoke code paths and structures for Wasm GC, this commit makes it so that we now reuse VM structures like
VMMemoryDefinition
and bounds-checking logic. Notably, we also reuse all the associated bounds-checking optimizations and, when possible, virtual-memory techniques to completely elide them.Furthermore, this commit adds support for growing GC heaps, reusing the machinery for growing memories, and makes it so that GC heaps always start out empty. This allows us to properly delay allocating the GC heap's storage until a GC object is actually allocated.
Fixes #9350