5
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

WebAssemblyのloopはまりどころ

はじめてWebAssemblyを書く時は恐らくloopではまります。

以下の関数を見て下さい。1からnまでの和を計算する関数のつもりです。

  ;; i32型引数$nを取りi32の返り値を返す関数。さらにローカル変数として$iと$sumを使う。
  ;; ローカル変数は0初期化されるのでそのまま使う
  (func $sum_bad (param $n i32) (result i32)  (local $i i32) (local $sum i32)
    (loop $loop
      ;; もし$iが$nを以上ならloopから抜ける
      (br_if $loop (i32.le_s (get_local $n) (get_local $i)))
      ;; $sum = $sum + $i
      (set_local $sum (i32.add (get_local $sum) (get_local $i)))
      ;; $i = $i + 1
      (set_local $i (i32.add (get_local $i) (i32.const 1))))
    ;; ループから抜けたらreturn
    (return (get_local $sum)))

これを動かすと

sum_bad: 0

と返ってきます。何故でしょう。

なんとloopはただの後方ジャンプのためのラベルで、末尾まで到達してもloopの位置まで戻りません。戻りたければ br を呼ぶとloopの位置に飛ぶことになっています。
loopに対するbrが一般の言語でいう continue 相当になっている訳ですね。なので上記実装だとloop内は一度しか実行されずに抜けてしまいます。

それを踏まえた実装がこちら。

  (func $sum_good (param $n i32) (result i32)  (local $i i32) (local $sum i32)
    ;; loopから脱出するためのラベルを作っておく
    (block $exit 
      (loop $loop
        ;; ループから脱出するときは$exitの方を指定する
        (br_if $exit (i32.le_s (get_local $n) (get_local $i)))
        (set_local $sum (i32.add (get_local $sum) (get_local $i)))
        (set_local $i (i32.add (get_local $i) (i32.const 1)))
        ;; **ちゃんと** ループするためにbrを呼ぶ
        (br $loop)))
    (return (get_local $sum)))

今回のコードはこちらで試せます。皆様ご安全に。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
5
Help us understand the problem. What are the problem?