Skip to content

Compile-time imports in JS-API #1479

@eqrion

Description

@eqrion

After the last meeting on stringref, it seemed like providing a concrete proposal for compile-time imports could aid the discussion.

Overview

The current compilation model of WebAssembly through the JS-API provides imports when instantiating a module. This prevents web runtimes from being able to assume anything about an import beyond the type given while compiling without speculation or other tricks.

This issue proposes a minimal set of changes to allow specifying certain imports while compiling a module. This must be done carefully to not break certain invariants and optimizations that web runtimes currently rely on.

This issue does not propose any values to compile-time import. I will file a separate issue to proposes some possible builtins and a process around standardizing them.

Design constraints

Modules can be shared across web workers using postMessage

Not all import values are shareable across workers. We must be able to send shareable import values across workers, and reject unshareable values.

It’s possible that we could disable sharing modules that have any compile-time imports for an initial version, but this would need to be solved in the fullness of time.

Compiled modules can be serialized to the network cache

Web engines can cache optimized code in a network cache entry keyed off of a HTTP fetch request. If the optimized code is specialized to runtime provided import values, we will need to expand the cache key to include those values. There is a risk that specializing to keys that change every page load could effectively disable code caching.

Decoding the imports section can happen on a different thread from the imports object

Parsing and compiling a module can happen on background threads which cannot perform property lookups on an imports object.

Reading from the imports object requires knowing the keys

See ‘read the imports’. Import value lookup is performed using JavaScript ‘get property’ which requires knowledge of the key you’re looking up. It’s not possible to pull all possible values from the imports object eagerly as it may be a JavaScript proxy or other exotic object which does not provide iteration over all possible keys.

We should standardize the web interfaces that can be specialized to

Specializing to an import can be critical to the runtime performance of the module. We should provide strong guarantees about when specialization happens and to which imports.

Do not conflict with future core wasm features

Do our best to not conflict with potential future wasm proposals, such as pre-imports, staged compilation, module linking, or the component model. Make minimal or no changes to the core specification.

Proposal

Add a WebIDL attribute for shareable

This attribute is to be used on WebAssembly builtins, and possibly other Web interfaces in the future. They can be used with the structured clone algorithm, and as compile-time imports.

As they are well defined for structured clone, they are valid to be sent through postMessage. We may prevent them from being stored in user-facing persistent storage, such as IndexedDB. This is the situation with modules, as well.

Modify the JS-API compilation methods to accept optional options

dictionary WebAssemblyImportValue {
    required USVString module;
    required USVString name;
    required any value;
}
dictionary WebAssemblyCompileOptions {
    optional sequence<WebAssemblyImportValue> imports;
}

interface Module {
  constructor(BufferSource bytes, optional WebAssemblyCompileOptions options);
  ...
}

namespace WebAssembly {
	Promise<Module> compile(
BufferSource bytes,
optional WebAssemblyCompileOptions options);
	...
};

The imports field is a list of import values to apply when compiling. It is not the same kind of imports object as used when instantiating, due to the above mentioned design constraints around threading and ‘get property’.

Every import key of ‘module’ and ‘name’ must be specified at most once, or else a LinkError will be thrown. Every value provided must have the shareable WebIDL attribute.

A module compiled with imports extends the ‘Compile a WebAssembly module’ algorithm to check that the import values are compatible with the module. This could be expressed with a new embedding function module_validate_imports(module, externval) which only performs import matching and does not mutate the store. Any issues are reported with a LinkError.

The provided import values are stored in the module object. Any import provided at compile-time does not need to be provided during instantiation. The WebAssembly.Module.imports() static method will also exclude listing these imports.

Any provided import value may be specialized to when compiling the module if the engine deems it profitable. It is expected that WebAssembly standard builtins will be guaranteed to be specialized to if they are exposed by an engine.

Because every shareable value is valid for structured clone, the compiled module can always be sent with postMessage. shareable values are also expected to be safe to be cached by the browser in the network cache.

Open Questions

Should we throw LinkError for importing a non shareable value

The above proposal throws a LinkError if you import a non shareable value. It could be possible to import a non shareable value if we were to prevent that module from being sent with postMessage. Web engines with module caching would likely not specialize on those values to keep the resulting module cacheable.

Is there a better design for the compile-time imports object?

Having a different structure for the two different kinds of imports is very unfortunate.

We could use the same imports object at compile as in instantiation, but engines would be forced to do a bounce back to the originating JS thread to read the imports object when they’re compiling off the main thread. Maybe there is another option here?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions