最近環境が整いつつあるWebAssemblyを触ってみます。
題材としてJavaを実行できる環境を作ってみたいと思います。そうです、Javaアプレットのような、ブラウザで動くJavaインタプリタ(JavaVM)です。
最初に、お断りしておきます。
Javaのバイトコードを解釈するJavaインタプリタではありますが、最低限のバイトコードしか実装していません。また、java/lang配下のほんの一部のクラスしか実装していないので、実用的には使い物にならないです。個人的に、Javaインタプリタの勉強のために作ってみたものです。
期待してこられた方にはすみません。
以下の通り進めていきます。
- WebAssemblyコードを生成するコンパイラをインストールする。
- プロジェクトフォルダを作成する
- WebAssemblyに埋め込むJavaソースコードをコンパイルする。
- JavaインタプリタのC言語ソースコードをコンパイルする。
- Webに配置してWebAssemblyに埋め込んだJavaバイトコードの動作確認する。
- WebAssemblyに埋め込まなかったJavaソースコードをコンパイルする。
- WebAssemblyに埋め込まなかったJavaバイトコードの動作確認する。
JavaインタプリタのC言語ソースコードとして、「waba」を参考にさせていただきました。非常に勉強になりました。ありがとうございました。
wabasoft
http://wabasoft.com/
WebAssemblyコンパイラには、「emscripten」を利用しました。
https://emscripten.org/index.html
以下、続編です。
WebAssemblyでJavaVMを動かす:補足
emscriptenのインストール
Windows10のWSL環境にインストールしてみました。
ですので、Python2.7を使うのですが、インストール済みでした。
> python --version
Python 2.7.15rc1
> git clone https://github.com/juj/emsdk.git
> cd emsdk
> ./emsdk install latest
> ./emsdk activate latest
これで、WebAssemblyコンパイラである「emcc」がインストールされました。
使うときには、パスを通す必要があるため、以下を実行します。
> source ./emsdk_env.sh --build=Release
#プロジェクトフォルダの作成
適当なフォルダを作って作業します。
C言語のソースファイル一式を同フォルダに配置します。
以下のGitHubに上げておきました。
https://github.com/poruruba/javaemu
> git clone https://github.com/poruruba/javaemu.git
> cd javaemu
#WebAssemblyに埋め込むJavaソースコードをコンパイルする
Javaソースコードをコンパイルします。
Javaソースコードは2種類に分けて、配置の方法を変えます。
一つ目は、WebAssemblyに埋め込むJavaソースで、もう一つは、WebAssemblyに埋め込まないJavaソースです。
後者は、通常のユーザ作成のJavaソースコードです。一方、java/langなどは通常のユーザは利用するだけで、変更することのないシステムのJavaソースが前者です。また、インタプリタとの連携やC言語でしか処理できないネイティブ関数があるJavaクラスも前者になります。
ソースコードは、java_srcのフォルダ配下に作っていくことを前提としています。
コンパイルしたクラスファイルを置くフォルダを作ります。pre_classesとします。
> mkdir pre_classes
コンパイルは以下の通りです。
>javac -encoding UTF8 -d pre_classes -sourcepath java_src java_src/base/framework/*.java java_src/java/lang/*.java java_src/test/*.java
base/frameworkとjava/lang配下のJavaソースファイルをシステムのJavaソースとしています。java_src/test/*.java は、後でテスト用に使うものですので、テストが終わったら抜いてください。
これで、pre_classesフォルダ配下に、Javaクラスファイルができあがりました。
Windows上でOracle JDKを使いました。OpenJDKの場合、実行環境上のクラスファイルとバッティングしてしまって、排他する方法がわかりませんでした。。。
#C言語ソースコードをコンパイルする
さあ、コンパイルしましょう
> emcc *.c -s WASM=1 -o javaemu.js -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'UTF8ArrayToString', 'stringToUTF8Array']" --preload-file pre_classes
(参考)コンパイル時オプション
https://emscripten.org/docs/tools_reference/emcc.html#emccdoc
-s WASM=1 はWASMファイルを生成することを示すおまじないです。
-o javaemu.js は、WASMファイル(javaemu.wasm)とユーティリティのJavascript(javaemu.js)とWebAssemblyに埋め込むJavaクラスファイルをまとめたファイル(javaemu.data)を生成するためのものです。
javaemuという名前は適当です。好きな名前にしてください。
-s EXTRA_EXPORTED_RUNTIME_METHODS は、javaemu.jsが提供する関数のうち、Webに配置するHTMLページのJavascriptから呼び出すことを許可する関数名です。ccallは必須ですが、それ以外の2つは有用なので設定しました。
--preload-file は、WebAssemblyに埋め込むファイルが置かれたフォルダ名を指定します。指定したフォルダ名配下のファイルが、javaemu.dataというファイルにまとめられます。
出来上がったファイルのうちWebの配置するのは、「javaemu.wasm」と「javaemu.js」と「javaemu.data」です。
javaemu.wasmが、まさしくC言語をコンパイルしたWebAssemblyファイルです。
WebページのJavascriptから呼び出される関数は、main.cに定義しています。
int EMSCRIPTEN_KEEPALIVE setInoutBuffer(const unsigned char *buffer, long bufferSize );
int EMSCRIPTEN_KEEPALIVE setRomImage(unsigned char *romImage, long romSize );
int EMSCRIPTEN_KEEPALIVE callStaticMain(char *className, char *param );
Webに配置してWebAssemblyに埋め込んだJavaバイトコードの動作確認する。
GitHubから落としてきたファイルのうちのhtmlをWebサーバにアップします。
動作確認のためのページも用意しておきました。
さきほど生成した3つのファイルも、htmlフォルダに配置します。
> cp javaemu.wasm javaemu.js javaemu.data html/
または
> cd html
> ln -s ../javaemu.wasm ../javaemu.js ../javaemu.data .
> cd ..
(HTMLのルートフォルダにおいてください。そうしないと、javaemu.jsがうまくwasmやdataファイルを読んでくれないです)
ありがたいことに、emscriptenには簡易Webサーバ機能があるので、それを使います。ポート番号は8080にしました。
emrun --serve_root html --no_browser --port 8080 .
それでは、ブラウザからアクセスしてみます。
ブラウザとして、Windows上のChromeで動かしてみました。
わかりやすいように、F12を押して開発者コンソールを表示しています。
Class Nameのこところに、テスト用に埋め込んだJavaクラス名を入力します。「test/TestFunc」です。
もととなったソースコードは以下の感じです。
package test;
import base.framework.System;
import base.framework.Convert;
import base.framework.Util;
public class TestFunc{
public static void main( String[] args ){
try
{
System.println("Hello test/TestFunc");
if( args.length >= 1 )
System.println("args[0]=" + args[0]);
String[] input = System.getInput(-1);
for( int i = 0 ; i < input.length ; i++ )
System.println("params[" + i + "]=" + input[i]);
System.setOutput(new String[]{ "World", "こんばんは" });
}catch( Exception ex )
{
System.print( ex.toString() );
ex.printStackTrace();
}
}
}
Input ArgやInput Paramsにも適当な文字列をいれます。
さっそく「start wasm」ボタンを押下します。
以下の感じで、C言語で実装した関数を呼び出します。
WebAssemblyファイルをロードして実行するには、手間がかかるのですが、そこはjavaemu.jsがうまくやってくれています。
this.output_return = Module.ccall('callStaticMain', // name of C function
"number", // return type
["string", "string"], // argument types
[this.class_name, this.input_arg]); // arguments
すると、ブラウザのConsoleにごちゃごちゃデバッグ文字が表示された中に、以下のような文字列が表示されたかと思います。JavaインタプリタによるJavaクラスファイルの実行完了です。
Hello test/TestFunc
args[0]=ABCD
params[]=あいうえお
params[1]=かきくけこ
また、ページにも以下が表示されたかと思います。
Output Return 0
Output Params [ "World", "こんばんは" ]
WebAssemblyに埋め込まなかったJavaソースコードをコンパイルする。
今度は、いろいろなJavaソースを作成して動かしてみましょう。
java_src 配下に作っていきます。
今回は、test2フォルダ以下に作っていきます。
コンパイルは以下の通りです。
> mkdir classes
> javac -encoding UTF8 -d classes -sourcepath java_src -classpath pre_classes java_src/test2/*.java
クラスファイル格納用に、フォルダclassesを作っています。
pre_classesはWebAssemblyファイルに埋め込まれている前提なので、すでにあるクラスファイルとみなしてコンパイルします。
次に、作ったクラスファイルをJarファイルにします。
> jar cvf classes.jar -C classes .
これで、classesフォルダ配下のファイルが、classes.jarファイルにまとめられました。test2のクラスファイル以外のファイルも作られてましたが、実害はないので無視してもいいですし、Jarファイル対象から除いてもよいです。
「ファイルを選択」ボタンを押下し、先ほど作成した「classes.jar」を指定します。
そうするとその下に、Jarファイルに含まれるクラスファイル一覧が表示されます。
Jarファイルの展開、すなわち、ZIPファイルの展開には、zlib.jsのunzip.min.jsを使わせていただきました。
zlib.js
https://github.com/imaya/zlib.js
それではもう一度、ブラウザから実行します。
Class Nameに、今度はJarファイルに含まれるtest2/TestFuncを指定して、「start wasm」ボタンを押下して実行します。
コンソールに以下のように表示されましたでしょうか。test2/TestFuncとなって、該当ソースが実行されているのがわかります。
Hello test2/TestFunc
args[0]=ABCD
[]=あいうえお
[1]=かきくけこ
補足
Javaインタプリタは、wabaからかなりカスタマイズしていまして、バグだらけですので、それだけお断りしておきます。。。
解説は次回の記事で。まずはここまで。
以下、続編です。
WebAssemblyでJavaVMを動かす:補足
以上。