読者です 読者をやめる 読者になる 読者になる

KOYAMA Yoshiaki のブログ

プログラミングについての試行錯誤をつらつら書き溜めていきます。

OS X Mountain Lion 10.8.5 上でウェブブラウザ Google Chrome に使われている JavaScript engine V8 を試す。

enchantMOON ; The Hypertext Authoring Tablet
http://enchantmoon.com/

賛否両論がある enchantMOON を 2013/07/13(Sat) にアスキーストアで予約しました。随分待たされましたが、2013/09/21(Sat) に到着しました。URL を入力して Web を開くシールを作ってみました。次回にでも公開したいと思います。


enchantMOON が到着するまでに時間があったので、以前から興味があった enchantMOON の JavaScript Based Virtual Machine (EAGLEVM) に利用されている V8 を試してみました。

Chrome V8 — Google Developers
https://developers.google.com/v8/

V8 は、ウェブブラウザ Google Chrome に使われている JavaScript engine です。プログラミング言語 C++ で書かれています。私の C++ の知識は、

C++ Primer(5th Edition)

C++ Primer (5th Edition)

C++ Primer (5th Edition)

を読んだ程度です。ほとんど C++ でプログラミングをしたことがない初心者です。

ソースのダウンロードは

How to Download and Build V8 - Chrome V8 — Google Developers
https://developers.google.com/v8/build
Source - v8 - How to get the V8 source code - V8 JavaScript Engine - Google Project Hosting
http://code.google.com/p/v8/wiki/Source

Git を使わず Subversion を利用しました。

ターミナル上で

~ $ svn checkout http://v8.googlecode.com/svn/trunk/ v8

上記コマンド svn を用いてソースコードを入手しました。

BuildingWithGYP - v8 - Instructions to build V8 using GYP - V8 JavaScript Engine - Google Project Hosting
http://code.google.com/p/v8/wiki/BuildingWithGYP

OS X Mountain Lion 上では GYP が正しく動作しないようなので、上記ページと v8/build/ReadMe.txt を参考に

~ $ cd v8
~/v8 $ make dependencies
~/v8 $ make x64.release
~/v8 $ make x64.release.check

上記コマンドを実行しました。

Getting Started - Chrome V8 — Google Developers
https://developers.google.com/v8/get_started
Getting Started
	Audience
	Hello World
	Run the Hello World example

上記ページ、最初に紹介されている Hello World のソースは handle、scope、context が追加されていないので、ビルドできません。その下の Hello World サンプルソースを hello_world.cc として保存し、v8 フォルダにコピーします。ターミナル上で次のコマンド

~/v8 $ g++ -Iinclude hello_world.cc -o hello_world out/x64.release/libv8_base.x64.a out/x64.release/libv8_snapshot.a out/x64.release/libicu{data,i18n,uc}.a -lpthread

を実行するとビルドできました。実際に hello_world を実行すると次のように

~/v8 $ ./hello_world 
Hello, World!
~/v8 $ 

表示されます。

ディレクトリ v8/out/x64.release には

~/v8 $ cd out/x64.release/
~/v8/out/x64.release $ ls
cctest			libv8_nosnapshot.x64.a	obj.target
d8			libv8_snapshot.a	preparser
libicudata.a		lineprocessor		process
libicui18n.a		mksnapshot.x64		shell
libicuuc.a		obj
libv8_base.x64.a	obj.host
~/v8/out/x64.release $ 

hello_world.cc のビルドに必要なライブラリ

libv8_base.x64.a
ibv8_snapshot.a
libicudata.a	
libicui18n.a	
libicuuc.a

と v8/samples のサンプルをビルドした実行ファイル

shell
process
lineprocessor

Javascript インタプリタ d8 が含まれています。以下が d8 の実行例です。

~ $ cd v8/out/x64.release/
~/v8/out/x64.release $ ./d8
V8 version 3.22.1 [console: dumb]
d8> var x=Math.sqrt(2);
undefined
d8> x
1.4142135623730951
d8> var r = function(x,y,x1,y1) { return Math.sqrt((x1-x)*(x1-x)+(y1-y)*(y1-y)); };
undefined
d8> r(0,0,1,1);
1.4142135623730951
d8> r(0,0,1,Math.sqrt(3));
1.9999999999999998
d8> quit()
~/v8/out/x64.release $ 

v8/samples ディレクトリのサンプルソースを理解するには、

~ $ cd v8/samples
~/v8/samples $ ls
count-hosts.js		process.cc		shell.cc
lineprocessor.cc	samples.gyp
~/v8/samples $ 
Getting Started - Chrome V8 — Google Developers
https://developers.google.com/v8/get_started
Getting Started
Embedder's Guide - Chrome V8 — Google Developers
https://developers.google.com/v8/embed
▼ Documentation
	Embedder's Guide

