Skip to content

Instantly share code, notes, and snippets.

@LukasKalbertodt
Last active September 22, 2020 12:18
Show Gist options
  • Save LukasKalbertodt/821ab8b85a25f4c54544cc43bed2c39f to your computer and use it in GitHub Desktop.
Save LukasKalbertodt/821ab8b85a25f4c54544cc43bed2c39f to your computer and use it in GitHub Desktop.
Several Rust & WebAssembly notes

Compiling Rust to Wasm manually with the LLVM wasm-backend (without Emscripten)

EDIT November 2017: recently the target wasm32-unknown-unknown was added to rustc which uses the LLVM WASM backend and works without Emscripten. This is now the recommended way of generating WASM code from Rust (as it is much easier). Thus, this gist document is pretty much useless now. A great resource on getting started with WASM and Rust is hellorust.com: Setup and Minimal Example.





Preparation

  1. Download the latest llvm version, preferably directly from their svn repo or this git mirror. Compile LLVM with enabled wasm-backend (this will take around one hour):
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly ..
make -j8
  1. Download and compile Binaryen. Just clone the git repo, cd into it and follow the build instructions.

  2. Install a recent version of rustc (stable channel is fine, though!).

  3. Make sure all of the above software is installed and correctly in your $PATH. We need several executables usually hidden in the bin/ folder.

Compile

  1. Write your awesome Rust library (add.rs):
#[no_mangle]
pub fn add_twenty_seven(n: i32) -> i32 {
    n + 27
}

We add the #[no_mangle] attribute to preserve the exact function name instead of mangling it (this is useful for usage from other Rust code, therefore it's on by default).

  1. Compile the library into llvm bitcode (resulting in add.bc):
rustc --crate-type=lib --emit=llvm-bc add.rs
  1. Convert llvm-bc to wasm assembly in a linear assembly format (resulting in add.s):
llc -march=wasm32 add.bc
  1. Convert the .s assembly into an s-expression assembly format (resulting in add.wast):
s2wasm -o add.wast add.s

This should result in something like this:

(module
 (table 0 anyfunc)
 (memory $0 1)
 (export "memory" (memory $0))
 (export "add_twenty_seven" (func $add_twenty_seven))
 (func $add_twenty_seven (param $0 i32) (result i32)
  (i32.add
   (get_local $0)
   (i32.const 27)
  )
 )
)
  1. Finally, assembly the wast file into the binary add.wasm file:
wasm-as add.wast

Execute it

You can embed it into a website like so:

index.html:

<html>
<head>
<script src="main.js"></script>
</head>
<body>Hi</body>
</html>

main.js:

function loadWasm() {
    fetch('add.wasm').then(response => 
        response.arrayBuffer()
    ).then(buffer =>
        WebAssembly.instantiate(buffer)
    ).then(({module, instance}) => {
        console.log(instance.exports.add_twenty_seven);
        console.log(instance.exports.add_twenty_seven(4));
    });
}

loadWasm()

Next, start a local webserver. This is necessary, file server does not work!

python3 -m http.server 8000

Visit localhost:8000 in your browser, open the JS console and you should see:

function 0() { [native code] }
31

Using functions from JavaScript in Rust

print.rs:

extern {
    fn print(n: i32);
}

#[no_mangle]
pub fn print_twenty_seven_more(n: i32) {
    unsafe {
        print(n + 27);
    }
}

main.js:

function loadWasm() {
    var importObj = {env: {
        print: console.log,
    }};

    fetch('print.wasm').then(response => 
        response.arrayBuffer()
    ).then(buffer =>
        WebAssembly.instantiate(buffer, importObj)
    ).then(({module, instance}) => {
        console.log(instance.exports.print_twenty_seven_more(2));
    });
}

loadWasm()

Compile the Rust file exactly as explained above. Also use the index.html from above. The JS console should show 29.

#!/bin/bash
# Super simple script to compile Rust to wasm. Usage:
# ./compile.sh foo.rs
if [ -z ${1+x} ]; then
echo "missing argument: rust source file"
exit 1
fi
stem="${1%.*}"
rustc -O --crate-type=lib --emit=llvm-bc "${stem}.rs" && \
llc -march=wasm32 "${stem}.bc" && \
s2wasm -o "${stem}.wast" "${stem}.s" && \
wasm-as "${stem}.wast"
@OpenGG
Copy link

OpenGG commented Nov 17, 2017

I try rustc --crate-type=lib --emit=llvm-bc -C opt-level=3 add.rs, and the output comes without the _ZN4core9panicking5panic* import.

@LukasKalbertodt
Copy link
Author

@OpenGG Sorry, apparently I don't receive notifications for gist documents. Unfortunately, I don't really know why you have this panic reference in your output. But please see the edit at the very top of this gist: compiling to wasm is much easier now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment