伝統的な形式で WebAssembly を記述したい方向けです。
サンプル(1)
簡単な関数の例です。
test.s
;# i32cpy(x) = x
.export i32cpy ;# i32cpy 関数を外部参照可能に
i32cpy: .code i32 ;# 返値型が i32 の関数 i32cpy
x: .param i32 ;# 最初の引数型は i32
local.get x ;# スタックに x を積む
end ;# スタックの上端(x)が返値
;# i32add(x, y) = x + y
.export i32add ;# i32cpy 関数を外部参照可能に
i32add: .code i32 ;# 返値型が i32 の関数 i32add
x: .param i32 ;# 最初の引数型は i32
y: .param i32 ;# 2番目の引数型は i32
local.get x ;# スタックに x を積む
local.get y ;# スタックに y を積む
i32.add ;# スタックから x,y を取り崩し、(x+y) を積む
end ;# スタックの上端(x+y)が返値
;# f32vlen(x, y, z) = sqrt(x * x + y * y + z * z)
.export f32vlen ;# f32vlen 関数を外部参照可能に
f32vlen: .code f32 ;# 返値型が f32 の関数 f32vlen
x: .param f32 ;# 最初の引数型は f32
y: .param f32 ;# 2番目の引数型は f32
z: .param f32 ;# 3番目の引数型は f32
local.get x ;# stack: [x]
local.get x ;# stack: [x, x]
f32.mul ;# stack: [(x * x)]
local.get y ;# stack: [(x * x), y]
local.get y ;# stack: [(x * x), y, y]
f32.mul ;# stack: [(x * x), (y * y)]
f32.add ;# stack: [(x * x + y * y)]
local.get z ;# stack: [(x * x + y * y), z]
local.get z ;# stack: [(x * x + y * y), z, z]
f32.mul ;# stack: [(x * x + y * y), z * z]
f32.add ;# stack: [(x * x + y * y + z * z)]
f32.sqrt ;# stack: [sqrt(x * x + y * y + z * z)]
end ;# スタックの上端 sqrt(...) が返値
;# f32vnrm(x, y, z) : normalize(x,y,z) → (x',y',z')
.export f32vnrm ;# f32vlen 関数を外部参照可能に
f32vnrm: .code f32,f32,f32 ;# 返値型が [f32,f32,f32] の関数 f32vlen
x: .param f32 ;# 最初の引数型は f32
y: .param f32 ;# 2番目の引数型は f32
z: .param f32 ;# 3番目の引数型は f32
n: .local f32 ;# 一時的な変数 f32 型
local.get x ;# stack: [x]
local.get y ;# stack: [x, y]
local.get z ;# stack: [x, y, z]
call f32vlen ;# f32vlen 関数を呼び出す / stack: [sqrt(...)]
local.set n ;# スタックから取り出して n へ / stack: []
local.get x ;# stack: [x]
local.get n ;# stack: [x, n]
f32.div ;# stack: [x/n]
local.get y ;# stack: [x/n, y]
local.get n ;# stack: [x/n, y, n]
f32.div ;# stack: [x/n, y/n]
local.get z ;# stack: [x/n, y/n, z]
local.get n ;# stack: [x/n, y/n, z, n]
f32.div ;# stack: [x/n, y/n, z/n]
end ;# スタックの上端3つが返値
テスト
アセンブル
$ wasmgen test.s --base64+q,w=64,c -
'AGFzbQEAAAABHARgAX8Bf2ACf38Bf2ADfX19AX1gA319fQN9fX0DBQQAAQIDBycE',
'BmkzMmNweQAABmkzMmFkZAABB2YzMnZsZW4AAgdmMzJ2bnJtAAMKQQQEACAACwcA',
'IAAgAWoLFAAgACAAlCABIAGUkiACIAKUkpELHQEBfSAAIAEgAhACIQMgACADlSAB',
'IAOVIAIgA5UL',
HTML+JavaScriptでテスト
test.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p>i32cpy = <span id="tagI32Cpy">[-]</span></p>
<p>i32add = <span id="tagI32Add">[-]</span></p>
<p>f32vlen = <span id="tagF32VLen">[-]</span></p>
<p>f32vnrm = <span id="tagF32VNrm">[-]</span></p>
<script>
// アセンブルした test.s を Base64 符号化
const test_wasm_binary = [
'AGFzbQEAAAABHARgAX8Bf2ACf38Bf2ADfX19AX1gA319fQN9fX0DBQQAAQIDBycE',
'BmkzMmNweQAABmkzMmFkZAABB2YzMnZsZW4AAgdmMzJ2bnJtAAMKQQQEACAACwcA',
'IAAgAWoLFAAgACAAlCABIAGUkiACIAKUkpELHQEBfSAAIAEgAhACIQMgACADlSAB',
'IAOVIAIgA5UL'
].join('');
// test.s の関数をテストする
function test(wasm_module)
{
const wasm = wasm_module.instance.exports; // .wasm の Export Section 情報
tagI32Cpy.innerText = wasm.i32cpy(0); // = 0
tagI32Add.innerText = wasm.i32add(1, 2); // = 3
tagF32VLen.innerText = wasm.f32vlen(0.1, 0.2, 0.3); // = √0.14
tagF32VNrm.innerText = wasm.f32vnrm(0.1, 0.2, 0.3);
}
/*
* 引数
* wasm: Base64 符号化された .wasm バイナリ
* caller: .wasm の Import Section 情報
* 返値
* .wasm をコンパイルする Promise
*/
function wasmInstantiateStreaming(wasm, caller)
{
const bin = Uint8Array.from(atob(wasm), c => c.charCodeAt(0));
const res = new Response(bin, {headers: {"Content-Type":"application/wasm"}});
return WebAssembly.instantiateStreaming(res, caller ?? {});
}
// .wasm をコンパイルして実行
wasmInstantiateStreaming(test_wasm_binary).then(test);
</script>
</body>
</html>
結果
i32cpy = 0
i32add = 3
f32vlen = 0.37416577339172363
f32vnrm = 0.26726120710372925,0.5345224142074585,0.8017836809158325
サンプル(2)
エンディアン(バイト順)を入れ替えます。
byteswap.s
;; -*- WASM -*-
@option type.unique = true
;;
;; 作業領域
;;
.import.memory "module", "memory", 1
;;
;; マクロ
;;
$func .defmacro name, table ; 関数<name>をマクロ定義
.export name ; export される関数<name>
name .code ; 関数 <name>
offs .param i32 ; 引数 offs : メモリ上のオフセット(16byte境界)
cnt .param i32 ; 引数 cnt : 16byte 単位での処理回数
block ; $end 用
local.get cnt ; if (cnt == 0)
u32.eqz $end ; goto $end
$cont: loop
local.get offs ; v128.store 用位置
local.get offs ; v128.load 用位置
v128.load ; offs から 16byte 読み取り
i64x2.const 0, 0 ; i8x16.shuffle 用ダミー
i8x16.shuffle *table ; table による入れ替え
v128.store ; データの直接更新
local.get offs ; offs += 16
u32.const 16
u32.add
local.set offs
local.get cnt ; cnt -= 1
u32.const 1
u32.sub
local.tee cnt
br_if $cont ; if (cnt) goto $cont
end.loop ; loop に対する end
$end: end.block ; block に対する end
end ; 終了
.endmacro ; マクロ定義終了
;; マクロ展開による関数の生成
;;
;; byteswap16(offs, cnt)
;; byteswap32(offs, cnt)
;; byteswap64(offs, cnt)
;;
$func byteswap16, [1,0]*8[+][0:16:2:2] ; [1,0, 3,2, 5,4, 7,6, 9,8, 11,10, 13,12, 15,14]
$func byteswap32, [3:-1]*4[+][0:16:4:4] ; [3,2,1,0, 7,6,5,4, 11,10,9,8, 15,14,13,12]
$func byteswap64, [7:-1]+[15:7] ; [7,6,5,4,3,2,1,0, 15,14,13,12,11,10,9,8]
;; end: byteswap.s
テスト
アセンブル
$ ./wasmgen byteswap.s --deflate --base64+q=2,w=64,c -
"eNqlzL0KwjAYheHzJWn6k6YVr6A6ufpD53Zw9B6s2EUNFatIp16w1xCwWToIguDZ",
"Xjg8qFpDAEjSnvU92JSkaY6PSy1NbZpbx0Bc8OHhL3h06O51+6yuyxwYY70CjZFv",
"wKIX8R1YkdFWgxcZMlgI2Bgfs5rAmSd8GQYq0nGaWCWGdzk5zZBRSec5aSilftQ4",
"I/jSEyoKgzTR8X+ak5zoJCd+0d7JGC1v",
HTML+JavaScriptでテスト
「パフォーマンス テスト」をクリックすると、16MiB 分の実行時間をミリ秒(ms)で表示します。
test2.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ByteSwap テスト</title>
<style>
body {
margin: auto;
width: 750px;
}
table {
border: solid 0px;
border-left: solid 1px lightgray;
border-top: solid 1px lightgray;
border-collaspe: collaspe;
border-spacing: 0;
}
th, td {
border: solid 0px;
border-right: solid 1px lightgray;
border-bottom: solid 1px lightgray;
padding: 4px;
}
</style>
</head>
<body>
<center>
<h3>WebAssembly を使った ByteSwap テスト</h3>
<hr>
<table>
<tr><th colspan="3">Uint16Array</th></tr>
<tr>
<td><code id="tagData16"></code></td>
<td> → </td>
<td><code id="tagSwap16"></code></td>
</tr>
</table>
<div> </div>
<table>
<tr><th colspan="3">Uint32Array</th></tr>
<tr>
<td><code id="tagData32"></code></td>
<td> → </td>
<td><code id="tagSwap32"></code></td>
</tr>
</table>
<p><button onclick="test();">更新</button></p>
<hr>
<p><button onclick="onperftest();">パフォーマンス テスト</button></p>
<table>
<tr><th>JavaScript</th><th>byteswap16</th><td id="tagPerfJS" style="text-align: right">未実行</td></tr>
<tr><th>WebAssembly</th><th>ByteSwap.byteswap</th><td id="tagPerfWA" style="text-align: right">未実行</td></tr>
</table>
<p> </p>
<p> </p>
<p> </p>
</center>
<script>
/*
* ByteSwap クラス
*
* WebAssembly の SIMD 命令を使用する
*
*/
class ByteSwap {
static #compileStart = false;
static #wasmMemory = new WebAssembly.Memory({initial:1});
static #wasmModule = null;
static #wasmExports = null;
static createModule()
{
if (ByteSwap.#compileStart)
return;
ByteSwap.#compileStart = true;
const imports = {module: {memory: ByteSwap.#wasmMemory}}
// byteswap.s を wasmgen でアセンブルしたバイナリを deflate 圧縮して Base64 化
const base64wasm = [
"eNqlzL0KwjAYheHzJWn6k6YVr6A6ufpD53Zw9B6s2EUNFatIp16w1xCwWToIguDZ",
"Xjg8qFpDAEjSnvU92JSkaY6PSy1NbZpbx0Bc8OHhL3h06O51+6yuyxwYY70CjZFv",
"wKIX8R1YkdFWgxcZMlgI2Bgfs5rAmSd8GQYq0nGaWCWGdzk5zZBRSec5aSilftQ4",
"I/jSEyoKgzTR8X+ak5zoJCd+0d7JGC1v",
].join('');
const bin = Uint8Array.from(atob(base64wasm), c => c.charCodeAt(0));
const res = new Response(
new Blob([bin]).stream().pipeThrough(
new DecompressionStream('deflate')),
{headers: {"Content-Type": "application/wasm"}});
return WebAssembly.instantiateStreaming(res, imports)
.then(ByteSwap.#instantiateCallback);
}
static #instantiateCallback(mod)
{
ByteSwap.#wasmModule = mod;
ByteSwap.#wasmExports = mod.instance.exports;
}
static byteswap(binary) {
const wasm = ByteSwap.#wasmExports;
const memory = ByteSwap.#wasmMemory;
if (!wasm)
throw Error("InitializeByteSwap が完了していません");
let func;
if ((binary instanceof Int8Array) ||
(binary instanceof Uint8Array) ||
(binary instanceof Uint8ClampedArray))
return binary; // byteswap 不要
else if ((binary instanceof Int16Array) ||
(binary instanceof Uint16Array))
func = wasm.byteswap16;
else if ((binary instanceof Int32Array) ||
(binary instanceof Uint32Array) ||
(binary instanceof Float32Array))
func = wasm.byteswap32;
else if ((binary instanceof BigInt64Array) ||
(binary instanceof BigUint64Array) ||
(binary instanceof Float64Array))
func = wasm.byteswap64;
else
throw TypeError(); // 引数は TypeArray オブジェクトのみ
if (!binary.length)
return binary;
const bsize = (memory.buffer.byteLength + 0xffff) >> 16;
const msize = (binary.byteLength + 0xffff) >> 16;
if (bsize < msize)
memory.grow(msize - bsize);
const count = (binary.byteLength + 15) >> 4;
const view = new binary.constructor(memory.buffer, 0, binary.length);
view.set(binary);
func(0, count);
return new binary.constructor(view);
}
}
/*
* テスト
*/
function make_random_binary(array, size) {
const tmp = new array(1);
const max = Math.pow(2, tmp.BYTES_PER_ELEMENT << 3);
return new array([...Array(size)].map(() => Math.trunc(Math.random() * max)));
}
function binary_dump(binary, addr) {
const unit_size = binary.BYTES_PER_ELEMENT;
const prefix0 = '0000000000000000'
const hex = (n, l) => (prefix0 + n.toString(16)).slice(-l);
const lw = 16 / unit_size;
const aw = (binary.byteLength - 1).toString(16).length;
const uw = unit_size << 1;
const lines = Math.trunc((binary.length + lw - 1) / lw);
const dat = ((y, x) => {
const p = y * lw + x;
return p < binary.length ? ' ' + hex(binary[p], uw) : '';
});
return [...Array(lines)].map((_, y) =>
[addr ? hex(y * lw, aw) + ': ' : '',
[...Array(lw)].map((_, x) => dat(y, x)).join(''),
'\n'].join('')).join('');
}
function test_byteswap(array, size, tags) {
const data = make_random_binary(array, size);
const swap = ByteSwap.byteswap(data);
tags.data.innerText = binary_dump(data, true);
tags.swap.innerText = binary_dump(swap);
}
function test() {
test_byteswap(Uint16Array, 32, {data:tagData16, swap:tagSwap16});
test_byteswap(Uint32Array, 16, {data:tagData32, swap:tagSwap32});
}
/*
* パフォーマンス テスト
*/
function byteswap16(array) {
return array.map(v => (v >> 8) | (v << 8));
}
let test_binary;
function perftest() {
const byte_size = 16 * 1024 * 1024; // 16MiB
if (!test_binary)
test_binary = make_random_binary(Uint16Array, byte_size);
const p_sj = performance.now();
const js_u16a = byteswap16(test_binary)
const p_ej = performance.now();
const p_j = p_ej - p_sj;
const p_sw = performance.now();
const wa_u16a = ByteSwap.byteswap(test_binary);
const p_ew = performance.now();
const p_w = p_ew - p_sw;
const perfstr = ((v) => {
const s = '' + Math.trunc(v * 10);
return s.slice(0, -1) + '.' + s.slice(-1) + 'ms';
});
tagPerfJS.innerText = perfstr(p_j);
tagPerfWA.innerText = perfstr(p_w);
return;
for (const index in js_u16a) {
if (js_u16a[index] != wa_u16a[index]) {
console.log(`${js_u16a[index].toString(16)} != ${wa_u16a[index].toString(16)}`);
break;
}
}
}
function onperftest() {
setTimeout(perftest);
}
/*
* 初期処理
*/
ByteSwap.createModule().then(test);
</script>
</body>
</html>