hiphop php でPHPからジェネレートされたC++コードを読んでみよう。 (後編)

PHP PHP advent calendar です。
はてなダイアリーの投稿時の文字列制限により、前編と後編になってしまいました。
前編に続きhiphop phpPHPからジェネレートされたC++コードを読んでみようの後編を書きます。

forとwhile文

ループを見て行きましょう。

<?php
$for_sum = 0;
for($a = 0 ; $a <= 10 ; $a ++)
{
    $for_sum += $a;
}

$while_sum = 0;
$a = 10;
while($a)
{
   $while_sum += $a;
   $a --;
}

var_dump($for_sum,$while_sum);


php/test20.cpp

Variant pm_php$test20_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test20.php, pm_php$test20_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_for_sum ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss8818e723, "for_sum")) : g->GV(for_sum);
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);
  Variant &v_while_sum ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssf07d0c82, "while_sum")) : g->GV(while_sum);

  v_for_sum = 0LL;
  {
    LOOP_COUNTER(1);
    for (v_a = 0LL; (not_more(v_a, 10LL)); v_a++) {
      LOOP_COUNTER_CHECK(1);
      {
        v_for_sum += v_a;
      }
    }
  }
  v_while_sum = 0LL;
  v_a = 10LL;
  LOOP_COUNTER(2);
  {
    while (toBoolean(v_a)) {
      LOOP_COUNTER_CHECK(2);
      {
        v_while_sum += v_a;
        v_a--;
      }
    }
  }
  LINE(17,(x_var_dump(2, v_for_sum, Array(array_createvi(1, toVPOD(v_while_sum))))));
  return true;
}

ふつーに forループ、 whileループに変換してくれました。
ここまで、ふつーにやってくれると、ループカウンタの v_a が Variant型なのが残念ですね。
ループカウンタが int型しか取らないことはソースを見ればわかるので、int型にして欲しかったところです。
それと、 for (v_a = 0LL; (not_more(v_a, 10LL)); v_a++) の v_a++ は intなどのスカラー値じゃないんだから、C++では ++v_a にした方が早いと思うんですけど、どうなんでしょう?


ここも、まだまだ早くなる可能性があると思っておきましょう。
特にループカウンタは、チューニングの効果は絶大なはずで、今後に期待したいところです。




さて、ソースコードの中に、LOOP_COUNTER と LOOP_COUNTER_CHECK と いう見慣れないマクロがあります。
これは何をしているのでしょうか?

runtime/base/macros.h

#ifdef INFINITE_LOOP_DETECTION
#define LOOP_COUNTER(n)
#define LOOP_COUNTER_CHECK(n)                                           \
  if ((++lc & 1023) == 0) {                                             \
    check_request_timeout_ex(fi, lc);                                   \
  }
#define LOOP_COUNTER_CHECK_INFO(n)                                      \
  if ((++lc & 1023) == 0) {                                             \
    check_request_timeout_info(info, lc);                               \
  }
#else
#define LOOP_COUNTER(n)
#define LOOP_COUNTER_CHECK(n)
#define LOOP_COUNTER_CHECK_INFO(n)
#endif

無限ループを検出する機能っぽいですね。
INFINITE_LOOP_DETECTION は、Cmakefiles でにより、 INFINITE_LOOP_DETECTION=1 となっています。
ディフォルトで無限ループ検出機能が働いています。
逆に言えば、俺のコードで無限ループとかマジあり得ないんですけどって人は、これをOFFにすることにより、さらに高速に動作させることが可能なわけです。
夢が広がりますね。

foreach文

PHPの foreach文は大変便利です。
C++には foreachに該当するループ文はありません。
さて、これはどのように変換されるのでしょうか?

<?php

$arr = array(0,1,2,3,4,5,6,7,8,9);
foreach($arr as $key => $val)
{
    echo "{$key} => {$val}\n";
}
Variant pm_php$test21_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test21.php, pm_php$test21_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_arr ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss7cb08d68, "arr")) : g->GV(arr);
  Variant &v_key ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss12e90587, "key")) : g->GV(key);
  Variant &v_val ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss66e0b782, "val")) : g->GV(val);

  v_arr = s_sa50d82ba2;
  {
    LOOP_COUNTER(1);
    for (ArrayIter iter3 = v_arr.begin(null_string, true); !iter3.end(); ++iter3) {
      LOOP_COUNTER_CHECK(1);
      iter3.second(v_val);
      v_key.assignVal(iter3.first());
      {
        {
          echo(toString(v_key));
          echo(NAMSTR(s_ss8f4bca72, " => "));
          echo(toString(v_val));
          echo(NAMSTR(s_ss66d2232c, "\n"));
        }
      }
    }
  }
  return true;
}

なんか、C++ STL map 、そして iteratorチックな変換になりました。
iteratorなので ループカウンタも ++iter3 と 後置インクリメントにしていますね。ココらへんは C++ の罠というか、わかりづらいところです。
言語名がC++って名前なのに、 前置インクリメント ++C を推奨しなければいけないというが理不尽ですね。
(おっと、C++の悪口はそこまでだ。 闇の軍団に消されるぞ)


変換されたソースコードSTL mapを使ったことがある人だったら、特に不思議な点はないかと思います。

break 2

PHPでは、 break 2などで2層ループをぶち抜いたbreakができます。
たぶん、perlから受け継いだ機能なんでしょうかね。

便利なんですが、 C言語から派生した言語ではあまり見ない構文です。
(どうでもいいんですけど、アスキーのマルチゲームスクリプターって言語にも break 2;がありました。知っている人だけが懐かしむネタ。)