上記ページが参考になります。サンプル Hello World や Contexts、自作の関数を定義するのに利用する Templates など、基本的な仕組みを理解するのに便利です。

v8: v8::String Class Reference
http://izs.me/v8-docs/classv8_1_1String.html

また、上記ページで各クラスの説明を見ることができます。ディレクトリ v8/src、v8/include には、ソースやヘッダがあるので勉強になると思います。

私自身、初歩的なことがわかっていなかったので、

// hello_world.cc
Handle<String> source = String::New("'Hello' + ', World!'");

の String::New() は Local 返すのに Handle に代入していたので、疑問に思っていたのですが、Local は Handle を継承しているのですね!

また、process.cc 351行目

string ObjectToString(Local<Value> value) {
  String::Utf8Value utf8_value(value);
  return string(*utf8_value);
}

の *utf8_value が理解できなかったのですが、

~ $ cd v8/include
~/v8/include $ grep -e "class V8_EXPORT Utf8Value" *.h
v8.h:  class V8_EXPORT Utf8Value {
~/v8/include $ 
// ~/v8/include/v8.h 1838 行目
  class V8_EXPORT Utf8Value {
   public:
    explicit Utf8Value(Handle<v8::Value> obj);
    ~Utf8Value();
    char* operator*() { return str_; }
    const char* operator*() const { return str_; }
    int length() const { return length_; }

String::Utf8Value には、上の char* operator*() { return str_; } ように * 演算子が定義されているのですね!

Sample Code - Chrome V8 — Google Developers
https://developers.google.com/v8/samples
▼ Documentation
	Sample Code

上記ページでサンプルコード、process.cc、shell.cc の簡単な説明があります。


~/v8/samples $ ls 
count-hosts.js		process.cc		shell.cc
lineprocessor.cc	samples.gyp
~/v8/samples $ 

上記 v8/samples ディレクトリあるサンプルコード shell.cc、process.cc、lineprocessor.cc を説明したいと思います。


説明をしやすいように、下記のようにディレクトリ v8/samples 内のすべてのファイルをディレクトリ v8/out/x64.release/ に cp コマンドを使ってコピーしました。

~ $ cd v8/samples/
~/v8/samples $ cp *.* ../out/x64.release/
~/v8/samples $ 


■サンプルコード shell.cc

shell.cc は、コマンドライン引数として --help、--shell、ファイル名を指定できます。

~ $ cd v8/out/x64.release/
~/v8/out/x64.release $ ./shell
V8 version 3.22.1 [sample shell]
> 1+1
2
> var x=1;
> print(x);
1
> var text = read("shell.cc");
> print(text);
....
> load("count-hosts.js");
> version();
3.22.1
> quit();
~/v8/out/x64.release $ 

shell.cc
99行目より、自作関数として print()、read()、load()、quit()、version() が使えます。

print(x)		: 引数を表示する。
read(filename)	: 引数のファイルを読み込む
load(filename)	: 引数のファイルを読み込み、コンパイルして実行する。
quit()			: プログラム終了
version() 		: バージョン表示

shell のコマンドライン引数に javascript ファイルを指定すると javascript ファイルを実行します。

~/v8/out/x64.release $ ./shell count-hosts.js 
~/v8/out/x64.release $ 

実行した後、shell モードに入りたい場合は、コマンドライン引数に --shell を指定します。

~/v8/out/x64.release $ ./shell count-hosts.js --shell
V8 version 3.22.1 [sample shell]
> Initialize();
> var request = {host:"localhost", path:"/", referrer:"apple.com", userAgent:"Safari"};
> var options = {verbose:0};
> var output = {};
> Process(request);
> print(output);
[object Object]
> print(request.host);
localhost
> print(output["localhost"]);
1
> quit();
~/v8/out/x64.release $ 


■サンプルコード process.cc

process.cc は v8/samples ディレクトリある Javascript ファイル count-hosts.js を利用します。

プログラム process.cc 内で log 関数、変数 options.verbose、output を作り、process.cc 599行目

const int kSampleSize = 6;
StringHttpRequest kSampleRequests[kSampleSize] = {
  StringHttpRequest("/process.cc", "localhost", "google.com", "firefox"),
  StringHttpRequest("/", "localhost", "google.net", "firefox"),
  StringHttpRequest("/", "localhost", "google.org", "safari"),
  StringHttpRequest("/", "localhost", "yahoo.com", "ie"),
  StringHttpRequest("/", "localhost", "yahoo.com", "safari"),
  StringHttpRequest("/", "localhost", "yahoo.com", "firefox")
};
StringHttpRequest("/process.cc", "localhost", "google.com", "firefox"),
を例にとると
var request = {path:"/proces.cc", host:"localhost",  referrer:"google.com", userAgent:"firefox"};
になります。

で宣言されているそれぞれの request を Javascript ファイル count-hosts.js 内の Process 関数で呼び出します。結果が、変数 output に格納されます。output の内容は、process,cc 652行目

PrintMap(&output);

で表示します。実行結果は

~/v8/out/x64.release $ ./process count-hosts.js 
google.com: 1
google.net: 1
google.org: 1
yahoo.com: 3
~/v8/out/x64.release $ 

になります。 ./process のコマンドライン引数として verbose=true を宣言すると log 関数が呼び出されます。

~/v8/out/x64.release $  ./process count-hosts.js verbose=true
Logged: Processing google.com/process.cc from localhost@firefox
Logged: Processing google.net/ from localhost@firefox
Logged: Processing google.org/ from localhost@safari
Logged: Processing yahoo.com/ from localhost@ie
Logged: Processing yahoo.com/ from localhost@safari
Logged: Processing yahoo.com/ from localhost@firefox
google.com: 1
google.net: 1
google.org: 1
yahoo.com: 3
~/v8/out/x64.release $ 


■サンプルコード lineprocessor.cc

lineprocessor は、コマンドライン引数として --main-cycle-in-cpp、--main-cycle-in-js のどちらかを指定することで違った動作をします。ループ処理を C++側か、Javascript 側で行うかの違いがあります。

サンプルコード lineprocessor.cc の 56 行目にある

function ProcessLine(input_line) {
    return ">>>" + input_line + "<<<";
}

の部分を main-cycle-in-cpp.js という名前で保存します。

コマンドライン引数を main-cycle-in-cpp.js、--main-cycle-in-cpp とし、実行すると

~/v8/out/x64.release $ ./lineprocessor main-cycle-in-cpp.js --main-cycle-in-cpp 
hello
>>>hello<<<
bye
>>>bye<<<
^C
~/v8/out/x64.release $ 

lineprocessor.cc 内で入力処理を行い、Javascript ファイルの関数 ProcessLine(input_line) を呼び出します。関数 ProcessLine(input_line) の返り値を下記 RunCppCycle 関数内の

    printf("%s\n", cstr);

で表示します。

272行目

bool RunCppCycle(v8::Handle<v8::Script> script,
                 v8::Local<v8::Context> context,
                 bool report_exceptions) {

lineprocessor.cc 側の上記 RunCppCycle 関数内でループ処理を行います。

サンプルコード lineprocessor.cc の 66 行目にある

while (true) {
  var line = read_line();
  if (!line) {
    break;
  }
  var res = line + " | " + line;
  print(res);
}

の部分を main-cycle-in-js.js という名前で保存します。

コマンドライン引数を main-cycle-in-js.js 、--main-cycle-in-js としてし、実行すると

~/v8/out/x64.release $ ./lineprocessor main-cycle-in-js.js --main-cycle-in-js 
hello
hello | hello
bye
bye | bye
^C
~/v8/out/x64.release $ 

lineprocessor.cc 内で read_line() を定義し、Javascript 側でループ処理をおこないます。

lineprocessor.cc の肝は、デバッガに対応していることです。


Google Chrome Developer Tools for Java

V8 でデバッガを利用するには、Google Chrome Developer Tools for JavaEclipse が必要です。

chromedevtools - Google Chrome Developer Tools for Java - Google Project Hosting
http://code.google.com/p/chromedevtools/

が、参考になります。

2013/09/29(Sun) 現在、OS X Mountain Lion バージョン 10.8.5 上では、

~ $ java -version
java version "1.6.0_51"
Java(TM) SE Runtime Environment (build 1.6.0_51-b11-457-11M4509)
Java HotSpot(TM) 64-Bit Server VM (build 20.51-b01-457, mixed mode)
~ $ 

Java のバージョンは、1.6.0_51で問題ありません。

Eclipse は、余計なものがインストールされないように

Eclipse Downloads
http://www.eclipse.org/downloads/

Eclipse Standard 4.3.1, 196 MB
Downloaded 381 Times  Other Downloads
Mac OS X 64 Bit

から、一番上の Eclipse Standard 4.3.1, 196 MB Mac OS X 64 Bit をダウンロードしました。ダウンロードした eclipse-standard-kepler-SR1-macosx-cocoa-x86_64.tar.gz ファイルを Finder 上でダブルクリックして展開し、作成された eclipse フォルダを /アプリケーション フォルダに移動します。

eclipse を再インストールルする時は、/アプリケーション フォルダ内の eclipse フォルダ と ~/書類/workspace フォルダを削除してください。workspace フォルダには、不可視ファイルが含まれているので必ず削除してください。

DebuggerTutorial - chromedevtools - First steps of using Debugger. - Google Chrome Developer Tools for Java - Google Project Hosting
http://code.google.com/p/chromedevtools/wiki/DebuggerTutorial

上記 URL を参考に、SDK をインストールする必要があります。

/アプリケーション/eclipse フォルダ 内の Eclipse を Finder 上でダブルクリックして起動します。

メニュー Help/Install New Software.... を選びます。

図 1

図 1の一番上のテキストフィールド Wok with: に

http://chromedevtools.googlecode.com/svn/update/dev/

を入力します。Add ボタンをクリックします。

図 2

図 2 のダイアログが開くので、Name: に

Google Chrome Developer Tools

と入力して OK ボタンをクリックします。

図 3

図 3 の中央に表示される大きなエリアの Google Chrome Developer Tools 横にある ▼ をクリックして開きます。

Chromium JavaScript Remote Debugger” にチェックを入れます。

後は Next > ボタンを 2 回クリックし、ライセンス同意ボタンを選択して終了します。途中、セキュリティ警告が表示されますが、OK ボタンをクリックしてください。


■サンプルコード lineprocessor.cc をデバッガで試す

AddDebuggerSupport - v8 - Adding debugger support to application HOW-TO. - V8 JavaScript Engine - Google Project Hosting
http://code.google.com/p/v8/wiki/AddDebuggerSupport

上記 URL が参考になります。

まず、最初にターミナルで、コマンドライン引数 -p 9222、--callback を追加して lineprocessor 実行します。

~/v8/out/x64.release $ ./lineprocessor -p 9222 main-cycle-in-cpp.js --main-cycle-in-cpp --callback

それから、

図 4

図 4のように Eclipse を起動、メニュー Run/Debug Configurations... を選択します。

図 5

図 5 のように、左エリア内の Standalone V8 VM を選択して、上部ノートに + が付いたアイコンをクリックします。図 6のように設定が新規に作成されます。

図 6

図 6 のように remote タブの Host: localhost、Port: 9222、show debugger network cominucation console にチェックを入れます。 Apply ボタンをクリックしてから Debug ボタンをクリックします。

図 7

上図のように表示されます。左上の一番上のアイコンをクリックすると下図のように表示されます。

図 8

Project Explorer の New_configuration の▼を開き、main-cycle-in-cpp.js をダブルクリックして開きます。
ソース main-cycle-in-cpp.js のブレークポイントを仕掛けたい行をクリックすると、図 9 のように青色反転させます。

図 9

図 10

図 10 のメニュー 'Run'/'Add V8/Chrome Javascript Exception Breakpoint'/'Togle Line Breakpoint' を選択して、ブレークポイントを仕掛けます。

図 11

ターミナル側で

./lineprocessor capitalizer.js -p 9222 --main-cycle-in-cpp --callback
hello

hello と文字を入力して Return キーを押します。

するとブレークポイントで実行が止まり、デバッガーを表示していいか尋ねてくるので、Yes ボタンをクリックします。

Variables タブで、変数 input_line に "Hello" という文字列が渡されているのが確認できると思います。

メニュー Window/Show Toolbar を選び、ツールバーを表示します。ツールバーの赤色正方形の横にステップイン、ステップオーバーなどのボタンがあります。

図 12

Resume ボタン(緑色の再生ボタン) 押すとターミナル側で

~ $ cd v8/out/x64.release/
~/v8/out/x64.release $ ./lineprocessor -p 9222 main-cycle-in-cpp.js --main-cycle-in-cpp --callback
hello
>>>hello<<<

図 13

>>>hello<<< という文字が表示されます。ターミナルで CTRL + C を押せば終了させることができます。


また、main-cycle-in-js.js、--main-cycle-in-js のコマンドライン引数を lineprocessor に渡しても、同じようにデバッガを使うことができます。

~/v8/out/x64.release $  ./lineprocessor main-cycle-in-js.js --main-cycle-in-js  -p 9222 --callback
hello

変数 line の内容を表示するには、ソースの line という文字にカーソルを合わせます。 図 14 のように "hello"というポップアップが現れます。

while (true) {
    var line = read_line();
    if (!line) {
        break;
    }
    var res = line + " | " + line;
    print(res);
}

図 14