はじめに
JavaScriptエンジンの実装の一つであるduktapeをブラウザ上で動作させたい。と思いEmscriptenを使うことにしました。
動機
まぁブラウザ自体がJavaScriptエンジンを持っているのになぜわざわざduktapeを動作させたいのか? というと、以前記事を書いた esp32-arduinoでJavaScript(duktape)を使う
と関係があります。
今私はマイコンモジュールであるESP32を利用したゲーム機「o-bako」を作っています。
現状o-bakoではLuaを使ってゲームを記述できるようになっています。
そしてWebブラウザで動作するゲーム機のシミュレータも作りました。ここではブラウザ上で動作するLuaランタイムであるfengariを利用しました。
さて、ここまで作ったのですが、Luaというのは結構マイナーな言語です。できればもう少しメジャーな言語スクリプト言語でゲームが書きたいな・・と思い始めました。
そこで目を付けたのがduktapeです。duktapeはLuaとよく似たAPIを提供しているJavaScriptのエンジンです。
esp32-arduinoでJavaScript(duktape)を使うで検証したようにESP32でも動作します。
あとはブラウザシミュレータをどのように実装するかです。duktapeはJavaScriptのサブセットであるためブラウザのJavaScriptエンジンとは微妙に互換性が無いので、ブラウザのJavaScriptエンジンを使うのは難しそうです。またセキュリティの観点からもブラウザのJavaScriptエンジンで任意のコードを動かすのはあまりやりたくありません。
ということでEmscriptenでduktapeをビルドしよう!と思いついたわけです。
教えて!
ここまでのところで、「いやいやそういうことなら、この言語のほうが良いよ!」とか「ブラウザ上で安全にJavaScirptを評価するお勧めの方法があるよ!」といった知見をお持ちの方はぜひ教えてください!
とりあえず私はEmscriptenでduktapeをビルドするという方法を選んだので、以降その方法を紹介します。
duktapeとEmscripten
もともとduktapeはEmscriptenでのコンパイルもできるように作られていましたが、その成果物がリリースに含まれていません。
Makefileを見る限りこれはデモページのために作られているようです。
手っ取り早く成果物を手に入れるためにはデモページに含まれるファイルdukweb.js をダウンロードすればよさそうですが、せっかくなので自分でビルドしてEmscriptenを作ろうと思いました。
王道(面倒なのでやらない)
duktapeはMakefileにより成果物を作り出すようになっており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://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>
<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この記事によると「開発者コンソール」を開いているとasm.jsの最適化が行われないようです。
「開発者コンソール」を閉じるといい感じの速度で動くようになりました。
気づいていなくてしばらくハマりました。
まとめ
Emscriptenを使ってduktapeをブラウザ上で動かすことができました。