<?php
for($a = 0 ; $a < 10 ; $a ++ )
{
    for($b = 0 ; $b < 10 ; $b ++)
    {
        if ($b == 2)
        {
           break;
        }
        if ($a == 5)
        {
           break 2;
        }
    }
}
var_dump($a,$b);

無理な制御文を使おうとすると、登場するは・・・・

Variant pm_php$test23_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test23.php, pm_php$test23_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);
  Variant &v_b ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssf8576bd5, "b")) : g->GV(b);

  {
    LOOP_COUNTER(1);
    for (v_a = 0LL; (less(v_a, 10LL)); v_a++) {
      LOOP_COUNTER_CHECK(1);
      {
        {
          LOOP_COUNTER(2);
          for (v_b = 0LL; (less(v_b, 10LL)); v_b++) {
            LOOP_COUNTER_CHECK(2);
            {
              if (equal(v_b, 2LL)) {
                {
                  break;
                }
              }
              if (equal(v_a, 5LL)) {
                {
                  goto break1;
                }
              }
            }
          }
        }
      }
    }
    break1:;
  }
  LINE(17,(x_var_dump(2, v_a, Array(array_createvi(1, toVPOD(v_b))))));
  return true;
}

無理な制御文を使おうとすると必ず登場する goto 先生が登場しました。
これは仕方ないですね。


余談ですが、 perlredo; などの命令を PHPC言語に変換する時も gotoを使うという選択肢が上がったりします。
もちろん、使わないでもできはするんですが、余計な構文が増えたりして、いまいちだったりします。

gotoは自由にジャンプできる柔軟性を与えてくれますが、あまりにも強力な機能なので、gotoを弱めたループ制御文を言語がもっていることが望まれます。

switch文

switch文を見ていきます。

<?php

$a = 10;
switch($a)
{
case 10:
   $b = "10";
   break;
case 2:
   $b = "2";
   break;
}

var_dump($b);
Variant pm_php$test29_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test29.php, pm_php$test29_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);
  Variant &v_b ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssf8576bd5, "b")) : g->GV(b);

  v_a = 10LL;
  switch ((v_a).hashForIntSwitch(10LL, 0LL)) {
  case 10LL:
    {
      v_b = NAMSTR(s_ssa4ae39ed, "10");
      break;
    }
  case 2LL:
    {
      v_b = NAMSTR(s_ssebb8aab9, "2");
      break;
    }
  }
  LINE(14,(x_var_dump(1, v_b)));
  return true;
}

これも、ふつーの switch文になりました。
わざわざ hashForIntSwitchメソッドなんてswitch用のメソッドを作る所がニクイですね。



しかし・・・ PHPのswitchにも文字列が使えます。
C++なswitchはC言語から受け継いだ数値や文字などの単純なものしか使えません。
caseに文字列を使うとどうなるのでしょうか?

<?php
$a = 10;
switch($a)
{
case 10:
   $b = "10";
   break;
case "2":
   $b = "2string!";
   break;
}

var_dump($b);

case "2" が文字列になりました。
さて、、、

Variant pm_php$test30_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test30.php, pm_php$test30_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);
  Variant &v_b ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssf8576bd5, "b")) : g->GV(b);

  v_a = 10LL;
  {
    Variant switch2 = (v_a);
    if (equal(switch2, (10LL))) goto case_2_0;
    if (equal(switch2, (NAMSTR(s_ssebb8aab9, "2")))) goto case_2_1;
    goto break1;
  }

  case_2_0:
    {
      v_b = NAMSTR(s_ssa4ae39ed, "10");
      goto break1;
    }
  case_2_1:
    {
      v_b = NAMSTR(s_ss5bb3adb6, "2string!");
      goto break1;
    }
  break1:;
  LINE(14,(x_var_dump(1, v_b)));
  return true;
}

switch文から goto を使う方法に変わりました。
なので、switch文で数字型で済むときは数字で揃えとくと、 hiphopのチューニングが効くみたいですね。
あんまりswitch文使う機会がないかもしれないけど、、、

例外

熱い議論が交わされることが多い例外についてです。
こいつはどのように変換されるのでしょうか?

<?php
function myfunc($a)
{
    throw new Exception("hoe-");
}

try
{
    myfunc(1);
}
catch(Exception $e)
{
    echo "catch!";
}
void f_myfunc(CVarRef v_a) {
  FUNCTION_INJECTION_NOMEM(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(1, toVPOD(v_a)), );
  throw_exception(LINE(5,(p_Exception(((c_Exception*)coo_Exception())->create(NAMVAR(s_svsd792abf7, "hoe-"))))));
}
namespace hphp_impl_splitter {}
Variant ifa_myfunc(void *extra, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(count < 1)) throw_missing_arguments("myfunc", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  return (f_myfunc(arg0), null);
}
Variant i_myfunc(void *extra, CArrRef params) {
  return invoke_func_few_handler(extra, params, &ifa_myfunc);
}
CallInfo ci_myfunc((void*)&i_myfunc, (void*)&ifa_myfunc, 1, 0, 0x0000000000000000LL);
Variant pm_php$test17_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test17.php, pm_php$test17_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_e ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss6b7ea7eb, "e")) : g->GV(e);

  try {
    LINE(10,(f_myfunc(NAMVAR(s_svib794f8ce, 1LL))));
  } catch (Object e) {
    if (e.instanceof(NAMSTR(s_ssae8717ad, "exception"))) {
      v_e = e;
      echo(NAMSTR(s_ss93e46d56, "catch!"));
    } else {
      throw;
    }
  }
  return true;
}

