WebAssemblyを試してみる

2025年 9月18日 Posted 野々瀨(フロントエンドエンジニア)

今回はEmscriptenを使用してシンプルなCのコードをWebAssembly形式へとコンパイルし、ブラウザーで動かすまでの簡単な流れをご紹介しようと思います。

WebAssemblyとは

WebAssemblyとは、Web上(ブラウザー)からAssembly言語(機械語)を実行できるようにし、Webコンテンツを高速に動作するためのバイナリコード形式の仕組みです。CやC++、Rust、Goなどの言語をWebAssembly形式にコンパイルすることで、Webブラウザを始めとするさまざまな環境で実行することができます。

ちなみにAssemblyはasmと略されることから、WebAssemblyではWasm(ワズム)と略して呼ばれることもあります。

対応ブラウザー

WebAssemblyに対応しているブラウザーは次の画像のとおりです。

Can I useによるWebAssemblyの対応ブラウザの表https://caniuse.com/wasm

コンパイラ

WebAssemblyへのコンパイラは、各言語に対応したコンパイラがあります。

元となる言語コンパイラ
C / C++ LLVM + Clang
Emscripten
Java TeaVM
GraalVM
Go Go
Rust Rust + wasm-pack
CUDA CUDA LLVM Compiler

今回は冒頭のとおりEmscriptenを使用してC言語からコンパイルします。

前提環境

次の環境を前提に説明します。

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\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_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を実行するかどうかを指定します。

例)INVOKE_RUN=1

NO_EXIT_RUNTIME 0 mainを実行した後にランタイムを停止しないようにするかを指定します。

例)NO_EXIT_RUNTIME=1

EXPORTED_RUNTIME_METHODS 外から扱えるようにするランタイムメソッドを指定します。

例)EXPORTED_RUNTIME_METHODS=ccall,cwrap

EXPORTED_FUNCTIONS main以外の関数を定義した際、wasmに含む(エクスポートする)関数を指定します。
指定する関数は先頭にアンダースコアを付けます。
カンマで区切ることで複数指定することができます。
mainを実行したい場合は「_main」も指定する必要があるので注意してください。

例)EXPORTED_FUNCTIONS=_main,_add,_sub

WASM 1 出力されるファイルの構成を指定します。0でJavaScriptファイルのみ、1でJavaScriptファイルとwasmファイル、wasmサポート状況に応じて0または1どちらかを自動で出力します。

例)WASM=0

詳しくは、公式サイトにある次の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

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_METHODSccallを含んで指定した時に使用することができます。

引数説明
第一引数 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);
})();