-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Add a Best Practices section to the website. NFC #27334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| .. _best-practices: | ||
|
|
||
| ============== | ||
| Best Practices | ||
| ============== | ||
|
|
||
| This guide provides recommendations and best practices for compiling C and C++ | ||
| to the modern web using WebAssembly and Emscripten. | ||
|
|
||
| Following this guide when reporting bugs can also help speed up diagnosing and | ||
| fixing issues since you will be on the well-trodden path. | ||
|
|
||
| In some cases emscripten will guide in the direction of these best practices by | ||
| emitting warnings, etc, but that is not always possible. | ||
|
|
||
|
|
||
| General Recommendations | ||
| ======================= | ||
|
|
||
| - **Don't pass settings that are enabled by default.** To keep command lines | ||
| clean and concise, omit redundant options that are already default in modern | ||
| Emscripten (such as ``-sWASM=1``). | ||
| - **Prefer standard compiler flags** over Emscripten-specifc ones where | ||
| possible (for example, prefer ``-pthread`` over ``-sUSE_PTHREADS`` and `-m64` | ||
| over ``-sMEMORY64``). | ||
| - **Use simple comma-separated lists** for list-based settings (for example, | ||
| ``-sEXPORTED_FUNCTIONS=main,malloc`` rather than JSON arrays like | ||
| ``-sEXPORTED_FUNCTIONS=['_main','_malloc']``). | ||
| - **Don't include the "=1" suffix for boolean flags.** For example, write | ||
| ``-sSTRICT`` and ``-sALLOW_MEMORY_GROWTH`` rather than ``-sSTRICT=1`` or | ||
| ``-sALLOW_MEMORY_GROWTH=1``. | ||
| - **Use separate compilation** by compiling ``.cpp`` sources to ``.o`` object | ||
| files before linking rather than combining everything into a single monolithic | ||
| compiler invocation. | ||
| - **Don't rely on raw ``extern "C"`` pointer manipulation** when interacting | ||
| with complex C++ objects across the JavaScript boundary; prefer Embind. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This point feels out of place in this list. It is at minimum debatable, I'd say? Raw pointer manipulation is very simple and efficient once you know it. Generally speaking, I don't think we should recommend embind so generally, given the overhead. It is better that people make the effort to be efficient where it matters, and use embind where speed isn't an issue, but hard to put that into a short bullet point...
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe "perfer embind, unless you really know what you doing" although I'm not sure how to say that exactly either.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's best to leave it out. There isn't a simple-enough best practice to recommend.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think what I'm trying to do here is have newcomers avoid trying do direct pointer manipulation, and present embind as the well-lit path to getting nice integration between native and JS code.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My worry is that people start using embind even for simple things that don't need it, and then later ask "wait why is everything so slow?" I guess I disagree about embind being the well-lit path. Or maybe it is well-lit but has pitfalls 😄
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could it make sense to have a recommendation based on the type of API being exported to JS? e.g. use Embind if you have a C++ API or you want to do X and Y with your interop, or use something else if it's a C API?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we could phrase it as a warning against direct pointer manipulation unless you have a good reason (and good skills) to go that route. I'm also thinking if recommending using point + subarray when passing data around rather than using a lot of calls. e.g. prefer |
||
|
|
||
|
|
||
| Recommended Flags | ||
| ================= | ||
|
|
||
| - ``-sSTRICT``: Opt into strict modern Emscripten behavior, disabling | ||
| deprecated or legacy compatibility features. | ||
| - ``-sEXPORT_ES6``: Output a modern ES6 module (``module.mjs``). This option | ||
|
sbc100 marked this conversation as resolved.
|
||
| implies ``-sMODULARIZE`` so the generated code will be encapsulated and not | ||
| impact the global namespace. | ||
| - ``-sENVIRONMENT=web``: Limit the runtime support to only the environments you | ||
| are targeting. This reduces code size by, for example, omitting Node.js and | ||
| compatibility code. | ||
| - ``-Werror -Wall``: Treat warnings as errors to catch C++ bugs and invalid | ||
| compiler settings early. | ||
| - ``-O3``, ``-Os``, or ``-Oz``: For release builds, choose ``-O3`` when runtime | ||
| performance is most critical, or ``-Os`` / ``-Oz`` when minimizing binary | ||
| payload size is the priority. | ||
| - ``-flto``: Enable Link-Time Optimization (LTO) during both the compilation and | ||
| linking steps of release builds for maximum runtime performance and size | ||
| reduction. | ||
|
|
||
|
|
||
| Separate Compilation Workflows | ||
| ============================== | ||
|
|
||
| For non-trivial projects, always separate the compilation step (compiling source | ||
| files to object files) from the linking step (combining object files into the | ||
| final WebAssembly and JavaScript outputs). This enables incremental builds and | ||
| matches standard C/C++ development practices. | ||
|
|
||
| When using separate compilation, ensure that optimization flags and settings | ||
| that affect code generation (such as ``-flto``, ``-O3``, ``-g``, or | ||
| ``-pthread``) are passed at **both** compile and link times. | ||
|
|
||
| **Compilation step (producing object files):** | ||
|
|
||
| .. code-block:: bash | ||
|
|
||
| em++ -O3 -flto -c main.cpp -o main.o $(CXXFLAGS) | ||
|
|
||
| **Linking step (producing the ES6 module and WebAssembly binary):** | ||
|
|
||
| .. code-block:: bash | ||
|
|
||
| em++ -O3 -flto -sSTRICT -sEXPORT_ES6 --bind main.o -o module.mjs $(LDFLAGS) | ||
|
|
||
|
|
||
| Debug vs. Release Profiles | ||
| -------------------------- | ||
|
|
||
| When configuring build profiles, keep compile and link flags consistent within | ||
| each configuration: | ||
|
|
||
| - **Release Builds:** Use ``-Oz`` or ``-Os`` (or ``-O3`` for CPU-bound tasks) | ||
| combined with ``-flto``. | ||
|
sbc100 marked this conversation as resolved.
|
||
| - **Debug Builds:** Use ``-g`` when compiling and either ``-g``, | ||
| ``-gline-tables-only``, or ``-gsource-map`` when linking. Avoid ``-O`` or | ||
| ``-flto`` during debug builds for faster | ||
| compilation and accurate debugging. | ||
|
|
||
|
|
||
| Modern Web Workflows and Common Pitfalls | ||
| ======================================== | ||
|
|
||
| Interoperability with JavaScript | ||
| -------------------------------- | ||
|
|
||
| When exposing C++ functionality to JavaScript, prefer :ref:`embind` (``--bind``) | ||
| over raw ``extern "C"`` functions. Embind naturally handles C++ classes, | ||
| overloaded functions, smart pointers, ``std::string``, and ``std::vector`` | ||
| without requiring manual memory conversions or unsafe casting in JavaScript. | ||
|
|
||
| Asynchronous Code Execution and Main Thread Blocking | ||
| ---------------------------------------------------- | ||
|
|
||
| **Don't run long synchronous loops on the browser main thread.** The browser uses | ||
| co-operative multitasking; blocking the main UI thread prevents rendering and freezes | ||
| the web page. | ||
|
|
||
| - Restructure infinite loops to yield to the event loop using | ||
| :c:func:`emscripten_set_main_loop` (see :ref:`emscripten-runtime-environment-howto-main-loop`). | ||
| - For synchronous-looking C++ code that must pause (or interact with asynchronous | ||
| JavaScript APIs such as ``fetch()`` or Web Promises) without refactoring into callbacks, | ||
| use :ref:`yielding_to_main_loop` (``-sASYNCIFY``) or JavaScript Promise Integration (``-sJSPI``). | ||
| - Offload heavy compute or blocking operations to background workers using | ||
| :doc:`multithreading and pthreads <../porting/pthreads>` (``-pthread``). | ||
|
|
||
| Virtual Filesystem and I/O | ||
| -------------------------- | ||
|
|
||
| Standard C/C++ file operations (such as ``fopen`` or ``std::ifstream``) operate | ||
| on Emscripten's virtual in-memory filesystem (``MEMFS`` by default). See the | ||
| :ref:`file-system-overview` for an architectural overview. | ||
|
|
||
| - Do not assume direct access to the host file system. | ||
| - For small temporary files, ``MEMFS`` is sufficient. | ||
| - For persistent client-side data storage across browser sessions, use asynchronous | ||
| storage backends such as :ref:`filesystem-api-idbfs` or the :ref:`Filesystem-API`. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.