try{} catch{} はふつーにC++言語ネイティブなものになりました。
普段、そんなにtry-catch書かないので、これをネイティブにされてもあんまり恩恵はないんですけど、、 ネイティブ化されることでコンパイラによる最適化が期待されます。
(そもそも、利用者が catch区をがりがり書かないといけないようなライブラリは設計がおかしい。)


あえて重箱の隅をつつくなら、 catch (Object e) で catch (Object& e) と参照になっていないのはなんでだろう?ってことですか。
めったに呼ばれないcatchについてーgdgdいうのもなんてけど。。。



例外を投げる方を見て行きましょう。

throw new Exception("hoe-");
↓
throw_exception(LINE(5,(p_Exception(((c_Exception*)coo_Exception())->create(NAMVAR(s_svsd792abf7, "hoe-"))))));

throw は throw_exception 関数になりました。
runtime/base/builtin_functions.cpp

void throw_exception(CObjRef e) {
  if (!Eval::Debugger::InterruptException(e)) return;
  throw e;
}


一部を除き、ふつーに C++のthrowで例外を投げています。


で、このEval::Debugger::InterruptExceptionって何だろう。
runtime/eval/debugger/debugger.cpp

bool Debugger::InterruptException(CVarRef e) {
  if (RuntimeOption::EnableDebugger) {
    ThreadInfo *ti = ThreadInfo::s_threadInfo.getNoCheck();
    if (ti->m_top && ti->m_reqInjectionData.debugger) {
      Eval::InterruptSite site(ti->m_top, e);
      Eval::Debugger::Interrupt(ExceptionThrown, NULL, &site);
      if (site.isJumping()) {
        return false;
      }
    }
  }
  return true;
}

デバッガをつないだ時の何かなんですかね? よくわかりません。
まだ先は長いので、とりあえず進みましょう。
知っている人は教えてください。

includeするテンプレート

極小な開発を除き、PHPではHTMLなテンプレートをソースの中に埋め込むのではなく、外出ししてテンプレート化するのが流行りです。
includeするテンプレートが C++ にどのように変換されるのか見て行きましょう。

<?php
$a = 10;
include("test24.tpl");
test24.tpl
hello <?php echo $a ?>

今回は、C++に変換されたソースが長くなるのが嫌だったので、このような書き方をしました。
このテンプレートの実装はセキュリティホールを抱えるものになってしまうので、 実際使うときは、htmlspecialchars しましょう。

cppのコードを見る前に変換されたコードのディレクトリの中を見てみます。

test24.cpp
test24.fws.h
test24.h
test24.tpl.nophp.cpp
test24.tpl.nophp.fws.h
test24.tpl.nophp.h

なんか、test24.tpl.nophp.* って奴が増えてますね。


test24.cpp

Variant pm_php$test24_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test24.php, pm_php$test24_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);

  v_a = 10LL;
  LINE(3,(pm_php$test24_tpl(false, variables, g)));
  return true;
}


includeはpm_php$test24_tplという関数呼び出しという形に変わりました。
さて、テンプレートの方はどうなっているのでしょうか?

test24.tpl.nophp.cpp

Variant pm_php$test24_tpl(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test24.tpl, pm_php$test24_tpl);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);

  echo(NAMSTR(s_ss18b6f89c, "hello "));
  echo(toString(v_a));
  echo(NAMSTR(s_ss66d2232c, "\n"));
  return true;
}


見事に、 echoに変更されました。
なんか、javaJSPの変換結果を見ているようです。
想像通りというか、まーこうするしかないので仕方ないですね。

クラス

やっと、クラスです。長かった。
まだ起きている人はいるのでしょうか。

<?php
class myclass
{
   public $privar = 10;
   public $pubvar = 20;
   public function pubfunc($a)
   {
       return $a + $this->privar + $this->pubvar;
   }
}

$class = new myclass();
$a = $class->pubfunc(10);

var_dump($a);


変換されたソースを見ると、 cls というディレクトリが増えています。
この中にラクラスを変換したヘッダーファイルが入ります。

cls/myclass.h
php/test11.cpp
php/test11.fws.h
php/test11.h

cls/myclass.h

/* SRC: test11.php line 3 */
FORWARD_DECLARE_CLASS(myclass);
extern const ObjectStaticCallbacks cw_myclass;
class c_myclass : public ObjectData {
  public:

  // Properties
  int64 m_privar;
  int64 m_pubvar;

  // Class Map
  DECLARE_CLASS_NO_SWEEP(myclass, myclass, ObjectData)
  static const ClassPropTable os_prop_table;
  c_myclass(const ObjectStaticCallbacks *cb = &cw_myclass) : ObjectData(cb, false), m_privar(10LL), m_pubvar(20LL) {}
  public: Numeric t_pubfunc(CVarRef v_a);
  DECLARE_METHOD_INVOKE_HELPERS(pubfunc);
};
ObjectData *coo_myclass() NEVER_INLINE;

ヘッダーファイルだから、定義しか書いてないようですね。
実態の方は、PHPを変換した php/test11.cpp に一緒に書いてあります。


php/test11.cpp

