-
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,141 @@ | ||
| .. _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 you towards these best practices by | ||
| emitting warnings, 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-specific 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']``). | ||
| - **Avoid long lists on the command line**; Use the ``@filename`` instead (for | ||
| example, ``-sEXPORTED_FUNCTIONS=@exported_funcs.txt``). | ||
| - **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 | ||
|
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 would put all the things before this in a "lint" section. Separate compilation and C++/JS interop feel like substantially different things, choices about the build system and design. |
||
| files before linking rather than combining everything into a single monolithic | ||
| compiler invocation. | ||
| - **Avoid direct usage of C/C++ functions from JavaScript**; prefer higher-level | ||
| interfaces such as :js:func:`cwrap` and/or :ref:`embind` that support more | ||
| than purely numeric types. | ||
|
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 still disagree with this. Direct usage is simply faster, and often works well. How about
|
||
|
|
||
|
|
||
| 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. | ||
|
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. Should we recommend Closure here? |
||
|
|
||
|
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 section overlaps with our Optimizing Code page. Perhaps link to there? Or perhaps have a section here for optimizations that summarizes that page? In particular, while most of this list is optimizations, things like STRICT feel more like "lint" (group with "avoid |
||
|
|
||
| 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 optimization | ||
| flags (such as ``-O2`` or ``-O3``) 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, | ||
|
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 overlaps with the above text, and I have the same response. |
||
| 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 cooperative 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:`Asyncify <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. | ||
|
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 isn't a "best practice" so much as a "how to" - ? |
||
| - 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`. | ||
|
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. Perhaps just link to the main filesystem page for filesystem recommendations? This could also summarize that page, if we want that (if so, perhaps mention WasmFS). |
||
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.