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 by



2008/09/03


v8エンジン盛り上がってますね。
さて、今日はv8エンジンのshell.ccを弄って、シェル内部にTwitterオブジェクトを作ってみました。とはいっても、仕組みは簡単。FunctionTemplateから得たPrototypeTemplateにメソッドを追加、さらにInstanceTemplateにプロパティusernameとpasswordを足しているだけです。
  v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New();
  v8::Local<v8::Template> p = t->PrototypeTemplate();
  p->Set("friendsTimeline", v8::FunctionTemplate::New(TwitterFriendsTimeline));
  p->Set("updateStatus", v8::FunctionTemplate::New(TwitterUpdateStatus));
  v8::Local<v8::ObjectTemplate> i = t->InstanceTemplate();
  i->Set(v8::String::New("username"), v8::String::New(""));
  i->Set(v8::String::New("password"), v8::String::New(""));

  global->Set(v8::String::New("Twitter"), t);
このTwitterFriendsTimelineには以下の様なコードを...
v8::Handle<v8::Value> TwitterFriendsTimeline(const v8::Arguments& args) {
  v8::Handle<v8::Value> ret = v8::Undefined();
  v8::Local<v8::Object> This = args.This();
  v8::HandleScope handle_scope;
  v8::TryCatch try_catch;

  v8::String::AsciiValue username(This->Get(v8::String::New("username")));
  v8::String::AsciiValue password(This->Get(v8::String::New("password")));

  CURL* curl = NULL;
  CURLcode res = CURLE_OK;
  char *auth;

  response_data = NULL;
  response_size = 0;
  curl = curl_easy_init();
  if (!curl) return v8::ThrowException(v8::String::New("Error: unknown"));

  int n = strlen(*username) + strlen(*password);
  auth = (char*) malloc(n + 2);
  memset(auth, 0, n + 2);
  strcpy(auth, *username);
  strcat(auth, ":");
  strcat(auth, *password);

  curl_easy_setopt(curl, CURLOPT_URL, "http://twitter.com/statuses/friends_timeline.json");
  curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handle_returned_data);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
  res = curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  free(auth);

  if (res == CURLE_OK) {
    char* json = NULL;
    json = (char*) malloc(response_size+1);
    memset(json, 0, response_size+1);
    memcpy(json, (char*) response_data, response_size);
    v8::Handle<v8::String> source = v8::String::New(json);
    free(json);

    v8::Handle<v8::Script> script = v8::Script::Compile(source);
    if (script.IsEmpty()) {
        ret = v8::ThrowException(v8::String::New("Error: unknown"));
        goto leave;
    }
    v8::Handle<v8::Value> result = script->Run();
    if (script.IsEmpty()) {
        ret = v8::ThrowException(v8::String::New("Error: unknown"));
        goto leave;
    }
    if (!result->IsArray()) {
        ret = v8::ThrowException(v8::String::New("Error: unknown"));
        goto leave;
    }
    ret = result;
  }

leave:
  if (response_data) free(response_data);
  response_data = NULL;
  return ret;
}
さらにTwitterUpdateStatusには v8::Handle<v8::Value> TwitterUpdateStatus(const v8::Arguments& args) {
  v8::Handle<v8::Value> ret = v8::Undefined();
  v8::Local<v8::Object> This = args.This();
  v8::HandleScope handle_scope;
  v8::TryCatch try_catch;

  if (args.Length() != 1) return v8::ThrowException(v8::String::New("Error: usage(message)"));

  v8::String::AsciiValue username(This->Get(v8::String::New("username")));
  v8::String::AsciiValue password(This->Get(v8::String::New("password")));

  v8::Handle<v8::Object> global = v8::Context::GetCurrent()->Global();
  v8::Local<v8::Function> func = v8::Function::Cast(*global->Get(v8::String::New("encodeURIComponent")));
  v8::Local<v8::Value> funcargs[1];
  funcargs[0] = args[0]->ToString();
  v8::Local<v8::Value> result_val = func->Call(global, 1, funcargs);
  v8::String::AsciiValue escaped(result_val->ToString());

  char* status = url_encode_alloc(*escaped);

  CURL* curl = NULL;
  CURLcode res = CURLE_OK;
  char *auth;
  char url[2048];

  response_data = NULL;
  response_size = 0;
  curl = curl_easy_init();
  if (!curl) return v8::ThrowException(v8::String::New("Error: unknown"));

  memset(url, 0, sizeof(url));
  strncpy(url, "http://twitter.com/statuses/update.xml", sizeof(url)-1);
  strncat(url, "?status=", sizeof(url)-1);;
  strncat(url, status, sizeof(url)-1);
  free(status);

  int n = strlen(*username) + strlen(*password);
  auth = (char*) malloc(n + 2);
  memset(auth, 0, n + 2);
  strcpy(auth, *username);
  strcat(auth, ":");
  strcat(auth, *password);

  curl_easy_setopt(curl, CURLOPT_URL, url);
  curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handle_returned_data);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
  curl_easy_setopt(curl, CURLOPT_POST, 1);
  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
  res = curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  free(auth);

  if (res != CURLE_OK) {
    ret = v8::ThrowException(v8::String::New("Error: unknown"));
  }
  if (response_data) free(response_data);
  response_data = NULL;
  return ret;
}
これだけ準備出来れば、あとは var twitter = new Twitter()
twitter.username = "my-username"
twitter.password = "my-password"
var statuses = twitter.friendsTimeline();
for (var n = 0; n < statuses.length; n++) {
    print(statuses[n].user.screen_name, ":", statuses[n].text);
}
twitter.updateStatus("v8エンジン12気筒");
こんなスクリプト書いて # shell twitter.js とかやればfollowerのステータスが出た後でメッセージがポストされます。
簡単ですね。
ちなみに既存のPrintではAsciiValueを使いますが、AsciiValueではUTF-8が8bit落ちするのでsetlocaleを使ってワイドキャラでprintfする様修正しています。
コードはcodereposのこの辺に置いときます。汚いソースですが、よろしければ参考まで...
Posted at by




scons入れてwin32でビルドしてみたよ。ベンチマークも取ってみた。
環境はWindows XP、P4 3GHz CPU、1G memで。

まずmozillaのjsエンジン(ちょっと昔)
JavaScript-C 1.7.0 2007-10-03 (js.exe)
Richards: 87
DeltaBlue: 84
Crypto: 44
RayTrace: 78
EarleyBoyer: 87
----
Score: 74

次にrhino
Rhino 1.6 release 7 (win32 nativeビルド版)
Richards: 6
DeltaBlue: 5
Crypto: 4
RayTrace: 11
EarleyBoyer: 12
----
Score: 7

Rhino 1.7 release 7 2008 03 06(java版 js-14.jar)
Richards: 30
DeltaBlue: 30
Crypto: 24
RayTrace: 47
EarleyBoyer: 53
----
Score: 35

そしてv8エンジン
V8 version 0.2.5 (shell.exe)
Richards: 1020
DeltaBlue: 981
Crypto: 622
RayTrace: 580
EarleyBoyer: 1070
----
Score: 827
はぇーーーーーーーーーーーーーーー!(゚д゚;)

ベンチマークはbenckmarksフォルダにあるrun.jsで起動します。

追記
取り直したらこんな結果もでた
Richards: 1391
DeltaBlue: 1224
Crypto: 940
RayTrace: 774
EarleyBoyer: 1360
----
Score: 1110
もっと詳細が知りたい方は以下を参照。
Google V8 についてしらべてみました - TokuLog 改めB日記
Posted at by