multi-memory icon indicating copy to clipboard operation
multi-memory copied to clipboard

Use-case for multi-memory: Component Model object transfer browser polyfill

Open eyebrowsoffire opened this issue 2 years ago • 2 comments

The component model (https://github.com/WebAssembly/component-model) is based on a "shared-nothing" contract between two wasm modules, which means they do not directly import each other's memory. In the component model, when two components (wasm modules) communicate, the host runtime is responsible for facilitating copies of objects between the two components according to what is specified by their interface types, which allows each component to have isolated control over its own memory.

However, no browsers support this functionality yet, and those working on the component model have produced polyfill code that emulates the functionality of a host with component model support within the browser. However, without multi-memory support, the code copying of these objects from one memory to another must be written in JavaScript, which essentially means all interop between components is burdened with a jump to the JavaScript environment and back. That has been shown in our microbenchmarking to be much more costly than direct wasm-to-wasm calls (~14x performance difference in our benchmarks). With multi-memory support, the host polyfill for copying objects across modules could be emitted in WebAssembly, which means we would be able to avoid a context switch into the JavaScript environment.

eyebrowsoffire avatar Feb 02 '23 22:02 eyebrowsoffire

I was talking about this with @conrad-watt and a few others at WasmCon this past week and I wanted to fill in some more details here as well. Much of this is concretely implemented today so I wanted to provide a few links to things for folks to take a look at if they're interested.

The component model can be polyfilled to run in a browser using jco transpile which "decompiles" a component into a set of core wasm modules plus JS glue. This supports arbitrary components, including components with other nested components inside of them. This decompilation process is implemented in Wasmtime (where part of Wasmtime is compiled to wasm to run with jco which is written in JS) and is engineered to implement the component model on top of a core wasm implementation. On the web this means that everything is implemented with WebAssembly.Module, for example.

Part of Wasmtime's compilation process, however, is that whenever two components communicate with each other (e.g. one function is exported by a component and then imported into another) an "adapter module" is generated. This adapter module is a core wasm module which translates between the two components and applies validation. For example it would verify that an enum is in-bounds or that a string is valid unicode. Additionally this copies between memories so the adapter will copy a list<u8> from one component to another. This is done by using the multi-memory proposal inside of the adapter module and it imports the linear memories of the source and destination components.

This means that jco is emitting core wasm modules which use multi-memory to implement components. This does not work in a browser currently due to this proposal not being phase 4 yet, so a separte polyfill pass has been implemented which translates all loads/stores from memories 1+ into function calls. JS will then perfom the load/store on behalf of the component from the relevant memory and return the result.

The final result of this is that it's possible to run, with jco transpile, components on the web today. These components can internally have multiple components that communicate with each other using types like list<u8> and string. An example of this is a set of exercises I used last week as well as exercises prepared by @guybedford (hello! wanted to cc you on this issue as well if you're intereseted).


Performance-wise I have not personally been benchmarking so I do not have numbers. I have no reason to second-guess the numbers from @eyebrowsoffire above, they sound right to me. I've personally been gunning for feature-complete-ness at this time to have the ability to run demos and proofs-of-concept in JS at this time. Nevertheless I wanted to point out that the component model's polyfill to JS has a concrete implementation today which is actively using multi-memory and can be turned on with a flip of a switch.

I'd be happy to share more details if others are interseted, but otherwise figured this would perhaps be a useful data point towards advancing the phase stage of this proposal.

alexcrichton avatar Sep 11 '23 20:09 alexcrichton

This means that jco is emitting core wasm modules which use multi-memory to implement components. This does not work in a browser currently due to this proposal not being phase 4 yet, so a separte polyfill pass has been implemented which translates all loads/stores from memories 1+ into function calls. JS will then perfom the load/store on behalf of the component from the relevant memory and return the result.

Nevertheless I wanted to point out that the component model's polyfill to JS has a concrete implementation today which is actively using multi-memory and can be turned on with a flip of a switch.

Just to specifically amplify these paragraphs, jco was shown off at Wasmcon, attracting a lot of attention, and (IIUC) is currently the only way to deploy "componentized" code to the Web. Based on this, it's likely that we'll start to see code being deployed to the Web that would be significantly faster if multi-memory was widely supported/enabled by default.

conrad-watt avatar Sep 11 '23 23:09 conrad-watt