/* preface starts */
extern CallInfo ci_;
/* preface finishes */
/* SRC: test11.php line 3 */
IMPLEMENT_CLASS_NO_DEFAULT_SWEEP(myclass)
const InstanceOfInfo c_myclass::s_instanceof_table[] = {
  {0x124B454009539DD2LL,1,"myclass",&cw_myclass},
};
const int c_myclass::s_instanceof_index[] = {
  1,
  0,-1,
};
CallInfo c_myclass::ci_pubfunc((void*)&c_myclass::i_pubfunc, (void*)&c_myclass::ifa_pubfunc, 1, 4, 0x0000000000000000LL);
Variant c_myclass::i_pubfunc(MethodCallPackage &mcp, CArrRef params) {
  return invoke_meth_few_handler(mcp, params, &ifa_pubfunc);
}
Variant c_myclass::ifa_pubfunc(MethodCallPackage &mcp, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(mcp.obj == 0)) {
    return ObjectData::ifa_dummy(mcp, count, INVOKE_FEW_ARGS_PASS_ARGS, ifa_pubfunc, coo_myclass);
  }
  c_myclass *self ATTRIBUTE_UNUSED (static_cast<c_myclass*>(mcp.obj));
  if (UNLIKELY(count < 1)) throw_missing_arguments("myclass::pubfunc", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  return (self->t_pubfunc(arg0));
}
const MethodCallInfoTable c_myclass::s_call_info_table[] = {
  { 0x4A7B5BE10021188ALL, 1, 7, "pubfunc", &c_myclass::ci_pubfunc },
  { 0, 1, 0, 0 }
};
const int c_myclass::s_call_info_index[] = {
  1,
  0,-1,
};
const ObjectStaticCallbacks cw_myclass = {
  (ObjectData*(*)(ObjectData*))coo_myclass,
  c_myclass::s_call_info_table,c_myclass::s_call_info_index,
  c_myclass::s_instanceof_table,c_myclass::s_instanceof_index,
  &c_myclass::s_class_name,
  &c_myclass::os_prop_table,0,0,0,0x0
};
/* SRC: test11.php line 7 */
Numeric c_myclass::t_pubfunc(CVarRef v_a) {
  INSTANCE_METHOD_INJECTION_ROOTLESS(myclass, myclass::pubfunc);
  INTERCEPT_INJECTION("myclass::pubfunc", array_createvi(1, toVPOD(v_a)), r);
  return ((v_a + m_privar) + m_pubvar);
}
namespace hphp_impl_splitter {}
ObjectData *coo_myclass() {
  return NEWOBJ(c_myclass)();
}
Variant pm_php$test11_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test11.php, pm_php$test11_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_class ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssc82dbd12, "class")) : g->GV(class);
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);

  {
    LINE(13,0);
    const p_myclass &tmp0((p_myclass(((c_myclass*)coo_myclass()))));
    v_class = tmp0;
  }
  {
    LINE(14,0);
    MethodCallPackage mcp0;
    CVarRef obj0 = v_class;
    mcp0.methodCall((obj0), NAMSTR(s_ss758adc76, "pubfunc"), 0x4A7B5BE10021188ALL);
    const CallInfo *cit0 ATTRIBUTE_UNUSED = mcp0.ci;
    Variant tmp1(((mcp0.ci->getMeth1Args())(mcp0, 1, NAMVAR(s_svi542bad8b, 10LL))));
    v_a.assignVal(tmp1);
  }
  LINE(16,(x_var_dump(1, v_a)));
  return true;
}
namespace hphp_impl_splitter {}

// Class tables
static const int64 cpt_static_inits[] = {
  (int64)&NAMVAR(s_svid7a79683, 20LL),
  (int64)&NAMVAR(s_svi542bad8b, 10LL),
};
static const ClassPropTableEntry cpt_table_entries[] = {
  {0x1CA3E3D9527D20FALL,0,0,0,68,4,GET_PROPERTY_OFFSET(c_myclass, m_pubvar),&NAMSTR(s_ssa42bbae9, "pubvar") },
  {0x27731D65F3F36A14LL,-1,1,0,68,4,GET_PROPERTY_OFFSET(c_myclass, m_privar),&NAMSTR(s_ss3b22d7c5, "privar") },

};
static const int cpt_hash_entries[] = {
  // myclass hash
  -1,-1,0,-1,1,-1,-1,-1,
  // myclass lists
  -1,
  -1,
  -1,
};
const ClassPropTable c_myclass::os_prop_table = {
  7,1,-1,-1,-1,-1,9,0,
  cpt_hash_entries+0,0,cpt_table_entries+0,cpt_static_inits
};


ぬわー。なんかただでさえ長いソースがさらに長くなりました。
実行される所を順番に見て行きましょう。

オブジェクトの構築
$class = new myclass();
↓
  {
    LINE(13,0);
    const p_myclass &tmp0((p_myclass(((c_myclass*)coo_myclass()))));
    v_class = tmp0;
  }

newでクラスを作る部分は、 coo_myclass関数の呼び出しに変わりました。
C++ネイティブに実装されたクラス p_myclass を構築していますね。

メソッドの呼び出し
$a = $class->pubfunc(10);
↓
  {
    LINE(14,0);
    MethodCallPackage mcp0;
    CVarRef obj0 = v_class;
    mcp0.methodCall((obj0), NAMSTR(s_ss758adc76, "pubfunc"), 0x4A7B5BE10021188ALL);
    const CallInfo *cit0 ATTRIBUTE_UNUSED = mcp0.ci;
    Variant tmp1(((mcp0.ci->getMeth1Args())(mcp0, 1, NAMVAR(s_svi542bad8b, 10LL))));
    v_a.assignVal(tmp1);
  }

メソッドを呼び出すのに mcp0.methodCall というのを使っています。
さらに、メソッド名を0x4A7B5BE10021188ALLという「ハッシュ値」で与えています
これにはちょっと驚きです。


