SVX日記
2023-10-13(Fri) WebAssembly $42
「一般的なブラウザでRubyが動くなら実に喜ばしい」などと思って始めたWebAssemblyだが、だんだんRubyはどうでもよくなってきた。Z80やX68000でやってたようなアセンブラ遊びをまたやれる。これだけCPUパフォーマンスが上がってきた今日この頃、ちゃんと価値を伴ったアセンブラ遊びが再びできるとは思ってもみなかった。調べるとWebAssemblyは、スタックマシンでレジスタの数に制限はないようだ。絶妙にネイティブアセンブラと違うところが面白いではないか。
さて、WebAssemblyにおける「hello, world」に当たる足し算が終わったところで、次はJavaScriptとWebAssembly間で情報を共有できる「グローバル変数」をテストしてみることにする。
;; https://github.com/mdn/webassembly-examples/blob/main/js-api-examples/global.wat
(module
(import "js" "global" (global $g (mut i32)))
(func (export "getGlobal") (result i32)
global.get $g
)
(func (export "incGlobal")
global.get $g
i32.const 1
i32.add
global.set $g
)
)
<!-- https://developer.mozilla.org/ja/docs/WebAssembly/JavaScript_interface/Global -->
<HTML>
<HEAD>
<TITLE>WebAssembly Global Test</TITLE>
</HEAD>
<BODY>
<SCRIPT>
const global = new WebAssembly.Global({ value: 'i32', mutable: true }, 8);
console.log('a:', global.value);
global.value = 16;
console.log('b:', global.value);
global.value += 8;
console.log('c:', global.value);
const importObjects = {
js: { global },
};
WebAssembly.instantiateStreaming(fetch('global.wasm'), importObjects).then(
(obj) => {
let g = obj.instance.exports.getGlobal();
console.log('d:', g);
obj.instance.exports.incGlobal();
console.log('e:', global.value);
},
);
console.log('f:', global.value);
</SCRIPT>
</BODY>
</HTML>
a: 8
b: 16
c: 24
f: 24
d: 24
e: 25
WebAssembly.Globalでglobalというグローバル変数インスタンスを作り、JavaScript内で演算する様子をa, b, cで、WebAssembly内で演算する様子をd, e, fで確認する……と、意外な結果に。fの結果がdより前に出ていて、WebAssembly内の演算結果が反映されていない。
どうも、参考にしたサンプルの「WebAssembly.instantiateStreaming」というwasmを読み込むメソッドに、並列実行を行う性質があることが原因のようだ。いやしかしこんな挙動は扱いにくいだけで、完全に余計なお世話なんだが……。
だいぶアレコレと調べ上げウンウンと数多くの試行を繰り返した挙句、落ち着いたのが以下のJavaScriptコードだ。「Streaming」なのにワザワザ「await」するという冗談みたいなコードだが、これが一番シンプルで機械語サブルーチンを扱いやすい形にできる方法だった。
<!-- https://developer.mozilla.org/ja/docs/WebAssembly/JavaScript_interface/Global -->
<HTML>
<HEAD>
<TITLE>WebAssembly Global Test</TITLE>
</HEAD>
<BODY>
<SCRIPT>
async function main() {
const global = new WebAssembly.Global({ value: 'i32', mutable: true }, 8);
console.log('a:', global.value);
global.value = 16;
console.log('b:', global.value);
global.value += 8;
console.log('c:', global.value);
const importObjects = {
js: { global },
};
const obj = await WebAssembly.instantiateStreaming(fetch('global.wasm'), importObjects);
let g = obj.instance.exports.getGlobal();
console.log('d:', g);
obj.instance.exports.incGlobal();
console.log('e:', global.value);
console.log('f:', global.value);
}
main();
</SCRIPT>
</BODY>
</HTML>
a: 8
b: 16
c: 24
d: 24
e: 25
f: 25