Skip to content

Latest commit

 

History

History
112 lines (92 loc) · 5.95 KB

README.md

File metadata and controls

112 lines (92 loc) · 5.95 KB

Emception

Compile C/C++ code with Emscripten in the browser. You can see it in action in the live demo.

Build

To build Emception, simply clone the repo, and run ./build-with-docker.sh. I've only built it on Linux, but you should be able to build it from anywhere you can run Docker. Bear in mind that the build will take a long while (specially cloning/building llvm), so go get a a cup of coffee.

git clone https://github.com/jprendes/emception.git
cd emception
./build-with-docker.sh

This will generate the files in build/emception. To build the demo, from the demo folder run npm install and npm run build. You can then run the demo locally running npx serve build/demo.

pushd demo
npm install
npm run build
popd
npx serve build/demo

Interesting sub-projects

This is a list of parts of this project that I think could be interesting on their own.

llvm-box

This is clang/llvm compiled to WebAssembly. It bundles many llvm executables in one binary, kind of like BusyBox bundles Unix command line utilities in one binary. llvm-box packs the following executables: clang, lld, llvm-nm, llvm-ar, llvm-objcopy, and llc.

This is done to reduce the binary size. With all the executables living in the same binary, they can share the same standard libraries as well as the common llvm codebase.

binaryen-box

Just like with llvm-box, binaryen-box bundles many binaryen executables in one WebAssembly binary. binaryen-box packs the following executables: wasm-as, wasm-ctor-eval, wasm-emscripten-finalize, wasm-metadce, and wasm-opt wasm-shell.

As with llvm-box, this is done to reduce binary size.

wasm-transform

This is the tool used to bundle many executables into one binary module. wasm-transform modifies the wasm object files from different executables so that they can be linked together into one binary. It also prints out the metadata required to create a new main function that dispatches execution to the different binaries.

wasm-transform applies the following changes on WebAssembly object files:

  • renames the main symbol, appending a hash computed from the object file path.
  • identifies the functions that run global constructors (called init functions) and renames them so that they are valid C function names and appends a hash computed from the object file path. Then it marks them as normal functions and removes them from the list of init functions.

Additionally, for each renamed symbol it prints:

  • a line starting with entrypoint followed by the new name of the renamed main function.
  • a line starting with constructor followed by a number representing the priority of the init function, and the new name of the renamed init function. Init functions should be run by priority in ascending order.

The information printed by wasm-transform can be used to create a new main function that dispatches execution to the renamed main functions, e.g., based on the value of argv[0].

For example, bundling executable_a and executable_b would look like this:

$ wasm-transform executable_a.o executable_a_transformed.o
constructor 65535 _GLOBAL__sub_I_executable_a_cpp_123
entrypoint main_123
$ wasm-transform executable_b.o executable_b_transformed.o
constructor 65535 _GLOBAL__sub_I_executable_b_cpp_456
entrypoint main_456

With that information, the dispatching main.cpp function could look like this:

#include <string_view>

extern "C" void _GLOBAL__sub_I_executable_a_cpp_123();
extern "C" void main_123();

extern "C" void _GLOBAL__sub_I_executable_b_cpp_456();
extern "C" void main_456();

int main(int argc, char const ** argv) {
    std::string_view const argv0 = argv[0];
    if (argv0.ends_with("executable_a")) {
        _GLOBAL__sub_I_executable_a_cpp_123();
        return main_123(argc, argv);
    } else if (argv0.ends_with("executable_b")) {
        _GLOBAL__sub_I_executable_b_cpp_456();
        return main_456(argc, argv);
    }
    return 1;
}

Everything can then be linked together:

em++ dispatching_main.cpp executable_a_transformed.o executable_b_transformed.o -o bundled_executables.mjs

wasm-package

A tar-like application, that can pack several files in one archive. It preserves file permissions and symlinks. Its main purpose is to pre-load files in an Emscripten module, using a native build to pack the files, and a WebAssembly build to unpack them.

This works particularly well in conjunction with a brotli build in WebAssembly for servers that don't serve brotli compressed content (like GitHub pages): Create a brotli module, a wasm-package module, and your target module, all sharing the same file system using SHAREDFS.js (see below). Write the compressed file in a temporary location and use the brotli build to decompress it. Then use the wasm-package to unpack the archive content. Now your module should be able to access the preloaded files.

fsroot.js

This is an Emscripten library that lets you change the type of the root filesystem of an emscripten module. By default emscripten uses a MEMFS for the root, and mounts other type of filesystems in it (with the exception of NODERAWFS, which completely overrides the FS). With fsroot.js you can specify the root to be a different type of FS. For example

import Module from "./module.mjs";

const first = await new Module(...);
const second = await new Module({
    ROOT: {
        type: "PROXYFS",
        opts: {
            root: "/",
            fs: first.FS,
        },
    }
    ...
});

first.FS.writeFile("/tmp/test", "hello world!");
const str = second.FS.readFile("/tmp/test", { encoding: "utf8" });
console.log(str); // logs "hello world!"

With this, first and second would share the root filesystem. This is achieved by mounting a PROXYFS into the root or second. One consideration is that two MEMFS are always mounted, one in /dev, and the other in /self/proc, since this is usully the desired behaviour.