Skip to content

golemfactory/sp-wasm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

93 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SpiderMonkey-based WebAssembly Sandbox

Build Status

A WebAssembly sandbox using standalone SpiderMonkey engine. For v8 version, see golemfactory/v8-wasm.

This WebAssembly sandbox is used in current development version of Golem: golem/apps/wasm. If you would like to launch a gWASM task in Golem, see here.

Quick start guide

This guide assumes you have successfully built the wasm-sandbox binary; for build instructions, see section Build instructions below. If you are running Linux, then you can also use the prebuilt binaries from here.

1. Create and cross-compile simple program

Let us create a simple hello world style program which will read in some text from in.txt text file, read your name from the command line, and save the resultant text in out.txt. We'll demonstrate how to cross-compile apps to Wasm for use in Golem in two languages of choice: C and Rust.

1.1 C/C++

#include <stdio.h>

int main(int argc, char** argv) {
  char* name = argc >= 2 ? argv[1] : "anonymous";
  size_t len = 0;
  char* line = NULL;
  ssize_t read;

  FILE* f_in = fopen("in.txt", "r");
  FILE* f_out = fopen("out.txt", "w");

  while ((read = getline(&line, &len, f_in)) != -1)
      fprintf(f_out, "%s\n", line);

  fprintf(f_out, "%s\n", name);

  fclose(f_out);
  fclose(f_in);

  return 0;
}

There is one important thing to notice here. The sandbox communicates the results of computation by reading and writing to files. Thus, every Wasm program is required to at the very least create an output file. If your code does not include file manipulation in its main body, then the Emscripten compiler, by default, will not initialise JavaScript FS library, and will trip the sandbox. This will also be true for programs cross-compiled from Rust.

Now, we can try and compile the program with Emscripten. In order to do that you need Emscripten SDK installed on your system. For instructions on how to do it, see here.

$ emcc -o simple.js simple.c

Emscripten will then produce two files: simple.js and simple.wasm. The produced JavaScript file acts as glue code and sets up all of the rudimentary syscalls in JavaScript such as MemFS (in-memory filesystem), etc., while the simple.wasm is our C program cross-compiled to Wasm.

1.2 Rust

With Rust, firstly go ahead and create a new binary with cargo

$ cargo new --bin simple

Then go ahead and paste the following to simple/src/main.rs file

use std::env;
use std::fs;
use std::io::{self, Read, Write};

fn main() -> io::Result<()> {
    let args = env::args().collect::<Vec<String>>();
    let name = args.get(1).map_or("anonymous".to_owned(), |x| x.clone());

    let mut in_file = fs::File::open("in.txt")?;
    let mut contents = String::new();
    in_file.read_to_string(&mut contents)?;

    let mut out_file = fs::File::create("out.txt")?;
    out_file.write_all(&contents.as_bytes())?;
    out_file.write_all(&name.as_bytes())?;

    Ok(())
}

As was the case with C program, it is important to notice here that the sandbox communicates the results of computation by reading and writing to files. Thus, every Wasm program is required to at the very least create an output file. If your code does not include file manipulation in its main body, then the Emscripten compiler, by default, will not initialise JavaScript FS library, and will trip the sandbox.

In order to cross-compile Rust to Wasm compatible with Golem's sandbox, firstly we need to install rustc 1.38.0 toolchain which includes fastcomp backend for wasm32-unknown-emscripten target

$ rustup toolchain add 1.38.0

Then, we need to install the required target which is wasm32-unknown-emscripten. The easiest way of doing so, as well as generally managing your Rust installations, is to use rustup

$ rustup target add wasm32-unknown-emscripten --toolchain 1.38.0

Note that cross-compiling Rust to this target still requires that you have Emscripten SDK installed on your system. For instructions on how to do it, see here.

Now, we can compile our Rust program to Wasm. Make sure you are in the root of your Rust crate, i.e., at the top of simple if you didn't change the name of your crate, and run

$ cargo +1.38.0 build --target=wasm32-unknown-emscripten --release

If everything went OK, you should now see two files: simple.js and simple.wasm in simple/target/wasm32-unknown-emscripten/release. Just like in C program's case, the produced JavaScript file acts as glue code and sets up all of the rudimentary syscalls in JavaScript such as MemFS (in-memory filesystem), etc., while the simple.wasm is our Rust program cross-compiled to Wasm.

2. Create input and output dirs and files

The sandbox will require us to specify input and output paths together with output filenames to create, and any additional arguments (see CLI arguments explained section below for detailed specification of the required arguments). Suppose we have the following file structure locally

  |-- in/
  |    |
  |    |-- in.txt
  |
  |-- out/

