Friday, June 7, 2019

Faster link time for Qt WebAssembly

I have built Qt and various apps using emscripten so many times over the last couple of years, it isn't even funny.

One detractor with building Qt applications for the web using Qt for WebAssembly, is the time it takes to build the client application. Especially during linking, it takes a huge amount of time to produce a binary for Qt WebAssembly.
This is caused by all the magic that the linker in emscripten is doing.

Luckily, llvm can now produce wasm binaries directly so emscripten does not have to go through any additional steps to output wasm. Emscripten and Qt can utilize this feature to reduce the length of time it takes to link and emit a .wasm binary.

YAY! \0/

I did some very non scientific measurements on build time. Using my development machine with make -j8:

Qt (not including configure)textedit
default configure Qt for WebAssemblyuser 20m3.706suser 2m46.678s
using -device-option WASM_OBJECT_FILES=1user 23m30.230suser 0m29.435s

The result is that building Qt takes a tad longer, but building the client application is significantly faster. Which means development iterations will be much faster. In this instance, I used the textedit example in Qt. The build time went from 2 minutes and 46 seconds for a default non object files build, to 29 seconds! Zip! Zoom! Zang! Dang! that's fast (for wasm)

Unfortunately, this is not currently done by default by either Qt, emscripten or llvm. There is a way to do this which involves building llvm from upstream and telling emscripten to use that.

For the emscripten and binaryen version, I tested the current 1.38.32

From this bug report
Morten has fleshed out a method for doing this.


Getting started:

(Note: this is "how-I-did-it", not necessarily "how-it-should-be-done".)
1. Clone the following repositories:

2. Check out the version you want to use 
  • emscripten and binaryen have matching emsdk version tags, for example "1.38.23".
  • llvm has its own version numbers, and must be synced up manually. Currently:
    • emscripten <= 1.38.23 : llvm 8.0 (8.0.0-rc2 branch)
    • emscripten > 1.38.23 : llvm 9.0 (master branch)
      (em++/emcc will complain if you have the incorrect llvm version)

3. Build
  • emscripten is all python, no build needed.
  • binaryen: "cmake -GNinja && ninja"
  • llvm:
    cmake -GNinja
    (one of the WebAssembly targets may be redundant)

4. Configure Environment
I use the following:
export EMSDK="/Users/msorvig/dev/emsdks/emscripten-1.38.23"
export PATH="$EMSDK:$PATH"
export LLVM="/Users/msorvig/dev/emsdks/llvm-8.0.0-build/bin"
export BINARYEN="/Users/msorvig/dev/emsdks/binaryen-1.38.23"
export EM_CONFIG="/Users/msorvig/dev/emsdks/.emscripten-vanillallvm-1.38.23"
export EM_CACHE="/Users/msorvig/dev/emsdks/.emscripten-vanillallvm-cache-1.38.23"
Where .emscripten-vanillallvm-1.38.23 is a copy of the .emscripten config file that emsdk generates.


You will need to adjust the various paths to your system, of course. 
Then to configure and build Qt, add -device-option WASM_OBJECT_FILES=1 to the normal Qt for WebAssembly configure line.

The one I normally use is:

configure -xplatform wasm-emscripten -developer-build -nomake tests -nomake examples -opensource -confirm-license -verbose -compile-examples -no-warnings-are-errors -release  -device-option WASM_OBJECT_FILES=1

Works like a charm for the Qt 5.13 and the 5.13.0 branches of the Qt source code repo. I tried this with the 5.13.0 beta4 WebAssembly binaries, but got:

wasm-ld: warning: function signature mismatch:
so a complete rebuild is required.

There is a chapter regarding Qt for WebAssembly in the book, Hands-On Mobile and Embedded Development with Qt 5