Emscriptenを使ってブラウザでWebAssemblyを動かす

# はじめに

最近WebAssemblyがあついですね。
というわけでC言語からWebAssemblyを作成して`Hello WebAssembly`してみようと思います。
コンパイラにはWebAssembly界隈で広く使われている[Emscripten](https://emscripten.org/)を使用します。

本記事では、以下のステップで進めます。
使用する環境はWindows10となっています。

- [1. 環境構築-Emscriptenのインストール](#1-環境構築-emscriptenのインストール)
- [2. C言語でサンプルプログラムを実装](#2-c言語でサンプルプログラムを実装)
- [3. C言語からWebAssemblyへコンパイル](#3-c言語からwebassemblyへコンパイル)
- [4. WebAssemblyをブラウザで実行](#4-webassemblyをブラウザで実行)
- [5. デベロッパーツールでWebAssemblyの中身を見る](#5-デベロッパーツールでwebassemblyの中身を見る)

なお、以下の公式ページでもC言語からWebAssemblyを動かす手順が示されています。

https://developer.mozilla.org/ja/docs/WebAssembly/C_to_wasm

# 1. 環境構築-Emscriptenのインストール

はじめにEmscriptenをインストールします。
Emscriptenは以下のページからダウンロードします。
パッケージマネージャーを利用したインストール方法が書かれていますが、gitレポジトリからクローンするだけでも使えるようになります。

https://emscripten.org/

今回は以下のgitレポジトリをクローンしたものを使用します。

https://github.com/emscripten-core/emsdk

クローンしたのち、[こちら](https://emscripten.org/docs/getting_started/downloads.html#installation-instructions-using-the-emsdk-recommended)に記載の通り、クローンしたフォルダ内で以下のコマンドを実行します。

```powershell
emsdk install latest
emsdk activate latest
emsdk_env.bat
```

特にエラーが出なければインストールは完了です。

# 2. C言語でサンプルプログラムを実装

続いてサンプルプログラムを作成します。
`echo.c`ファイルを作成し、下記のように実装します。
なお、本記事ではブラウザでWebAssemblyを実行するまでの流れに注目していますので、プログラムの詳細は省きます。
処理内容としては、引数で与えられた文字列に対して`Hello, <文字列> !`と表示するだけの簡単なものになっています。

```c:echo.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char* echo(char* ptr){
    int n;
    char* str;

    n = 10 + strlen(ptr);
    str = (char*)malloc(n*sizeof(char));
    memset(str, '\0', (n*sizeof(char)));

    strcpy(str, "Hello, ");
    strcat(str, ptr);
    strcat(str, " !");

    return str;
}

int main(int argc, char **argv) {
    char* ptr = echo("WebAssembly");
    printf("%s", ptr);
    free(ptr);
    return 0;
}
```

# 3. C言語からWebAssemblyへコンパイル

Emscriptenを利用する場合、`emcc`コマンドでコンパイルします。
パッケージマネージャを利用せずにインストールした場合、`emcc`コマンドを実行するプロンプトで`emsdk_env.bat`を実行します。
Linuxの場合は`emsdk_env.sh`となります。

以下は`emsdk_env.bat`実行時の出力です。

```powershell
D:\webassembly>d:\Tools\emsdk\emsdk_env.bat
Setting up EMSDK environment (suppress these messages with EMSDK_QUIET=1)
Adding directories to PATH:
PATH += D:\Tools\emsdk
PATH += D:\Tools\emsdk\upstream\emscripten
PATH += D:\Tools\emsdk\node\14.18.2_64bit\bin

Setting environment variables:
PATH = D:\Tools\emsdk;D:\Tools\emsdk\upstream\emscripten;D:\Tools\emsdk\node\14.18.2_64bit\bin;<以下略>
EMSDK = D:/Tools/emsdk
EM_CONFIG = D:\Tools\emsdk\.emscripten
EMSDK_NODE = D:\Tools\emsdk\node\14.18.2_64bit\bin\node.exe
EMSDK_PYTHON = D:\Tools\emsdk\python\3.9.2-nuget_64bit\python.exe
JAVA_HOME = D:\Tools\emsdk\java\8.152_64bit
Clearing existing environment variable: EMSDK_PY
```

それではここで、`emcc`コマンドを用いでC言語のプログラムをWebAssemblyにコンパイルしてみます。
コマンドは以下の通りです。

```powershell
D:\webassembly>emcc echo.c -o echo.html -sWASM=1 -sEXIT_RUNTIME=1
```

これにより以下のファイルが出力されます。

- `echo.html`：emscriptenが出力するサンプルhtml
- `echo.js`：html内からWebAssemblyを呼び出すためのjavascript
- `echo.wasm`：WebAssemblyの実行ファイル

参考までに、`emcc`コマンドでよく使いそうなオプション引数等を[(参考) emccコマンドのオプションについて](#参考-emccコマンドのオプションについて)を示します。
読み込み時にmain関数を実行するかしないか、などもオプション引数でコントロールできます。

# 4. WebAssemblyをブラウザで実行

作成された`echo.html`を使ってWebAssemblyを実行してみます。
ただし、単純に`echo.html`を開くだけではwasmファイルは実行されません。これはセキュリティ上の理由からで、ブラウザのCORSポリシーによりjavascriptやwebassemblyのファイルが読み込まれないためです。

ブラウザのCORSを無効にすることもできますが、ここでは真っ当にWebサーバを起動することにします。

Pyhtonを使うのであれば以下のコマンドで簡易的なhttpサーバーは起動できます。

```powershell
python -m http.server 8000
```

起動したWebサーバへアクセスすると以下のようにコンパイルされた資材が確認できるかと思います。

![list.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705474/8cbde0bb-66e9-4268-e157-51d8001ead59.png)


ここで`echo.html`を選択すると、プログラムのmain関数が実行され。以下のような表示になります。
これでWebAssemblyが実行されていることが確認できました。

![exec.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705474/ef0a8384-b012-77ad-cbf4-123a0a8e49a5.png)

# 5. デベロッパーツールでWebAssemblyの中身を見る

ブラウザで実行されているWebAssemblyについては、ブラウザのデベロッパーツールで確認することが出来ます。
Chromeの場合だと以下のように、「ソース」タブを開き`echo.wasm`を選択するとテキスト形式で実行ファイルの中身が表示されます。

![developper.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705474/9f73134a-d3b5-4fb0-097a-1a5cdcebc519.png)

※ WebAssemblyを使用しているサイトやサービスについても同様に、デベロッパーツールでWebAssemblyのファイルを覗くことが出来ます。処理内容が丸裸になりますのでWebAssemblyを本番運用する際は注意しましょう。

# (参考) `emcc`コマンドのオプションについて

`emcc`コマンドでよく使われそうなオプションを以下に示します。


| オプション引数    | 説明                                                                                                                | 
| ----------------- | ------------------------------------------------------------------------------------------------------------------- | 
| -o <target>       | 出力ファイル名とファイルタイプ                                                                                      | 
| -O0               | 最適化を実施しない（デフォルト）                                                                                    | 
| -O1               | 簡単な最適化を実施。コンパイル時にLLVMの-O1オプションを適用。リンク時にいくつかのランタイムアサーションを含めない。 | 
| -O2               | -O1よりも多くの最適化を行う。リンク時にjsの最適化も実施。                                                           | 
| -O3               | -O2よりも多くの最適化を行う。リリース時の推奨。                                                                     | 
| -Os               | -O3と同様だが、よりコードサイズを小さくするような最適化を行う。                                                     | 
| -Oz               | -Osと同様だが、さらにコードサイズを小さくするような最適化を行う。                                                   | 
| -g0               | コードがデバッグできるように何もしない。                                                                            | 
| -g1               | リンク時にjsのスペースを保持する。                                                                                  | 
| -g2, --profiling  | リンク時に関数名を保持する。                                                                                        | 
| -g3, -g           | コンパイル時にデバッグ情報やjsのスペース、関数名、LLVMデバッグ情報を保持する。                                      | 
| --emit-symbol-map | リンク時に関数名とwasmの関数インデックスの対応を出力する。                                                          | 
| -sOPTION[=VALUE]  | オプション                                                                                                          | 

`-sOPTION`で指定できるオプションは以下のようなものがあります。

| オプション引数               | 説明                                                                                                                                                                                       | 
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 
| -sWASM                       | 0:wasmではなくjsを出力する。1:wasmを出力する。2:wasmとjsの両方を出力する。wasmの実行に失敗したときはWsm2JSモードでjsが実行される。                                                         | 
| -sEXPORTED_FUNCTIONS         | deadコードと見做されて削除されないようにexportする関数一覧。"_関数名"の形で記載する。指定しない際は"_main"が暗黙的に適用される。STANDALONE_WASMモードの際は"__start"が暗黙的に適用される。 | 
| -sEXPORT_ALL                 | DCE(Dead Code Elimination)を実行せずにすべてのシンボルを保持する。                                                                                                                         | 
| -sEXPORTED_RUNTIME_METHODS   | ランタイムからModuleへexportする関数名一覧。                                                                                                                                               | 
| -sINVOKE_RUN                 | wasm読み込み時にmain関数を実行するか否か。falseに設定した場合でもModule.callMain()で実行できる。                                                                                           | 
| -sWARN_ON_UNDEFINED_SYMBOLS  | jsで定義されていない関数があった際にwarningを出力する。                                                                                                                                    | 
| -sERROR_ON_UNDEFINED_SYMBOLS | jsで定義されていない関数があった際にerrorを出力する。                                                                                                                                      | 
| -sEXIT_RUNTIME               | 0を指定した場合main関数終了後にランタイムを終了させない。                                                                                                                                  | 
| -sSTANDALONE_WASM            | JavaScript無しで実行できるwasmを出力する。実行にはwasiなどのAPIが必要。                                                                                                                    | 

なお、オプションに複数の値を設定する際は以下のような複数の書き方ができます
これらも[リファレンス](https://emscripten.org/docs/tools_reference/emcc.html)に記載されていますので詳細はこちらをご参照ください。

```
-sEXPORTED_FUNCTIONS=foo,bar
-sEXPORTED_FUNCTIONS="foo","bar"
-sEXPORTED_FUNCTIONS=["foo","bar"]
-sEXPORTED_FUNCTIONS=[foo,bar]
-sEXPORTED_FUNCTIONS="['liblib.so']"
-s"EXPORTED_FUNCTIONS=['liblib.so']"
```

`emcc`コマンドのオプション引数の全量はこちらのページをご参照ください。

https://emscripten.org/docs/tools_reference/emcc.html

`-sOPTION`で指定できるオプションはこちらに記載されています。

https://github.com/emscripten-core/emscripten/blob/main/src/settings.js