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.
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.
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.
#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.
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.
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!
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
- 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>"
- 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>
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 .
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"
We currently do not offer any support for building the sandbox natively on other OSes.
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
- 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 at65,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 ornodejs
. Therefore, we have added basic emulation of the random device that is fully deterministic. For details, see #5.
More examples of precompiled Wasm binaries can be found in golemfactory/wasm-store repo.
All contributions are welcome. See CONTRIBUTING.md for pointers.
Licensed under GNU General Public License v3.0.