Zephirの中身を少し眺めたメモ

  • 4
    Like
  • 0
    Comment

PHPのサブセットみたいな言語(*.zep)からPHPの拡張モジュール(Cソースコード経由で最終的には*.soになる)を出力するZephirという言語があります。PHPフレームワークPhalconの実装言語としても有名ですね。

このZephirがどうやって*.zepを解釈して*.cを吐き出してるのか気になったので確認してみました。

Zephir入門

Zephirのインストールは「Installation — Zephir 0.9.4 documentation」に沿って行いました。

ドキュメント通りにインストールするとなぜかrootを要求されますが、install-nosudoを使えば$HOME/bin/以下にzephirコマンドがインストールされます。

チュートリアルに沿って下記のように*.zepファイルを作成します。

$ zephir init utils
$ cd utils
$ vi utils/greeting.zep
greeting.zep
namespace Utils;

class Greeting
{

    public static function say()
    {
        var_dump("hello world!");
    }

}

では、さっそくビルドしてみましょう。

$ zephir build

これでエクステンションutils.soがビルドされます。また、sudo権限があれば勝手にPHP拡張モジュールのインストールまでやってくれます。

途中経過で作られたCソースコードext/utils/greeting.zep.cの中身を見てみましょう。

PHP_METHOD(Utils_Greeting, say) {

        zval _0;
        zval *this_ptr = getThis();

        ZVAL_UNDEF(&_0);

        ZEPHIR_MM_GROW();

        ZEPHIR_INIT_VAR(&_0);
        ZEPHIR_INIT_NVAR(&_0);
        ZVAL_STRING(&_0, "hello world!");
        zephir_var_dump(&_0 TSRMLS_CC);
        ZEPHIR_MM_RESTORE();

}

独自マクロだらけですが、確かにPHP拡張モジュールっぽいCソースコードが出力されています。

コンパイラの本体を探す

さて、上記Cソースコードが出力されるまでの処理を追ってみましょう。

zephirコマンドの中身はシェルスクリプトで、最終的にPHPスクリプトcompiler.phpを呼び出しています。その後の処理でビルド専用のPHP拡張モジュールzephir_parser.soを指定してPHPを起動するようになっており、構文解析などはこの拡張モジュール内で実装されていることがわかります。

逆に言うと、zephirコマンドはZephirコンパイル用のPHP拡張に依存しているため、PHPのバージョンが変わるとZephir自体の再インストールが必要になります。

字句解析・構文解析の本体

Zephirの字句解析・構文解析の本体は下記ファイルです。

字句解析にはre2cを、構文解析にはLEMONを利用しています。

*.reファイルを見るとZephirの対応しているトークンが確認できます。

たとえば、下記の定義からZephirでは浮動小数点数の指数形式(1e10のような形式)をサポートしていないことがわかります。

                DOUBLE = ([\-]?[0-9]+[\.][0-9]+);
                DOUBLE {
                        token->opcode = ZEPHIR_T_DOUBLE;
                        token->value = estrndup(start, YYCURSOR - start);
                        token->len = YYCURSOR - start;
                        s->active_char += (YYCURSOR - start);
                        q = YYCURSOR;
                        return 0;
                }

構文解析機本体はruntime/base.czephir_parse_program()です。zephir_get_token()1で字句解析器からトークンを取り出し、zephir_()2で構文解析を行っています。構文解析の結果、*.zep ファイルに対応する構文木がPHPの配列の形で(!)帰ってきます。

Cソースコードの生成

構文木からCソースコードを出力する部分はピュアPHPで記述されています。Library/CompilerFile.phppreCompile()が処理の本体になります。

そこから先の処理はあまり追っていませんが、Library/以下が構文ごとにCソースコードを出力するような処理に対応していると思います。

これだけでもすごい量ですが、自動生成されるCコードから呼び出されるPHP APIのラッパー(Zephir Kernel)もかなりの実装量です。

どういうモチベーションでこんなことをする気になったのか気になりますね。

LLVMベースのJITコンパイル実装の夢

runtime/以下のソースコードを眺めると、LLVMに関係しそうな関数呼び出しがチラホラ存在します。

zephir.c
        if (!ZEPHIRT_GLOBAL(module)) {

                ZEPHIRT_GLOBAL(module) = LLVMModuleCreateWithName("zephir");
                ZEPHIRT_GLOBAL(builder) = LLVMCreateBuilder();

                LLVMInitializeNativeTarget();
                LLVMLinkInJIT();

                if (LLVMCreateExecutionEngineForModule(&ZEPHIRT_GLOBAL(engine), ZEPHIRT_GLOBAL(module), &msg) == 1) {
                        fprintf(stderr, "%s\n", msg);
                        LLVMDisposeMessage(msg);
                        return;
                }

                ZEPHIRT_GLOBAL(pass_manager) =  LLVMCreateFunctionPassManagerForModule(ZEPHIRT_GLOBAL(module));
                LLVMAddTargetData(LLVMGetExecutionEngineTargetData(ZEPHIRT_GLOBAL(engine)), ZEPHIRT_GLOBAL(pass_manager));
        }

これは*.zepのJITコンパイル実装のようです。どうやらZephirはZend Engine上で動くスクリプト言語としての野望も持っているようですね。開発状況としては実験段階のようですが、ロマンを感じます。

まとめ

ZephirはPHPに文法が近いという話を聞いていたので、どうせ言語といっても文字列置換程度かと想像していたんですが、思ったより言語っぽい実装になっていたのは意外でした。

個人的にはLEMONの存在を知ったのが収穫でした。小さいパーサーを作るときに便利かもしれません。


  1. re2cが生成する関数。 

  2. LEMONが生成する関数。変な関数名になっているのはコード生成時にこのプレフィックスを指定したため。