@ 261106.8
current
;; 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)
    )
  )
)