Avoid potential negative array index access to cached text.
[LibreOffice.git] / static / README.wasm.md
blobf39a79247d0090ae10bf6ee3f4d7fac3fe5fa085
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 The build generates a Writer-only LO build. You should be able to run either
20     $ emrun --serve_after_close instdir/program/qt_soffice.html
21     $ emrun --serve_after_close workdir/LinkTarget/Executable/qt_vcldemo.html
22     $ emrun --serve_after_close workdir/LinkTarget/Executable/qt_wasm-qt5-mandelbrot.html
24 REMINDER: Always start new tabs in the browser, reload might fail / cache!
25 INFO: latest browser won't work anymore with 0.0.0.0 and need 127.0.0.1.
27 ## Setup for the LO WASM build (with Qt)
29 We're using Qt 5.15.2 with Emscripten 2.0.31. There are a bunch of Qt patches
30 to fix the most grave bugs. Also newer Emscripten versions have various bugs
31 with the FS image support.
33 - See below under Docker build for another build option
35 ### Setup emscripten
37 <https://emscripten.org/docs/getting_started/index.html>
39     git clone https://github.com/emscripten-core/emsdk.git
40     ./emsdk install 2.0.31
41     ./emsdk activate --embedded 2.0.31
43 Example `bashrc` scriptlet:
45     EMSDK_ENV=$HOME/Development/libreoffice/git_emsdk/emsdk_env.sh
46     [ -f "$EMSDK_ENV" ] && \. "$EMSDK_ENV" 1>/dev/null 2>&1
48 ### Setup Qt
50 <https://doc.qt.io/qt-5/wasm.html>
52 Most of the information from <https://doc.qt.io/qt-6/wasm.html> is still valid for Qt5;
53 generally the Qt6 WASM documentation is much better, because it incorporated many
54 information from the Qt Wiki.
56 FWIW: Qt 5.15 LTS is not maintained publicly and Qt WASM has quite a few bugs. Most
57 WASM fixes from Qt 6 are needed for Qt 5.15 too. Allotropia offers a Qt repository
58 with the necessary patches cherry-picked.
60     git clone https://github.com/allotropia/qt5.git
61     cd qt5
62     git checkout v5.15.2+wasm
63     ./init-repository --module-subset=qtbase
64     ./configure -xplatform wasm-emscripten -feature-thread -prefix <whatever>
65     make -j<CORES> module-qtbase
67 Optionally you can add the configure flag "-compile-examples". But then you also have to
68 patch at least mkspecs/wasm-emscripten/qmake.conf with EXIT_RUNTIME=0, otherwise they will
69 fail to run. In addition, building with examples will break with some of them, but at that
70 point Qt already works and also most examples.
71 Building with examples will break with some of them, but at that point Qt already works.
72 Or just skip them. Other interesting flags might be "-nomake tests -no-pch -ccache".
74 Linking takes quite a long time, because emscripten-finalize rewrites the whole WASM files
75 with some options. This way the LO WASM needs at least 64GB RAM. For faster link times add
76 "-s WASM_BIGINT=1", change to ASSERTIONS=1 nd use -g3 to prevent rewriting the WASM file
77 and generating source maps (see emscripten.py, finalize_wasm, and avoid modify_wasm = True).
78 This is just needed for Qt examples, as LO already uses the correct flags!
80 The install is not really needed, as LO currently just uses qtbase on its own. You can do
82     make -j<CORES> install
84     make -j8 -C qtbase/src install_subtargets
86 Current Qt fails to start the demo webserver: <https://bugreports.qt.io/browse/QTCREATORBUG-24072>
88 Use `emrun --serve_after_close` to run Qt WASM demos.
90 ### Setup LO
92 `autogen.sh` is patched to use emconfigure. That basically sets various
93 environment vars, especially `EMMAKEN_JUST_CONFIGURE`, which will create the
94 correct output file names, checked by `configure` (`a.out`).
96 There's a distro config for WASM, but it just provides --host=wasm32-local-emscripten, which
97 should be enough setup. The build itself is a cross build and the cross-toolset just depends
98 on a minimal toolset (gcc, libc-dev, flex, bison); all else is build from source, because the
99 final result is not depending on the build system at all.
101 Recommended configure setup is thusly:
103 * grab defaults
104     `--with-distro=LibreOfficeWASM32`
106 * local config
107     `QT5DIR=/dir/of/git_qt5/qtbase`
109 * if you want to use ccache on both sides of the build
110     `--with-build-platform-configure-options=--enable-ccache`
111     `--enable-ccache`
113 FWIW: it's also possible to build an almost static Linux LibreOffice by just using
114 --disable-dynloading --enable-customtarget-components. System externals are still
115 linked dynamically, but everything else is static.
117 #### Experimental (AKA currently broken) WASM exception + SjLj build
119 You can build LO with WASM exceptions, which should be "much" faster then the JS
120 based Emscripten EH handling. For setjmp / longjmp (SjLj) used by the PNG and JPEG
121 libraries error handling, this needs Emscripten 3.1.3+. That builds, but execution
122 still fails early with a signature mismatch call to Task::UpdateMinPeriod in LO's
123 job scheduler code. Unfortunately the build also needs a Qt build with
124 "-s SUPPORT_LONGJMP=wasm", which is incompatible with the JS EH + SjLj.
126 The LO configure flag is simply an additional --enable-wasm-exceptions. Qt5 can
127 be patched in qtbase/mkspecs/wasm-emscripten/qmake.conf with the addition of
129     QMAKE_CFLAGS += -s SUPPORT_LONGJMP=wasm
130     QMAKE_CXXFLAGS += -s SUPPORT_LONGJMP=wasm
132 ### "Deploying" soffice.wasm
134     tar -chf wasm.tar --xform 's/.*program/lo-wasm/' instdir/program/soffice.* \
135         instdir/program/qt*
137 Your HTTP server needs to provide additional headers:
138 * add_header Cross-Origin-Opener-Policy same-origin
139 * add_header Cross-Origin-Embedder-Policy require-corp
141 The default html to use should be qt_soffice.html
143 ### Debugging setup
145 Since a few months you can use DWARF information embedded by LLVM into the WASM
146 to debug WASM in Chrome. You need to enable an experimental feature and install
147 an additional extension. The whole setup is described in:
149 https://developer.chrome.com/blog/wasm-debugging-2020/
151 This way you don't need source maps (much faster linking!) and can resolve local
152 WASM variables to C++ names!
154 Per default, the WASM debug build splits the DWARF information into an additional
155 WASM file, postfixed '.debug.wasm'.
157 ### Using Docker to cross-build with emscripten
159 If you prefer a controlled environment (sadly emsdk install/activate
160 is _not_ stable over time, as e.g. nodejs versions evolve), that is
161 easy to replicate across different machines - consider the docker
162 images we're providing.
164 Config/setup file see
165 <https://git.libreoffice.org/lode/+/ccb36979563635b51215477455953252c99ec013>
169     docker-compose build
171 in the lode/docker dir to get the container prepared. Run
173     PARALLELISM=4 BUILD_OPTIONS= BUILD_TARGET=build docker-compose run --rm \
174         -e PARALLELISM -e BUILD_TARGET -e BUILD_OPTIONS builder
176 to perform an actual `srcdir != builddir` build; the container mounts
177 checked-out git repo and output dir via `docker-compose.yml` (so make
178 sure the path names there match your setup):
180 The lode setup expects, inside the lode/docker subdir, the following directories:
182 - core (`git checkout`)
183 - workdir (the output dir - gets written into)
184 - cache (`ccache tree`)
185 - tarballs (external project tarballs gets written and cached there)
188 ## Ideas for an UNO bridge implementation
190 My post to Discord #emscripten:
192 "I'm looking for a way to do an abstract call
193 from one WASM C++ object to another WASM C++ object, so like FFI / WebIDL,
194 just within WASM. All my code is C++ and normally I have bridge code, with
195 assembler to implement the function call /RTTI and exception semantics of the
196 specified platform. Code is at
197 <https://cgit.freedesktop.org/libreoffice/core/tree/bridges/source/cpp_uno>.
198 I've read a bit about `call_indirect` and stuff, but I don't have yet a good
199 idea, how I could implement this (and  there is an initial feature/wasm branch
200 for the interested). I probably need some fixed lookup table, like on iOS,
201 because AFAIK you can't dynamically generate code in WASM. So any pointers or
202 ideas for an implementation? I can disassemble some minimalistic WASM example
203 and read clang code for `WASM_EmscriptenInvoke`, but if there were some
204 standalone code or documentation I'm missing, that would be nice to know."
206 We basically would go the same way then the other backends. Write the bridge in
207 C++, which is probably largely boilerplate code, but the function call in WAT
208 (<https://github.com/WebAssembly/wabt>) based on the LLVM WASM calling
209 conventions in `WASM_EmscriptenInvoke`. I didn't get a reply to that question for
210 hours. Maybe I'll open an Emscripten issue, if we really have to implement
211 this.
213 WASM dynamic dispatch:
215 - <https://fitzgeraldnick.com/2018/04/26/how-does-dynamic-dispatch-work-in-wasm.html>
217 ### UNO bindings with Embind
219 Right now there's a very rough implementation in place. With lots of different
220 bits unimplemented. And it _might_ be leaking memory. i.e. Lots of room for
221 improvement! ;)
223 Some usage examples through javascript of the current implementation:
224 ```js
225 // inserts a string at the start of the Writer document.
226 xModel = Module.getCurrentModelFromViewSh();
227 xTextDocument = new Module.com$sun$star$text$XTextDocumentRef(xModel, Module.UnoReference_Query.UNO_QUERY);
228 xText = xTextDocument.getText();
229 xSimpleText = new Module.com$sun$star$text$XSimpleTextRef(xText, Module.UnoReference_Query.UNO_QUERY);
230 xTextCursor = xSimpleText.createTextCursor();
231 xTextRange = new Module.com$sun$star$text$XTextRangeRef(xTextCursor, Module.UnoReference_Query.UNO_QUERY);
232 xTextRange.setString(new Module.OUString("string here!"));
233 xModel.delete(); xTextDocument.delete(); xText.delete(); xSimpleText.delete(); xTextCursor.delete(); xTextRange.delete();
236 ```js
237 // changes each paragraph of the Writer document to a random color.
238 xModel = Module.getCurrentModelFromViewSh();
239 xTextDocument = new Module.com$sun$star$text$XTextDocumentRef(xModel, Module.UnoReference_Query.UNO_QUERY);
240 xText = xTextDocument.getText();
241 xEnumAccess = new Module.com$sun$star$container$XEnumerationAccessRef(xText, Module.UnoReference_Query.UNO_QUERY);
242 xParaEnumeration = xEnumAccess.createEnumeration();
244 while (xParaEnumeration.hasMoreElements()) {
245     xParagraph = new Module.com$sun$star$text$XTextRangeRef();
246     xParagraph.set(xParaEnumeration.nextElement(), Module.UnoReference_Query.UNO_QUERY);
247     if (xParagraph.is()) {
248         xParaProps = new Module.com$sun$star$beans$XPropertySetRef(xParagraph, Module.UnoReference_Query.UNO_QUERY);
249         xParaProps.setPropertyValue(new Module.OUString("CharColor"), new Module.Any(Math.floor(Math.random() * 0xFFFFFF), Module.UnoType.long));
250     }
256 ## Tools for problem diagnosis
258 * `nm -s` should list the symbols in the archive, based on the index generated by ranlib.
259   If you get linking errors that archive has no index.
262 ## Emscripten filesystem access with threads
264 This is closed, but not really fixed IMHO:
266 - <https://github.com/emscripten-core/emscripten/issues/3922>
268 ## Dynamic libraries `/` modules in emscripten
270 There is a good summary in:
272 - <https://bugreports.qt.io/browse/QTBUG-63925>
274 Summary: you can't use modules and threads.
276 This is mentioned at the end of:
278 - <https://github.com/emscripten-core/emscripten/wiki/Linking>
280 The usage of `MAIN_MODULE` and `SIDE_MODULE` has other problems, a major one IMHO is symbol resolution at runtime only.
281 So this works really more like plugins in the sense of symbol resolution without dependencies `/` rpath.
283 There is some clang-level dynamic-linking in progress (WASM dlload). The following link is already a bit old,
284 but I found it a god summary of problems to expect:
286 - <https://iandouglasscott.com/2019/07/18/experimenting-with-webassembly-dynamic-linking-with-clang/>
289 ## Mixed information, links, problems, TODO
291 More info on Qt WASM emscripten pthreads:
293 - <https://wiki.qt.io/Qt_for_WebAssembly#Multithreading_Support>
295 WASM needs `-pthread` at compile, not just link time for atomics support. Alternatively you can provide
296 `-s USE_PTHREADS=1`, but both don't seem to work reliable, so best provide both.
297 <https://github.com/emscripten-core/emscripten/issues/10370>
299 The output file must have the prefix .o, otherwise the WASM files will get a
300 `node.js` shebang (!) and ranlib won't be able to index the library (link errors).
302 Qt with threads has further memory limit. From Qt configure:
303 ````
304 Project MESSAGE: Setting PTHREAD_POOL_SIZE to 4
305 Project MESSAGE: Setting TOTAL_MEMORY to 1GB
306 ````
308 You can actually allocate 4GB:
310 - <https://bugzilla.mozilla.org/show_bug.cgi?id=1392234>
312 LO uses a nested event loop to run dialogs in general, but that won't work, because you can't drive
313 the browser event loop. like VCL does with the system event loop in the various VCL backends.
314 Changing this will need some major work (basically dropping Application::Execute).
316 But with the know problems with exceptions and threads, this might change:
318 - <https://github.com/emscripten-core/emscripten/pull/11518>
319 - <https://github.com/emscripten-core/emscripten/issues/11503>
320 - <https://github.com/emscripten-core/emscripten/issues/11233>
321 - <https://github.com/emscripten-core/emscripten/issues/12035>
323 We're also using emconfigure at the moment. Originally I patched emscripten, because it
324 wouldn't create the correct a.out file for C++ configure tests. Later I found that
325 the `emconfigure` sets `EMMAKEN_JUST_CONFIGURE` to work around the problem.
327 ICU bug:
329 - <https://github.com/emscripten-core/emscripten/issues/10129>
331 Alternative, probably:
333 - <https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Intl>
335 There is a wasm64, but that still uses 32bit pointers!
337 Old outdated docs:
339 - <https://wiki.documentfoundation.org/Development/Emscripten>
341 Reverted patch:
343 - <https://cgit.freedesktop.org/libreoffice/core/commit/?id=0e21f6619c72f1e17a7b0a52b6317810973d8a3e>
345 Generally <https://emscripten.org/docs/porting>:
347 - <https://emscripten.org/docs/porting/guidelines/api_limitations.html#api-limitations>
348 - <https://emscripten.org/docs/porting/files/file_systems_overview.html#file-system-overview>
349 - <https://emscripten.org/docs/porting/pthreads.html>
350 - <https://emscripten.org/docs/porting/emscripten-runtime-environment.html>
352 This will be interesting:
354 - <https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-an-event-loop>
356 This didn't help much yet:
358 - <https://github.com/emscripten-ports>
360 Emscripten supports standalone WASI binaries:
362 - <https://github.com/emscripten-core/emscripten/wiki/WebAssembly-Standalone>
363 - <https://www.qt.io/qt-examples-for-webassembly>
364 - <http://qtandeverything.blogspot.com/2017/06/qt-for-web-assembly.html>
365 - <http://qtandeverything.blogspot.com/2020/>
366 - <https://emscripten.org/docs/api_reference/Filesystem-API.html>
367 - <https://discuss.python.org/t/add-a-webassembly-wasm-runtime/3957/12>
368 - <http://git.savannah.gnu.org/cgit/config.git>
369 - <https://webassembly.org/specs/>
370 - <https://developer.chrome.com/docs/native-client/>
371 - <https://emscripten.org/docs/getting_started/downloads.html>
372 - <https://github.com/openpgpjs/openpgpjs/blob/master/README.md#getting-started>
373 - <https://developer.mozilla.org/en-US/docs/WebAssembly/Using_the_JavaScript_API>
374 - <https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-intro.md>
375 - <https://www.ip6.li/de/security/x.509_kochbuch/openssl-fuer-webassembly-compilieren>
376 - <https://emscripten.org/docs/introducing_emscripten/about_emscripten.html#about-emscripten-porting-code>
377 - <https://emscripten.org/docs/compiling/Building-Projects.html>
379 ## Building headless LibreOffice as WASM for use in another product
381 ### Set up Emscripten
383 Follow the instructions in the first part of this document.
385 ### No Qt needed.
387 You don't need any dependencies other than those that normally are
388 downloaded and compiled when building LibreOffice.
390 ### Set up LO
392 For instance, this autogen.input works for me:
394 `--disable-debug`
395 `--enable-sal-log`
396 `--disable-crashdump`
397 `--host=wasm32-local-emscripten`
398 `--disable-gui`
399 `--with-main-module=writer`
401 For building LO core for use in COWASM, it is known to work to use
402 Emscripten 3.1.30 (and not just 2.0.31 which is what the LO+Qt5 work
403 has been using).
405 ### That's all
407 After all, in this case you are building LO core headless for it to be used by other software.
409 Note that a soffice.wasm will be built, but that is just because of
410 how the makefilery has been set up. We do need the soffice.data file
411 that contains the in-memory file system needed by the LibreOffice
412 Technology core code during run-time, though. That is at the moment
413 built as a side-effect when building soffice.wasm.