| ;; See bench.js. |
| |
| (module |
| ;; A chain of three types. Each type has a "next" field, so we can form linked |
| ;; lists. |
| (type $A (sub (struct (field $next (ref null $A))))) |
| (type $B (sub $A (struct (field $next (ref null $A))))) |
| (type $C (sub $B (struct (field $next (ref null $A))))) |
| |
| (type $func (func (param (ref $A)) (result i32))) |
| |
| ;; Internal helper to iterate over a linked list and call a function on each |
| ;; item, and return the sum of those calls' results. |
| (func $iter (param $list (ref null $A)) (param $func (ref $func)) (result i32) |
| (local $sum i32) |
| (loop $loop |
| (if |
| (ref.is_null |
| (local.get $list) |
| ) |
| (then |
| (return |
| (local.get $sum) |
| ) |
| ) |
| (else |
| (local.set $sum |
| (i32.add |
| (local.get $sum) |
| (call_ref $func |
| (ref.as_non_null |
| (local.get $list) |
| ) |
| (local.get $func) |
| ) |
| ) |
| ) |
| (local.set $list |
| (struct.get $A $next |
| (local.get $list) |
| ) |
| ) |
| (br $loop) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Using the helper, and depending on inlining to optimize this, lets us |
| ;; write the exports concisely. First, code to compute the length of the list |
| ;; (for comparison purposes). |
| (func $len (export "len") (param $list (ref $A)) (result i32) |
| (call $iter |
| (local.get $list) |
| ;; Add one each time this is called. |
| (ref.func $one) |
| ) |
| ) |
| (func $one (param $list (ref $A)) (result i32) |
| (i32.const 1) |
| ) |
| |
| ;; At each point in the linked list, check if both the current and next item |
| ;; are inputs are $B, using an if to short-circuit when possible. |
| (func $iff-both (export "iff-both") (param $list (ref $A)) (result i32) |
| (call $iter |
| (local.get $list) |
| (ref.func $do-iff-both) |
| ) |
| ) |
| (func $do-iff-both (param $list (ref $A)) (result i32) |
| (if (result i32) |
| (ref.test (ref $B) |
| (struct.get $A $next |
| (local.get $list) |
| ) |
| ) |
| (then |
| (ref.test (ref $B) |
| (local.get $list) |
| ) |
| ) |
| (else |
| (i32.const 0) |
| ) |
| ) |
| ) |
| |
| ;; The same computation, but using an and, so both tests always execute. |
| (func $and (export "and") (param $list (ref $A)) (result i32) |
| (call $iter |
| (local.get $list) |
| (ref.func $do-and) |
| ) |
| ) |
| (func $do-and (param $list (ref $A)) (result i32) |
| (i32.and |
| (ref.test (ref $B) |
| (struct.get $A $next |
| (local.get $list) |
| ) |
| ) |
| (ref.test (ref $B) |
| (local.get $list) |
| ) |
| ) |
| ) |
| |
| ;; Similar, but return 1 if either test succeeds (using an if). |
| (func $iff-either (export "iff-either") (param $list (ref $A)) (result i32) |
| (call $iter |
| (local.get $list) |
| (ref.func $do-iff-either) |
| ) |
| ) |
| (func $do-iff-either (param $list (ref $A)) (result i32) |
| (if (result i32) |
| (ref.test (ref $B) |
| (struct.get $A $next |
| (local.get $list) |
| ) |
| ) |
| (then |
| (i32.const 1) |
| ) |
| (else |
| (ref.test (ref $B) |
| (local.get $list) |
| ) |
| ) |
| ) |
| ) |
| |
| |
| ;; The same computation, but using an or, so both tests always execute. |
| (func $or (export "or") (param $list (ref $A)) (result i32) |
| (call $iter |
| (local.get $list) |
| (ref.func $do-or) |
| ) |
| ) |
| (func $do-or (param $list (ref $A)) (result i32) |
| (i32.or |
| (ref.test (ref $B) |
| (struct.get $A $next |
| (local.get $list) |
| ) |
| ) |
| (ref.test (ref $B) |
| (local.get $list) |
| ) |
| ) |
| ) |
| |
| ;; Use a select to do a test of "is next null ? 0 : test curr". |
| (func $select (export "select") (param $list (ref $A)) (result i32) |
| (call $iter |
| (local.get $list) |
| (ref.func $do-select) |
| ) |
| ) |
| (func $do-select (param $list (ref $A)) (result i32) |
| (select |
| (i32.const 0) |
| (ref.test (ref $B) |
| (local.get $list) |
| ) |
| (ref.is_null |
| (struct.get $A $next |
| (local.get $list) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Use an iff to do the same. |
| (func $iff-nextor (export "iff-nextor") (param $list (ref $A)) (result i32) |
| (call $iter |
| (local.get $list) |
| (ref.func $do-iff-nextor) |
| ) |
| ) |
| (func $do-iff-nextor (param $list (ref $A)) (result i32) |
| (if (result i32) |
| (ref.is_null |
| (struct.get $A $next |
| (local.get $list) |
| ) |
| ) |
| (then |
| (i32.const 0) |
| ) |
| (else |
| (ref.test (ref $B) |
| (local.get $list) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Use an if over three tests: "test if next is B or C depending on if curr is |
| ;; B." |
| (func $iff-three (export "iff-three") (param $list (ref $A)) (result i32) |
| (call $iter |
| (local.get $list) |
| (ref.func $do-iff-three) |
| ) |
| ) |
| (func $do-iff-three (param $list (ref $A)) (result i32) |
| (local $next (ref null $A)) |
| (local.set $next |
| (struct.get $A $next |
| (local.get $list) |
| ) |
| ) |
| (if (result i32) |
| (ref.test (ref $B) |
| (local.get $list) |
| ) |
| (then |
| (ref.test (ref $C) |
| (local.get $next) |
| ) |
| ) |
| (else |
| (ref.test (ref $B) |
| (local.get $next) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Use a select for the same. |
| (func $select-three (export "select-three") (param $list (ref $A)) (result i32) |
| (call $iter |
| (local.get $list) |
| (ref.func $do-select-three) |
| ) |
| ) |
| (func $do-select-three (param $list (ref $A)) (result i32) |
| (local $next (ref null $A)) |
| (local.set $next |
| (struct.get $A $next |
| (local.get $list) |
| ) |
| ) |
| (select |
| (ref.test (ref $C) |
| (local.get $next) |
| ) |
| (ref.test (ref $B) |
| (local.get $next) |
| ) |
| (ref.test (ref $B) |
| (local.get $list) |
| ) |
| ) |
| ) |
| |
| ;; Creation functions. |
| |
| (func $makeA (export "makeA") (param $next (ref null $A)) (result anyref) |
| (struct.new $A |
| (local.get $next) |
| ) |
| ) |
| |
| (func $makeB (export "makeB") (param $next (ref null $A)) (result anyref) |
| (struct.new $B |
| (local.get $next) |
| ) |
| ) |
| |
| (func $makeC (export "makeC") (param $next (ref null $A)) (result anyref) |
| ;; This function is not used in benchmarks yet, but it keeps the type $C |
| ;; alive, which prevents $B from looking like it could be final, which might |
| ;; allow the optimizer to simplify more than we want. |
| (struct.new $C |
| (local.get $next) |
| ) |
| ) |
| ) |
| |