WebAssembly 対応

NagiScript を WebAssembly にコンパイルし、ブラウザで実行する方法を学びます。


基本的な Wasm コンパイル

現行仕様(v0.2.0)

Wasm 出力のコマンドは nagiscript wasm main.ngs [-o PATH] です(-o PATH は省略可能)。出力は .wat(可能であれば .wasm + .d.ts)です。

bash
# 実行例: main.ngs を Wasm へ
nagiscript wasm main.ngs -o output.wasm

生成されるファイル:


エクスポートする関数

Wasm / C へ公開する関数は export "C" fn で宣言し、-> 型 と明示的な return を使います(export fn ... は誤りです)。

main.ngs
export "C" fn add(a: i32, b: i32) -> i32 {
    return a + b
}

export "C" fn process(n: i32) -> i32 {
    return n * 2
}

ブラウザでの利用

HTML ファイル

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>NagiScript Wasm</title>
</head>
<body>
    <div id="output"></div>
    <script>
        async function loadWasm() {
            const response = await fetch('output.wasm');
            const bytes = await response.arrayBuffer();
            const { instance } = await WebAssembly.instantiate(bytes, {
                env: {
                    memory: new WebAssembly.Memory({ initial: 256 })
                }
            });

            // 関数を呼び出す
            const result = instance.exports.add(10, 20);
            document.getElementById('output').textContent = result;
        }
        loadWasm();
    </script>
</body>
</html>

React との連携

ドキュメント化保留

JSX(.ngsx)用のランタイムには props 基盤が存在しますが、ドキュメント化は保留です。以下の React 側サンプル(TypeScript の JSX)は参考用です。

コンポーネント例

WasmComponent.tsx
import React, { useEffect, useState } from 'react';

function WasmComponent() {
    const [result, setResult] = useState<number | null>(null);

    useEffect(() => {
        async function loadWasm() {
            const response = await fetch('/output.wasm');
            const bytes = await response.arrayBuffer();
            const { instance } = await WebAssembly.instantiate(bytes);

            const result = instance.exports.process(42);
            setResult(result);
        }
        loadWasm();
    }, []);

    return (
        <div>
            <h1>NagiScript Wasm Result</h1>
            <p>Result: {result}</p>
        </div>
    );
}

export default WasmComponent;

型定義ファイル (.d.ts)

コンパイル時に自動生成される型定義:

output.d.ts
// output.d.ts
export function add(a: number, b: number): number;
export function process(input: string): string;

メモリ操作

JS 側からのメモリアクセス

memory.js
const memory = new WebAssembly.Memory({ initial: 256 });
const { instance } = await WebAssembly.instantiate(bytes, {
    env: { memory }
});

// メモリに文字列を書き込む
const encoder = new TextEncoder();
const str = encoder.encode("Hello, Wasm!");
const ptr = instance.exports.malloc(str.length);
new Uint8Array(memory.buffer, ptr, str.length).set(str);

// Wasm 関数を呼び出す
instance.exports.process_string(ptr, str.length);

ビルド設定

ロードマップ(未実装)

プロジェクト設定ファイル(project.toml)によるビルド設定は将来仕様です。現行は nagiscript wasm main.ngs [-o PATH] の CLI で出力先を指定します。

project.toml

project.toml
[build]
target = "wasm"
output = "dist/"

[build.wasm]
memory_initial = 256
memory_maximum = 512

パフォーマンスのヒント

  1. 関数エクスポートを最小限に: 必要な関数のみエクスポート
  2. バッチ処理: 大量のデータは一度に渡す
  3. TypedArray の活用: メモリ操作には Uint8Array を使用