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 log
test.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 log
test.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 log
test.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