-
Notifications
You must be signed in to change notification settings - Fork 93
Description
Here I'd like to write up the result of some discussion that was had today about optional imports. In the fullness of time optional imports is expected to be a relatively complex feature that's integrated into type-checking, WIT, the binary format, etc. In the near future however it should be possible to get much of the benefit with far less work.
Motivation
The loose objective at this time is that it would be nice to enable wasi-libc to use the latest-and-greatest WASI APIs immediately as soon as they're ready. Today there is no great way to answer the question of "when have sufficient runtimes upgraded to a WASI version that wasi-libc can use it" and the hope is that we can sidestep the question entirely.
A concrete use case today is the exit-with-code
function in wasi:cli/exit
. This is currently unstable but may likely become stable soon, at which point it's up to guests to be able to use it.
Design
The main idea in the near future is that we can avoid a bunch of spec/runtime work by exclusively implementing optional imports in the toolchain, notably bindings generators and wasm-component-ld
. The general flow of things would look something along the lines of:
- Bindings generators would support per-function configuration of "this should be an optional import". For example Rust
wit-bindgen
'sgenerate!
macro has anasync
option and that would be mirrored as a newoptional
option. In this manner select functions can be generated as "optional" instead of required.- Example:
wasi-libc
's binding generation for WASIp2 would pass--optional wasi:cli/exit/exit-with-code
to thewit-bindgen c
CLI invocation.
- Example:
- Language bindings would model optional imports perhaps similarly to weak functions. There'd be a generated function which would return a nullable function pointer. This pointer would be checked at runtime to determine whether the import was present or not (null-vs-not) and applications would dispatch appropriately.
- Example: in C
exit-with-code
would be bound as something along the lines of
typedef void(*wasi_cli_exit_exit_with_code_func_t)(int); // type signature of `wasi:cli/exit/exit-with-code` exit_with_code_func_t wasi_cli_exit_get_exit_with_code(void); // get the function pointer
- Example: in C
- Bindings generators would continue to import the exact same signature they import today, name mangling and all. Bindings generators would also import the same named-function, prefixed with
[is-available]
. This new function would have the signature(func (result i32))
- Example: the raw wasm imports would look like
(import "wasi:cli/[email protected]" "exit-with-code" (func (param i32))) (import "wasi:cli/[email protected]" "[is-available]exit-with-code" (func (result i32))) ;; ... or with BuildTargets.md ... (import "cm32p2|wasi:cli/[email protected]" "exit-with-code" (func (param i32))) (import "cm32p2|wasi:cli/[email protected]" "[is-available]exit-with-code" (func (result i32)))
- Bindings generators would supplement the current custom section that holds documentation/stability information with these optional annotations, indicating whether a function should be considered optional or not.
- Example: Somewhere around here in
PackageMetadata
a new map would be added for optional imports.
- Example: Somewhere around here in
wasm-component-ld
will gain new--target-world-wit ./path --target-world foo:bar/world
flags. These flags indicate what the world the final artifact should target, regardless of what the inputs are:- Example:
wasm-component-ld --target-world-wit ./my-wit --target-world wasi:cl/[email protected] ...
- Example:
- When creating a component, the
wit-component
crate would then test to see if optional imports are present in the target world. If an optional import is present in the target world (and the target world is specified), then the final component imports the function. This function is lowered as usual and the[is-available]
function is a synthesized function that returnsi32.const 1
. If the optional import is not present in the target world, or if the target world is not specified, then the[is-available]
function is synthesized to returni32.const 0
and the actual function import is satisfied with a function that only containsunreachable
.
With all of this put together this will have achieved the goal that it's possible for wasi-libc to update immediately to use new WASI APIs without affecting consumers by default. The componentization process will require a flag to opt-in to using these new APIs by passing a new --target-world*
flag to the linker via toolchain specific methods. If this isn't done then no optional import will be used, and if specified optional imports will only be turned on if they're present in the target world.
Downsides
The major downside to this approach is that there's no actual dynamic detection at component runtime, instead the "detection" is still done at compile time. This means that even if a runtime supports the API and the guest has optional support for using it the API may not be used. Fully fixing this though is thought to be "phase 2" with more full integration into the component model itself which is expected to take significantly more time than the implementation outlined above.
I've done my best to capture the result of our discussion today, but if anything looks awry to folks please let me know! I realize that this issue will not actually result in any changes in this repository itself since it's not proposing that the component model is changed just yet. Despite this though I wanted to put this in a relatively central location to get discussion since it affects lots of pieces of tooling and stakeholders.