関数は、C++ネイティブな関数が呼ばれるように実装されているのに、クラスメソッドは自前で名前解決をするのでしょうか?
なんでこんな実装になっているのでしょうか?
マジックメソッドなどを使われた場合に面倒になるからでしょうか?
その時だけは特殊ルーチンで逃げてくれて普通はネイティブ対応でチューニングされたソースにして欲しかったですね。
まだまだ、早くなる余地があるとプラス思考でいきましょう。



クラスメソッド呼び出しを追いかけてみましょう。
文字列で与えたメソッド名は name メンバ変数に格納しています。backtrace用か何かでしょうか?
そして、prehash という値で、クラステーブルを検索していますね。

runtime/base/builtin_functions.cpp

HOT_FUNC
bool MethodCallPackage::methodCall(CVarRef self, CStrRef method,
                                   int64 prehash /* = -1 */) {
  isObj = true;
  ObjectData *s = self.objectForCall();
  rootObj = s;
  name = &method;
  return s->o_get_call_info(*this, prehash);
}


こうやって、名前解決したものを使って、メソッドを呼び出します。

const CallInfo *cit0 ATTRIBUTE_UNUSED = mcp0.ci;
Variant tmp1(((mcp0.ci->getMeth1Args())(mcp0, 1, NAMVAR(s_svi542bad8b, 10LL))));
v_a.assignVal(tmp1);


なんで、こーゆー風になっているのか知っている人は教えてください。
とりあえずは、将来的には、C++ネイティブなクラスを呼び出すようになって、最適化されると思っておきましょう。
hiphop phpはまだまだ早くなるのかもしれないのです。

クラスの継承

せっかくクラスなんだから、継承させたいこともあるでしょう。
継承がどのように表現されるか見て行きましょう。

test25.php

<?php
class baseclass
{
     private function prifunc($a)
     {
        return $a + 1;
     }
     protected function protfunc($a)
     {
        return $a + 1;
     }
     public function pubfunc($a)
     {
        return $a + 1;
     }

}

class myclass extends baseclass
{
   public function pubfunc($a)
   {
      return $this->protfunc( $a + 2 ); //$a + 3
   }
}

$class = new myclass();
$a = $class->pubfunc(10);

var_dump($a);


今回もディレクトリを見ていきましょう。

cls/baseclass.h
cls/myclass.h
php/test25.cpp
php/test25.fws.h
php/test25.h


クラス単位でヘッダーが作られるので、 cls/baseclass.h が増えています。


順番に見て行きましょう。

cls/baseclass.h

FORWARD_DECLARE_CLASS(baseclass);
extern const ObjectStaticCallbacks cw_baseclass;
class c_baseclass : public ObjectData {
  public:

  // Properties

  // Class Map
  DECLARE_CLASS_NO_SWEEP(baseclass, baseclass, ObjectData)
  c_baseclass(const ObjectStaticCallbacks *cb = &cw_baseclass) : ObjectData(cb, false) {}
  public: Numeric t_prifunc(CVarRef v_a);
  public: Numeric t_protfunc(CVarRef v_a);
  public: virtual Variant t_pubfunc(Variant v_a);
  DECLARE_METHOD_INVOKE_HELPERS(prifunc);
  DECLARE_METHOD_INVOKE_HELPERS(protfunc);
  DECLARE_METHOD_INVOKE_HELPERS(pubfunc);
};
ObjectData *coo_baseclass() NEVER_INLINE;

おや、 private や protected で指定したものがすべて public になっていますね。

private function prifunc($a)
{
return $a + 1;
}
protected function protfunc($a)
{
return $a + 1;
}
public function pubfunc($a)
{
return $a + 1;
}

↓↓↓↓↓↓

public: Numeric t_prifunc(CVarRef v_a);
public: Numeric t_protfunc(CVarRef v_a);
public: virtual Variant t_pubfunc(Variant v_a);


コードジェネレートされたクラスに対して、アクセス修飾子なんてあんまり意味が無いので、仕方ないですね。
(最終的にアセンブラになったときなんて、すべては一直線のメモリ上でしかないんですし。)



今回は、 pubfunc を継承先で オーバーライドしているため、ちゃんと virtual がついて、public: virtual Variant t_pubfunc(Variant v_a); となっています。ココらへんはさすがですね。


継承先のクラスのヘッダを見てみましょう。
これはふつーです。

cls/myclass.h

FORWARD_DECLARE_CLASS(myclass);
extern const ObjectStaticCallbacks cw_myclass;
class c_myclass : public c_baseclass {
  public:

  // Properties

  // Class Map
  DECLARE_CLASS_NO_SWEEP(myclass, myclass, baseclass)
  c_myclass(const ObjectStaticCallbacks *cb = &cw_myclass) : c_baseclass(cb) {}
  public: virtual Variant t_pubfunc(Variant v_a);
  DECLARE_METHOD_INVOKE_HELPERS(pubfunc);
};
ObjectData *coo_myclass() NEVER_INLINE;


最後に、、、PHP本体の部分です。
今回は、クラスとメソッドが増えているので、行数はさらに増大しています。
Page Downキーの用意はいいですか?

