Document implementation-defined resource limits in Canonical ABI#616
Document implementation-defined resource limits in Canonical ABI#616ricochet wants to merge 1 commit intoWebAssembly:mainfrom
Conversation
Allow implementations to non-deterministically trap on cross-component calls when resource limits are exceeded, covering handle table capacity and value transfer size. This aligns with how memory.grow works in Core WebAssembly. See WebAssembly/WASI#890 for context.
| this issue, allowing guest wasm code to handle failure by eagerly returning | ||
| some value of the declared return type to indicate failure. | ||
|
|
||
| ### Implementation-Defined Limits |
There was a problem hiding this comment.
I wasn't sure if we should place this here or in the Explainer.md
| * An implementation may trap when the total size of values being transferred | ||
| across a component boundary (via lifting or lowering) exceeds an | ||
| implementation-defined limit. For example, lifting a `list<u8>` of length N | ||
| from guest linear memory requires an intermediate allocation of at least N | ||
| bytes on the host, and implementations may refuse to perform such an | ||
| allocation when N is very large. |
There was a problem hiding this comment.
I understand why we need to allow traps with the current ABI.
If/when we have lazy lowering, can this trap condition be removed?
And maybe even go the opposite way: with lazy lowering, the callee should never trap if it can not process it all at once.
There is no way for a guest to find out what the host considers "very large". Microcontrollers might already find 1MB very large. We shouldn't end up in a situation where guests need to play a game of "guess the host's limit" and be penalized with a trap in case they guess wrong.
There was a problem hiding this comment.
Agreed. Even with our current eager ABI, because hosts have host powers, it seems like, as long as the WIT function signature allows some sort of graceful failure return value, a host should ideally return a failure value on too-large-parameter instead of trapping. (Adding lazy lowering will just add the equivalent expressive power to wasm-implemented functions.)
Now I can imagine that implementing graceful failure would take time and effort and so trapping might be the right choice in the short term. However, because a host runtime is always allowed to unilaterally abort execution (think: host crash, timeout), and because trap = "abort execution" in component-land, trapping is just the host exercising its freedom and it's more of a quality of implementation issue.
I think what both these arguments mean is that we don't need this bullet.
| * An implementation may trap when the handles table has reached an | ||
| implementation-defined capacity that is at or below `Table.MAX_LENGTH`. This | ||
| allows implementations to bound the host-side memory used to track resources, | ||
| waitables and other table elements. This is analogous to how `memory.grow` | ||
| may non-deterministically return `-1` in Core WebAssembly when the | ||
| implementation cannot satisfy the allocation. |
There was a problem hiding this comment.
This is analogous to how
memory.growmay non-deterministically return-1
One difference is that memory.grow gracefully returns control flow back to the guest. A trap doesn't.
Reaching a max table capacity is similar to OOM and many languages don't handle that. Even Rust doesn't support that on stable yet. Because of that, I think trapping is indeed a reasonable default for the C/M.
OTOH, there are also environments where the guest is fully prepared to handle resource exhaustion and would much rather prefer missing out on a single resource over trapping the entire process.
If trapping becomes the default, is there a way for a guest to opt-out of this via e.g. a canonopt? Turning every resource handle into an option of a resource handle at the ABI layer.
There was a problem hiding this comment.
Great points. For host-implemented functions being passed handles from guest-to-host, I think the same reasoning as above applies (the host should ideally fail gracefully or, because it's always allowed, trap; too-many-host-handles seems like it'd be pretty rare in non-pathological scenarios so trapping seems Fine).
When the guest is lowering a handle with the current eager ABI, indeed there is already a trap_if(i > Table.MAX_LENGTH) in the add function in the Table section of the ABI, so nothing new needs to be said for this case. But for the Lazy ABI, that's a good point @badeend that we could consider making this fallible.
But overall, I think this bullet might not be needed either.
Allow implementations to non-deterministically trap on cross-component
calls when resource limits are exceeded, covering handle table capacity
and value transfer size.
This aligns with how memory.grow works in Core WebAssembly.
See WebAssembly/WASI#890 for context.