Fork me on GitHub

2011/11/02


このエントリーをはてなブックマークに追加
Windowsで作業する事も多いので、普段からthincaさんが書いたCoffeeScript on JScriptを使ってたんだけど、JScriptを使ってるという点がどうも気持ち悪くて、コンパイルエラーメッセージに「Microsoft JScript 実行時エラー」とか出るとかなり (´・ω・`)ショボーンな気分でした。
あと、thincaさんのは-eオプションで動かした時にconsole.log()が無いのでエラー出る事もあって、ちょっとしたスクリプトを書くのに困ってました。

で、v8のソースリポジトリに含まれるシェルd8を使ってバッチファイルから呼び出して...ってやればうまく行くんじゃね?オレ天才!とか考えながら進めていったらd8では標準入力が使えないのもあって断念。
あと、この手のはcoffee-script.jsを一緒に持って歩く必要があって、なんか僕の趣味に合わなかったので(どんな趣味だよ)、実行モジュール1個だけで動いて、LinuxでもWindowsでも動いて、極力node(v8)と挙動の近い物が欲しくなった。

こうなれば...と思ってv8使ってC++だけで書いてみた。
mattn/coffee-script-v8 - GitHub

CoffeeScript compiler/runner

https://github.com/mattn/coffee-script-v8
coffee-script.jsのコードはソース埋め込みなので、実行モジュール1つあれば動きます。
v8は普通にコンパイルするとスタティックリンクなので、実行モジュールだけの依存になります。
Windowsだとコンパイルは
g++ -o coffee coffee.cc -lv8 -lwinmm -lws2_32
こんな感じ。他の環境だと
g++ -o coffee coffee.cc -lv8
で行けると思います。
「いや、coffee-script.jsのバージョン上がったらどないすんねん!」って人は、どうぞ改造して使って下さい!
Posted at 16:40 in ソフトウェア::lang::coffee
Tagged as: c++, coffeescript, v8, windows
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2011/03/25


このエントリーをはてなブックマークに追加
V8 JavaScript Engine 最近どのブログでも node.js ばかりでもう飽き飽きしてる皆さんこんばんわ。
node.js 面白いですよね!ェ

node.js ってアプリケーションを作る側(つまりライブラリを使う側)からすると、Web周りの便利なライブラリが既に色々あって、そのライブラリが一体どうやって動いてるのか気にすることってあんまり無いかと思います。

pure javascriptな物ならばコードを読むのは簡単です。ただしやれる事に限りがあります。node.js はGoogle製のJavaScript Engineであるv8をベースに作られているのですが、このv8はアプリケーションに組み込むのに適した構成になっていて、関数テンプレートやインスタンス、プロトタイプという各機能がC++のクラステンプレートで上手く表現出来ているライブラリです。Spidermonkeyも確かに扱うのは簡単なのですが、僕からすると若干C言語臭いというか、JavaScriptとC++との繋ぎが泥臭い感じがしています。
実際、Spidermonkeyを使ったembedがどの様になるかは先日phantomjsのGTK+版であるspecterjsを書いた時のコードを見てもらえると分かるかと思いますが、C言語からJavaScriptの型変換がいかにもフィルタ関数ぽく見えてしまいます。

今日はその辺りを上手く見せているv8で、どうやってembedを行うかを紹介して見ようと思います。

以下の手順にそっていけば、きっと貴方もv8で自作の埋め込みオブジェクトを実装出来る様になるでしょう。

まずv8をインストールしましょう。
v8 - V8 JavaScript Engine - Google Project Hosting

V8 JavaScript EngineV8 is Google's open source JavaScript engine.

http://code.google.com/p/v8/
subversionで以下の様にコードを取得します。
# svn checkout http://v8.googlecode.com/svn/trunk/ v8
ビルドにはSConsが必要です。ubuntuならばaptで、windowsならばオフィシャルからインストーラをダウンロードしてインストールするのが無難かと思います。
SCons: A software construction tool

What makes SCons better? Configuration files are Python scripts--use the power of a real programming...

http://www.scons.org/
参考にするのにまず最初にsamples/shell.ccを見るのをお勧めします。
JavaScriptの対話コンソールプログラムなのですが、グローバルやコンテキスト、関数wrap、スクリプト実行のやり方が分かるかと思います。
コンパイルは
# scons sample=shell
を実行します。
ここにどうやって自分のオブジェクトを埋め込んでいくかが今回の本筋です。
まず、適当にクラスを作ります。これはv8には依存させません。
class FooImpl {
private:
  std::string value;
public:
  FooImpl() {}
  ~FooImpl() {}
  void SetValue(const std::string v) {
    this->value = v;
  }
  const std::string GetValue() const {
    return this->value;
  }
  void ConcatValue(const std::string v) {
    this->value += v;
  }
};
簡単な文字列を内包するクラスです。GetValue()/SetValue()というアクセサ、ConcatValue()というメソッドを持っています。
これをv8からFooというクラスで表現します。ベースは先ほどのshellに埋め込みます。
まずクラステンプレートを作ります。JavaScriptなのでクラスとは言えど関数テンプレートでしかありません。
// Make class template
v8::Local<v8::FunctionTemplate> clazz = v8::FunctionTemplate::New(Foo_Construct);
clazz->SetClassName(v8::String::New("Foo"));
ここでNewに渡しているFoo_Constructが、v8上で
var f = new Foo();
と実行した際に呼ばれるコールバック関数です(関数名は分かりやすくしているだけなのでより良い関数名を付けた方がいいでしょう)。こちらは後で説明します。
次にインスタンステンプレートです。インスタンス化された際の形状を定義します。
// Make instance template
v8::Local<v8::ObjectTemplate> instanceTmpl = clazz->InstanceTemplate();
instanceTmpl->SetInternalFieldCount(1); // Store internal
instanceTmpl->SetAccessor(v8::String::New("value"), Foo_getValue, Foo_setValue); // Accessor
SetInternalFieldCountは何を意味しているかというと、このクラスに内部フィールドを幾つ持たせられるかを教えています。そうです。FooImplのインスタンスですね。
SetAccessorはアクセサ名とゲッタ・セッタ(Foo_getValue()/Foo_setValue())を渡してあげます。こちらも先ほどのFoo_Constructと同様にコールバック関数になっています。これについても後で説明します。
そしてプロトタイプテンプレートを作ります。
// Make method
v8::Local<v8::ObjectTemplate> prototypeTmpl = clazz->PrototypeTemplate();
prototypeTmpl->Set("concat", v8::FunctionTemplate::New(Foo_concat));
global->Set(v8::String::New("Foo"), clazz);
インスタンスとプロトタイプがC++上でも綺麗に色分けされていますね。ここではconcatというメソッドを実装しています。これまでと同様にFoo_concatというコールバック関数を指定します。
最後にglobalに対してFooという名前で関数テンプレートを登録します。
v8との繋ぎ目は以上で完了です。

さて、個々のコールバックを実装して行きましょう。まずコンストラクタ。
static v8::Handle<v8::Value> Foo_Construct(const v8::Arguments& args) {
  FooImpl* foo = new FooImpl();
  printf("construct Foo: %p\n", foo);
  v8::Local<v8::Object> thisObject = args.This();
  thisObject->SetInternalField(0, v8::External::New(foo));
  v8::Persistent<v8::Object> holder = v8::Persistent<v8::Object>::New(thisObject);
  holder.MakeWeak(foo, Foo_Dispose);
  return thisObject;
}
FooImplのインスタンスをSetInternalFieldで設定しています。
このthisをargsから取る辺りも、JavaScriptに似せてあって良いですね。ちなみにArguments::Callee()も存在します。
さて、ここでv8::Persistent::MakeWeak()を呼び出していますね。これはこのインスタンスを保持する予定のホールダーを意味していて、そのホールダーに対してインスタンスが弱参照である事を教えています。またその際、デストラクタをコールバックとして指定しています。
ではデストラクタを実装しましょう。
static void Foo_Dispose(v8::Persistent<v8::Value> handle, void* parameter) {
  FooImpl* foo = static_cast<FooImpl*>(parameter);
  printf("destruct Foo: %p\n", foo);
  delete foo;
  handle.Dispose();
}
MakeWeak呼び出しの第一引数がparameterで渡ってきますので、FooImplのインスタンスを得ます。ここでは単にdeleteしていますが、特別な後処理があるならばここで実装します。
同様の手順でアクセサコールバックを実装します。
static v8::Handle<v8::Value> Foo_getValue(v8::Local<v8::String> propertyName, const v8::AccessorInfo& info) {
  FooImpl* foo = static_cast<FooImpl*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value());
  return v8::String::New(foo->GetValue().c_str());
}

static void Foo_setValue(v8::Local<v8::String> propertyName, v8::Local<v8::Value> value, const v8::AccessorInfo& info) {
  FooImpl* foo = static_cast<FooImpl*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value());
  v8::String::Utf8Value utf8str(value);
  foo->SetValue(*utf8str);
}
GetInternalFieldからコンストラクタで登録していたFooImplのインスタンスを取得します。中身は普通のC++の処理です。
ここまで来るとconcatメソッドも同じですね。
static v8::Handle<v8::Value> Foo_concat(const v8::Arguments& args) {
  FooImpl* foo = static_cast<FooImpl*>(v8::Local<v8::External>::Cast(args.This()->GetInternalField(0))->Value());
  v8::String::Utf8Value utf8str(args[0]);
  foo->ConcatValue(*utf8str);
  return v8::Undefined();
}
第一引数をStringとしてFooImpl::concatを呼び出しています。

以上です。ただこのshellはコンソールから文字列を読み取って実行するループが行われていますのでv8からすると常にbussy状態になっています。共有ライブラリとしてビルド(USING_V8_SHAREDを指定してビルド)していない限りはメインスレッド上で実行されてしまいます。つまりオブジェクトの生死管理が実行されません。なおUSING_V8_SHAREDを指定してビルドされていれば
# ./shell --isolate --shell
として実行すれば別スレッドとして実行されます。
以前は
v8::internal::Heap::CollectAllGarbage();
という関数で強制GCを実施出来ましたが、最近ヘッダファイルから無くなりました。これはおそらく、スレッドが入り乱れても動作しなければならないv8上ではGCのタイミングはユーザが決めるべきではないとの判断だと思っています。その代わりにv8の開発ML上では以下のコードがFAQの様に良く出てきます。
while (!v8::V8::IdleNotification());
v8::V8::IdleNotificationの関数コメントには以下の様に書いてあります。
  /**
   * Optional notification that the embedder is idle.
   * V8 uses the notification to reduce memory footprint.
   * This call can be used repeatedly if the embedder remains idle.
   * Returns true if the embedder should stop calling IdleNotification
   * until real work has been done.  This indicates that V8 has done
   * as much cleanup as it will be able to do.
   */
  static bool IdleNotification();
訳すと
組み込み側がアイドル状態であることを意味するオプションの通知。
V8 はメモリの証跡を減らす為に通知を使用します。
これは、組み込み側がアイドル状態である間は何度でも呼び出す事が出来ます。
実際の処理が完了するまでの間、組み込み側が IdleNotification を呼び出すべきでない場合には true を返します。
これは V8 が可能な限り多くのクリーンアップを、この時点で行っていた事を意味します。
なのでGCの動作確認を行うのであればshellのRunShellに以下の行を入れる事になります。
// The read-eval-execute loop of the shell.
void RunShell(v8::Handle<v8::Context> context) {
  printf("V8 version %s\n", v8::V8::GetVersion());
  static const int kBufferSize = 256;
  // Enter the execution environment before evaluating any code.
  v8::Context::Scope context_scope(context);
  while (true) {
    char buffer[kBufferSize];
    printf("> ");
    char* str = fgets(buffer, kBufferSize, stdin);
    if (str == NULLbreak;
    v8::HandleScope handle_scope;
    ExecuteString(v8::String::New(str),
                  v8::String::New("(shell)"),
                  true,
                  true);
    // Notify idle
    while (!v8::V8::IdleNotification());
  }
  printf("\n");
}
さぁ実行しましょう。
# ./shell 
V8 version 3.2.4.1
> var a = new Foo
construct Foo: 0x9526c98
> a.value

> a.value = "foo"
foo
> a.concat("bar")
> a.value
foobar
> a = null
null
destruct Foo: 0x9526c98

ちゃんと動いてますね。デストラクタもきちんと走ってます。
ここまでのコードを以下のGistに貼り付けておきます。
mattn's gist: 885688 — Gist

shell.cc

https://gist.github.com/885688
以上が v8 を使った基本的な embed となります。どうでしょうか?貴方も作れそうな気がしませんか?

ぜひチャレンジしてみて下さい。なお、だいぶ昔ですが v8 から twitter や wassr を操作出来る shell の改良物を作った事があったので参考URLとして記しておきます。
/lang/cplusplus/twitter-v8 – CodeRepos::Share – Trac

twitter-v8

http://coderepos.org/share/browser/lang/cplusplus/twitter-v8
面白い物が出来たらぜひ公開してみて下さい。
Posted at 04:46 in ソフトウェア::lang::c
Tagged as: c++, v8
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2008/09/11


このエントリーをはてなブックマークに追加
tokuhiromさん作のfcgi-v8をCGIインタプリタとして、cho45さん作のblosxom.rhinoを改造してblosxom.v8というのを作ってみた。
v8にはunevalが無かったのでdankogaiさん作のuneval.jsを使わせて頂いた。
実行画面はblosxom.rhinoの書き換えなので、パクリ気味ですが...
blosxom-v8
fcgi-v8でファイル日付の取得が実装出来ていないので、今は日付は表示されていません。
blosxom.v8
よかったら遊んでみて下さい。
Posted at 18:00 in ソフトウェア::lang::javascript
Tagged as: blosxom, javascript, v8
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2008/09/05


このエントリーをはてなブックマークに追加
shell.ccから改造したから分からなくなったんだよね。
v8::Objectに内部保持するnativeな値(例えばハンドルとか)を保持するには
FILE* fp = fopen(...);
v8::Local<v8::Object> obj = v8::Object::New();
obj->Set(v8::String::New("value"), v8::External::New((void*)fp));
という様に、v8::Externalでメンバを追加してやれば良い。
ただ分からなくなってた原因としては、shell.ccが持ってるPrintという関数が
v8::Handle<v8::Value> Print(const v8::Arguments& args) {
  bool first = true;
  for (int i = 0; i < args.Length(); i++) {
    v8::HandleScope handle_scope;
    if (first) {
      first = false;
    } else {
      printf(" ");
    }
    v8::String::AsciiValue str(args[i]);
    printf("%s", *str);
  }
  printf("\n");
  return v8::Undefined();
}
となってて、何でも文字列化してくれる様に思えてた。で実際
var obj = test();
print(obj.value); // ここでクラッシュ
こんな事やると落ちてしまうんだけど、良く考えたらExternalって言ってるんだから値の出しようもない。確かに以下の様なコードをshell.ccに含ませ
global->Set(v8::String::New("hoge"), v8::External::New(NULL));
javascriptから"print(hoge)"で例外を発生させた際のデバッグ出力で確かめたら
==== Stack trace ============================================

(No security context)
    1: DefaultString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#)
(No security context)
    2: ToString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#)
    6: arguments adaptor frame: 1->0
Security context: 00E006A1 <FixedArray[41]>#2#
    7: /* anonymous */(this=00C10361 <JS Global Object>#3#)

==== Details ================================================

(No security context)
[1]: DefaultString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#) {
  // stack-allocated locals
  var v = 01000135 <undefined>
  var s = 01000135 <undefined>
  // expression stack (top to bottom)
  [04] : 01000361 <String[8]: toString>
  [03] : 00E0068D <Proxy>#1#
  [02] : 00E0068D <Proxy>#1#
--------- s o u r c e   c o d e ---------
function DefaultString(x) {?  if ((typeof(x.toString) === 'function')) {?    var s = x.toString();?    if (%IsPrimitive(s)) return s;?  }?  if ((typeof(x.valueOf) === 'function')) {?    var v = x.valueOf();?    if (%IsPrimitive(v)) return v;?  }?  throw %MakeTypeError('cannot_convert_to_primitive', []);?}
-----------------------------------------
}

(No security context)
[2]: ToString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#) {
  // expression stack (top to bottom)
  [02] : 01002845 <String[13]: DefaultString>
  [01] : 00C104BD <JS Object>#0#
  [00] : 0100232D <String[8]: ToString>
--------- s o u r c e   c o d e ---------
function ToString(x) {?  if ((typeof(x) === 'string')) return x;?  if ((typeof(x) === 'number')) return %NumberToString(x);?  if ((typeof(x) === 'boolean')) return x ? 'true' : 'false';?  if ((typeof(x) === 'undefined')) return 'undefined';?  return ((x === null)) ? 'null' : %ToString(%DefaultString(x));?}
-----------------------------------------
とNULL参照している事が分かる。v8からすると"訳分かんない物をToStringして表示しようとしただけさ。"と言ったところか。
つまりはv8::Externalの使い方が悪いわけではなく、v8::Externalを表示しようとしてたPrint関数がマズイのだ。さらに元々Externalなんか扱ってなかったshell.ccにExternalを埋め込んだ犯人が悪い。そう...私。
言ってしまえばExternal組み込んだんだから、Printもちゃんと修正しろよって事ですね。
if (argv[0]->IsExternal()) {
    printf("[native code]");
    continue;
}
よくブラウザなんかで見かける"[native code]"という表示になる様にしました。
この修正は「Google の JavaScript エンジン v8 で FastCGI する - TokuLog 改めB日記」での成果物"fcgi-v8"にも組み込んであります。
ただ、Extenalに保持する様な値というのは、ハンドルとかメモリ確保した領域だとか、状態を保持する様な物のはずなので、実際には確保したポインタ等のリストをグローバルな領域にを持っておき、終了時に解放するかPersistentなObjectTemplateでインスタンスを作り、WeakReferenceCallbackにて解放するのがベターという事になる。
Externalを使う側からすると、そこに何が入っててもおかしい訳でもなく、v8として取り決めもない(引数の型はvoidポインタ)。よって複数の種類を格納したい場合は種類も判別出来る物、例えば
typedef enum _OBJECT_TYPE {
    OBJECT_TYPE_FILE_POINTER, // FILE*
    OBJECT_TYPE_WINDOW_HANDLE // ウィンドウハンドル
} OBJECT_TYPE;

typedef struct _OBJECT_VALUE {
    OBJECT_TYPE type;
    void* object;
} OBJECT_VALUE;

// 設定
OBJECT_VALUE* val = new OBJECT_VALUE;
val->type = OBJECT_TYPE_FILE_POINTER;
val->object = fopen("test.dat", "rb");
obj->Set(v8::String::New("value"), v8::External::New((void*)val));

// 取得
v8::Local<v8::Value> valueobj = obj->Get(v8::String::New("value"));
if (!valueobj->IsExternal()) return v8::ThrowException(v8::String::New("Exception: invalid object"));
OBJECT_VALUE* val = static_cast< OBJECT_VALUE* >(v8::Handle<v8::External>::Cast(valueobj)->Value());
if (val->type == OBJECT_TYPE_FILE_POINTER) {
    // FILE*な処理
} else
if (val->type == OBJECT_TYPE_WINDOW_HANDLE) {
    // ウィンドウハンドルな処理
}
こんな構造と設定および取得をすべき、という事になる。ここまで気付くのに結構時間を使ってしまった。

さてこのPrint関数ですが、↑のリンクにある"fcgi-v8"には「Big Sky :: Google Chromeが使っているjavascript v8エンジンにTwitterにアクセス出来るクラスを作ってポストする。」でご紹介したtwitter-v8とは違う方法でPrintを実装してあります。
twitter-v8の方はコンソールに出力する物なのでsetlocaleとワイドキャラクタ出力(%S)でOKですが、fcgi-v8の場合はUCSからUTF-8への変換を組み込んであります。一応v8のプロジェクトサイトでIssue 17 : 「Add Strign::WriteUtf8」としてリクエストされているので、もしかすると今後このコードは必要なくなるかもしれませんね。
v8エンジンは高速な上、今まで私が見た中で一番組み込みやすい代物になっているかと思います。皆さんもv8使った面白いもの作ってみませんか。

twitter-v8、fcgi-v8それぞれ参加者をお待ちしております。
Posted at 16:57 in ソフトウェア::lang::javascript
Tagged as: javascript, v8
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip