Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions site/source/docs/compiling/best_practices.rst
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **Prefer standard compiler flags** over Emscripten-specifc ones where
- **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']``).
- **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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 😄

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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 getBytesFromWasm(ptr) over a sequence of getSingleValueFromWasm()



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
Comment thread
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``.
Comment thread
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`.
2 changes: 2 additions & 0 deletions site/source/docs/compiling/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Compiling and Running Projects

This section contains topics about building projects and running the output.

- :ref:`best-practices` covers recommended compiler flags, modern web workflows, and Dos and Don'ts for using Emscripten.
- :ref:`Building-Projects` shows how to use :ref:`emccdoc` as a drop-in replacement for *gcc* in your existing project.
- :ref:`WebAssembly` explains how Emscripten can be used to build WebAssembly files
- :ref:`Running-html-files-with-emrun` explains how to use *emrun* to run generated HTML pages in a locally launched web server.
Expand All @@ -19,6 +20,7 @@ This section contains topics about building projects and running the output.
.. toctree::
:hidden:

best_practices
Building-Projects
WebAssembly
Modularized-Output
Expand Down
4 changes: 0 additions & 4 deletions site/source/docs/porting/guidelines/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,3 @@ Emscripten can be used to compile almost any *portable* C/C++ code to JavaScript
api_limitations
function_pointer_issues
browser_limitations.rst