I wanted to try writing wasm manually (wat
format).
1 Lox test code#
I decided to try manually translating my Lox test code into wasm
print "# Testing scope"; var a = 5; print "outer a"; print a; { var a = 10; print "inner a"; print a; } print "outer a"; print a; print "# Testing expressions"; print true; print 2 + 1; print !((3 + 5 / 10) == 3.5); print "# Testing if statements"; if (false) { print "N true"; } else { print "Y false"; } if (true) { print "Y true"; } print "# Testing logical operators"; print "hi" or 2; // "hi" print "hi" and 2; // 2 print nil or "yeh"; // "yeh" print nil or nil; // nil print "# Testing while loops"; var i = 3; while (i > 0) { print i; i = i - 1; } print "# Testing for loops, Fibonacci sequence"; var a = 0; var temp; for (var b = 1; a < 10000; b = temp + b) { print a; temp = a; a = b; }
- https://developer.mozilla.org/en-US/docs/WebAssembly/Guides/Understanding_the_text_format[1] was useful
- https://stanford-cs242.github.io/f18/lectures/04-1-webassembly-practice.html[2] too
- https://webassembly.github.io/spec/core/text/instructions.html[3] has a list of all wasm instructions
- https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow[4]
Some things I don’t understand:
- why (local.get 0) vs local.get 0, and similarly (i32.add) vs i32.add?
- https://nishtahir.com/the-wasm-text-format/[5] shows parentheses and then shows without parentheses
- some pages like https://developer.mozilla.org/en-US/docs/WebAssembly/Guides/Understanding_the_text_format[6] show parentheses only sometimes — look at the
logAllMemory
function which uses both in the same function
other:
- wasmtime can compile wasm into x86 assembly; I tried this on godbolt[7]
- the loop construct in wasm[8] is more like do-while, so I need to turn while(a) { b } into if(a) do { b } while (a), or block Y { loop X { if (!a) break Y; b; branch X; } . After seeing the output of assemblyscript and wasm-opt, I see that it could instead be loop X { if (a) { b ; branch X; } }
Source: learning-wasm.js
2 JS vs wasm#
I’m going to convert this js code to wasm:
function main(seed, N) { const m = 0x7fff; const a = 1103515245; const c = 12345; for (let i = 0; i < N; i++) { seed = (a * seed + c) & m; } return seed; }
(func $loop (param $seed i32) (param $N i32) (result i32) (local $i i32) (local.set $i (i32.const 0)) (block $for_2_end (loop $for_2_start (local.get $i) ;; test !(i < N) (local.get $N) (i32.ge_s) (br_if $for_2_end) (local.get $seed) (i32.const 1103515245) (i32.mul) (i32.const 12345) (i32.add) (i32.const 0x7fff) (i32.and) (local.set $seed) ;; seed = (a * seed + c) & m (local.get $i) (i32.const 1) (i32.add) (local.set $i) ;; i++ (br $for_2_start) ) ) (local.get $seed) )
I also tried AssemblyScript[9]:
export function main(seed: i32, N: i32): i32 { const m: i32 = 0x7fff; const a: i32 = 1103515245; const c: i32 = 12345; for (let i = 0; i < N; i++) { seed = (a * seed + c) & m; } return seed; }
which generated this wasm:
(func $assembly/index/main (param $seed i32) (param $N i32) (result i32) (local $i i32) i32.const 0 local.set $i loop $for-loop|0 local.get $i local.get $N i32.lt_s if i32.const 1103515245 local.get $seed i32.mul i32.const 12345 i32.add i32.const 32767 i32.and local.set $seed local.get $i i32.const 1 i32.add local.set $i br $for-loop|0 end end local.get $seed return )
and that ran in the same speed as my hand-written wasm, 725ms for js and 145ms for wasm in Firefox; 660ms for js and 145ms for wasm in Chrome. My test code doens’t work in safari yet.
I also tried wasm2c
and ran the c code (with optimizer on) on my machine, and it took 116ms, so wasm wasn’t much slower than native code.