1
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モジュールを呼び出す方法

Last updated at Posted at 2025-01-28

はじめに

WebAssembly(Wasm)は、ブラウザ上でネイティブ級の高速コードを安全に実行できる仕組みとして知られています。「Wasmモジュールを呼び出す」という表現を聞くと、ついHTTPベースのAPIコールをイメージしがちですが、実際は ブラウザ内部での“関数呼び出し” に近い形で実行されます。

この記事では、ブラウザ(JavaScriptエンジン)からWasmモジュールを呼び出す仕組みをわかりやすく紹介しつつ、「同一プロセスで動くのにセキュリティは大丈夫なの?」 という疑問にも答えます。さらに、よくある質問(FAQ)を加えています。


1. 全体の流れ

まず、「Wasmを呼び出す」とはどんな流れなのかを大まかに押さえておきましょう。

  1. Wasmバイナリ(.wasmファイル)をブラウザに読み込む
    • fetch() で取得、あるいはscriptタグ経由で.jsラッパを読み込み
  2. WebAssemblyモジュールをインスタンス化
    • WebAssembly.instantiate()WebAssembly.compile()でロードする
    • Emscriptenの .js 出力を呼ぶ場合は内部で自動実行される
  3. モジュール内のエクスポートされた関数をJavaScriptから呼ぶ
    • まるで 「関数呼び出し」 のように実行
  4. 引数・戻り値の受け渡し
    • JavaScript側とWasm側でプリミティブ型をやり取り。メモリバッファを共有することも可能

このように、HTTPを介したネットワーク通信ではなくJavaScriptとWasmの同一プロセス内で関数呼び出しが行われます。


2. 仕組みを図で見る

  • JavaScript(ブラウザ)側で.wasmをロードし、モジュールを初期化
  • module.exportsにある関数をJS関数のようにコールして結果を受け取る
  • 同じプロセス内で動くので、高速&低レイテンシ

3. 実装例1: Emscriptenが生成したラッパを使うパターン

3.1 Emscriptenでコンパイル

たとえば、myfunc.c というCコードがあるとします。

// myfunc.c
int add(int a, int b) {
  return a + b;
}

これをEmscriptenで以下のようにコンパイルして.jsラッパを生成します:

emcc myfunc.c -o myfunc.js -s MODULARIZE=1 -s EXPORT_ALL=1

これにより、myfunc.jsmyfunc.wasm が出力され(myfunc.jsの内部でmyfunc.wasmをfetchする仕組み)、ブラウザで読み込めるようになります。

3.2 JavaScriptから呼び出す

<script src="myfunc.js"></script>
<script>
  Myfunc().then(module => {
    // Emscriptenは C関数のエクスポートを "_関数名" で公開する
    const result = module._add(10, 20);
    console.log(result); // 30
  });
</script>
  • このとき、_addC関数 int add(int, int) に対応し、ほぼ「JavaScriptの関数を呼ぶ」感覚。
  • Module._関数名 でWasm関数を呼べるイメージです。

4. 実装例2: WebAssembly標準API(直接実装)を使うパターン

const response = await fetch('myfunc.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {
  // imports: {...} // 必要なインポートがあれば追加
});

