04 Webassembly in-depth

Handling complex types

Let's return a String

  • wasm only knows some numeric types
  • Create String and return pointer
  • Read String from heap in JS

Rust

                
                    use std::ffi::CString;
                    use std::os::raw::c_char;

                    #[no_mangle]
                    pub extern "C" fn getWelcomeMessage() -> *mut c_char {
                        CString::new("Hello from Rust!")
                            .unwrap()
                            .into_raw()
                    }
                
                
                
                    *mut c_char
                
                
  • Pointer to a mutable c_char
  • Representation of C base type
                
                    CString::new(s)
                
                
  • C-Compatible String
  • nul-terminated
  • No nul bytes in the middle
                
                    .unwrap()
                    .into_raw()
                
                
  • Get the underlying c_char type
  • Convert to pointer
  • Hand off memory management
  • No GC of this memory by Rust

Memory leak?

  • String is on wasm heap
  • JS will not do GC for this

Rust

                
                    #[no_mangle]
                    pub extern "C" fn dealloc_str(ptr: *mut c_char) {
                        unsafe {
                            let _ = CString::from_raw(ptr);
                        }
                    }
                
                

unsafe

  • We read from an arbitrary pointer
  • No type-safety
  • No memory-safety

JavaScript

                
                    const module = results.instance.exports;
                    let pointer = module.getWelcomeMessage();
                    let message = copyCStr(module, pointer);
                    document.getElementById("container").textContent = message;
                
                
                
                    function copyCStr(module, ptr) {
                        let orig_ptr = ptr;
                        function* collectCString() {
                            let memory = new Uint8Array(module.memory.buffer);
                            while (memory[ptr] !== 0) {
                                if (memory[ptr] === undefined) { throw new Error("Tried to read undef mem") }
                                yield memory[ptr]
                                ptr += 1
                            }
                        }

                        const buffer_as_u8 = new Uint8Array(collectCString())
                        const utf8Decoder = new TextDecoder("UTF-8");
                        const buffer_as_utf8 = utf8Decoder.decode(buffer_as_u8);
                        module.dealloc_str(orig_ptr);
                        return buffer_as_utf8
                    }
                
                

copyCStr

  • Get Array from wasm memory buffer
  • Read until we get to the first 0
  • Decode given bytes as UTF-8
  • Call dealloc function from Rust

Exercise Time!

  • Return a string from Rust

But how do we pass them into wasm?

wasm-bindgen

  • Rust library
  • Allow high-level interaction
  • Polyfill for features like "host bindings"
  • Generate TypeScript typings 😍

wasm-bindgen

  • Not "pure" wasm anymore
  • Needs bindings on JS and Rust side
http://www.sdknews.com/firefox-os/making-webassembly-better-for-rust-for-all-languages

Steps

  1. Ensure Rust can use wasm-bindgen
  2. Compile Rust with wasm-bindgen
  3. Import into JS and call it

Importing and activating wasm_bindgen

                
                    #![feature(proc_macro, wasm_custom_section, wasm_import_module)]
                    extern crate wasm_bindgen;
                    use wasm_bindgen::prelude::*;
                
                

Hello World! 😎

                
                    #[wasm_bindgen]
                    pub fn greet(name: &str) -> String {
                        format!("Hello, {}!", name)
                    }
                
                
                
                    format!("Hello, {}!", name)
                
                
  • Rust Macro
  • "!"
  • More flexible than functions
  • e.g. number of parameters
                
                    fn greet(name: &str) -> String
                
                

Build Step

  • We have to build JS bindings too
  • Webassembly Studio will compile this
  • We only have to include these in our HTML

Build Step (current)

                
                    gulp.task("build", async () => {
                        const options = { lto: true, opt_level: 's', debug: true };
                        const data = await Service.compileFile(project.getFile("src/main.rs"), "rust", "wasm", options);
                        const outWasm = project.newFile("out/main.wasm", "wasm", true);
                        outWasm.setData(data);
                    });
                
                

build.ts

Compile Step

                
                    Service.compileFile(project.getFile("src/main.rs"), "rust", "wasm", options);
                
                
                
                    Service.compileFileWithBindings(project.getFile("src/main.rs"), "rust", "wasm", options);
                
                

Result

  • This will emit both: wasm code and JS-bindings
  • Write them in two files instead of one

Build Step (after)

                
                    gulp.task("build", async () => {
                        const options = { lto: true, opt_level: 's', debug: true };
                        const data = await Service.compileFileWithBindings(project.getFile("src/main.rs"), "rust", "wasm", options);

                        const outWasm = project.newFile("out/main.wasm", "wasm", true);
                        outWasm.setData(data.wasm);
                        const outJs = project.newFile("out/main_bindings.js", "javascript", true);
                        outJs.setData(data.wasmBindgenJs);
                    });
                
                

HTML

                    
                        <body>
                          <span id="container"></span>
                          <script src="../out/main_bindings.js"></script>
                          <script src="./main.js"></script>
                        </body>
                    
                

Last but not least => JS

                    
                        const { greet } = wasm_bindgen;

                        function showGreeting() {
                          const greeting = greet('JSConf Asia');
                          document.getElementById("container").textContent = greeting;
                        }

                        wasm_bindgen('../out/main.wasm')
                          .then(showGreeting)
                          .catch(console.error);
                    
                
                    
                        const { greet } = wasm_bindgen;
                    
                
  • wasm_bindgen is provided by main_bindings.js
  • We will access wasm through this only now
                    
                        wasm_bindgen('../out/main.wasm')
                    
                
  • Loading and compiling is now done by wasm_bindgen too

Exercise Time!

  • Try passing in Strings
  • Incorporate wasm_bindgen
  • Play around with it

How to use it for passing in Strings?

wasm-bindgen

  • We declare external functions in Rust
  • Bindgen will make them available to us
  • From window object

Extern block (window)

                    
                        #[wasm_bindgen]
                        extern {
                            fn alert(s: &str);
                        }
                    
                

Extern block (more specific)

                    
                        #[wasm_bindgen]
                        extern {
                            fn alert(s: &str);

                            #[wasm_bindgen(js_namespace = console)]
                            fn log(s: &str);

                            #[wasm_bindgen(js_namespace = console, js_name = log)]
                            fn console_log_u32(n: u32);
                        }
                    
                

Exercise Time!

  • Create new Rust function that calls a JS function

Resources

Questions?