team icon indicating copy to clipboard operation
team copied to clipboard

Splitting and lazy loading wasm modules

Open sendilkumarn opened this issue 7 years ago • 14 comments

This issue arises based on this discussion.

When converting an entire library into a wasm module, it is quite often we end up in huge size wasm output (~equal to the binary size). This will surely affect the wide usage of wasm.

One important usage of wasm is to use native libraries in the web application straightaway for the speed and other benefits. Splitting of wasm into chunks / modules will have its own benefits. This will probably followed by lazy-loading of the binaries as and when needed.

While, streaming compiler looks promising for libraries that are medium sized. It is better to provide an option to chunk the wasm and load them easily.

sendilkumarn avatar Feb 12 '18 09:02 sendilkumarn

JavaScript has an official way to lazy-load modules: the import() expression. This allows it to delay loading the module until it is needed. Naturally this allows for lazy-loading .wasm files as well, because .wasm files are ES6 modules.

I'm not sure if there are plans to support import() directly in WebAssembly, but it should be possible to accomplish something similar by having WebAssembly call into JavaScript, and then JavaScript can do the actual import() call.

However, it is important to note that the import() expression always loads modules relative to the file which contains the import(). So if you have a WebAssembly file foo.wasm, and a JavaScript file bar/qux.js, and the JavaScript file uses import(), then it will always import the modules relative to the bar directory. So when doing the lazy-loading, it will have to take that into account and compensate for it.

Pauan avatar Feb 12 '18 11:02 Pauan

Wasm modules don't support splitting up well yet, as shared components like allocators must be included in every component. For this to work well, we'd need dynamic linking support.... right now apparently they want you to go through js but I doubt that this is fast enough for usage in e.g. an allocator. IDK though, maybe js engines can inline such a layer.

est31 avatar Feb 13 '18 14:02 est31

I'm not sure if there are plans to support import() directly in WebAssembly, but it should be possible to accomplish something similar by having WebAssembly call into JavaScript, and then JavaScript can do the actual import() call.

The plan is to eventually have WASM modules just be ES modules. Once that's in place, import() should just work for WASM modules (unless there's some complication I'm missing).

linclark avatar Feb 13 '18 15:02 linclark

unless there's some complication I'm missing

Well I am not sure, as @est31 points out there is an ambiguity in sharing allocators and dynamic linking of the wasm modules.

using them as ES Modules is clear. But how can we split one giant wasm file into separate modules

sendilkumarn avatar Feb 13 '18 16:02 sendilkumarn

I think we could split at the crate boundary, assuming one of

  • All of an upstream crate's API was extern "C" (Annoying)

  • We define a Rust ABI that maps onto wasm. (Hard)

Exported generics, if allowed, would have to be monomorphised into the downstream crate, I guess.

Basically, it is the same issues we run into when splitting an existing binary across multiple dylibs... Which Rust doesn't have a great story for.

fitzgen avatar Feb 13 '18 16:02 fitzgen

I wonder if we can make any progress on this. wasm-bindgen now supports wasm-pack --target web which exports wasm as a ES module.

There is documentation on the webassembly website about dynamic loading using module imports/exports.

It would be nice to be able to compile libraries as dylibs that can be linked against for other components.

najamelan avatar Mar 24 '19 10:03 najamelan

Any news on this?

giacomocariello avatar Jul 25 '20 16:07 giacomocariello

FYI I have plans to implement profile-guided module splitting functionality in Binaryen to facilitate lazy loading of code that is not needed for startup. Once that work is farther along, perhaps we can coordinate on a way to expose that functionality ergonomically in wasm-pack.

tlively avatar Aug 12 '20 00:08 tlively

Hi! Is there any progress in this regards? :)

wdanilo avatar May 17 '22 04:05 wdanilo

FWIW Binaryen now has a wasm-split tool that does what I described back in 2020. It's integrated into Emscripten and documented here: https://emscripten.org/docs/optimizing/Module-Splitting.html#module-splitting.

This should work out of the box with Rust when compiling to the wasm32-unknown-emscripten target, but that's unfortunately not compatible with wasm-pack.

tlively avatar May 17 '22 04:05 tlively

Oh, that's super interesting! Thanks for letting me know. I'm so sad that wasm-pack does not support it though :/

wdanilo avatar May 17 '22 05:05 wdanilo

I'm not familiar with how the wasm-split tool works. Any chance something similar could be built for the wasm32-unknown-unknown target?

lukechu10 avatar May 17 '22 17:05 lukechu10

wasm-split itself isn't specific to Emscripten in any way, so it could be used with wasm32-unknown-unknown. The only tricky part is that it requires some special new JS to be provided to the split module as an import, so whatever tool generates the JS would need to be told how to generate that extra code. Alternatively, you could maintain your own JS and add the extra code yourself.

tlively avatar May 17 '22 17:05 tlively

I've built a prototype that addresses this issue in what I think is quite an elegant way. See https://github.com/rustwasm/wasm-bindgen/issues/3939

jbms avatar Apr 29 '24 19:04 jbms