Factorial in WebAssembly

Type a number into the field and the factorial will be calculated. JavaScript has no plain integers, all numbers are actually 64-bit floats. It thus cannot correctly handle numbers bigger than 53 bits. However, in order to handle the factorial of up to 20, we need 64-bit integers. By doing the formatting in Rust (and thus WebAssembly) and returning a string, we can correctly print the result. For values larger than 20 the result is still incorrect.


fact(_) = 1

The Code

The Rust code implements a simple fact function as well as a function formatting the 64-bit integer to a string called fact_str.

Download factorial.rs
Download factorial.wat (WebAssembly text format)

fn main() {}

#[no_mangle]
pub extern "C" fn fact(mut n: u32) -> u64 {
    let n = n as u64;
    let mut result = 1;
    while n > 0 {
        result = result * n;
        n = n - 1;
    }
    result
}

#[no_mangle]
pub extern "C" fn fact_str(n: u32) -> *mut c_char {
    let res = fact(n);
    let s = format!("{}", res);
    let s = CString::new(s).unwrap();
    s.into_raw()
}

The JavaScript code loads the WebAssembly module and has access to the exported function. However it can't call the fact function, as it 64-bit integers can't be passed back to JavaScript. We therefore have to call the formatting function instead and extract the string. The helper function copyCStr is defined in bundle.js.

window.Module = {};
fetchAndInstantiate("./factorial.wasm", {})
.then(mod => {
  Module.fact  = mod.exports.fact;
  Module.alloc = mod.exports.alloc;
  Module.dealloc_str = mod.exports.dealloc_str;
  Module.memory = mod.exports.memory;
  Module.fact_str = function(n) {
    let outptr = mod.exports.fact_str(n);
    let result = copyCStr(Module, outptr);
    return result;
  };
})

The compiled WebAssembly code is reasonably short. Try to understand what's happening!

(func $fact (param i32) (result i64)
  (local i64 i64)
  block  ;; label = @1
    block  ;; label = @2
      get_local 0
      i32.eqz
      br_if 0 (;@2;)
      get_local 0
      i64.extend_u/i32
      set_local 1
      i64.const 1
      set_local 2
      loop  ;; label = @3
        get_local 1
        get_local 2
        i64.mul
        set_local 2
        get_local 1
        i64.const -1
        i64.add
        tee_local 1
        i64.eqz
        i32.eqz
        br_if 0 (;@3;)
        br 2 (;@1;)
      end
      unreachable
    end
    i64.const 1
    set_local 2
  end
  get_local 2)