tdf#161878 Ignore nested SYMBOL field inside IF field
[LibreOffice.git] / static / README.wasm.md
blobcb5d0aa7fc3ed2780d68b25917337ae602511f0f
1 # Support for Emscripten Cross Build
3 This subdirectory provides support for building LibreOffice as WASM, with the Emscripten toolchain.
5 You can build LibreOffice for WASM for two separate purposes: 1)
6 Either to produce a WASM binary of LibreOffice as such, using Qt5 for
7 its GUI, or 2) just compiling LibreOffice core ("LibreOffice
8 Technology") to WASM without any UI for use in other software that
9 provides the UI, like Collabora Online built as WASM.
11 The first purpose was the original reason for the WASM port and this
12 document was originally written with that in mind. For the second
13 purpose, look towards the end of the document for the section
14 "Building headless LibreOffice as WASM for use in another product".
16 ## Status of LibreOffice as WASM with Qt
18 Configure `--with-package-format=emscripten` to have `workdir/installation/LibreOffice/emscripten`
19 populated with just the relevant files from `instdir`.
21 The build generates a Writer-only LO build. You should be able to run either
23     $ emrun --serve_after_close workdir/installation/LibreOffice/emscripten/qt_soffice.html
24     $ emrun --serve_after_close workdir/LinkTarget/Executable/qt_vcldemo.html
25     $ emrun --serve_after_close workdir/LinkTarget/Executable/qt_wasm-qt5-mandelbrot.html
27 REMINDER: Always start new tabs in the browser, reload might fail / cache!
28 INFO: latest browser won't work anymore with 0.0.0.0 and need 127.0.0.1.
30 ## Setup for the LO WASM build (with Qt)
32 We're using Qt 5.15.2 with Emscripten 3.1.46. There are a bunch of Qt patches
33 to fix the most grave bugs. Also there's rapid development in Emscripten, so
34 using another version often causes arbitrary problems.
36 - See below under Docker build for another build option
38 ### Setup emscripten
40 <https://emscripten.org/docs/getting_started/index.html>
42     git clone https://github.com/emscripten-core/emsdk.git
43     ./emsdk install 3.1.46
44     ./emsdk activate 3.1.46
46 Example `bashrc` scriptlet:
48     EMSDK_ENV=$HOME/Development/libreoffice/git_emsdk/emsdk_env.sh
49     [ -f "$EMSDK_ENV" ] && \. "$EMSDK_ENV" 1>/dev/null 2>&1
51 ### Setup Qt
53 <https://doc.qt.io/qt-5/wasm.html>
55 Most of the information from <https://doc.qt.io/qt-6/wasm.html> is still valid for Qt5;
56 generally the Qt6 WASM documentation is much better, because it incorporated many
57 information from the Qt Wiki.
59 FWIW: Qt 5.15 LTS is not maintained publicly and Qt WASM has quite a few bugs. Most
60 WASM fixes from Qt 6 are needed for Qt 5.15 too. Allotropia offers a Qt repository
61 with the necessary patches cherry-picked.
63 With "-opensource -confirm-license" you agree to the open source license.
65     git clone https://github.com/allotropia/qt5.git
66     cd qt5
67     git checkout v5.15.2+wasm
68     ./init-repository --module-subset=qtbase
69     ./configure -opensource -confirm-license -xplatform wasm-emscripten -feature-thread -prefix <whatever> QMAKE_CFLAGS+=-sSUPPORT_LONGJMP=wasm QMAKE_CXXFLAGS+=-sSUPPORT_LONGJMP=wasm
70     make -j<CORES> module-qtbase
72 Optionally you can add the configure flag "-compile-examples". But then you also have to
73 patch at least mkspecs/wasm-emscripten/qmake.conf with EXIT_RUNTIME=0, otherwise they will
74 fail to run. In addition, building with examples will break with some of them, but at that
75 point Qt already works and also most examples. Or just skip them. Other interesting flags
76 might be "-nomake tests -no-pch -ccache".
78 Linking takes quite a long time, because emscripten-finalize rewrites the whole WASM files with
79 some options. This way the LO WASM possibly needs 64GB RAM. For faster link times add
80 "-s WASM_BIGINT=1", change to ASSERTIONS=1 and use -g3 to prevent rewriting the WASM file and
81 generating source maps (see emscripten.py, finalize_wasm, and avoid modify_wasm = True). This is
82 just needed for Qt examples, as LO already uses the correct flags!
84 It's needed to install Qt5 to the chosen prefix. Else LO won't find all needed files in the
85 right place. For installation you can do
87     make -j<CORES> install
89     make -j8 -C qtbase/src install_subtargets
91 Current Qt fails to start the demo webserver: <https://bugreports.qt.io/browse/QTCREATORBUG-24072>
93 Use `emrun --serve_after_close` to run Qt WASM demos.
95 ### Setup LO
97 `autogen.sh` is patched to use emconfigure. That basically sets various
98 environment vars, especially `EMMAKEN_JUST_CONFIGURE`, which will create the
99 correct output file names, checked by `configure` (`a.out`).
101 There's a distro config for WASM, but it just provides --host=wasm32-local-emscripten, which
102 should be enough setup. The build itself is a cross build and the cross-toolset just depends
103 on a minimal toolset (gcc, libc-dev, flex, bison); all else is build from source, because the
104 final result is not depending on the build system at all.
106 Recommended configure setup is thusly:
108 * grab defaults
109     `--with-distro=LibreOfficeWASM32`
111 * local config
112     `QT5DIR=/dir/of/qt5/install/prefix`
114 * if you want to use ccache on both sides of the build
115     `--with-build-platform-configure-options=--enable-ccache`
116     `--enable-ccache`
118 FWIW: it's also possible to build an almost static Linux LibreOffice by just using
119 --disable-dynloading --enable-customtarget-components. System externals are still
120 linked dynamically, but everything else is static.
122 ### "Deploying" soffice.wasm
124     tar -chf wasm.tar --xform 's/.*program/lo-wasm/' instdir/program/soffice.* \
125         instdir/program/qt*
127 Your HTTP server needs to provide additional headers:
128 * add_header Cross-Origin-Opener-Policy same-origin
129 * add_header Cross-Origin-Embedder-Policy require-corp
131 The default html to use should be qt_soffice.html
133 ### Debugging setup
135 Since a few months you can use DWARF information embedded by LLVM into the WASM
136 to debug WASM in Chrome. You need to enable an experimental feature and install
137 an additional extension. The whole setup is described in:
139 https://developer.chrome.com/blog/wasm-debugging-2020/
141 This way you don't need source maps (much faster linking!) and can resolve local
142 WASM variables to C++ names!
144 Per default, the WASM debug build splits the DWARF information into an additional
145 WASM file, postfixed '.debug.wasm'.
147 ### Using Docker to cross-build with emscripten
149 If you prefer a controlled environment (sadly emsdk install/activate
150 is _not_ stable over time, as e.g. nodejs versions evolve), that is
151 easy to replicate across different machines - consider the docker
152 images we're providing.
154 Config/setup file see
155 <https://git.libreoffice.org/lode/+/ccb36979563635b51215477455953252c99ec013>
159     docker-compose build
161 in the lode/docker dir to get the container prepared. Run
163     PARALLELISM=4 BUILD_OPTIONS= BUILD_TARGET=build docker-compose run --rm \
164         -e PARALLELISM -e BUILD_TARGET -e BUILD_OPTIONS builder
166 to perform an actual `srcdir != builddir` build; the container mounts
167 checked-out git repo and output dir via `docker-compose.yml` (so make
168 sure the path names there match your setup):
170 The lode setup expects, inside the lode/docker subdir, the following directories:
172 - core (`git checkout`)
173 - workdir (the output dir - gets written into)
174 - cache (`ccache tree`)
175 - tarballs (external project tarballs gets written and cached there)
177 ### UNO bindings with Embind
179 Right now there's a very rough implementation in place. With lots of different
180 bits unimplemented. And it _might_ be leaking memory. i.e. Lots of room for
181 improvement! ;)
183 Some usage examples through javascript of the current implementation:
184 ```js
185 // inserts a string at the start of the Writer document.
186 Module.uno_init.then(function() {
187     const css = Module.uno.com.sun.star;
188     let xModel = Module.getCurrentModelFromViewSh();
189     if (xModel === null || !css.text.XTextDocument.query(xModel)) {
190         const desktop = css.frame.Desktop.create(Module.getUnoComponentContext());
191         const args = new Module.uno_Sequence_com$sun$star$beans$PropertyValue(
192             0, Module.uno_Sequence.FromSize);
193         xModel = css.frame.XComponentLoader.query(desktop).loadComponentFromURL(
194             'file:///android/default-document/example.odt', '_default', 0, args);
195         args.delete();
196     }
197     const xTextDocument = css.text.XTextDocument.query(xModel);
198     const xText = xTextDocument.getText();
199     const xTextCursor = xText.createTextCursor();
200     xTextCursor.setString("string here!");
204 ```js
205 // changes each paragraph of the Writer document to a random color.
206 Module.uno_init.then(function() {
207     const css = Module.uno.com.sun.star;
208     let xModel = Module.getCurrentModelFromViewSh();
209     if (xModel === null || !css.text.XTextDocument.query(xModel)) {
210         const desktop = css.frame.Desktop.create(Module.getUnoComponentContext());
211         const args = new Module.uno_Sequence_com$sun$star$beans$PropertyValue(
212             0, Module.uno_Sequence.FromSize);
213         xModel = css.frame.XComponentLoader.query(desktop).loadComponentFromURL(
214             'file:///android/default-document/example.odt', '_default', 0, args);
215         args.delete();
216     }
217     const xTextDocument = css.text.XTextDocument.query(xModel);
218     const xText = xTextDocument.getText();
219     const xEnumAccess = css.container.XEnumerationAccess.query(xText);
220     const xParaEnumeration = xEnumAccess.createEnumeration();
221     while (xParaEnumeration.hasMoreElements()) {
222         const next = xParaEnumeration.nextElement();
223         const xParagraph = css.text.XTextRange.query(next.get());
224         const xParaProps = css.beans.XPropertySet.query(xParagraph);
225         const color = new Module.uno_Any(
226             Module.uno_Type.Long(), Math.floor(Math.random() * 0xFFFFFF));
227         xParaProps.setPropertyValue("CharColor", color);
228         next.delete();
229         color.delete();
230     }
236 ## Tools for problem diagnosis
238 * `nm -s` should list the symbols in the archive, based on the index generated by ranlib.
239   If you get linking errors that archive has no index.
242 ## Emscripten filesystem access with threads
244 This is closed, but not really fixed IMHO:
246 - <https://github.com/emscripten-core/emscripten/issues/3922>
248 ## Dynamic libraries `/` modules in emscripten
250 There is a good summary in:
252 - <https://bugreports.qt.io/browse/QTBUG-63925>
254 Summary: you can't use modules and threads.
256 This is mentioned at the end of:
258 - <https://github.com/emscripten-core/emscripten/wiki/Linking>
260 The usage of `MAIN_MODULE` and `SIDE_MODULE` has other problems, a major one IMHO is symbol resolution at runtime only.
261 So this works really more like plugins in the sense of symbol resolution without dependencies `/` rpath.
263 There is some clang-level dynamic-linking in progress (WASM dlload). The following link is already a bit old,
264 but I found it a god summary of problems to expect:
266 - <https://iandouglasscott.com/2019/07/18/experimenting-with-webassembly-dynamic-linking-with-clang/>
269 ## Mixed information, links, problems, TODO
271 More info on Qt WASM emscripten pthreads:
273 - <https://wiki.qt.io/Qt_for_WebAssembly#Multithreading_Support>
275 WASM needs `-pthread` at compile, not just link time for atomics support. Alternatively you can provide
276 `-s USE_PTHREADS=1`, but both don't seem to work reliable, so best provide both.
277 <https://github.com/emscripten-core/emscripten/issues/10370>
279 The output file must have the prefix .o, otherwise the WASM files will get a
280 `node.js` shebang (!) and ranlib won't be able to index the library (link errors).
282 Qt with threads has further memory limit. From Qt configure:
283 ````
284 Project MESSAGE: Setting PTHREAD_POOL_SIZE to 4
285 Project MESSAGE: Setting TOTAL_MEMORY to 1GB
286 ````
288 You can actually allocate 4GB:
290 - <https://bugzilla.mozilla.org/show_bug.cgi?id=1392234>
292 LO uses a nested event loop to run dialogs in general, but that won't work, because you can't drive
293 the browser event loop. like VCL does with the system event loop in the various VCL backends.
294 Changing this will need some major work (basically dropping Application::Execute).
296 But with the know problems with exceptions and threads, this might change:
298 - <https://github.com/emscripten-core/emscripten/pull/11518>
299 - <https://github.com/emscripten-core/emscripten/issues/11503>
300 - <https://github.com/emscripten-core/emscripten/issues/11233>
301 - <https://github.com/emscripten-core/emscripten/issues/12035>
303 We're also using emconfigure at the moment. Originally I patched emscripten, because it
304 wouldn't create the correct a.out file for C++ configure tests. Later I found that
305 the `emconfigure` sets `EMMAKEN_JUST_CONFIGURE` to work around the problem.
307 ICU bug:
309 - <https://github.com/emscripten-core/emscripten/issues/10129>
311 Alternative, probably:
313 - <https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Intl>
315 There is a wasm64, but that still uses 32bit pointers!
317 Old outdated docs:
319 - <https://wiki.documentfoundation.org/Development/Emscripten>
321 Reverted patch:
323 - <https://cgit.freedesktop.org/libreoffice/core/commit/?id=0e21f6619c72f1e17a7b0a52b6317810973d8a3e>
325 Generally <https://emscripten.org/docs/porting>:
327 - <https://emscripten.org/docs/porting/guidelines/api_limitations.html#api-limitations>
328 - <https://emscripten.org/docs/porting/files/file_systems_overview.html#file-system-overview>
329 - <https://emscripten.org/docs/porting/pthreads.html>
330 - <https://emscripten.org/docs/porting/emscripten-runtime-environment.html>
332 This will be interesting:
334 - <https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-an-event-loop>
336 This didn't help much yet:
338 - <https://github.com/emscripten-ports>
340 Emscripten supports standalone WASI binaries:
342 - <https://github.com/emscripten-core/emscripten/wiki/WebAssembly-Standalone>
343 - <https://www.qt.io/qt-examples-for-webassembly>
344 - <http://qtandeverything.blogspot.com/2017/06/qt-for-web-assembly.html>
345 - <http://qtandeverything.blogspot.com/2020/>
346 - <https://emscripten.org/docs/api_reference/Filesystem-API.html>
347 - <https://discuss.python.org/t/add-a-webassembly-wasm-runtime/3957/12>
348 - <http://git.savannah.gnu.org/cgit/config.git>
349 - <https://webassembly.org/specs/>
350 - <https://developer.chrome.com/docs/native-client/>
351 - <https://emscripten.org/docs/getting_started/downloads.html>
352 - <https://github.com/openpgpjs/openpgpjs/blob/master/README.md#getting-started>
353 - <https://developer.mozilla.org/en-US/docs/WebAssembly/Using_the_JavaScript_API>
354 - <https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-intro.md>
355 - <https://www.ip6.li/de/security/x.509_kochbuch/openssl-fuer-webassembly-compilieren>
356 - <https://emscripten.org/docs/introducing_emscripten/about_emscripten.html#about-emscripten-porting-code>
357 - <https://emscripten.org/docs/compiling/Building-Projects.html>
359 ### Threads and the event loop
361 The Emscripten emulation of pthreads requires the JS main thread event loop to be able to promptly
362 respond both when spawning and when exiting a pthread.  But the Qt5 event loop runs on the JS main
363 thread, so the JS main thread event loop is blocked while a LO VCL Task is executed.  And our
364 pthreads are typically spawned and joined from within such Task executions, which means that the JS
365 main thread event loop is not available to reliably perform those Emscripten pthread operations.
367 For pthread spawning, the solution is to set -sPTHREAD_POOL_SIZE to a sufficiently large value, so
368 that each of our pthread spawning requests during an inappropriate time finds a pre-spawned JS
369 Worker available.
371 There are patterns (like, at the time of writing this, the configmgr::Components::WriteThread) where
372 a pthread can get spawned and joined and then re-spawned (and re-joined) multiple times during a
373 single VCL Task execution (i.e., without the JS main thread event loop having a chance to get in
374 between any of those operations).  But as the underlying Emscripten pthread exiting operations will
375 therefore queue up, the pthread spawning operations will eventually run out of -sPTHREAD_POOL_SIZE
376 pre-spawned JS Workers.  The solution here is to change our pthread usage patterns accordingly, so
377 that such pthreads are rather kept running than being joined and re-spawned.
379 (-sPROXY_TO_PTHREAD would move the Qt5 event loop off the JS main thread, which should elegantly
380 solve all of the above issues.  But Qt5 just doesn't appear to be prepared to run on anything but
381 the JS main thread; e.g., it tries to access the global JS `window` object in various places, which
382 is available on the JS main thread but not in a JS Worker.)
384 ## Building headless LibreOffice as WASM for use in another product
386 ### Set up Emscripten
388 Follow the instructions in the first part of this document.
390 ### No Qt needed.
392 You don't need any dependencies other than those that normally are
393 downloaded and compiled when building LibreOffice.
395 ### Set up LO
397 For instance, this autogen.input works for me:
399 `--disable-debug`
400 `--enable-sal-log`
401 `--disable-crashdump`
402 `--host=wasm32-local-emscripten`
403 `--disable-gui`
404 `--with-main-module=writer`
405 `--with-package-format=emscripten`
407 For building LO core for use in COWASM, it is known to work to use
408 Emscripten 3.1.30 (and not just 2.0.31 which is what the LO+Qt5 work
409 has been using in the past).
411 ### That's all
413 After all, in this case you are building LO core headless for it to be used by other software.
415 Note that a soffice.wasm will be built, but that is just because of
416 how the makefilery has been set up. We do need the soffice.data file
417 that contains the in-memory file system needed by the LibreOffice
418 Technology core code during run-time, though. That is at the moment
419 built as a side-effect when building soffice.wasm.