概要
wasmが、スタックマシンらしいので、やってみた。
plunkerでwasmのwatのsandboxやってみた。
fizzbuzz、やってみた。
写真
サンプルコード
let currentExports = null;
document.getElementById('compileBtn').addEventListener('click', async () => {
// 全角スペースを自動で半角2個に置換して取得
const watCode = document.getElementById('watCode').value.replace(/ /g, ' ');
const statusBox = document.getElementById('statusBox');
const statusOutput = document.getElementById('statusOutput');
const logOutput = document.getElementById('logOutput');
const dynamicFunctions = document.getElementById('dynamicFunctions');
// 表示の初期化
statusBox.style.display = 'none';
statusBox.classList.remove('error');
logOutput.innerText = "";
dynamicFunctions.innerHTML = "";
try {
const wabt = await WabtModule();
// 1. パースとバイナリ化
let wasmModule = wabt.parseWat("main.wat", watCode);
let buffer = wasmModule.toBinary({ log: true, canonicalize_lebs: true, relocatable: false, write_debug_names: true });
wasmModule.destroy();
logOutput.innerText = buffer.log || `bytesize: ${buffer.buffer.byteLength}`;
// 2. インスタンス化
const results = await WebAssembly.instantiate(buffer.buffer);
currentExports = results.instance.exports;
// 3. エクスポートされた関数の自動抽出
const exportedFunctions = [];
for (const name in currentExports)
{
if (typeof currentExports[name] === 'function') {
// 関数の引数の数を取得
const paramCount = currentExports[name].length;
exportedFunctions.push({ name, paramCount });
}
}
if (exportedFunctions.length === 0)
{
dynamicFunctions.innerHTML = "<p style='color:orange;'>関数がエクスポートされていません。(export \"名前\" (func $名)) を書いてください。</p>";
return;
}
// 4. UIの動的生成
exportedFunctions.forEach(func => {
// 文字列ポインタを取得するための内部関数(get_ptrなど)は、実行ボタンの生成をスキップする
if (func.name === 'get_ptr' || func.name === 'get_len')
return;
const card = document.createElement('div');
card.className = 'func-card';
// 関数名ラベル
const nameSpan = document.createElement('span');
nameSpan.className = 'func-name';
nameSpan.innerText = `${func.name} :`;
card.appendChild(nameSpan);
// 引数の数だけインプットボックスを作る
const inputs = [];
for (let i = 0; i < func.paramCount; i++)
{
const input = document.createElement('input');
input.type = 'number';
input.className = 'param-input';
input.value = Math.floor(Math.random() * 10) + 1;
input.placeholder = `引数${i+1}`;
card.appendChild(input);
inputs.push(input);
}
// 実行ボタン
const runBtn = document.createElement('button');
runBtn.className = 'run-func-btn';
runBtn.innerText = '実行';
// 結果表示用スパン
const resultSpan = document.createElement('span');
resultSpan.className = 'func-result';
// 個別関数の実行イベント
runBtn.addEventListener('click', () => {
const args = inputs.map(input => parseInt(input.value, 10) || 0);
try
{
// Wasmの関数を動的に実行
const val = currentExports[func.name](...args);
// 【判定】もしWasm側からメモリ(mem)が共有されており、かつ特定の文字列出力関数だった場合
if (currentExports.mem && (func.name === 'fizzbuzz' || currentExports.get_len)) {
// メモリの開始位置を取得(get_ptrがなければ0番地からとみなす)
const ptr = currentExports.get_ptr ? currentExports.get_ptr() : 0;
// 文字列の長さを決定(fizzbuzz関数の場合は戻り値valが長さ。Hello Worldなどの場合はget_len()の値)
const len = (func.name === 'fizzbuzz') ? val : currentExports.get_len();
// メモリ空間からバイトデータを抽出してデコード
const memView = new Uint8Array(currentExports.mem.buffer, ptr, len);
const decoder = new TextDecoder('utf-8');
resultSpan.innerHTML = `<br><div style="background:#f0f0f0; padding:10px; margin-top:5px; border-radius:4px; font-size:14px; white-space:pre-wrap; max-height:200px; overflow-y:auto; color:#333; font-family:monospace; line-height:1.4;">${decoder.decode(memView)}</div>`;
}
else
{
// 通常の数値計算関数の結果表示
resultSpan.innerText = `➡ 結果: ${val}`;
}
}
catch (e)
{
resultSpan.innerText = `➡ エラー: ${e.message}`;
}
});
card.appendChild(runBtn);
card.appendChild(resultSpan);
dynamicFunctions.appendChild(card);
});
}
catch (error)
{
statusBox.style.display = 'block';
statusBox.classList.add('error');
statusOutput.innerText = `コンパイルエラー:\n${error.message}`;
}
});
fizzbuzzコード
(module
;; 結果を保存するための1ページ(64KB)のメモリ
(memory (export "mem") 1)
;; JavaScript側で文字列を取り出すための関数
(func $get_ptr (result i32) i32.const 0)
(export "get_ptr" (func $get_ptr))
;; 100までのFizzBuzzを実行し、書き込んだ文字列の長さを返す
(func $fizzbuzz (result i32)
(local $i i32) ;; ループのカウンター (1〜100)
(local $ptr i32) ;; メモリの書き込み位置(ポインタ)
(local.set $i (i32.const 1))
(local.set $ptr (i32.const 0))
(block $break
(loop $top
;; $i が 101 になったらループを抜ける
(br_if $break (i32.eq (local.get $i) (i32.const 101)))
;; 最初の一文字目以外はカンマとスペース ", " を入れる
(if (i32.gt_u (local.get $i) (i32.const 1))
(then
;; ',' = 44, ' ' = 32
(i32.store8 (local.get $ptr) (i32.const 44))
(local.set $ptr (i32.add (local.get $ptr) (i32.const 1)))
(i32.store8 (local.get $ptr) (i32.const 32))
(local.set $ptr (i32.add (local.get $ptr) (i32.const 1)))
)
)
;; --- FizzBuzzの判定条件 ---
;; 1. 15で割り切れるか(FizzBuzz)
(if (i32.eqz (i32.rem_u (local.get $i) (i32.const 15)))
(then
;; "FizzBuzz" をメモリにストア (F=70, i=105, z=122, z=122, B=66, u=117, z=122, z=122)
(i32.store8 (local.get $ptr) (i32.const 70))
(i32.store8 (i32.add (local.get $ptr) (i32.const 1)) (i32.const 105))
(i32.store8 (i32.add (local.get $ptr) (i32.const 2)) (i32.const 122))
(i32.store8 (i32.add (local.get $ptr) (i32.const 3)) (i32.const 122))
(i32.store8 (i32.add (local.get $ptr) (i32.const 4)) (i32.const 66))
(i32.store8 (i32.add (local.get $ptr) (i32.const 5)) (i32.const 117))
(i32.store8 (i32.add (local.get $ptr) (i32.const 6)) (i32.const 122))
(i32.store8 (i32.add (local.get $ptr) (i32.const 7)) (i32.const 122))
(local.set $ptr (i32.add (local.get $ptr) (i32.const 8)))
)
(else
;; 2. 3で割り切れるか(Fizz)
(if (i32.eqz (i32.rem_u (local.get $i) (i32.const 3)))
(then
(i32.store8 (local.get $ptr) (i32.const 70))
(i32.store8 (i32.add (local.get $ptr) (i32.const 1)) (i32.const 105))
(i32.store8 (i32.add (local.get $ptr) (i32.const 2)) (i32.const 122))
(i32.store8 (i32.add (local.get $ptr) (i32.const 3)) (i32.const 122))
(local.set $ptr (i32.add (local.get $ptr) (i32.const 4)))
)
(else
;; 3. 5で割り切れるか(Buzz)
(if (i32.eqz (i32.rem_u (local.get $i) (i32.const 5)))
(then
(i32.store8 (local.get $ptr) (i32.const 66))
(i32.store8 (i32.add (local.get $ptr) (i32.const 1)) (i32.const 117))
(i32.store8 (i32.add (local.get $ptr) (i32.const 2)) (i32.const 122))
(i32.store8 (i32.add (local.get $ptr) (i32.const 3)) (i32.const 122))
(local.set $ptr (i32.add (local.get $ptr) (i32.const 4)))
)
(else
;; 4. それ以外(数値を文字列に変換してメモリに書き込む)
;; ※ここでは100まで対応の簡易的な10進数変換(1桁、2桁、3桁)
(if (i32.ge_u (local.get $i) (i32.const 100))
(then
(i32.store8 (local.get $ptr) (i32.const 49)) ;; '1'
(i32.store8 (i32.add (local.get $ptr) (i32.const 1)) (i32.const 48)) ;; '0'
(i32.store8 (i32.add (local.get $ptr) (i32.const 2)) (i32.const 48)) ;; '0'
(local.set $ptr (i32.add (local.get $ptr) (i32.const 3)))
)
(else
(if (i32.ge_u (local.get $i) (i32.const 10))
(then
;; 10の位
(i32.store8 (local.get $ptr) (i32.add (i32.const 48) (i32.div_u (local.get $i) (i32.const 10))))
;; 1の位
(i32.store8 (i32.add (local.get $ptr) (i32.const 1)) (i32.add (i32.const 48) (i32.rem_u (local.get $i) (i32.const 10))))
(local.set $ptr (i32.add (local.get $ptr) (i32.const 2)))
)
(else
;; 1桁の数値
(i32.store8 (local.get $ptr) (i32.add (i32.const 48) (local.get $i)))
(local.set $ptr (i32.add (local.get $ptr) (i32.const 1)))
)
)
)
)
)
)
)
)
)
)
;; カウンターをインクリメントしてループを継続
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $top)
)
)
;; 最終的な文字列の長さを返す
local.get $ptr
)
(export "fizzbuzz" (func $fizzbuzz))
)
成果物
以上。