// exportsオブジェクトに、C関数が公開される
const result = instance.exports.add(3, 4);
console.log(result); // 7
  • この場合、.wasmをfetch→arrayBuffer→instantiate()` という流れを手動で実装
  • instance.exports.add関数オブジェクトとして呼び出せる
  • HTTPベースのリクエストではなく、同プロセス内で「関数を呼ぶ」形

5. HTTPコールとは何が違う?

5.1 同一プロセス内での関数呼び出し

REST APIGraphQLなどのHTTPベース通信は、リクエストをサーバーへ送って応答を受け取る仕組みですが、Wasmの場合は同じブラウザプロセスでJSとWasmが動作します。ネットワーク経由の遅延がなく、データをメモリ上で直接やりとり可能です。

5.2 低レイテンシ・メモリ共有

HTTP経由ではJSONやXMLなどのシリアライズ・デシリアライズが発生しますが、Wasm呼び出しはバイナリレベルでデータを受け渡しでき、とても高速です。

5.3 リモートサーバーとの通信はオプション

必要に応じてJavaScript側でfetch()WebSocketなどを使い、取得したデータをWasmモジュールに渡すことでサーバー連携も可能ですが、その通信部分はJavaScriptが担うという点がHTTP APIとの大きな違いです。


6. 同一プロセスだけど安全なの? 〜Wasmのメモリサンドボックス〜

「同一プロセス内にJavaScriptエンジンとWasmが存在するなら、バッファオーバーランやメモリリークが相互に影響しないのか? セキュリティ上問題はないのか?」という疑問がよく挙がります。

6.1 WebAssemblyのサンドボックス設計

結論: WebAssemblyでは、専用の線形メモリに隔離され、境界チェックによってバッファオーバーランを防止する仕組みが根幹にあるため、同じプロセスであってもJSや他のWasmモジュールを破壊するリスクは極めて低いです。

  • JavaScriptヒープJSエンジンの内部メモリにはWasmコードが直接アクセス不可
  • Wasmコードが扱う「線形メモリ(バッファ)」は境界チェックが入り、範囲外アクセスはトラップ(例外)を発生
  • 他のWasmモジュールやホストJSオブジェクトを乗っ取れない構造

6.2 バッファオーバーランやメモリリークへの対処

  • バッファオーバーラン:
    • Wasm命令で範囲外アクセスを行うと例外(トラップ)が起き、ホストを破壊する前に停止。
  • メモリリーク:
    • C/C++のmalloc/freeを誤るとWasm線形メモリ内でリークは起き得ますが、モジュール内部のヒープが無駄に使われるだけで、ブラウザ全体を破壊するわけではありません。

6.3 セキュリティ上の仕組み

  • Wasm命令セットは制限付きで、RCE(リモートコード実行)的な攻撃が難しい。
  • インポート/エクスポート関数が厳密に管理され、勝手にホストJSやOS資源を触れない。
  • 実装バグやゼロデイは別として、設計上はネイティブコードより安全とも言われる。

7. 比較表:Wasm関数呼び出し vs HTTP APIコール

以下は、Wasm呼び出しHTTP APIコール(REST/GraphQLなど)の主要な違いをまとめた簡単な表です。

項目 Wasm呼び出し HTTP APIコール (REST/GraphQL)
データ受け渡し 同一プロセス内の関数呼び出し
バイナリ/メモリ共有が可能
ネットワークを介したリクエスト/レスポンス
JSONなどテキスト形式が主流
レイテンシ 非常に低い
(関数コールと同等)
ネットワーク遅延あり
(サーバー往復)
セキュリティモデル Wasmサンドボックス
境界チェックでバッファオーバーランを防止
HTTPS通信や認証が基本
サーバーサイドの認可も必要
実装例 JS → Wasmモジュール_exports JS (fetch/axiosなど) → サーバーのAPIエンドポイント
用途 ブラウザ内で高速計算
ローカル関数呼び出し
サーバー側でデータベースアクセスなど大規模処理
クライアント→サーバー連携
  • このように、Wasm呼び出しはAPIコールに似た「呼び出し→結果」構造を持つが、通信ではなくローカル関数として動作する点が大きく異なります。

8. Wasmモジュールから直接gRPC-Webで外部サーバーにアクセスできるの?

ブラウザとWasmの通信方法は理解できましたが、ブラウザのWasmはソケットを開いてgRPC-Webで直接サーバーと通信できるでしょうか?

8.1 結論:ブラウザWasm単体では直接アクセス不可、JS経由が必要

  • ブラウザのWasm低レベルソケットHTTP/2の直接操作ができません。
  • gRPC-Web通信を行う際は、JavaScriptのHTTP/Fetch/WebSocket等を利用する形になります。
    • つまり、Wasmモジュールが生成したバイナリをJS側で送信し、レスポンスを受け取ってWasmに戻す、という二段階が必要です。

8.2 gRPC-Web + protobuf構造

  • Wasmモジュールがprotobufライブラリを使い、メッセージのencode/decodeだけを行う
  • 実際のHTTP通信はJSのgRPC-Webライブラリがfetch()XMLHttpRequestを使って実行
  • Wasmはソケットに直接アクセスできないため、こうしたラッパ構造になる

8.3 サーバーサイドWasmなら可能性あり

  • サーバーサイドのWasmランタイム(WASIなど)でTCP/UDPソケットがサポートされれば、ネイティブgRPCクライアントを移植し、直接外部サーバーにアクセスできる可能性があります。
  • ただしブラウザWasmとは違い、ホストOSへの依存やランタイムの実装状況に左右される点に注意。

9. FAQ

Q1. サーバーとの通信は不要なのですか?

A1. 必要ならfetch()WebSocket等でサーバーとデータをやり取りし、Wasmに受け渡すことが多いです。Wasm自体はHTTPコールを行わず、JSが橋渡しする形になります。

Q2. 逆アセンブルなどでコードが漏れませんか?

A2. WebAssemblyはバイナリ形式ですが、解析・逆アセンブルは可能です。ソース保護には難読化やライセンス対策が必要な場合があります。

Q3. メモリ容量を無制限に使われる危険は?

A3. Wasmの線形メモリには初期サイズ最大サイズを指定できます。Emscriptenで-s INITIAL_MEMORY-s MAXIMUM_MEMORYを設定しておけば暴走を防げます。

Q4. メモリリークが起きたらブラウザ全体に影響しませんか?

A4. 原則、Wasmモジュール内のヒープを無駄に消費するだけで、ブラウザ本体や他のモジュールを破壊するわけではありません。極端に大きなメモリ要求はブラウザの制限で失敗する場合もあります。

Q5. ネイティブコードより安全と言われるのは本当?

A5. Wasmは境界チェック制限付き命令セットで、従来のC/C++コードによるバッファオーバーラン攻撃を大幅に減らせます。ランタイムやブラウザ実装にバグが無い限り、ネイティブより安全と評価されることも多いです。

Q6. 大規模な数値計算ならサーバーサイドのほうが良いのでは?

A6. ユースケースによります。ブラウザでリアルタイム応答やUI連携が必要ならWasmが有利。大規模処理やDBアクセスが必須ならサーバー側で実行(Wasm or ネイティブ)のほうが適しています。

Q7. ブラウザWasmからgRPC-Web/Protobufを“直接”呼び出すのは?

A7. ブラウザではソケットアクセスが禁止されており、HTTP/2のネイティブ利用は不可です。実際には JavaScriptライブラリ(gRPC-Webなど) を経由し、WasmはProtobufのencode/decodeだけ行う構造になります。

Q8. サーバーサイドWasmならネットワークソケットが使える?

A8. WASIなどがネットワークAPIをサポートすれば、ネイティブのgRPCクライアントをWasmに移植し、直接外部サーバーにアクセスすることも可能と考えられます。ただしブラウザ環境とは別物です。


9. まとめ

  • ブラウザからWasmモジュールを呼び出す流れは、同プロセス内の「関数呼び出し」のようなモデルで動作し、RESTやGraphQLのようなHTTP通信は介在しません。
  • このため非常に低レイテンシかつメモリを共有した高速な処理が可能。
  • 「同じプロセスならバッファオーバーラン等で破壊されるのでは?」という懸念に対しては、WebAssemblyのサンドボックス設計がしっかり対策を講じており、バッファオーバーランやメモリリークがJSエンジンや他のWasmモジュールに波及するリスクは極めて低いです。
  • gRPC-Web通信に関しては、ブラウザWasm単体でソケットを直接扱えないため、JavaScriptのgRPC-Webライブラリを介してHTTPリクエストを行う必要がある。その結果、WasmからはProtobufのエンコード/デコード処理を担い、実際のHTTP通信はJSが担当する構造を取る。
  • もし必要ならJavaScript側でHTTP通信を行い、Wasmにデータを渡すことでサーバーとの連携も可能になります。
  • サーバーサイドWasm(WASIなど)ならソケットAPIを直接使えるかもしれないが、ブラウザWasm環境とは別物。

結論:

  • 「APIコール」に似た形で呼び出すが、実態はローカル関数呼び出しとして同プロセスで動作する。
  • Wasmの境界チェックによりメモリアクセスは安全性が高く、JSヒープや他モジュールを破壊しない。
  • HTTP通信やPWA、サーバーレスなどとも組み合わせでき、ブラウザ上でネイティブ級の性能と安全性を両立できる点が大きな魅力。

参考リンク


以上

1
0
1

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
1
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?