/* preface starts */
extern CallInfo ci_;
/* preface finishes */
/* SRC: test25.php line 20 */
IMPLEMENT_CLASS_NO_DEFAULT_SWEEP(myclass)
const InstanceOfInfo c_myclass::s_instanceof_table[] = {
  {0x635A0930E7852DD5LL,1,"baseclass",&cw_baseclass},
  {0x124B454009539DD2LL,1,"myclass",&cw_myclass},
};
const int c_myclass::s_instanceof_index[] = {
  3,
  -1,0,1,-1,
};
CallInfo c_myclass::ci_pubfunc((void*)&c_myclass::i_pubfunc, (void*)&c_myclass::ifa_pubfunc, 1, 4, 0x0000000000000000LL);
Variant c_myclass::i_pubfunc(MethodCallPackage &mcp, CArrRef params) {
  return invoke_meth_few_handler(mcp, params, &ifa_pubfunc);
}
Variant c_myclass::ifa_pubfunc(MethodCallPackage &mcp, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(mcp.obj == 0)) {
    return ObjectData::ifa_dummy(mcp, count, INVOKE_FEW_ARGS_PASS_ARGS, ifa_pubfunc, coo_myclass);
  }
  c_myclass *self ATTRIBUTE_UNUSED (static_cast<c_myclass*>(mcp.obj));
  if (UNLIKELY(count < 1)) throw_missing_arguments("myclass::pubfunc", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  return (self->c_myclass::t_pubfunc(arg0));
}
const MethodCallInfoTable c_myclass::s_call_info_table[] = {
  { 0x4A7B5BE10021188ALL, 1, 7, "pubfunc", &c_myclass::ci_pubfunc },
  { 0, 1, 0, 0 }
};
const int c_myclass::s_call_info_index[] = {
  1,
  0,-1,
};
const ObjectStaticCallbacks cw_myclass = {
  (ObjectData*(*)(ObjectData*))coo_myclass,
  c_myclass::s_call_info_table,c_myclass::s_call_info_index,
  c_myclass::s_instanceof_table,c_myclass::s_instanceof_index,
  &c_myclass::s_class_name,
  0,0,0,&cw_baseclass,0x0
};
/* SRC: test25.php line 22 */
Variant c_myclass::t_pubfunc(Variant v_a) {
  INSTANCE_METHOD_INJECTION_ROOTLESS_NOMEM(myclass, myclass::pubfunc);
  INTERCEPT_INJECTION("myclass::pubfunc", array_createvi(1, toVPOD(v_a)), r);
  return LINE(24,(t_protfunc((v_a + 2LL))));
}
namespace hphp_impl_splitter {}
/* SRC: test25.php line 3 */
IMPLEMENT_CLASS_NO_DEFAULT_SWEEP(baseclass)
const InstanceOfInfo c_baseclass::s_instanceof_table[] = {
  {0x635A0930E7852DD5LL,1,"baseclass",&cw_baseclass},
};
const int c_baseclass::s_instanceof_index[] = {
  1,
  -1,0,
};
CallInfo c_baseclass::ci_protfunc((void*)&c_baseclass::i_protfunc, (void*)&c_baseclass::ifa_protfunc, 1, 68, 0x0000000000000000LL);
CallInfo c_baseclass::ci_prifunc((void*)&c_baseclass::i_prifunc, (void*)&c_baseclass::ifa_prifunc, 1, 132, 0x0000000000000000LL);
CallInfo c_baseclass::ci_pubfunc((void*)&c_baseclass::i_pubfunc, (void*)&c_baseclass::ifa_pubfunc, 1, 4, 0x0000000000000000LL);
Variant c_baseclass::i_prifunc(MethodCallPackage &mcp, CArrRef params) {
  return invoke_meth_few_handler(mcp, params, &ifa_prifunc);
}
Variant c_baseclass::i_protfunc(MethodCallPackage &mcp, CArrRef params) {
  return invoke_meth_few_handler(mcp, params, &ifa_protfunc);
}
Variant c_baseclass::i_pubfunc(MethodCallPackage &mcp, CArrRef params) {
  return invoke_meth_few_handler(mcp, params, &ifa_pubfunc);
}
Variant c_baseclass::ifa_prifunc(MethodCallPackage &mcp, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(mcp.obj == 0)) {
    return ObjectData::ifa_dummy(mcp, count, INVOKE_FEW_ARGS_PASS_ARGS, ifa_prifunc, coo_baseclass);
  }
  c_baseclass *self ATTRIBUTE_UNUSED (static_cast<c_baseclass*>(mcp.obj));
  if (UNLIKELY(count < 1)) throw_missing_arguments("baseclass::prifunc", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  return (self->t_prifunc(arg0));
}
Variant c_baseclass::ifa_protfunc(MethodCallPackage &mcp, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(mcp.obj == 0)) {
    return ObjectData::ifa_dummy(mcp, count, INVOKE_FEW_ARGS_PASS_ARGS, ifa_protfunc, coo_baseclass);
  }
  c_baseclass *self ATTRIBUTE_UNUSED (static_cast<c_baseclass*>(mcp.obj));
  if (UNLIKELY(count < 1)) throw_missing_arguments("baseclass::protfunc", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  return (self->t_protfunc(arg0));
}
Variant c_baseclass::ifa_pubfunc(MethodCallPackage &mcp, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(mcp.obj == 0)) {
    return ObjectData::ifa_dummy(mcp, count, INVOKE_FEW_ARGS_PASS_ARGS, ifa_pubfunc, coo_baseclass);
  }
  c_baseclass *self ATTRIBUTE_UNUSED (static_cast<c_baseclass*>(mcp.obj));
  if (UNLIKELY(count < 1)) throw_missing_arguments("baseclass::pubfunc", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  return (self->c_baseclass::t_pubfunc(arg0));
}
const MethodCallInfoTable c_baseclass::s_call_info_table[] = {
  { 0x4A7B5BE10021188ALL, 1, 7, "pubfunc", &c_baseclass::ci_pubfunc },
  { 0x5620E6422EA47907LL, 1, 7, "prifunc", &c_baseclass::ci_prifunc },
  { 0x1694CB36FB987947LL, 0, 8, "protfunc", &c_baseclass::ci_protfunc },
  { 0, 1, 0, 0 }
};
const int c_baseclass::s_call_info_index[] = {
  7,
  -1,-1,0,-1,-1,-1,-1,1,

};
const ObjectStaticCallbacks cw_baseclass = {
  (ObjectData*(*)(ObjectData*))coo_baseclass,
  c_baseclass::s_call_info_table,c_baseclass::s_call_info_index,
  c_baseclass::s_instanceof_table,c_baseclass::s_instanceof_index,
  &c_baseclass::s_class_name,
  0,0,0,0,0x0
};
/* SRC: test25.php line 5 */
Numeric c_baseclass::t_prifunc(CVarRef v_a) {
  INSTANCE_METHOD_INJECTION_ROOTLESS(baseclass, baseclass::prifunc);
  INTERCEPT_INJECTION("baseclass::prifunc", array_createvi(1, toVPOD(v_a)), r);
  return (v_a + 1LL);
}
namespace hphp_impl_splitter {}
/* SRC: test25.php line 9 */
Numeric c_baseclass::t_protfunc(CVarRef v_a) {
  INSTANCE_METHOD_INJECTION_ROOTLESS(baseclass, baseclass::protfunc);
  INTERCEPT_INJECTION("baseclass::protfunc", array_createvi(1, toVPOD(v_a)), r);
  return (v_a + 1LL);
}
namespace hphp_impl_splitter {}
/* SRC: test25.php line 13 */
Variant c_baseclass::t_pubfunc(Variant v_a) {
  INSTANCE_METHOD_INJECTION_ROOTLESS(baseclass, baseclass::pubfunc);
  INTERCEPT_INJECTION("baseclass::pubfunc", array_createvi(1, toVPOD(v_a)), r);
  return (v_a + 1LL);
}
namespace hphp_impl_splitter {}
ObjectData *coo_myclass() {
  return NEWOBJ(c_myclass)();
}
ObjectData *coo_baseclass() {
  return NEWOBJ(c_baseclass)();
}
Variant pm_php$test25_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test25.php, pm_php$test25_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_class ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssc82dbd12, "class")) : g->GV(class);
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);

  {
    LINE(28,0);
    const p_myclass &tmp0((p_myclass(((c_myclass*)coo_myclass())->create())));
    v_class = tmp0;
  }
  {
    LINE(29,0);
    MethodCallPackage mcp0;
    CVarRef obj0 = v_class;
    mcp0.methodCall((obj0), NAMSTR(s_ss758adc76, "pubfunc"), 0x4A7B5BE10021188ALL);
    const CallInfo *cit0 ATTRIBUTE_UNUSED = mcp0.ci;
    Variant tmp1(((mcp0.ci->getMeth1Args())(mcp0, 1, NAMVAR(s_svi542bad8b, 10LL))));
    v_a.assignVal(tmp1);
  }
  LINE(31,(x_var_dump(1, v_a)));
  return true;
}


