Skip to content

Asyncify: spill reference-typed locals to tables - #9159

Open
NellowTCS wants to merge 3 commits into
WebAssembly:mainfrom
NellowTCS:asyncify-reference-types
Open

NellowTCS wants to merge 3 commits into
WebAssembly:mainfrom
NellowTCS:asyncify-reference-types

Conversation

@NellowTCS

@NellowTCS NellowTCS commented Sep 26, 2026 •

Copy link
Copy Markdown

Fixes #3739.

Right now wasm-opt --asyncify aborts if a reference-typed value is live across an unwind. Asyncify spills locals into a linear-memory stack, and references can't be stored there, so it gives up.
This was annoying for my IPC/RPC runtime, Saikuro, so I decided to go fix Asyncify.

As discussed by @kripken, @martianboy, and @surma in #3739, the fix is to spill references into tables instead of linear memory.

The Asyncify ABI/API remains unchanged, so this shouldn't impact users beyond fixing reference-type support, I believe.
Asyncify still only supports one active pause at a time, though. As a result, asyncify_start_unwind now traps if it sees a nonzero cursor.

However, multi-pause support would need per-pause regions in the tables. As pointed out, that's a separate problem and a much bigger change, so I left it out of this PR.

Note: This is essentially a cleaned-up and "productionized" version of the proof-of-concept @alexdoesh posted in #3739.

@NellowTCS
NellowTCS requested a review from a team as a code owner September 26, 2026 05:28
@NellowTCS
NellowTCS requested review from kripken and removed request for a team September 26, 2026 05:28
@NellowTCS NellowTCS changed the title spill reference locals to growable tables Asyncify: spill reference-typed locals to tables Sep 26, 2026
@kripken

kripken commented Sep 28, 2026

Copy link
Copy Markdown
Member

Back in the day we did think this would make sense eventually, but meanwhile JSPI provides what Asyncify does but in the VM itself, which is a lot more efficient. That support of course includes reference-typed locals.

It sounds like you might be using Asyncify in some other way? Can you explain the motivation and how important this is for you?

@NellowTCS

Copy link
Copy Markdown
Author

Hi!!!
I definitely agree that JSPI does this (and does it much better), but the problem with it is adoption. Currently, it's a new Baseline feature, so a lot of devices/people don't have support for it yet.

I was planning to use this for Saikuro's executor abstraction (saikuro-exec) on its WASM path. I had implemented a block_on that prefers JSPI when available, but will eventually (using the changes in this PR) also fall back to Asyncify in environments where JSPI is unavailable.

The main issue I ran into is that reference-typed values can remain live across suspension points (which is often the case in Saikuro). In those cases Asyncify currently aborts, which makes it impossible to transform otherwise-valid modules.

@kripken

kripken commented Sep 29, 2026

Copy link
Copy Markdown
Member

I definitely agree that JSPI does this (and does it much better), but the problem with it is adoption. Currently, it's a new Baseline feature, so a lot of devices/people don't have support for it yet.

Maybe not yet, but should be very soon, as quickly as vendors push out browser updates? (Safari 27 was the last major browser to add this, and was recently released).

Overall this is not a simple change, so I wonder if it makes sense to land or not, given it will be obsolete shortly.

@NellowTCS

NellowTCS commented Sep 30, 2026 •

Copy link
Copy Markdown
Author

That's fair!

The main reason I'm trying to get reference spilling working in Asyncify is that Saikuro is meant to run across a variety of environments (Wasmtime/Wasmer for standalone, embedded WebViews, etc.) where JSPI is often not supported.

Without it, the only alternative currently implemented to suspend execution in Wasm is a busy-poll spin loop. But because Wasm holds the CPU while spinning, futures waiting on host events/JS promises can't resolve and end up timing out/deadlocking, which is... not ideal.

@kripken

kripken commented Sep 30, 2026

Copy link
Copy Markdown
Member

I see now, then it is for non-Web places... yes, wasm stack switching is still a way off, and it is what is needed there.

In that case maybe this makes sense to land. Let me read the code some more.

Comment thread src/passes/Asyncify.cpp Outdated
type,
asyncifyMemory));
offset += size;
if (type.isRef()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Places like this are getting quite verbose with the two different code paths now, ref and non-ref. How about moving this into a helper function?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be resolved now

Comment thread src/passes/Asyncify.cpp Outdated
type,
asyncifyMemory));
offset += size;
if (type.isRef()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reference types crash wasm-opt‘s asyncify pass

2 participants