Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion design/mvp/CanonicalABI.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,31 @@ nature of eliminating `realloc`, switching to [lazy lowering] would obviate
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
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't sure if we should place this here or in the Explainer.md


Beyond the explicit `trap_if` checks in the Python code below, implementations
may also trap during any cross-component call when
implementation-defined resource limits have been reached. In particular:
* 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.
Comment on lines +114 to +119
Copy link
Member

Choose a reason for hiding this comment

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

This is analogous to how memory.grow may 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.

Copy link
Member

Choose a reason for hiding this comment

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

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.

* 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.
Comment on lines +120 to +125
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Member

Choose a reason for hiding this comment

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

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.


In both cases, the resulting trap is non-deterministic: the same call with the
same arguments may succeed at one point and trap at another depending on the
current resource usage of the implementation. Guest code should be written
defensively, but WASI-level interfaces are also encouraged to use `result`
return types with explicit error cases for resource exhaustion where possible,
enabling guests to handle limits gracefully rather than trapping.

## Embedding

A WebAssembly Component Model implementation will typically be *embedded* into
Expand Down Expand Up @@ -492,7 +517,9 @@ free list in the free elements of `array`.

The limit of `2**28` ensures that the high 2 bits of table indices are unset
and available for other use in guest code (e.g., for tagging, packed words or
sentinel values).
sentinel values). As described in the [Introduction](#introduction),
implementations may trap at a lower limit than `MAX_LENGTH` based on
implementation-defined resource constraints.


#### Resource State
Expand Down