WebAssemblyを試してみる
2025年 9月18日 Posted 野々瀨(フロントエンドエンジニア)
今回はEmscriptenを使用してシンプルなCのコードをWebAssembly形式へとコンパイルし、ブラウザーで動かすまでの簡単な流れをご紹介しようと思います。
WebAssemblyとは
WebAssemblyとは、Web上(ブラウザー)からAssembly言語(機械語)を実行できるようにし、Webコンテンツを高速に動作するためのバイナリコード形式の仕組みです。CやC++、Rust、Goなどの言語をWebAssembly形式にコンパイルすることで、Webブラウザを始めとするさまざまな環境で実行することができます。
ちなみにAssemblyはasmと略されることから、WebAssemblyではWasm(ワズム)と略して呼ばれることもあります。
対応ブラウザー
WebAssemblyに対応しているブラウザーは次の画像のとおりです。
コンパイラ
WebAssemblyへのコンパイラは、各言語に対応したコンパイラがあります。
元となる言語 | コンパイラ |
---|---|
C / C++ | LLVM + Clang Emscripten |
Java | TeaVM GraalVM |
Go | Go |
Rust | Rust + wasm-pack |
CUDA | CUDA LLVM Compiler |
今回は冒頭のとおりEmscriptenを使用してC言語からコンパイルします。
前提環境
次の環境を前提に説明します。
- Windows 11
- Git
- ApacheなどのWebサーバー
Emscriptenのインストール
https://emscripten.org/docs/getting_started/downloads.html
開発を行うディレクトリーに移動し、GitHubページよりソースをクローンします。
git clone https://github.com/emscripten-core/emsdk.git
次のコマンドを実行し、インストールを行います。
emsdk install latest
Resolving SDK alias 'latest' to '4.0.9'Resolving SDK version '4.0.9' to 'sdk-releases-cb2a69bce627bd2247624c71fc12907cb8785d2f-64bit'Installing SDK 'sdk-releases-cb2a69bce627bd2247624c71fc12907cb8785d2f-64bit'..Installing tool 'node-22.16.0-64bit'..Downloading: C:/emsdk/downloads/node-v22.16.0-win-x64.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/node-v22.16.0-win-x64.zip, 37497627 BytesUnpacking 'C:/emsdk/downloads/node-v22.16.0-win-x64.zip' to 'C:/emsdk/node/22.16.0_64bit'Done installing tool 'node-22.16.0-64bit'.Installing tool 'python-3.13.3-64bit'..Downloading: C:/emsdk/downloads/python-3.13.3-0-win-amd64.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/python-3.13.3-0-win-amd64.zip, 29736374 BytesUnpacking 'C:/emsdk/downloads/python-3.13.3-0-win-amd64.zip' to 'C:/emsdk/python/3.13.3_64bit'Done installing tool 'python-3.13.3-64bit'.Installing tool 'releases-cb2a69bce627bd2247624c71fc12907cb8785d2f-64bit'..Downloading: C:/emsdk/downloads/cb2a69bce627bd2247624c71fc12907cb8785d2f-wasm-binaries.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/win/cb2a69bce627bd2247624c71fc12907cb8785d2f/wasm-binaries.zip, 534499429 BytesUnpacking 'C:/emsdk/downloads/cb2a69bce627bd2247624c71fc12907cb8785d2f-wasm-binaries.zip' to 'C:/emsdk/upstream'Done installing tool 'releases-cb2a69bce627bd2247624c71fc12907cb8785d2f-64bit'.Done installing SDK 'sdk-releases-cb2a69bce627bd2247624c71fc12907cb8785d2f-64bit'.
次のコマンドでSDKをアクティブな状態にします。
emsdk activate latest
Resolving SDK alias 'latest' to '4.0.9'Resolving SDK version '4.0.9' to 'sdk-releases-cb2a69bce627bd2247624c71fc12907cb8785d2f-64bit'Setting the following tools as active:node-22.16.0-64bitpython-3.13.3-64bitreleases-cb2a69bce627bd2247624c71fc12907cb8785d2f-64bit
Next steps:- Consider running
emsdk activate
with --permanent or --systemto have emsdk settings available on startup.Adding directories to PATH:PATH += C:\emsdkPATH += C:\emsdk\upstream\emscriptenSetting environment variables:PATH = C:\emsdk;C:\emsdk\upstream\emscriptenEMSDK = C:/emsdkEMSDK_NODE = C:\emsdk\node\22.16.0_64bit\bin\node.exeEMSDK_PYTHON = C:\emsdk\python\3.13.3_64bit\python.exeClearing existing environment variable: EMSDK_PYThe changes made to environment variables only apply to the currently running shell instance. Use the 'emsdk_env.bat' to re-enter this environment later, or if you'd like to register this environment permanently, rerun this command with the option --permanent.
開発の開始
現在のコマンドライン上でemcc
コマンドを扱えるようにするため、次のバッチファイルを実行し、環境変数のPATHを一時的に変更します。
emcmdprompt.bat
Setting up EMSDK environment (suppress these messages with EMSDK_QUIET=1)Adding directories to PATH:PATH += C:\emsdkPATH += C:\emsdk\upstream\emscripten
Setting environment variables:PATH = C:\emsdk;C:\emsdk\upstream\emscriptenEMSDK = C:/emsdkEMSDK_NODE = C:\emsdk\node\22.16.0_64bit\bin\node.exeEMSDK_PYTHON = C:\emsdk\python\3.13.3_64bit\python.exeClearing existing environment variable: EMSDK_PY
次のコマンドを実行し、バージョンが表示されることを確認できれば完了です。
emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 4.0.9 (bbf1caa6e24f64fca9eb6a13a9e02d3f42123e77)clang version 21.0.0git (https:/github.com/llvm/llvm-project 2f05451198e2f222ec66cec4892ada0509519290)Target: wasm32-unknown-emscriptenThread model: posixInstalledDir: C:\emsdk\upstream\bin
Cのソースコードの準備
サンプルとして次のようなCのコードを準備します。
#include <stdio.h>
int main() {
printf("Hello World\n");
}
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
ファイル名はここでは「hello-world.c」として保存しています。
コンパイル
次のようにemcc Cのソースファイルパス
とコマンドを入力し、実行するとコンパイルします。
emcc hello-world.c
この初期状態では、ソースファイルと同階層に「a.out.js」と「a.out.wasm」が生成されます。.wasmファイルはwasm本体、JavaScriptファイルはwasmファイルをWeb環境で扱うためのひも付けを行うファイルです。
-o 出力ファイルパス
オプションを指定すると、出力先とファイル名を変更することができます。
emcc hello-world.c -o hello-world.js
-s
オプションを指定すると、出力の仕方などさまざまな設定を行うことができます。基本的には-s オプション名[=設定値]
で指定します。
emcc hello-world.c -s INVOKE_RUN=0 -s NO_EXIT_RUNTIME=1 -s EXPORTED_RUNTIME_METHODS=ccall
オプション | 初期値 | 説明と例 |
---|---|---|
INVOKE_RUN |
1 | モジュールが読み込まれた時に、mainを実行するかどうかを指定します。
例) |
NO_EXIT_RUNTIME |
0 | mainを実行した後にランタイムを停止しないようにするかを指定します。
例) |
EXPORTED_RUNTIME_METHODS |
外から扱えるようにするランタイムメソッドを指定します。
例) |
|
EXPORTED_FUNCTIONS |
main以外の関数を定義した際、wasmに含む(エクスポートする)関数を指定します。 指定する関数は先頭にアンダースコアを付けます。 カンマで区切ることで複数指定することができます。 mainを実行したい場合は「_main」も指定する必要があるので注意してください。 例) |
|
WASM |
1 | 出力されるファイルの構成を指定します。0でJavaScriptファイルのみ、1でJavaScriptファイルとwasmファイル、wasmサポート状況に応じて0または1どちらかを自動で出力します。
例) |
詳しくは、公式サイトにある次のEmscripten Compiler Settingsページをご覧ください。
https://emscripten.org/docs/tools_reference/settings_reference.html
この記事では次のコマンドでコンパイル・出力したJavaScriptファイルとwasmファイルを使用します。
emcc hello-world.c -o hello-world.js -s EXPORTED_RUNTIME_METHODS=ccall -s EXPORTED_FUNCTIONS=_main,_add,_sub
- hello-world.js
- hello-world.wasm
HTMLとJavaScriptの準備
適当なHTMLファイルを用意し、コンパイル時に出力されたJavaScriptファイルを読み込みます。ここではHTMLのファイル名を「hello-world.html」とします。
<script src="hello-world.js"></script>
続いて次のJavaScriptコードを記述します。
let resultAdd = Module.ccall(
'add',
'number',
['number', 'number'],
[1, 2]
);
let resultSub = Module.ccall(
'sub',
'number',
['number', 'number'],
[10, 3]
);
console.log(resultAdd);
console.log(resultSub);
Module.ccall
メソッドはCのコードで定義した関数を実行することができます。コンパイル時に-s EXPORTED_RUNTIME_METHODS
にccall
を含んで指定した時に使用することができます。
引数 | 型 | 説明 |
---|---|---|
第一引数 | string | 実行したい関数名を指定します。 |
第二引数 | string | null | 第一引数で指定した関数の戻り値の型を指定します。 |
第三引数 | string | null | 第一引数で指定した関数の引数の型を指定します。 |
第四引数 | string | null | 第一引数で指定した関数の引数の値を指定します。 |
第二引数、第三引数の値は次の値を指定することができます。
指定値 | 説明 |
---|---|
number | 引数が数値の型として明示します。 |
string | 引数が文字列の型として明示します。 |
array | 引数が配列の型として明示します。 |
null | 特に指定がない場合に指定します。例えば、戻り値がなければModule.ccall の第二引数にnull を指定します。 |
Webサーバーへファイルを置く
EmscriptenのJavaScriptでFetch APIが使われていますので、Webサーバー環境下でしか動作しません。Emscriptenで出力されたJavaScriptファイルとwasmファイル、HTMLファイルをWebサーバー上に置きます。
ブラウザーで確認
置いたWebサーバーのURLへアクセスします。開発者ツールのコンソールに表示されていることを確認できます。
注意点
Emscriptenではコンパイル時に出力されるJavaScriptを読み込む必要があります。本来wasmファイルはFetch APIから読み込みを行いますが、Emscriptenで出力されたwasmファイルは、Fetch APIから直接扱うことができませんので、注意が必要です。
// Emscriptenのwasmファイルはこのようなことはできない
(async () => {
const response = await fetch('hello-world.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer);
const instance = await WebAssembly.instantiate(module, {
imports : {
imported_func : () => {}
}
});
const result = instance.exports.fibonacci(42);
console.log(result);
})();