Skip to content
This repository has been archived by the owner on Jul 27, 2022. It is now read-only.

Development

Nick Bray edited this page Oct 9, 2013 · 2 revisions

To create an application that can target both Portable Native Client (PNaCl) and Emscripten, it is necessary to work within the constraints of both platforms. To do this, an application must be written to use the Pepper Plugin API, must not use threads, and must not rely on memory mapping or any sort of memory page protection.

Use the Pepper Plugin API

To be compatible with PNaCl, it is necessary to use the Pepper Plugin API to interact with the browser. In the general case, Emscripten lets a developer define JavaScript functions that can be invoked synchronously from native code. pepper.js provides a set of JavaScript functions that implement Pepper and invoking any other JavaScript functions would break cross-toolchain compatibility.

Use JavaScript's Concurrency Model

To be compatible with Emscripten, it is necessary to structure the program so that it does not create additional threads and does not block the main thread. In other words, the program must be written as if it were an event-driven JavaScript program. The Pepper Plugin API imposes the same non-blocking requirement on its main thread of execution, so this constraint is equivalent to requiring that a Pepper plugin only runs on the main thread and does not create any additional threads.

Memory Behaves Differently

Emscripten also exposes a simplified version of the traditional native memory model: memory is a linear array. This means that page protections do not exist, memory accesses never fault, and mmap is not supported. The Pepper Plugin API implicitly uses mmap in a few of its APIs, and pepper.js emulates mmaping by silently copying on use any memory that may have been modified. This approach has obvious performance implications, but for the moment it provides the best emulation of Pepper’s semantics.

Note: not having page protections results in a subtle "gotcha" when porting to Emscripten. Dereferencing a null pointer (or accessing unmapped memory of any sort) will cause a segfault in Native Client (and pretty much any other native platform) whereas it will succeed in Emscripten and return junk data. According to the C spec, dereferencing a null pointer results in undefined behavior, so this is theoretically "working as intended". In practice, however, existing code may rely on null pointer dereferences causing memory faults to implicitly assert a pointer is not null. This is a subtle portability issue for Emscripten and generally a bad idea, even when not targeting Emscripten.

Conditional Compilation

Of course, all of these constraints can be worked around using the C preprocessor and conditional compilation. For example, threading can be enabled on Native Client by guarding the relevant code with #if defined(__native_client__) ... #endif. Emscripten-specific functionality can be conditioned on defined(__EMSCRIPTEN__). This approach is generally not recommended, but there are situations where the benefits outweigh the additional complexity - such as performance improvements from multithreading or calling directly to JavaScript rather than mediating through postMessage.

C++ Exceptions

The use of C++ exceptions is currently discouraged for two reasons. First, Emscripten disables exception handling by default for -O1 and higher. This can be overridden by passing -s DISABLE_EXCEPTION_CATCHING=0 to Emscripten, but doing so may or may not result in a noticeable performance penalty. Additional code will be generated at every call site an exception could propagate through. Second, exceptions are currently not supported by PNaCl.