Paste the following text in the in.txt file

// in.txt
You are running Wasm!

3. Run!

After you have successfully run all of the above steps up to now, you should have the following file structure locally

  |-- simple.js
  |-- simple.wasm
  |
  |-- in/
  |    |
  |    |-- in.txt
  |
  |-- out/

We can now run our Wasm binary inside the sandbox

  1. using Docker (if you've followed Using Docker (recommended) build instructions)
docker run --mount type=bind,source=$PWD,target=/workdir --workdir /workdir \
            wasm-sandbox:latest -I in/ -O out/ -j simple.js -w simple.wasm \
            -o out.txt -- "<your_name>"
  1. natively (if you're using the prebuilt binaries, or you've built natively following Natively on Linux build instructions)
$ wasm-sandbox -I in/ -O out/ -j simple.js -w simple.wasm \
               -o out.txt -- "<your_name>"

Here, -I maps the input dir with all its contents (files and subdirs) directly to the root / in MemFS. The output files, on the other hand, will be saved in out/ local dir. The names of the expected output files have to match those specified with -o flags. Thus, in this case, our Wasm bin is expected to create an output file /out.txt in MemFS which will then be saved in out/out.txt locally.

After you execute Wasm bin in the sandbox, out.txt should be created in out/ dir

  |-- simple.js
  |-- simple.wasm
  |
  |-- in/
  |    |
  |    |-- in.txt
  |
  |-- out/
  |    |
  |    |-- out.txt

with the contents similar to the following

// out.txt
You are running Wasm!
<your_name>

Build instructions

Using Docker (recommended)

To build using Docker, simply run

$ ./build.sh

If you are running Windows, then you can invoke the command in the shell script manually in the command line as follows

docker build -t wasm-sandbox:latest .

Natively on Linux

NOTE: Building the sandbox from source requires rustc 1.38.0 due to fastcomp backend compability for wasm32-unknown-emscripten target, and other changes that are incompatible with SpiderMonkey Rust wrappers.

To build natively on Linux, first install rustc 1.38.0 toolchain

$ rustup toolchain add 1.38.0

Next, you need to follow the installation instructions of servo/rust-mozjs and servo/mozjs. The latter is Mozilla's Servo's SpiderMonkey fork and low-level Rust bindings, and as such, requires C/C++ compiler and Autoconf 2.13. See servo/mozjs/README.md for detailed building instructions.

After following the aforementioned instructions, to build the sandbox, run

$ cargo +1.38.0 build --release

If you would like to build with SpiderMonkey's debug symbols and extensive logging, run instead

$ cargo +1.38.0 build --release --features "debugmozjs"

Natively on other OSes

We currently do not offer any support for building the sandbox natively on other OSes.

CLI arguments explained

wasm-sandbox -I <input-dir> -O <output-dir> -j <wasm-js> -w <wasm> -o <output-file>... -- <args>...

where

  • -I path to the input dir
  • -O path to the output dir
  • -j path to the Emscripten JS glue script
  • -w path to the Emscripten WASM binary
  • -o paths to expected output files
  • -- anything after this will be passed to the WASM binary as arguments

By default, basic logging is enabled. If you would like to enable more comprehensive logging, export the following variable

RUST_LOG=debug

Caveats

  • Building the sandbox from source requires rustc 1.38.0 due to fastcomp backend compability for wasm32-unknown-emscripten target, and other changes that are incompatible with SpiderMonkey Rust wrappers.
  • Sometimes, if the binary you are cross-compiling is of substantial size, you might encounter a asm2wasm validation error stating that there is not enough memory assigned to Wasm. In this case, you can circumvent the problem by adding -s TOTAL_MEMORY=value flag. The value has to be an integer multiple of 1 Wasm memory page which is currently set at 65,536 bytes.
  • When running your Wasm binary you encounter an OOM error at runtime, it usually means that the sandbox has run out-of-memory. To alleviate the problem, recompile your program with -s ALLOW_MEMORY_GROWTH=1.
  • Emscripten, by default, doesn't support /dev/(u)random emulation targets different than either browser or nodejs. Therefore, we have added basic emulation of the random device that is fully deterministic. For details, see #5.

Wasm store

More examples of precompiled Wasm binaries can be found in golemfactory/wasm-store repo.

Contributing

All contributions are welcome. See CONTRIBUTING.md for pointers.

License

Licensed under GNU General Public License v3.0.