最終更新:

WebAssemblyの仕組み:小さな計算をJavaScriptから呼んでみよう


WATから作り、JavaScriptで呼ぶ

📝 add.wat文字で書くadd.wasmバイナリJavaScriptから呼ぶexports.sum(2, 3)🌐 画面に表示2 + 3 = 5
加算はwasmの関数、DOMへの表示はJavaScriptが担当します。速度の比較ではありません。
✅ このガイドで学べること
  • 身近な課題で役割を説明できる
  • 最初の操作と確認方法がわかる
ひよこ ひよこ
WebAssemblyって、JavaScriptの代わり?
ペンギン先生 ペンギン先生
一緒に使える仕組みだよ。ブラウザなどが実行できるバイナリ形式で、ほかの言語から変換した処理をJavaScript側から呼ぶこともできるんだ。
ひよこ ひよこ
バイナリだと中身がわからないな…。
ペンギン先生 ペンギン先生
WATという文字で読める形式もあるよ。今回は整数を2つ受け取って足す関数をWATで書き、.wasmへ変換して動かしてみよう。
ひよこ ひよこ
CやRustを覚えてからじゃないと無理?
ペンギン先生 ペンギン先生
実際の開発ではそうした言語と変換用の道具を使うけれど、仕組みの入口は小さなWATでも確かめられるよ。まず「作る・読み込む・呼ぶ」を分けて追おう。
ひよこ ひよこ
どうやってJavaScriptから使うの?
ペンギン先生 ペンギン先生
wasmのファイルを読み込み、実行できるインスタンスを作るよ。公開した関数がexportsに現れるので、その関数へ引数を渡すんだ。
ひよこ ひよこ
画面やボタンもwasmだけで作る?
ペンギン先生 ペンギン先生
今回の画面への表示はJavaScriptで行うよ。ブラウザのDOMを、普通のJavaScriptと同じようにwasmが直接操作するわけではないんだ。必要な機能は外側との連携を設計するよ。
ひよこ ひよこ
必ずJavaScriptより速い?
ペンギン先生 ペンギン先生
処理や入力の量、変換元のコード、呼び出しの回数などで変わるよ。JavaScriptのエンジンにも最適化がある。小さな足し算の例は速度の優劣を測るものではないんだ。
ひよこ ひよこ
安全な形式ならバグはない?
ペンギン先生 ペンギン先生
実行環境から隔離し、メモリ領域の外へのアクセスなどを制限する仕組みはあるよ。ただし、計算の誤りや領域内のデータ破損など、あらゆる不具合を防ぐわけではないんだ。
ひよこ ひよこ
覚え方を教えて!
ペンギン先生 ペンギン先生
「WebAssembly」って出てきたら「外で作った処理を、対応する実行環境へ渡す形式」と思えばだいたいOK!

最初のゴール:wasmの関数で2+3=5

JavaScriptから、WebAssemblyの小さな関数を呼びます。CやRustの大きな開発環境を入れる前に、関数を公開して、呼び出し結果が5になる流れを追ってみましょう。

ここではNode.jsとnpm、WATを変換するWABT、ローカル配信用のPythonを使います。作業用の新しいフォルダを用意してください。例は32ビット整数の加算です。

1. WATで関数を書く

add.watを作ります。

(module
  (func (export "sum") (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add
  )
)

paramは受け取る値、resultは返す値の型です。local.getで2つの値を積み、i32.addで加算します。export "sum"は外から呼べる名前を付けています。

2. wasmファイルへ変換する

WABTはWebAssemblyのための道具集です。npmで配布されるWABTのコマンドを使って変換します。

npx --package=wabt wat2wasm add.wat -o add.wasm

初回はパッケージの取得確認が出る場合があります。実行するパッケージを確認してください。同じフォルダにadd.wasmができたら、次へ進みます。

3. JavaScriptから呼ぶ

index.htmlを作ります。

<!doctype html>
<html lang="ja">
<meta charset="utf-8">
<title>wasmの足し算</title>
<link rel="icon" href="data:,">
<p id="result">読み込み中</p>
<script type="module">
  const response = await fetch('./add.wasm');
  if (!response.ok) throw new Error('wasmの取得に失敗');
  const bytes = await response.arrayBuffer();
  const { instance } = await WebAssembly.instantiate(bytes);
  document.querySelector('#result').textContent =
    `2 + 3 = ${instance.exports.sum(2, 3)}`;
</script>
</html>

フォルダでローカルサーバーを起動します。WindowsでPythonのランチャーを使う例は次のとおりです。macOSやLinuxでは、環境に合わせてpython3を使います。

py -m http.server 8000 --bind 127.0.0.1

ブラウザでhttp://127.0.0.1:8000/を開き、「2 + 3 = 5」が表示されれば成功です。終了するときはサーバーを起動したターミナルでCtrl + Cを押します。ファイルを直接ダブルクリックするのではなく、HTTPで配信してfetchが読めるようにします。

処理を分けて見る

  • WAT → wasm:人が読める形式をバイナリへ変換します。
  • 読み込みとインスタンス化:実行環境がモジュールを検証し、呼べる状態を作ります。
  • 関数の呼び出し:JavaScriptがsumへ値を渡し、結果を受け取ります。
  • 画面への表示:JavaScriptがDOMを更新します。

この例は仕組みを確認するもので、性能の比較ではありません。実用では、変換元の言語、必要な入出力、実行環境の対応を選びます。WASIのような外側の機能を使う仕組みもありますが、WebAssemblyだけでどんなOS機能にも直接アクセスできるわけではありません。

よくあるつまずき

  • 404になる:index.htmlとadd.wasmが配信フォルダにあるか確認します。
  • sumがない:変換元のexport名と、JavaScript側の名前をそろえます。
  • 読み込み中のまま:開発者ツールのConsoleとNetworkでエラーと取得先を確認します。

instantiateStreamingで読み込みながら処理する方法もあります。その場合はサーバーのapplication/wasmというContent-Typeも重要です。上の例はバイト列を読み終えてからインスタンス化します。

次に学ぶなら

呼び出す側を理解するならJavaScript入門、言語からの変換へ進むならRust入門がつながります。

参考資料