枕を欹てて聴く

香炉峰の雪は簾を撥げて看る

embed V8

embed V8がちょう楽しいので.
最近JSの処理系のソースコードを見るのが個人的な楽しみだったのだけれど, embed V8が思ってたよりもずっと手軽に処理系を触れると知って...

原文
Embedder's Guide - Chrome V8 — Google Developers
id:edvakf さんによる邦訳. 素晴らしく, 読むとわくわくしてきます.
Embedder's Guide - V8 JavaScript Engine - JavaScriptで遊ぶよ - g:javascript

で, V8の該当ソース読み, C++で動かしてみて, id:edvakfさんの最後に言ってる疑問が確証もとれたので.

#include <v8.h>
#include <stdio.h>

using namespace v8;

static Handle<Value>
acGetsGetter(Local<String> propName, const AccessorInfo& info)
{
  HandleScope scope;
  static char buffer[1024];
  Local<Object> self = info.This();
  Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
  void * ptr = wrap->Value();
  FILE* fh = reinterpret_cast<FILE*>(ptr);
  if(!fgets(buffer, 1024, fh)){
    return Undefined();
  }
  printf("open func in proto : %s\n",
         (info.Holder()->HasRealNamedProperty(String::New("open")))? "true" : "false");
  printf("open func in this  : %s\n",
         (info.This()->HasRealNamedProperty(String::New("open")))? "true" : "false");
  printf("holder === this : %s\n", (info.Holder() == info.This())? "true": "false");

  return String::New(buffer);
}

static Handle<Value>
printLine(const Arguments& args)
{
  HandleScope scope;
  if(args.Length() != 1)
    return ThrowException(String::New("usage: print(str)"));
  String::AsciiValue str(args[0]);
  printf("%s\n", *str);
  return True();
}

static Handle<Value>
returnThis(const Arguments& args)
{
  return args.This();
}

static Handle<Value>
fileOpen(const Arguments& args)
{
  HandleScope scope;

  if (args.Length () != 2)
      return ThrowException(String::New("usage: open(fname, mode)"));
  String::AsciiValue fname(args[0]);
  String::AsciiValue mode(args[1]);

  FILE *fh = fopen(*fname, *mode);
  if (! fh)
      return ThrowException(String::New("cannot open file"));

  args.This()->SetInternalField(0, External::New(fh));

  return args.This();
}

static Handle<Value>
fileClose(const Arguments& args)
{
  HandleScope scope;

  Local<Value> intl_field = args.This()->GetInternalField(0);
  FILE *fh
      = reinterpret_cast<FILE *>(Handle<External>::Cast(intl_field)->Value());

  fclose(fh);

  return True();
}

static Handle<Value>
fileGets(const Arguments& args)
{
  HandleScope scope;
  static char buffer[1024];

  Local<Value> intl_field = args.This()->GetInternalField(0);
  FILE *fh
      = reinterpret_cast<FILE *>(Handle<External>::Cast(intl_field)->Value());

  if (! fgets(buffer, 1024, fh)) {
      return Undefined();
  }

  return String::New(buffer);
}

