How far can i get with Swift WebAssembly in 30 minutes?

thanks everyone for the tips!

:bomb: 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.

:vertical_traffic_light: 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?

:gear: 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.

:beach_umbrella: 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.

6 Likes