Skip to content

Optional Imports Phase 1: Tooling ConventionsΒ #555

@alexcrichton

Description

@alexcrichton

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:

  1. Bindings generators would support per-function configuration of "this should be an optional import". For example Rust wit-bindgen's generate! macro has an async option and that would be mirrored as a new optional 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 the wit-bindgen c CLI invocation.
  2. 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
  3. 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)))
  4. 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.
  5. 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] ...
  6. 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 returns i32.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 return i32.const 0 and the actual function import is satisfied with a function that only contains unreachable.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions