概要
wasmが、スタックマシンらしいので、やってみた。
plunkerでwasmのwatのsandboxやってみた。
ボタンでサンプルが変わる。
写真
サンプルコード
const templates = {
calc: `(module
;; 足し算
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add)
(export "add" (func $add))
;; 引き算
(func $sub (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.sub)
(export "sub" (func $sub))
;; 3乗の計算
(func $cube (param $n i32) (result i32)
local.get $n
local.get $n
local.get $n
i32.mul
i32.mul)
(export "cube" (func $cube))
)`,
hello: `(module
(memory (export "memory") 1)
(data (i32.const 0) "Hello, World! ")
(func $hello (export "hello") (result i32 i32)
(i32.const 0)
(i32.const 13)
)
)`,
fizzbuzz: `(module
(memory (export "mem") 1)
(func $get_ptr (result i32) i32.const 0)
(export "get_ptr" (func $get_ptr))
(func $fizzbuzz (result i32)
(local $i i32) (local $ptr i32)
(local.set $i (i32.const 1)) (local.set $ptr (i32.const 0))
(block $break
(loop $top
(br_if $break (i32.eq (local.get $i) (i32.const 101)))
(if (i32.gt_u (local.get $i) (i32.const 1))
(then
(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)))
)
)
(if (i32.eqz (i32.rem_u (local.get $i) (i32.const 15)))
(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))
(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
(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
(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
(if (i32.ge_u (local.get $i) (i32.const 100))
(then
(i32.store8 (local.get $ptr) (i32.const 49)) (i32.store8 (i32.add (local.get $ptr) (i32.const 1)) (i32.const 48))
(i32.store8 (i32.add (local.get $ptr) (i32.const 2)) (i32.const 48)) (local.set $ptr (i32.add (local.get $ptr) (i32.const 3)))
)
(else
(if (i32.ge_u (local.get $i) (i32.const 10))
(then
(i32.store8 (local.get $ptr) (i32.add (i32.const 48) (i32.div_u (local.get $i) (i32.const 10))))
(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
(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))
)`
};
function loadTemplate(key) {
document.getElementById('watCode').value = templates[key];
document.getElementById('dynamicFunctions').innerHTML = `<p style="color: #666; font-style: italic;">コードが切り替わりました。「コードをコンパイル」ボタンを押してください。</p>`;
}
loadTemplate('calc');
let currentExports = null;
document.getElementById('compileBtn').addEventListener('click', async () => {
const watCode = document.getElementById('watCode').value.replace(/ /g, ' ');
const statusBox = document.getElementById('statusBox');
const logOutput = document.getElementById('logOutput');
const dynamicFunctions = document.getElementById('dynamicFunctions');
if (statusBox)
statusBox.style.display = 'none';
logOutput.innerText = "";
dynamicFunctions.innerHTML = "";
try
{
const wabt = await WabtModule();
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}`;
const results = await WebAssembly.instantiate(buffer.buffer);
currentExports = results.instance.exports;
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 = "関数がエクスポートされていません。";
return;
}
exportedFunctions.forEach(func => {
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;
card.appendChild(input);
inputs.push(input);
}
const runBtn = document.createElement('button');
runBtn.className = 'run-func-btn';
runBtn.innerText = '実行';
card.appendChild(runBtn);
const resultSpan = document.createElement('span');
resultSpan.className = 'func-result';
resultSpan.style.marginLeft = '10px';
card.appendChild(resultSpan);
dynamicFunctions.appendChild(card);
runBtn.addEventListener('click', () => {
const args = inputs.map(input => parseInt(input.value, 10) || 0);
try
{
const res = currentExports[func.name](...args);
const memory = currentExports.memory || currentExports.mem;
if (memory)
{
let ptr = 0;
let len = 0;
let isString = false;
if (Array.isArray(res))
{
ptr = res[0];
len = res[1];
isString = true;
}
else if (func.name === 'fizzbuzz')
{
ptr = currentExports.get_ptr ? currentExports.get_ptr() : 0;
len = res;
isString = true;
}
if (isString)
{
const bytes = new Uint8Array(memory.buffer, ptr, len);
const text = new TextDecoder('utf-8').decode(bytes);
resultSpan.innerText = ` 出力: "${text}"`;
return;
}
}
resultSpan.innerText = ` 結果: ${res}`;
}
catch (err)
{
resultSpan.innerText = ` エラー: ${err.message}`;
console.error(err);
}
});
});
}
catch (e)
{
if (statusBox)
statusBox.style.display = 'block';
logOutput.innerText = e.toString();
console.error(e);
}
});
成果物
以上。
