0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

wasmで、アセンブラ その3

0
Posted at

概要

wasmが、スタックマシンらしいので、やってみた。
plunkerでwasmのwatのsandboxやってみた。
fizzbuzz、やってみた。

写真

image.png

サンプルコード


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))
)

成果物

以上。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?