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.
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)