thanks everyone for the tips!
running carton
i tried tonight to follow @svanimpe ’s example, trying to use carton
to run a minimal example, and very quickly ran into the sobering reality which is that carton
does not support my operating system, Ubuntu 24.04.
$ swift run carton dev --custom-index-page Web/index.html
Building for debugging...
[1/1] Write swift-version--75355AAB4E86B17C.txt
Build of product 'carton' complete! (0.54s)
- checking Swift compiler path: /home/ubuntu/.carton/sdk/wasm-6.0.2-RELEASE/usr/bin/swift
- checking Swift compiler path: /home/ubuntu/.swiftenv/versions/wasm-6.0.2-RELEASE/usr/bin/swift
Fetching release assets from https://api.github.com/repos/swiftwasm/swift/releases/tags/swift-wasm-6.0.2-RELEASE
Response contained body, parsing it now...
Swift/ErrorType.swift:253: Fatal error: Error raised at top level: This version of the operating system is not supported
💣 Program crashed: Illegal instruction at 0x00007c1401ee299f
Thread 4 crashed:
0 0x00007c1401ee299f _assertionFailure(_:_:file:line:flags:) + 351 in libswiftCore.so
1 0x00007c1401f32e1e swift_errorInMain + 829 in libswiftCore.so
2 async_MainTY2_ + 41 in carton at /swift/wasm-test/.build/checkouts/carton/Sources/carton/main.swift:35:11
33│ import CartonDriver
34│
35│ try await main(arguments: CommandLine.arguments)
│ ▲
36│
Backtrace took 0.03s
Press space to interact, D to debug, or any other key to quit (23s)
Swift makes this look a lot worse than really is, because it insists on backtracing the crash it exits with whenever it catches an error thrown from main
. this is actually a controlled exit thrown from here:
Swift on WebAssembly does not actually distribute binaries for Ubuntu 24.04. but surely we have seen that Swift on WebAssembly is capable of running on Ubuntu 24.04. so i modified the code to acquire the ubuntu22.04
binary on Ubuntu 24.04, which assumes that libc on Ubuntu 24.04 is compatible with libc on Ubuntu 22.04.
case "24.04":
return "ubuntu22.04"
this worked, and i was able to use this modified carton to run the carton dev
command.
on localhost
the carton dev
command complains effusively about Sendable
violations. it takes everything Swift Concurrency thinks is wrong about Carton, and swift-nio/Sources/NIOCore/ChannelPipeline.swift
and prints it to the console. SwiftPM prints multiple copies of each warning, making it difficult to tell what is really going on.
in the end though, carton dev
successfully starts up a server on localhost:8080
.
when i look at the page, at first there is no sign the WebAssembly is running, but after a short moment, it appears. in the web inspector network tab, it shows that the WebAssembly is 59.96 MB in size.
although that is very hefty, surely this is just the debug binary size, and the release WebAssembly will be much smaller… right?
optimizing the binary
i want to know how small the production WebAssembly is, so i try running the bundle
command, which is supposed to be the “release mode”.
$ swift run carton bundle --custom-index-page Web/index.html
Build of product 'Hello' complete! (6.72s)
Right after building the main binary size is 56.59 MB
After stripping debug info the main binary size is 49.96 MB
Running wasm-opt -Os --enable-bulk-memory --enable-sign-ext /swift/wasm-test/Bundle/main.wasm -o /swift/wasm-test/Bundle/main.wasm
wasm-opt -Os --enable-bulk-memory --enable-sign-ext /swift/wasm-test/Bundle/main.wasm -o /swift/wasm-test/Bundle/main.wasm
wasm-opt process failed.
Warning: wasm-opt failed to optimize the binary, falling back to the original binary.
If you don't have wasm-opt installed, you can install wasm-opt by running `brew install binaryen`, `apt-get install binaryen` or `npm install -g binaryen`
Copying resources to /swift/wasm-test/Bundle/JavaScriptKit_JavaScriptKit.resources
Bundle successfully generated at /swift/wasm-test/Bundle
okay, that’s not good. the production mode binary is 56.59 MB, which is only 3 MB smaller than the debug binary. stripping debug symbols takes it down to 49.96 MB, but that’s still enormous!
but it also looks like there’s another pass that needs to run, which requires installing some other tool binaryen
. this step takes it down to 44.77 MB.
Build of product 'Hello' complete! (0.51s)
Right after building the main binary size is 56.59 MB
After stripping debug info the main binary size is 49.96 MB
Running wasm-opt -Os --enable-bulk-memory --enable-sign-ext /swift/wasm-test/Bundle/main.wasm -o /swift/wasm-test/Bundle/main.wasm
wasm-opt -Os --enable-bulk-memory --enable-sign-ext /swift/wasm-test/Bundle/main.wasm -o /swift/wasm-test/Bundle/main.wasm
`wasm-opt` process finished successfully
After stripping debug info the main binary size is 44.77 MB
Copying resources to /swift/wasm-test/Bundle/JavaScriptKit_JavaScriptKit.resources
Bundle successfully generated at /swift/wasm-test/Bundle
perhaps this code is very repetitive. i wonder what would happen if we used gzip
on this?
$ gzip -9 Bundle/Hello.04ebb5a28d0d45fc.wasm
$ ls -l Bundle/
total 17408
-rw-r--r-- 1 ubuntu ubuntu 17683279 Jan 21 02:07 Hello.04ebb5a28d0d45fc.wasm.gz
drwxr-xr-x 3 ubuntu ubuntu 4096 Jan 21 02:07 JavaScriptKit_JavaScriptKit.resources
-rw-r--r-- 1 ubuntu ubuntu 56740 Jan 21 02:08 app.d38bdc6abc41d009.js
-rw-r--r-- 1 ubuntu ubuntu 337 Jan 21 02:08 index.html
-rw-r--r-- 1 ubuntu ubuntu 1614 Jan 21 02:08 index.js
-rw-r--r-- 1 ubuntu ubuntu 62313 Jan 21 02:08 intrinsics.js
-rw-r--r-- 1 ubuntu ubuntu 46 Jan 21 02:08 package.json
alright, so effective baseline Hello World size is 17.7 MB compressed.
what now?
17.7 MB is not unworkable if the WebAssembly is the entire web app. you can host this statically, serve it through a CDN, and walk users through updates as they roll out. in a lot of ways, this is no more annoying than updating an iOS app.
on the other extreme, it seems to be possible to get ultralight binaries by going the Embedded route. but a lot of this seems geared towards folks who are trying to ship WebAssembly on the scale of 500 KB or less (200 KB compressed?).
i’m curious to know if there is a way to target WebAssembly in the range of 5–10 MB uncompressed, that avoids dependencies such as Foundation, while not being restricted completely to the Embedded subset of the language. does such a thing exist?
you can’t really integrate something that is 17.7 MB into an existing web application, that kind of overhead is only justifiable if you are starting from scratch and the WebAssembly is the entire application. i want to know if it is possible to incrementally adopt WebAssembly with the eventual goal of having it replace the rest of the application.