Stream: git-wasmtime

Topic: wasmtime / PR #12942 GC: implement a grow-vs-collect heur...


view this post on Zulip Wasmtime GitHub notifications bot (Apr 02 2026 at 19:19):

cfallin requested fitzgen for a review on PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 02 2026 at 19:19):

cfallin opened PR #12942 from cfallin:gc-growth-heuristic to bytecodealliance:main:

This implements the heuristic discussed in #12860: it replaces the existing behavior where Wasmtime's GC, when allocating, will continue growing the GC heap up to its size limit before initiating a collection. That behavior optimizes for allocation performance but at the cost of resident memory size -- it is at one extreme end of that tradeoff spectrum.

There are a number of use-cases where there may be heavy allocation traffic but a relatively small live-heap size compared to the total volume of allocations. For example, lots of temporary "garbage" may be allocated by many workloads. Or, more pertinently to #12860, a C/C++ workload that uses the underlying GC heap only for exceptions, and uses those exceptions in a way that only one exnref is live at a time (no exn objects are stashed away and used later), will also generate a lot of "garbage" during normal execution. These kinds of workloads benefit significantly from more frequent collection to keep the resident-set size small. This also may benefit performance, even accounting for the cost of the collection itself, because it keeps the footprint of touched memory within higher cache-hierarchy levels.

In order to accommodate that kind of workload while also presenting reasonable behavior to large-working-set-size benchmarks, it is desirable to implement an adaptive policy. To that end, this PR implements a scheme similar to our OwnedRooted allocation/collection algorithm (and specified explicitly [here] by fitzgen): we use the last live-heap size (post-collection) compared to current capacity to decide whether to grow or collect. When the current capacity is more than twice the last live-heap size, we collect first; if we still can't allocate, then we grow. Otherwise, we just grow.

The idea is that (when combined with an exponential heap-growth rule) the continuous-allocation case will collect once at each power-of-two, then grow; this is "amortized constant time" overhead. A case with a stable working-set size but with some ups and downs will never hit a "threshold-thrashing" problem: the heap capacity will tend toward twice the live-heap size, in the steady state (see proof here for the analogous algorithm for OwnedRooted). Thus we have a nice, deterministic bound no matter what, with no bad (quadratic or worse) cases.

This PR adds a test that creates a bunch of almost-immediately-dead garbage (allocates a GC struct that is only live for one iteration of a loop) and checks the heap size at each iteration. To allow this check, it also adds a method to Store to get the current GC heap capacity, which seems like a generally useful kind of observability as well.

[here]: https://github.com/bytecodealliance/wasmtime/pull/12860#issuecomment-4158147842
[proof]: https://github.com/bytecodealliance/wasmtime/blob/e5b127ccd71dbd7d447a32722b2c699abc46fe61/crates/wasmtime/src/runtime/gc/enabled/rooting.rs#L617-L678

<!--
Please make sure you include the following information:

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->

view this post on Zulip Wasmtime GitHub notifications bot (Apr 02 2026 at 19:19):

cfallin requested wasmtime-core-reviewers for a review on PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 02 2026 at 20:12):

fitzgen created PR review comment:

Especially since layout.size() is being used here on increments.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 02 2026 at 20:12):

fitzgen created PR review comment:

Nitpick: this isn't quite the same as "live" since there may be unreachable objects we just haven't collected yet.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 02 2026 at 20:12):

fitzgen submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 02 2026 at 20:12):

fitzgen created PR review comment:

This function's behavior seems weird to me now. In particular, it is strange that we would always grow if bytes_needed is provided, even when collection freed up more than enough space in the GC heap.

I guess what I would expect is something like this:

if caller is Store::gc || post_gc_allocated_bytes < gc_heap_capacity / 2 {
    collect();
    if bytes_needed > gc_heap_capacity - post_gc_allocated_bytes {
        grow();
    }
} else {
    grow();
}

And I'm not even 100% sold on always-collect-if-the-caller-is-Store::gc but if we do want to have that behavior, then we probably want to pull this apart into multiple functions (collect-or-grow vs collect-and-then-grow-if-necessary) and just have Store::gc call the latter.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 02 2026 at 20:12):

fitzgen created PR review comment:

The free list can round sizes up to its min size and min alignment, so it is more accurate to do the following:

        let size = self.index(drc_ref).object_size();
        let layout = FreeList::layout(size);
        self.allocated_bytes -= layout.size();

view this post on Zulip Wasmtime GitHub notifications bot (Apr 02 2026 at 21:54):

github-actions[bot] added the label wasmtime:api on PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 02 2026 at 21:54):

github-actions[bot] added the label wasmtime:ref-types on PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 02 2026 at 21:54):

github-actions[bot] commented on PR #12942:

Subscribe to Label Action

cc @fitzgen

