Skip to content

WebAssembly Standalone

Alon Zakai edited this page Nov 27, 2019 · 40 revisions

When compiling to WebAssembly, emcc by default emits both a JavaScript file and a WebAssembly file. The JavaScript loads the WebAssembly which contains the compiled code. This is necessary in many cases as WebAssembly currently depends on a JavaScript runtime for features like longjmp, C++ exceptions, checking the date or time, printing to the console, using an API like WebGL, etc., and Emscripten will emit a JavaScript runtime to support those things.

However, if you don't need those things - if you have mostly pure computational code - or if you want to write your own JavaScript runtime, you can tell Emscripten to emit a "standalone" wasm file, one which doesn't depend on an Emscripten JS runtime. That lets you write your own runtime if you want, and you may be able to run the wasm in a a non-JS environment such as Wasmer, WAVM, or wasmtime.

Emscripten has a STANDALONE_WASM option that is the main way we support this (note that it requires the LLVM wasm backend). The flag is turned on automatically if you tell emcc to only emit a wasm file,

$ emcc source.c -o output.wasm

Otherwise, if you build with the flag,

$ emcc source.c -s STANDALONE_WASM

then it will emit a .js and .wasm file. The wasm is standalone as before, so you don't need the JS, but it may be convenient for running it on the Web.

Many APIs that emscripten uses depend on JS or Web APIs, though, like C++ exceptions as mentioned earlier, things like WebGL or WebAudio, etc. Calls to those APIs will still be emitted in standalone mode because we have no better alternative; this also gives you the option to add necessary APIs to a custom wasm embedding.

Aside from APIs that require JS or Web APIs, though, we try to use WASI APIs as much as possible so that the wasm can run in wasi-supporting runtimes.

For more details, see the blogpost on standalone wasm support, and the meshoptimizer example.

Memory and Table

In normal emscripten output we create the Memory and Table in JS, then import them in the wasm (that lets us start to operate on them in JS in parallel to wasm compilation). In standalone wasm, they are created in the wasm itself, which lets it not depend on outside code.

The memory is exported by default. You can also export the table if you want, using a wasm-ld flag, by passing -Wl,--export-table to emcc.

JS/wasm ABI

emcc can optionally emit useful metadata in the wasm file, that a runtime can use. See the EMIT_EMSCRIPTEN_METADATA option, added in #7815. The metadata includes versioning as well as things like the memory and table sizes the wasm needs, that the runtime needs to provide.

Let the optimizer remove the runtime

As of 1.37.29, emcc's optimizer is powerful enough to remove all runtime elements that are not used (it does this using meta-dce, dead code elimination that crosses the JavaScript/WebAssembly boundary). This can be helpful as then the necessary runtime is smaller and easier to replace, making the output even more standalone. To try this, simply build with something like

emcc source.c -Os

-Os is needed to make the optimizer work at full power. You can also use -Oz or -O3 (but not -O2 or lower).

Notes:

  • If you use C++ objects, the compiler must in many cases emit code to catch exceptions so that RAII destructors are called, and exceptions require a lot of runtime support. Build with -fno-exceptions to disable exceptions entirely.
  • More details on how this works.

Create a dynamic library

Dynamic libraries have a formal definition, and are designed to be loadable in a standard way. They also do not link in system libraries like libc. For those reasons they may be useful in some cases, but dynamic libraries also have downsides, such as having relocations for memory and function pointers, which add overhead that may be unnecessary if you are only using one module (and not linking several together), so overall they are not recommended - use STANDALONE_WASM as described earlier.

If you do want to build a dynamic library, use

emcc source.c -s SIDE_MODULE=1 -o target.wasm

That will emit the output dynamic library as target.wasm.

To use a side module, see loadWebAssemblyModule in src/runtime.js for some example loading code.

  • The conventions for a wasm dynamic library are here.
  • Note that there is no special handling of a C stack. A module can have one internally if it wants one (it needs to ask for the memory for it, then handle it however it wants).
  • Full example.

Just view your own compiled code

To just see your own compiled code you can build to a wasm object file,

emcc input.cpp -o output.wasm -c

You can inspect that wasm file using wabt's wasm2wat or binaryen's wasm-dis.

See also the next section on not linking in system libraries, which is another way to see just your own code.

Supplying your own system libraries

Emscripten will normally link in libc and other system library code as needed. If you want to avoid that and implement system libraries in the runtime that loads the wasm (which lets the wasm files be smaller) then you can build with EMCC_ONLY_FORCED_STDLIBS=1 in the environment, which will link in only system libs that you specify (so if you specify none, none are linked in). Any calls to libc and other system libraries will end up as unresolved symbols, which error by default, but you can pass -s ERROR_ON_UNDEFINED_SYMBOLS=0 to turn those into just warnings, and the wasm will have imports for them.

For example:

EMCC_ONLY_FORCED_STDLIBS=1  ./emcc tests/hello_world.c -o a.wasm -s ERROR_ON_UNDEFINED_SYMBOLS=0 -O3

That emits a very small wasm file that has an import for puts from libc. (For comparison, without EMCC_ONLY_FORCED_STDLIBS=1 libc is linked in, which makes the binary much larger.)