とてつもなく長いです。
結局のところ、前回と同じように、名前解決を自前でやって、飛ばしています。
真面目に読んでいると眠くなるので次いってみよう。

inferface

邪悪ではない継承の inferface 継承を見て行きましょう。


test26.php

<?php
interface baseinterface
{
     public function pubfunc($a);
}

class myclass implements baseinterface
{
   public function pubfunc($a)
   {
      return $a + 3;
   }
}

$class = new myclass();
$a = $class->pubfunc(10);

var_dump($a);


今回もディレクトリを見ていきましょう。

cls/baseinterface.h
cls/myclass.h
php/test26.cpp
php/test26.fws.h
php/test26.h


インターフェースが定義されたbaseinterfaceを見てみます。

cls/baseinterface.h

FORWARD_DECLARE_GENERIC_INTERFACE(baseinterface);
class c_baseinterface {
  // public: virtual void t_pubfunc(CVarRef v_a) = 0;
};


おや、空っぽです。
コメントアウトされている構文の通り、 C++ でも virtual hoge() = 0; で interface が表現できるのですが、何もありません。
これは、PHPで オブジェクト型への dynamic_cast がないからだと推測します。
C++で、 interfaceな継承する理由の一つに、 dynamic_castで引き回すというのがあると思いますが、PHPではこれに該当する機能はないはずです。(たぶん)
それがない PHPC++ に変換しているため、 C++ で virtual hoge() = 0; を作ることが、コンパイラにクラス用のオブジェクトのジャンプテーブルを作成させることになってしまい、余計な負荷になってしまうため、こーゆー実装にしたのではないかと思います。
だから interface クラスは空っぽなのだと思います。



次に、myclass クラス本体の定義を見てみます。

cls/myclass.h

FORWARD_DECLARE_CLASS(myclass);
extern const ObjectStaticCallbacks cw_myclass;
class c_myclass : public ObjectData {
  public:

  // Properties

  // Class Map
  DECLARE_CLASS_NO_SWEEP(myclass, myclass, ObjectData)
  c_myclass(const ObjectStaticCallbacks *cb = &cw_myclass) : ObjectData(cb, false) {}
  public: Numeric t_pubfunc(CVarRef v_a);
  DECLARE_METHOD_INVOKE_HELPERS(pubfunc);
};
ObjectData *coo_myclass() NEVER_INLINE;


やはり、 interface継承していませんし、 t_pubfunc は virtual でもありません。
パフォーマンスを優先するために、こーゆー風になっているのでしょう。(たぶん)


最後にPHP本体です。
やっぱり長いかー

