EmscriptenでJavaScriptエンジンduktapeをビルドする

# はじめに

JavaScriptエンジンの実装の一つである[duktape](https://duktape.org/)をブラウザ上で動作させたい。と思い[Emscripten](https://emscripten.org/)を使うことにしました。

# 動機
まぁブラウザ自体がJavaScriptエンジンを持っているのになぜわざわざduktapeを動作させたいのか？　というと、以前記事を書いた [esp32-arduinoでJavaScript(duktape)を使う
](https://qiita.com/inajob/items/1c721d45900f0ff06d2a) と関係があります。

今私はマイコンモジュールであるESP32を利用したゲーム機「[o-bako](https://github.com/inajob/o-bako-esp32)」を作っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100375/007a679c-7e33-dbc3-2042-ff730f94ce07.png)

現状o-bakoではLuaを使ってゲームを記述できるようになっています。
そして[Webブラウザで動作するゲーム機のシミュレータ](https://inajob.github.io/o-bako-simulator/index.html)も作りました。ここではブラウザ上で動作するLuaランタイムである[fengari](https://fengari.io/)を利用しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100375/2b5c742d-1583-6073-b778-0fbdee188896.png)

さて、ここまで作ったのですが、Luaというのは結構マイナーな言語です。できればもう少しメジャーな言語スクリプト言語でゲームが書きたいな・・と思い始めました。

そこで目を付けたのがduktapeです。duktapeはLuaとよく似たAPIを提供しているJavaScriptのエンジンです。
[esp32-arduinoでJavaScript(duktape)を使う](https://qiita.com/inajob/items/1c721d45900f0ff06d2a)で検証したようにESP32でも動作します。

あとはブラウザシミュレータをどのように実装するかです。duktapeはJavaScriptのサブセットであるためブラウザのJavaScriptエンジンとは微妙に互換性が無いので、ブラウザのJavaScriptエンジンを使うのは難しそうです。またセキュリティの観点からもブラウザのJavaScriptエンジンで任意のコードを動かすのはあまりやりたくありません。

ということでEmscriptenでduktapeをビルドしよう！と思いついたわけです。

# 教えて！

ここまでのところで、「いやいやそういうことなら、この言語のほうが良いよ！」とか「ブラウザ上で安全にJavaScirptを評価するお勧めの方法があるよ！」といった知見をお持ちの方はぜひ教えてください！

とりあえず私はEmscriptenでduktapeをビルドするという方法を選んだので、以降その方法を紹介します。


# duktapeとEmscripten

もともとduktapeはEmscriptenでのコンパイルもできるように作られていましたが、その成果物が[リリース](https://github.com/svaarala/duktape/releases)に含まれていません。
[Makefile](https://github.com/svaarala/duktape/blob/v2.3.0/Makefile#L583)を見る限りこれは[デモページ](https://duktape.org/dukweb.html)のために作られているようです。

手っ取り早く成果物を手に入れるためにはデモページに含まれるファイル[dukweb.js](https://duktape.org/dukweb.js) をダウンロードすればよさそうですが、せっかくなので自分でビルドしてEmscriptenを作ろうと思いました。

# 王道（面倒なのでやらない）

duktapeは[Makefile](https://github.com/svaarala/duktape/blob/v2.3.0/Makefile#L583)により成果物を作り出すようになっておりdukweb.jsもこれによって作られます。

つまり、このMakefileを手元で実行すればよいのですが・・

```
#
#  Makefile for the Duktape development repo.
#
#  This Makefile is intended for ONLY internal Duktape development
#  on Linux (or other UNIX-like operating systems), and covers:
#
#    - Building the Duktape source distributable.
#    - Running some basic test cases.
#    - Building the duktape.org website.
#
#  The Makefile now also works in a very limited fashion with Cygwin,
#  you can 'make dist' as long as you have enough software installed.
#
#  The source distributable has more platform neutral example Makefiles
#  for end user projects (though an end user should really just use their
#  own Makefile).
#
#  YOU SHOULD NOT COMPILE DUKTAPE WITH THIS MAKEFILE IN YOUR PROJECT!
#
```

まったくもってお勧めできない方法のようです。

# Emscriptenを個別に実行してdukweb.jsを生成する

要はduktape.cとdukweb.cをEmscriptenでビルドすればdukweb.jsのようなものが作れそうなので、Makefileの記述を参考にしながらEmscriptenでビルドすることにしました。

手元の環境はWindowsなので、インストール方法を調べます・・　[Windows上でのEmscriptenのセットアップ](https://qiita.com/takao_mofumofu/items/c331d0e4345eb0f353ae) これかな？

しかし今やコンテナの時代。こんな面倒そうなセットアップをちまちまやりたくありません。

少し調べると https://hub.docker.com/r/trzeci/emscripten/ というコンテナイメージに行き着きました。これを使えばemscriptenのインストールをする必要がありません。（Dockerがインストールしてあれば）

ということで、適当なディレクトリにduktapeのリリース物を展開して、下記を実行します。

```
$ docker run --rm -v //<適当なディレクトリ>/duktape-2.3.0/duktape-2.3.0:/src -u emscripten trzeci/emscripten emcc src/duktape.c dukweb/dukweb.c  -DEMSCRIPTEN  -O2 --memory-init-file 0 -o out.js --closure 1 -I ./src -s EXPORTED_FUNCTIONS='["_dukweb_is_open", "_dukweb_open","_dukweb_close","_dukweb_eval"]' -s 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall", "cwrap"]'
```

これで`out.js` `out.wasm`が得られます。

引数については https://github.com/svaarala/duktape-wiki/blob/master/Portability.md#emscripten などに詳細が載っています。
また　EXPORTED_FUNCTIONS はC言語の関数のうちJavaScript側に公開するものの指定のようです。さらにEXTRA_EXPORTED_RUNTIME_METHODSはJavaScriptからC言語側の関数を呼び出す際に必要なユーティリティ関数をJavaScript側に公開する設定のようです。


そしてこれを呼び出すための簡単なHTML・JavaScriptを書きます

```html
<html>
<body>
<script src="out.js"></script>
<script>

function a(){
  console.log("call a");
}

window.addEventListener("load",function(){
  setTimeout(function(){ // とりあえずwasmのロードを待つ
    let dukwebEval = c.cwrap("dukweb_eval", "string", ["string"]); // Cの関数dukweb_evalをJSの関数化
    console.log("load");
    c.ccall("dukweb_open"); // Cの関数dukweb_openを呼び出す
    dukwebEval("Dukweb = {};")
    // duktape内からブラウザのJSを呼び出すための関数
    dukwebEval("Dukweb.eval = this.emscripten_run_script; delete this.emscripten_run_script;")
    // 試しにブラウザのJSを呼び出す。
    dukwebEval("Dukweb.eval('(window.a())')");
  }, 100);
});

</script>
</body>
</html>
```

一通り動くことが確認できました。
`cwrap`や`ccall`は、様々な説明を見る限り`module`などという名前の変数で提供されるのが一般的のようでしたが、今回はなぜか`c`という名前なのがちょっと気になります。
Emscriptenが出力するJavaScriptがそのように設定しているので、もしかしたらどこかで設定できるのかもしれません。

またdukweb_evalの中で`this.emscripten_run_script`関数を呼び出すことで、ブラウザのJavaScriptを呼び出すことができるようです。
この時グローバルの変数にアクセスする場合は明示的に`window`を記載する必要があることに気づかずしばらくハマりました。

# 注意！ 開発者コンソールを開いていると実行速度が遅くなります。

どうも実行速度が遅い・・　と思っていたのですが、どうやら「開発者コンソール」を開いていると速度が落ちるようです。

[Slow javascript execution in IE11 until developer tools are enabled](https://stackoverflow.com/questions/48580255/slow-javascript-execution-in-ie11-until-developer-tools-are-enabled)この記事によると「開発者コンソール」を開いているとasm.jsの最適化が行われないようです。

「開発者コンソール」を閉じるといい感じの速度で動くようになりました。
気づいていなくてしばらくハマりました。

# まとめ

Emscriptenを使ってduktapeをブラウザ上で動かすことができました。