int main(int argc, char *argv[])
{
  HandleScope scope;
  TryCatch try_catch;

  Handle<ObjectTemplate> global = ObjectTemplate::New();

  Handle<FunctionTemplate> ft = FunctionTemplate::New();
  Handle<ObjectTemplate>   pt = ft->PrototypeTemplate();
  Handle<ObjectTemplate>   ot = ft->InstanceTemplate();

  pt->Set(String::New("open"), FunctionTemplate::New(fileOpen));
  pt->Set(String::New("close"), FunctionTemplate::New(fileClose));
  pt->Set(String::New("gets"), FunctionTemplate::New(fileGets));
  pt->Set(String::New("returnThis"), FunctionTemplate::New(returnThis));

  // Accessor
  pt->SetAccessor(String::New("acGets"),
                  acGetsGetter);

  ot->SetInternalFieldCount(1);

  global->Set(String::New("print"), FunctionTemplate::New(printLine));
  global->Set(String::New("File"),
              ft,
              PropertyAttribute(ReadOnly | DontDelete));

  Handle<Context> context = Context::New(NULL, global);

  Context::Scope context_scope(context);


  Handle<String> source = String::New(
      "print('ok');\n"
      "var file = new File();\n"
      "print(file === file.returnThis());\n"
      "file.open('test2.cc', 'r')\n"
      "print(file.acGets);\n"
      "print(file.gets());\n"
      "file.close();\n"
  );

  Handle<Script> compiled = Script::Compile(source, Undefined());

  if(compiled.IsEmpty()){
    String::AsciiValue error(try_catch.Exception());

    fprintf(stderr, "compile error: %s\n", *error);
  } else {
    Handle<Value> result = compiled->Run();
    if(result.IsEmpty()){
      String::AsciiValue error(try_catch.Exception());
      fprintf(stderr, "runtime error: %s\n", *error);
    }
  }
  return 0;
}

JS部分は,

print('ok');
var file = new File();
print(file === file.returnThis());
file.open('test2.cc', 'r')
print(file.acGets);
print(file.gets());
file.close();

ですね. ソースはV8 (Google JavaScript Engine) を embed した感想とかあれこれ - daily dayflowerを参考にしました. 今回はとりあえず試運転的なので, 引数の型や値チェック, エラー処理が適当なのはご愛嬌で.

これの実行中に, 念願のHolderとThisが異なるという場合を見ることができます. まあ簡単に言えば,

function File(){
}
File.prototype.test = function File_test(){
  console.log("holder: "+File.prototype);
  console.log("this  : "+this);
}

ということですね. JSのようにGCがなくて, ClosureもできないC++では, thisだけでは不十分だからどっかにprototype objectへの参照も引数に渡し続けてるだろうと察しをつけていたのですが, やはりそういうことだったようです. Holderはその関数をもともとsetしたobjectですね. File_test関数をHoldしているobjectであるFile.prototypeと, Fileのinstanceであるthisの違いです.

v8のembedder用のソースでHolderを使っているのは, InstanceTemplateに追加したものだからです. このとき, Holderの値は, 当然InstanceTemplateがInstantiateされた値, すなわちThisと等しくなります.
これもjavascriptで言えば,

function File(){
  var that = this;
  that.test = function File_test(){
    console.log("holder: "+that);
    console.log("this: "  +this);
  }
}

ということですね. that === thisなのも納得です.

じゃあSet関数の時, 中でprototypeに定義してある値が欲しけりゃどうするんだって話になるのですが, 普通にThisからアクセスしたらHas関数なんかを見ればたどってくれるので, JSで出来る範囲はC++でもできるわけですね. Thisからchainをたどって取って来いってことでしょう.

いろいろ浅いので, 間違っていたらぜひ指摘していただけると, 勉強になってすごいありがたいです.

補足

V8さわるのすごいうきうきするので, 是非. ソース読んでるだけよりなんかいろいろ気分が上向きます.
JSは関数がfirst classで, prototypeといっても, 実態は関数をpropertyに持つobjectなので, functionはargumentsが一つ渡ってくるだけの簡素なつくりに対し, __lookupGetter__とかみたいな普通あんまりしない処理を経ないと手に入れることができないgetter / setterはprototypeやthisにより密な結合になっているのかと思った. しかしここらへんは推論.

V8のbuildがはじめ通りませんでした. なんでやー, 昔通ったやないかーとか思って検索すると, g++のversionの変化に伴うerrorの厳しさ向上が原因のようでした.
Issue 463 - v8 - Can't build due to violations of strict-aliasing rules - V8 JavaScript Engine - Google Project Hosting
debian sid だとg++がばっちり4.4で引っかかりました. 結論としては,

env GCC_VERSION="44" scons

とすると大丈夫です.


激しく個人的な感想としては, JavaScriptCoreが一番きれいに書いてある気がする. 読みやすいー.

Remove all ads