Skip to content

Add a new multi-value based ABI, parallel to the existing default ABI #247

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
alexcrichton opened this issue Feb 13, 2025 · 1 comment

Comments

@alexcrichton
Copy link
Collaborator

This is an idea that I've had bouncing around in my head for some time now but I wanted to write it down after discussing it with @sbc100 today in person as well. The basic motivation here is that I'd like to be able to write a function in Rust or C that translates to using multiple returns at the WebAssembly ABI level. The motivation here stems from the component model and the definition of the canonical ABI where right now at most one return value is supported before spilling items to the stack. While not the hardest problem to deal with in the world it'd be nicer to be able to support more return values as immediates instead of stack values (e.g. option<T> or result<T, E>).

Over the years though I've come to think that changing the default WebAssembly C ABI is the wrong way to do this. Recent learnings in the Rust community has shown that CLI flags controlling ABIs is a recipe for disaster as it's (a) easy to get flags misaligned, (b) hinders shipping precompiled artifacts, and (c) requires a fair bit of infrastructure to track what's using what. This means that I've personally concluded that a flag should not be used to change the ABI of a whole object file or compilation unit. Additionally I believe @tlively did some analysis awhile back and concluded that performance wouldn't really be helped all that much, which is also why I don't think it's worth changing the default ABI.

Instead what I'd propose to solve this is to add a new ABI to the documents here. That would translate to a new ABI in LLVM as well. The idea is that each function in C/C++/Rust would be able to define, on a per-function level, the ABI being used. Some properties of this new ABI would be:

  • Returning an aggregate would "flatten" the aggregate into multiple returns. For example returning #[repr(C)] struct A(i32, i32) in Rust would translate to (result i32 i32).
  • Other minor changes such as Pass small structs in parameters instead of memory #88 could perhaps be prototyped at the same time.
  • In C this might look like __attribute__((wasm_multivalue)) AggregateReturn foo() { ... }
  • In Rust this might look like extern "wasm-multivalue" fn foo() { ... }

Implementation-wise this could use of the cc <n> calling convention in LLVM which states:

Any calling convention may be specified by number, allowing target-specific calling conventions to be used. Target specific calling conventions start at 64.

For example we could reserve cc 64, on wasm targets, as the multi-value calling convention. Using this calling convention would require that the feature +multi-value was present, and otherwise it would generate a compiler error saying that the ABI can't be used when such a feature is disabled.

My hope is that this would enable implementing things and start migrating some select APIs, for example generated code, to this new ABI and benefit from multiple return values. This wouldn't interfere with anything else and doesn't need to involve any sort of transition plan from the current ABI to a new ABI. Instead this is a new parallel ABI to the existing one which enables intermixing them if desired. In the future a default change could be considered, but that'd otherwise be orthogonal to this change.

@dschuff
Copy link
Member

dschuff commented Feb 15, 2025

Generally speaking, I like the idea of ABIs being specifiable per-function. I think this is similar to what you can do on ARM with all the various ABIs that use the various optional hardware that have been added over the years, and I think we can probably take some inspiration from the way those are implemented.
I think there may still end up being e.g. clang flags that change the default for a compile unit, or LLVM backend flags that do likewise. It might be useful for LTO, for example (although LTO optimizations can already change calling conventions, e.g. LTO sets "fastcc" on x86 when possible).
But I think it's worth pursuing, and may well end up being impactful, especially given e.g. the more closed-world nature of emscripten and more use of LTO compared to native platforms.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants