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