Compiling to the web with Rust and emscripten

Hey there, you slithery Rustilian.

Have you heard the good word? Rust can now target asm.js and WebAssembly (wasm) via
emscripten. That means you can run Rust code on the web, and this capability
is available on Rust nightlies right now! Let me tell you how to Rust the web.

First, a disclaimer. This support is still a work in progress and there are
going to be a whole lot of bugs (report them, you better). It's at the stage
where it passes the test suite, but has not been vetted on real projects at
all
. And the runtime properties of the web are far different from traditional
platforms - in particular most of Rust's I/O stack is unimplemented and will
simply fail at runtime, and badly. No work has gone into optimizing these
ports, and in particular unwinding support is known to be excruciatingly slow
in emscripten. You're on the bleeding edge. You are a pioneer.

wasm support is almost entirely untested, but is likely to work. The wasm format
is not finalized. The way rustc emits wasm by default is by emitting a
javascript file that contains a wasm interpreter, alongside a wasm file. If
the interpreter detects that the JS engine supports wasm (which it almost
certainly won't!) then it will use the native implementation, if not it will use
its own interpreter. So it's gunna be slow.

The setup

We need rustup and the emscripten SDK. We'll install the Rust nightly
toolchain and the emscripten incoming toolchain. I've only tested this on Linux,
but macOS should work similarly. The Windows steps will be slightly different,
but in theory it should be possible there as well.

Rust installation:

curl -L https://sh.rustup.rs | sh -s -- -y --default-toolchain=nightly
source ~/.cargo/env
rustup target add asmjs-unknown-emscripten
rustup target add wasm32-unknown-emscripten

If you already have rustup installed, then instead of running the curl and
source commands above, just switch to the nightly toolchain with rustup default nightly.

Emscripten installation:

curl -O https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz
tar -xzf emsdk-portable.tar.gz
source emsdk_portable/emsdk_env.sh
emsdk update
emsdk install sdk-incoming-64bit
emsdk activate sdk-incoming-64bit

Running some code

Hello world in asm.js:

echo 'fn main() { println!("Hello, Emscripten!"); }' > hello.rs
rustc --target=asmjs-unknown-emscripten hello.rs
node hello.js

Hello world in wasm:

echo 'fn main() { println!("Hello, Emscripten!"); }' > hello.rs
rustc --target=wasm32-unknown-emscripten hello.rs
node hello.js

Note that when I ran these commands the first time the build took a long
time. It looks like emscripten is doing a lazy compile of binaryen the first
time you run it. Just give it time.

Building a real project

That's just the simplest thing, but cargo works too. For some examples of all this
in action see @jer's demo page.

52 Likes

Is there any guidance on running unit tests on with the new backends? I'd like to try adding these targets to the CI on a couple of my projects to see if it shakes out any bugs.

1 Like

What benefits do rust's memory safety and concurrency guarantees afford us when targeting a runtime that is garbage collected and single-threaded? Being able to target asm.js and WA is cool for sure, I'm just wondering what the practical implications are.

3 Likes

asmjs/wasm are not GC'd. It does currently need to be single-threaded, but when Web Workers gets finalized...

11 Likes

TIL. Very cool!

1 Like

I really don't like building LLVM. Can emsdk work with a local LLVM?

1 Like

The LLVM build fails at 80% on OSX during the emsdk install sdk-incoming-64bit step:

Stack dump:
0.	Program arguments: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -cc1 -triple x86_64-apple-macosx10.9.0 -emit-obj -disable-free -disable-llvm-verifier -main-file-name CGBlocks.cpp -mrelocation-model pic -pic-level 2 -mdisable-fp-elim -masm-verbose -munwind-tables -target-cpu core2 -target-linker-version 241.9 -gdwarf-2 -coverage-file /Users/alex/Programming/rust/emsdk_portable/clang/fastcomp/build_incoming_64/tools/clang/lib/CodeGen/CMakeFiles/clangCodeGen.dir/CGBlocks.cpp.o -resource-dir /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/6.0 -D CLANG_ENABLE_ARCMT -D CLANG_ENABLE_OBJC_REWRITER -D CLANG_ENABLE_STATIC_ANALYZER -D GTEST_HAS_RTTI=0 -D _GNU_SOURCE -D __STDC_CONSTANT_MACROS -D __STDC_FORMAT_MACROS -D __STDC_LIMIT_MACROS -D NDEBUG -I /Users/alex/Programming/rust/emsdk_portable/clang/fastcomp/build_incoming_64/tools/clang/lib/CodeGen -I /Users/alex/Programming/rust/emsdk_portable/clang/fastcomp/src/tools/clang/lib/CodeGen -I /Users/alex/Programming/rust/emsdk_portable/clang/fastcomp/src/tools/clang/include -I /Users/alex/Programming/rust/emsdk_portable/clang/fastcomp/build_incoming_64/tools/clang/include -I /Users/alex/Programming/rust/emsdk_portable/clang/fastcomp/build_incoming_64/include -I /Users/alex/Programming/rust/emsdk_portable/clang/fastcomp/src/include -stdlib=libc++ -O2 -Wall -W -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers -Wno-long-long -Wcovered-switch-default -Wdelete-non-virtual-dtor -Woverloaded-virtual -Wno-nested-anon-types -pedantic -std=c++11 -fdeprecated-macro -fdebug-compilation-dir /Users/alex/Programming/rust/emsdk_portable/clang/fastcomp/build_incoming_64/tools/clang/lib/CodeGen -ferror-limit 19 -fmessage-length 204 -fvisibility-inlines-hidden -stack-protector 1 -mstackrealign -fblocks -fno-rtti -fobjc-runtime=macosx-10.9.0 -fencode-extended-block-signature -fno-common -fdiagnostics-show-option -fcolor-diagnostics -vectorize-loops -vectorize-slp -o CMakeFiles/clangCodeGen.dir/CGBlocks.cpp.o -x c++ /Users/alex/Programming/rust/emsdk_portable/clang/fastcomp/src/tools/clang/lib/CodeGen/CGBlocks.cpp
1.	<eof> parser at end of file
2.	Code generation
3.	Running pass 'Function Pass Manager' on module '/Users/alex/Programming/rust/emsdk_portable/clang/fastcomp/src/tools/clang/lib/CodeGen/CGBlocks.cpp'.
4.	Running pass 'X86 DAG->DAG Instruction Selection' on function '@_ZN5clang7CodeGen15CodeGenFunction12EmitCallArgsINS_17FunctionProtoTypeEEEvRNS0_11CallArgListEPKT_N4llvm14iterator_rangeINS_4Stmt17ConstExprIteratorEEEPKNS_12FunctionDeclEj'
[ 80%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/Mangle.cpp.o
clang: error: unable to execute command: Segmentation fault: 11
clang: error: clang frontend command failed due to signal (use -v to see invocation)
Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin13.4.0
Thread model: posix
clang: note: diagnostic msg: PLEASE submit a bug report to http://developer.apple.com/bugreporter/ and include the crash backtrace, preprocessed source, and associated run script.
clang: note: diagnostic msg:
********************

PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:
Preprocessed source(s) and associated run script(s) are located at:
clang: note: diagnostic msg: /var/folders/7l/cvkn0kbj307fyjc6zkfs6g_w0000gn/T/CGBlocks-0de7cf.cpp
clang: note: diagnostic msg: /var/folders/7l/cvkn0kbj307fyjc6zkfs6g_w0000gn/T/CGBlocks-0de7cf.sh
clang: note: diagnostic msg:

********************
make[2]: *** [tools/clang/lib/CodeGen/CMakeFiles/clangCodeGen.dir/CGBlocks.cpp.o] Error 254
make[1]: *** [tools/clang/lib/CodeGen/CMakeFiles/clangCodeGen.dir/all] Error 2
make[1]: *** Waiting for unfinished jobs....
[ 80%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaCodeComplete.cpp.o
[ 80%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/MicrosoftCXXABI.cpp.o
[ 80%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/MicrosoftMangle.cpp.o
[ 80%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/NestedNameSpecifier.cpp.o
[ 80%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/NSAPI.cpp.o
[ 80%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaConsumer.cpp.o
[ 80%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaCoroutine.cpp.o
[ 80%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/OpenMPClause.cpp.o
[ 80%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaCUDA.cpp.o
[ 80%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/ParentMap.cpp.o
[ 81%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaDecl.cpp.o
[ 81%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/RawCommentList.cpp.o
[ 81%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/RecordLayout.cpp.o
[ 81%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaDeclAttr.cpp.o
[ 81%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/RecordLayoutBuilder.cpp.o
[ 81%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/SelectorLocationsKind.cpp.o
[ 81%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/Stmt.cpp.o
[ 81%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/StmtCXX.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/StmtIterator.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaDeclCXX.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/StmtObjC.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/StmtOpenMP.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/StmtPrinter.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaDeclObjC.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/StmtProfile.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/StmtViz.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/TemplateBase.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaExceptionSpec.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/TemplateName.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaExpr.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/Type.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/TypeLoc.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/TypePrinter.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaExprCXX.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/VTableBuilder.cpp.o
[ 82%] Building CXX object tools/clang/lib/AST/CMakeFiles/clangAST.dir/VTTBuilder.cpp.o
[ 82%] Linking CXX static library ../../../../lib/libclangAST.a
[ 82%] Built target clangAST
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaExprMember.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaExprObjC.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaFixItUtils.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaInit.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaLambda.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaLookup.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaObjCProperty.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaOpenMP.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaOverload.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaPseudoObject.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaStmt.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaStmtAsm.cpp.o
[ 82%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaStmtAttr.cpp.o
[ 83%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaTemplate.cpp.o
[ 83%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaTemplateDeduction.cpp.o
[ 83%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaTemplateInstantiate.cpp.o
[ 83%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaTemplateInstantiateDecl.cpp.o
[ 83%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaTemplateVariadic.cpp.o
[ 83%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/SemaType.cpp.o
[ 83%] Building CXX object tools/clang/lib/Sema/CMakeFiles/clangSema.dir/TypeLocBuilder.cpp.o
[ 83%] Linking CXX static library ../../../../lib/libclangSema.a
[ 83%] Built target clangSema
make: *** [all] Error 2
Build failed due to exception!
Working directory: /Users/alex/Programming/rust/emsdk_portable/clang/fastcomp/build_incoming_64
Command '['make', '-j3']' returned non-zero exit status 2
Installation failed!

Did you run out of memory?

I guess that's possible? I have plenty and I've built LLVM before.

1 Like

(it did work for me on osx)

Exciting!

Nit: When SharedArrayBuffers get finalized

1 Like

@jer Is there rust source code for your examples from Hello Rust available?

Great stuff!

Observations for anyone else following along at home:

  • The source emsdk_portable/emsdk_env.sh step requires that the python executable be a python2 interrupter.
  • After you finish the emsdk activate step, you have to manually add the emsdk_portable/emscripten/incoming folder to your $PATH
2 Likes

Any ideas how to build a library? I can build a binary (cargo new --bin), and run the resulting .js file. But what about a library? I tried all the documented crate-type attributes, but none resulted in a .js file

The asm.js example triggered a warning to install the Java Development Kit is this expected behaviour? Using MacOS Sierra.

In theory, can we expect a faster JavaScript library built by this than by native JavaScript?

1 Like

@booyaa I would not expect that.

@eminence I have not thought about what it would mean to create a js library. We would want to look into how emcc itself is used to create js libraries and figure out what that means for rustc.

asm.js and wasm modules have entry points that define the environment accessible to them (see 'External Code and Data' in the asm spec). The .js files output via --bin should simply be invoking this entry point at startup. And somehow it should be possible to emit a js file that does not automatically call this entry point and that customizes it for whatever purpose. I'm sure the emscripten docs explain it somewhere.

Not yet. I clean them up and push them in the following days.

You can certainly compile them, just not really use them.
For this, you have to use a bit of a hackish way: build a binary, but properly export required functions so you can call them from the JavaScript side. I will provide a guide on how to do that next.

1 Like

Emscripten can use the Closure compiler to reduce code size, but it should not do so by default. I think I had a similar thing once, but it was only a warning and didn't stop the compiling.