SVX日記
2023-12-01(Fri) WebAssemblyのstackで開発がstuck
「WebAssemblyのひとつだけの使い道」ということで、ボチボチと開発を進めていたのだが、どうにも不可解な動きがあって、理解するまでにだいぶかかってしまった。
main = ->
    importObjects = {
        console:    { log: (arg) => console.log(arg) },
    }
    obj = await WebAssembly.instantiateStreaming(fetch('test.wasm'), importObjects)
    console.log('call test(0)')
    obj.instance.exports.test(0)
    console.log('call test(1)')
    obj.instance.exports.test(1)
main()(module
    (import "console" "log" (func $log (param i32)))
    (func (export "test") (param $val i32)
        push        val
        if
            i32.push    10
            call        log
        else
            i32.push    20
            call        log
        end
    )
)call test(0)
20
call test(1)
10        push        val
        if
            i32.push    10
        else
            i32.push    20
        end
        call        logtest.wat:10:4: error: type mismatch in if true branch, expected [] but got [i32]
            i32.const       10
            ^^^^^^^^^
test.wat:13:3: error: type mismatch in if false branch, expected [] but got [i32]
        end
        ^^^
test.wat:14:3: error: type mismatch in call, expected [i32] but got []
        call        $log
        ^^^^        push        val
        if  (result i32)
            i32.push    10
        else
            i32.push    20
        end
        call        log        push        val
        if  (result i32) (result i32)
            i32.push    10
            i32.push    10
        else
            i32.push    20
            i32.push    20
        end
        call        log
        call        logtest.wat:9:3: error: multiple if results not currently supported.
        if  (result i32) (result i32)
        ^^        i32.push    100
        push        val
        if  (result i32)
            i32.push    10
            i32.add
        else
            i32.push    20
            i32.add
        end
        call        logtest.wat:12:4: error: type mismatch in i32.add, expected [i32, i32] but got [i32]
            i32.add
            ^^^^^^^
test.wat:15:4: error: type mismatch in i32.add, expected [i32, i32] but got [i32]
            i32.add
            ^^^^^^^
test.wat:17:3: error: type mismatch in function, expected [] but got [i32]
        call        $log
        ^^^^こうなることがどうにも理解できなくて、長らくグダグダしていた。MDNのifの項を読んでも、特段なにも触れられていない。が、これは「ifは関数コール」のようなものだ、と理解するべきだという結論にたどり着いた。
ifブロックに入ったら「スタックの内容は持ち込めない」し「スタックの内容は持ち出せない(ただし返値としてひとつだけは許容される)」ということで、これは関数コールの特性そのものである。これまでのアセンブラ知識が邪魔になって必要以上に理解するのに時間がかかってしまった。
        i32.push    100
        push        val
        if  (result i32)
            i32.push    10
        else
            i32.push    20
        end
        i32.add
        call        log        i32.push    100
blk1:   block   (result i32)
            i32.push    10
            push        val
            br_if       blk1
            drop
            i32.push    20
        end
        i32.add
        call        log以下は、引数が0だった場合に、100をログに出力させるプログラムだが、非0だった場合はスタックに100が残ることになる。が、それは許容されるらしい。関数コールと考えれば、それが捨てられるだろうことは、まぁ理解できなくもないのだが。
blk1:   block
            i32.push    100         ;; [ 100
            push        val         ;; [ 100 val
            br_if       blk1        ;; [ 100
            call        log         ;; [
        end
;;                                  ;; [ ???        i32.push    99              ;; [ 99
blk1:   block   (result i32)
            i32.push    100         ;; [ 99 100
            i32.push    12          ;; [ 99 100 12
            i32.push    11          ;; [ 99 100 12 11
            i32.push    10          ;; [ 99 100 12 11 10
            push        val         ;; [ 99 100 12 11 10 val
            br_if       blk1        ;; [ 99 100 12 11 10
            call        log         ;; [ 99 100 12 11
            drop                    ;; [ 99 100 12
            drop                    ;; [ 99 100
        end
        call        log             ;; [ ???
        call        log             ;; [ ???call test(0)
10
100
99
call test(1)
10
99[ツッコミを入れる]