/* preface starts */
extern CallInfo ci_;
/* preface finishes */
/* SRC: test26.php line 8 */
IMPLEMENT_CLASS_NO_DEFAULT_SWEEP(myclass)
const InstanceOfInfo c_myclass::s_instanceof_table[] = {
  {0x5EEED81878BA7A4CLL,1,"baseinterface",(const ObjectStaticCallbacks*)2},
  {0x124B454009539DD2LL,1,"myclass",&cw_myclass},
};
const int c_myclass::s_instanceof_index[] = {
  3,
  0,-1,1,-1,
};
CallInfo c_myclass::ci_pubfunc((void*)&c_myclass::i_pubfunc, (void*)&c_myclass::ifa_pubfunc, 1, 4, 0x0000000000000000LL);
Variant c_myclass::i_pubfunc(MethodCallPackage &mcp, CArrRef params) {
  return invoke_meth_few_handler(mcp, params, &ifa_pubfunc);
}
Variant c_myclass::ifa_pubfunc(MethodCallPackage &mcp, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(mcp.obj == 0)) {
    return ObjectData::ifa_dummy(mcp, count, INVOKE_FEW_ARGS_PASS_ARGS, ifa_pubfunc, coo_myclass);
  }
  c_myclass *self ATTRIBUTE_UNUSED (static_cast<c_myclass*>(mcp.obj));
  if (UNLIKELY(count < 1)) throw_missing_arguments("myclass::pubfunc", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  return (self->t_pubfunc(arg0));
}
const MethodCallInfoTable c_myclass::s_call_info_table[] = {
  { 0x4A7B5BE10021188ALL, 1, 7, "pubfunc", &c_myclass::ci_pubfunc },
  { 0, 1, 0, 0 }
};
const int c_myclass::s_call_info_index[] = {
  1,
  0,-1,
};
const ObjectStaticCallbacks cw_myclass = {
  (ObjectData*(*)(ObjectData*))coo_myclass,
  c_myclass::s_call_info_table,c_myclass::s_call_info_index,
  c_myclass::s_instanceof_table,c_myclass::s_instanceof_index,
  &c_myclass::s_class_name,
  0,0,0,0,0x0
};
/* SRC: test26.php line 10 */
Numeric c_myclass::t_pubfunc(CVarRef v_a) {
  INSTANCE_METHOD_INJECTION_ROOTLESS(myclass, myclass::pubfunc);
  INTERCEPT_INJECTION("myclass::pubfunc", array_createvi(1, toVPOD(v_a)), r);
  return (v_a + 3LL);
}
namespace hphp_impl_splitter {}
ObjectData *coo_myclass() {
  return NEWOBJ(c_myclass)();
}
Variant pm_php$test26_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test26.php, pm_php$test26_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_class ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssc82dbd12, "class")) : g->GV(class);
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);

  {
    LINE(16,0);
    const p_myclass &tmp0((p_myclass(((c_myclass*)coo_myclass()))));
    v_class = tmp0;
  }
  {
    LINE(17,0);
    MethodCallPackage mcp0;
    CVarRef obj0 = v_class;
    mcp0.methodCall((obj0), NAMSTR(s_ss758adc76, "pubfunc"), 0x4A7B5BE10021188ALL);
    const CallInfo *cit0 ATTRIBUTE_UNUSED = mcp0.ci;
    Variant tmp1(((mcp0.ci->getMeth1Args())(mcp0, 1, NAMVAR(s_svi542bad8b, 10LL))));
    v_a.assignVal(tmp1);
  }
  LINE(19,(x_var_dump(1, v_a)));
  return true;
}

思いの外、長くないですね。
interface継承が空っぽのクラスになってしまったため、行数はそれほど伸びていません。
ここでやっていることも、クラス全般でやられていたように、自前でメソッド名を解決してアクセスしています。

まとめ

hiphop php がジェネレートする C++ のコードについて簡単に見てきました。
結構頑張った最適化が行われていると思います。


ここまで、hiphopはがんばってはいるんだけど、
それにしても、hiphop php を使っている人をあんまりみませんね。
私も使っていませんでしたけどw


やはり、そこまでwebサーバが負荷になるケースというのが少ないのでしょう。
それに、webサーバって結構簡単にスケールさせることか出来ます。
だから、入れるとしてもAPC程度のキャッシュで十分ということでしょうか。


また、いちいちコンパイルしなくてはいけないという点も問題です。
置いたら動くというPHPの機動力を失わせてしまっています。
ただし、デプロイツールをうまく作りこんでしまえば、どうとでもなる気もしますけどね。


インフラサイドから言えば、ApacheLighttpd の運用監視にみんな慣れてしまって、 hiphop php が作る HTTPD に馴染めなかったのかもしれません。
さらに、libevent と curl などの主要パッケージに独自パッチを当てたものを入れなければならず、保守が難しいという点もあります。


それに、、web業界はピンきりで、中にはdbのindexって何?って人がコード書いていたりする闇もあるかもしれませんけどね(ひゃっはー)
早くて安いサーバが変えるようになってしまったので、多少あほなコードがあっても、小中規模だと大した問題にならないみたいな。




だけれども、高速化には技術者の夢とロマンが詰まっています。
動的言語を静的言語に変換するというのも、面白いものです。
そして、それによって、パフォーマンスが2〜3倍も上がるわけですから、どういう変換が行われているかというのは興味深いところではないでしょうか?



メリークリスマス。おしまい。

ふろく

今回の記事を書くため調査したデータ一式.zip
http://rtilabs.net/files/2011_12_25/hphp_test.zip