凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Emscripten で C++ の Hello World を JavaScript に変換してみた


はじめに

Emscripten は今流行りのコンパイラ基盤 LLVM を利用して C/C++ のコード(正確には LLVM-IR)を JavaScript に変換してくれるコンパイラです。フロントエンドである Clang を利用して C/C++ のコードを LLVM-IR という中間言語へ変換、Emscripten でこれを JavaScript に変換する流れになります。成果としては新しいどころだと、先月、 Unreal Engine 3 を JS にポーティング、WebGL でヌルサクするよ、というニュースが上がっていました。

また、gl.enchant.js と併用して使われる物理エンジンの ammo.js も Emscripten を利用しています。

はやりに乗っかろうと思い EmscriptenC++ を JS に変換できる環境を整えてみました。

環境

  • Mac OS X 10.8.2
  • Apple LLVM version 4.2 (clang-425.0.24) (based on LLVM 3.2svn)
  • Node.js v0.10.4

参考文献

書籍

LLVM についてまとめられている唯一の本です。マニアックなのですべて理解するのは難しかったので、私はコード部分以外にひと通り目を通してみました。

llvm の導入

Mac は XCode で clang が入りますが、llvm-link などはいないので入れる必要があります。

$ brew install llvm

自分で取ってきてビルドしようかと思ったのですが brew で探したらいらっしゃいました。
後で LLVM / Clang のディレクトリ指定があり、1箇所にまとまっている必要があるので、導入した llvm と同一のディレクトリに clang のリンクを張ってきます。

$ cd /usr/local/Cellar/llvm/3.2/bin
$ ln -s /usr/bin/clang* .


(追記:2013/04/16)
参考: C++のコードをブラウザ実行可能なJavaScriptに変換するEmscripten導入メモ - unstable diary
以下のコマンドで clang も一緒に入るようです。

$ brew install llvm --with-clang

emscripten の導入

$ git clone https://github.com/kripken/emscripten.git
$ cd emscripten
$ ./emcc

これで以下のようなメッセージが表示されます。

==============================================================================
Welcome to Emscripten!

This is the first time any of the Emscripten tools has been run.

A settings file has been copied to ~/.emscripten, at absolute path: /Users/hecomi/.emscripten

It contains our best guesses for the important paths, which are:

  LLVM_ROOT       = /usr/bin
  PYTHON          = /Library/Frameworks/Python.framework/Versions/2.7/bin/python
  NODE_JS         = /Users/hecomi/.nodebrew/current/bin/node
  EMSCRIPTEN_ROOT = /Users/hecomi/tmp/emscripten/emscripten

Please edit the file if any of those are incorrect.

This command will now exit. When you are done editing those paths, re-run it.
==============================================================================

~/.emscripten を覗くと、pythonEMSCRIPTEN_ROOT、LLVM_ROOT、PYTHON、NODE_JS、SPIDERMONKEY_ENGINE、V8_ENGINE、JAVA、TEMP_DIR の項目のパスの設定が書かれていますので適宜修正します。LLVM_ROOT を先ほど導入したパスに書き換え、EMSCRIPTEN_ROOTemscripten のディレクトリを移動した場所へ書き換えました。

EMSCRIPTEN_ROOT = os.path.expanduser(os.getenv('EMSCRIPTEN') or '/Users/hecomi/Tools/emscripten') # directory
LLVM_ROOT = os.path.expanduser(os.getenv('LLVM') or '/usr/local/Cellar/llvm/3.2/bin') # directory

Node.js が入っていない場合は、nodebrew 等を利用して Node.js を導入するか、d8 や spidermonkey をビルド、パスを通して下の方にある COMPILER_ENGINE をそれぞれの変数に書き換えて下さい。私の環境では Node.js を入れてあったので COMPILER_ENGINE は「NODE_JS」になっていました。
次に環境変数Emscripten のパスを追加します。私は zsh を使っているので以下の指定を追加してシェルを再起動しました。

# Emscripten
local EMSCRIPTEN_PATH="$HOME/Tools/emscripten"
export PATH="$EMSCRIPTEN_PATH:$PATH"

em++ でエラーが出ないかチェックします。

$ em++ -v
emcc (Emscripten GCC-like replacement + linker emulating GNU ld ) 2.0
Apple LLVM version 4.2 (clang-425.0.24) (based on LLVM 3.2svn)
Target: x86_64-apple-darwin12.2.1
Thread model: posix

これで準備完了です。

Hello, world!

#include <iostream>

int main()
{
	std::cout << "Hello, world!" << std::endl;
	return 0;
}

これを変換して node に食わせてみます。

$ em++ -o helloworld.js helloworld.cpp
$ node helloworld.js
Hello, world!

無事、動きました!変換後のコードは「123538 行」あったので貼り付けるのはやめておきます。iostream も含めて全部変換しているのかな...。
HTML に変換することもできます。

$ em++ -o helloworld.html helloworld.cpp
$ open helloworld.html


凄いですね。

おわりに

どんなものが変換できるのか、パフォーマンスがどうなのか等についても時間がアレば調べてみようと思います。
LLVM を利用して言語間の相互変換が簡単にできる未来とか来ると面白そうですね。