<details>
This issue or pull request has been labeled: "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 <code>.github/subscribe-to-label.json</code> configuration file.

Learn more.
</details>

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 22:20):

cfallin updated PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 22:20):

cfallin updated PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 22:20):

cfallin submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 22:20):

cfallin submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 22:20):

cfallin created PR review comment:

Clarified.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 22:20):

cfallin submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 22:20):

cfallin created PR review comment:

So this one is a little confusing to me too wrt intent. Note that this function's only caller is Store::gc, so we can safely const-prop your OR-condition; then the intent is I guess "always collect, then grow if request is greater than likely available size". I've updated to this and renamed to collect_and_maybe_grow_gc_heap to hopefully make that clearer.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 22:22):

cfallin created PR review comment:

Done!

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 22:33):

cfallin updated PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 22:33):

cfallin commented on PR #12942:

Updated; thanks!

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 23:16):

fitzgen submitted PR review:

Thanks!

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 23:16):

fitzgen created PR review comment:

Indentation looks like it got messed up here

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 23:25):

cfallin created PR review comment:

Hmm, actually, there's something weird in the current test failure that removing the if bytes_needed > ... conditional resolves -- will investigate more next week...

view this post on Zulip Wasmtime GitHub notifications bot (Apr 03 2026 at 23:25):

cfallin submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 08 2026 at 22:26):

cfallin commented on PR #12942:

@fitzgen I looked into the test failure here and it seems to be kind of a fundamental mismatch between the null collector's inline path and Store::gc's contract (here) that allocation may not succeed even after GC.

The DRC collector's allocation path goes through retry_after_gc_async (via the gc_alloc_raw libcall), which implements the new two-level retry behavior in this PR, which (given the algorithm) is guaranteed to grow if more space is needed. However, the null collector's inline path (NullCompiler::emit_inline_alloc) only calls the grow_gc_heap libcall (which calls Store::gc with bytes_needed) and then retries allocation once. That's not sufficient to cause a growth to occur with the new algorithm, as long as your requested heuristic) is present.

I think the actual bug here appears to be the mismatch in contracts between the inline alloc path and Store::gc's contract, but it also seems like we shouldn't be duplicating the growth heuristic or retry logic multiple times. I kind of prefer restoring the old Store::gc behavior to keep the null allocator working but could either (i) refactor the null allocator to have a double-retry inline or (ii) remove the inline allocation path altogether, and make the same libcall as the DRC collector, if you prefer.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 08 2026 at 22:47):

fitzgen commented on PR #12942:

However, the null collector's inline path (NullCompiler::emit_inline_alloc) only calls the grow_gc_heap libcall (which calls Store::gc with bytes_needed) and then retries allocation once. That's not sufficient to cause a growth to occur with the new algorithm, as long as your requested heuristic) is present

I think the grow_gc_heap libcall should just grow the GC heap directly, not try to do it indirectly through store.gc(Some(bytes_needed)) or store.grow_or_collect_gc_heap. We should just make StoreOpaque::grow_gc_heap in crates/wasmtime/src/runtime/store/gc.rs be pub(crate) and call it from the grow_gc_heap libcall.

How does that sound?

view this post on Zulip Wasmtime GitHub notifications bot (Apr 08 2026 at 23:33):

cfallin updated PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 08 2026 at 23:34):

cfallin updated PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 08 2026 at 23:34):

cfallin commented on PR #12942:

Ah, right, that's way simpler -- done!

view this post on Zulip Wasmtime GitHub notifications bot (Apr 08 2026 at 23:35):

cfallin updated PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 08 2026 at 23:36):

cfallin updated PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 08 2026 at 23:37):

cfallin has enabled auto merge for PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 08 2026 at 23:47):

fitzgen submitted PR review:

Thanks!

view this post on Zulip Wasmtime GitHub notifications bot (Apr 09 2026 at 02:13):

cfallin updated PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 09 2026 at 02:16):

cfallin has disabled auto merge for PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 09 2026 at 02:16):

cfallin commented on PR #12942:

There's a weird failure with the GC oom test that wasn't there before -- will try to bottom this out later when I have some more time.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 10 2026 at 17:14):

cfallin updated PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 10 2026 at 17:15):

cfallin has enabled auto merge for PR #12942.

view this post on Zulip Wasmtime GitHub notifications bot (Apr 10 2026 at 17:28):

cfallin added PR #12942 GC: implement a grow-vs-collect heuristic. to the merge queue

view this post on Zulip Wasmtime GitHub notifications bot (Apr 10 2026 at 17:51):

cfallin removed PR #12942 GC: implement a grow-vs-collect heuristic. from the merge queue

view this post on Zulip Wasmtime GitHub notifications bot (Apr 10 2026 at 17:52):

cfallin merged PR #12942.


Last updated: Apr 12 2026 at 23:10 UTC