SlideShare a Scribd company logo
2016/11/03
PHP Conference Japan 2016
do_aki
1
updated 2016-12-13
@do_aki
@do_aki
http://do-aki.net/
PHP AST 徹底解説
第1章
php のコンパイラ
PHP
Compiler in PHP
PHP Script
Opcode
Request
Output
Compiler
Lexing
Parsing
Compilation
VM
Execution
INCLUDE_OR_EVAL
コンパイルの流れ
字句解析 構文解析 Opcode生成
狭義のコンパイル
AST を生成
トークンに分解
Lexical Analysis
字句解析
• ソースコードをトークン(意味を持つ最
小の単位)に分解
• token_get_all 関数 で確認できる
• 余談: token_get_all の中では実際にコ
ンパイル処理が行われる(ただし、
Opcode 生成は省略)
ソースコード(php スクリプト)
<?php
function hello ( $name ) {
echo “HELLO $name“ ;
}
hello ( “php“ ) ;
字句解析
<?php
function hello ( $name ) {
echo “HELLO $name“ ;
}
hello ( “php“ ) ;
T_FUNCTION T_STRING ( ) {
}
T_ECHO
T_ENCAPSED_AND
_WHITESPACE ;
T_STRING ( ) ;
T_OPEN_TAG
T_VARIABLE
T_VARIABLE
T_VARIABLE“ “
ひとつひとつがトークン
(意味を持つ最小の単位)
Syntax Analysis
構文解析
• トークンの並びから構文を導く
• 構文に応じたASTを構築する
• 該当する構文が見つからないときは
Parse Error (7.0 から Exception)
構文解析
T_FUNCTION T_STRING ( ) {
}
T_ECHO T_ENCAPSED_AND_WHITESPACE ;
T_STRING ( ) ;
T_OPEN_TAG
T_VARIABLE
T_VARIABLE
function declaration
function call
AST構築
(by do-aki/phpast)
Bytecode Generation
Opcode生成
• AST を解析して Opcode を生成
• 狭義のコンパイル(zend_compile.c)
• いくつかの最適化が施される(後述)
• 構文としては正しいが不正なコードは
Compile Error (Fatal error)
ex: const A = 1 + f();
Opcode (vld)
line #* E I O op fetch ext return operands
----------------------------------------------------------------
2 0 E > RECV !0
3 1 NOP
2 FAST_CONCAT ~1 'HELLO+', !0
3 ECHO ~1
4 4 > RETURN null
line #* E I O op fetch ext return operands
----------------------------------------------------------------
2 0 E > NOP
6 1 INIT_FCALL 'hello'
2 SEND_VAL 'php'
3 DO_FCALL 0
4 > RETURN 1
function
hello()
call
hello()
この章のまとめ
• php はコンパイラを持っている
• 基本的には php スクリプトを読み込む度
にコンパイルが行われる
• 字句解析、構文解析、Opcode生成
構文解析の結果 AST が生成される
第2章
AST導入によって
変わったこと
cf.コンパイルの流れ
字句解析 構文解析 Opcode生成
狭義のコンパイル
AST を生成
トークンに分解
php5 (1 pass / 151構文(5.6))
字句解析 + 構文解析 + Opcode生成
php7 (2 pass / 127構文(7.0))
字句解析+構文解析 Opcode生成
php5 (1 pass / 151構文(5.6))
字句解析 + 構文解析 + Opcode生成
php7 (2 pass / 127構文(7.0))
字句解析+構文解析 Opcode生成
最適化の余地
Opcode生成時の
最適化例
定数の畳み込み
$sec_in_day = 60 * 60 * 24;
$sec_in_day = 86400;
※実は OpCache でも行われている
class A { const HOGE = ‘hoge‘; }
echo A::HOGE;
echo ‘hoge‘;
コンパイル時点で定義済みの定数に対してのみ有効
(autoload より pre include のほうが効きやすい)
静的関数展開(定数化)
• 関数呼び出しコストの削減
• 定数畳み込みとの組み合わせも有効
ex: strlen(’hoge’) + 1 -> 5
strlen(’hoge’) -> 4
ord(’A’) -> 65 / 7.1~
chr(65) -> ‘A‘ / 7.1~
静的関数展開(call)
• defined_funcが定義済みの場合のみ展開
• コンパイル時点で定義済みの関数に対し
てのみ発生 (定数と同様)
function func() {}
call_user_func(’func’);
func();
実際には、ほぼ等価であるものの若干異なり、
EXT_FCALL_BEGIN / EXT_FCALL_END が発行されない
静的関数展開(cast) / 7.1~
boolval($var) -> (bool)$var
intval($var) -> (int)$var
floatval($var) -> (float)$var
doubleval($var) -> (float)$var
strval($var) -> (string)$var
静的関数展開(Opcode変換)
is_null/is_bool/is_long/is_int/is_i
nteger/is_float/is_double/is_real/i
s_string/is_array/is_object/is_reso
urce -> TYPE_CHECK Op
defined -> DEFINED Op
静的関数展開の無効化
• 静的関数展開は、 CG(compiler_options)
に ZEND_COMPILE_NO_BUILTINS をセット
することで無効にできる
• CG(compiler_options) に
ZEND_COMPILE_NO_BUILTINS ビットをセッ
トすることで静的関数展開を無効にできる
• 拡張ならば、 CG(compiler_options) を制
御可能
静的zval構築
INIT_ARRAY(1) -> temp
ADD_ARRAY_ELEMENT(2) -> temp
ADD_ARRAY_ELEMENT(3) -> temp
ASSIGN(temp->$a)
ASSIGN([1,2,3]->$a)
$a = [1,2,3];
~5.6
7.0~
静的ショートサーキット
• condition 部分で行っている変数参照や関
数呼び出しに関する Opcode が生成されな
くなる
• JMPZ および 実行されることがないブロッ
クのOpcode は(無駄に)生成されてしまう
if (1 || condition) -> if (true)
if (0 && condition) -> if (false)
print の echo 化
• ZEND_PRINT 廃止 -> ZEND_ECHO に統一
• echo も print も同じ Opcode に
• print の戻り値が利用される場合のみ、
それを常に 1 で置き換え
return print(’hello’); echo ’hello’;
return 1;
条件コンパイル
“assert() は PHP 7 で言語構造となり、”
(http://php.net/manual/ja/function.assert.php)
とあるが、構文解析においては関数呼び出しで、
引数部のコード(AST) を逆変換している
assert($v === 0);
[zend.assertions >= 0]
assert(‘assert($v === 0)‘);
[zend.assertions < 0]
(assert の呼び出しがなったことに)
コンパイルタイミングによって
Opcode が変化する例
class A { const X = 1; }
a.php
require_once ‘a.php‘
echo A::X;
echo.php
require_once ‘a.php‘
require_once ‘echo.php‘
require.php
> php echo.php
echo.php をコンパイルする時点
では a.php はコンパイルされて
いない
> php require.php
echo.php をコンパイルする時点
で a.php はコンパイル済み
line #* E I O op fetch ext return operands
-----------------------------------------------------------
2 0 E > INCLUDE_OR_EVAL 'a.php', REQUIRE_ONCE
3 1 FETCH_CONSTANT ~1 'A', 'X'
2 ECHO ~1
3 > RETURN 1
line #* E I O op fetch ext return operands
-----------------------------------------------------------
0 E > INCLUDE_OR_EVAL 'a.php', REQUIRE_ONCE
3 1 ECHO 1
2 > RETURN 1
> php echo.php
> php require.php (の時の echo.php)
あらかじめ require しておくことで早くなる可能性も……?(未検証)
line #* E I O op fetch ext return operands
-----------------------------------------------------------
2 0 E > INCLUDE_OR_EVAL 'a.php', REQUIRE_ONCE
3 1 FETCH_CONSTANT ~1 'A', 'X'
2 ECHO ~1
3 > RETURN 1
line #* E I O op fetch ext return operands
-----------------------------------------------------------
0 E > INCLUDE_OR_EVAL 'a.php', REQUIRE_ONCE
3 1 ECHO 1
2 > RETURN 1
> php echo.php
> php require.php (の時の echo.php)
この章のまとめ
• AST 導入そのものによる影響は少ない
• コンパイルに関するコードがシンプルに
なり、最適化の余地が生まれた
• 小手先の最適化が不要になった
第3章 AST の
構造と特徴
Syntax tree(Parse tree)
構文木(解析木)
ex: 1 / (2 + 3)
1 2 3/ ( )+
解析木 :=トークンを葉として、
構成を木構造で表現したもの
Abstract syntax tree
抽象構文木
ex: 1 / (2 + 3)
1
2 3
+
/
抽象構文木 := 構文木から、
その後の処理に不要なデータ
をそぎ落としたもの
PHP の
抽象構文木
<?php
1/(2+3);
zend_ast (基本形)
• Zend/zend_ast.h / Zend/zend_ast.c
typedef uint16_t zend_ast_kind;
typedef uint16_t zend_ast_attr;
struct _zend_ast {
zend_ast_kind kind;
/* Type of the node (ZEND_AST_* enum constant) */
zend_ast_attr attr;
/* Additional attribute, use depending on node type */
uint32_t lineno;
/* Line number */
zend_ast *child[1];
/* Array of children (using struct hack) */
};
typedef struct _zend_ast zend_ast; // <- Zend/zend_types.h
Zend/zend_ast.h より 一部見やすさのために改変
zend_ast (基本形)
• Zend/zend_ast.h / Zend/zend_ast.c
typedef uint16_t zend_ast_kind;
typedef uint16_t zend_ast_attr;
struct _zend_ast {
zend_ast_kind kind;
/* Type of the node (ZEND_AST_* enum constant) */
zend_ast_attr attr;
/* Additional attribute, use depending on node type */
uint32_t lineno;
/* Line number */
zend_ast *child[1];
/* Array of children (using struct hack) */
};
typedef struct _zend_ast zend_ast; // <- Zend/zend_types.h
Zend/zend_ast.h より 一部見やすさのために改変
種別
行番号
子ノード
付属情報
PHP の
抽象構文木
<?php
1/(2+3);
種別
付属情報
子ノード
子ノード
zend_ast_kind
• ZEND_AST_*
• 全98種 (7.0) / 7.1は97種
• 大まかに分類して4系統
– 特殊ノード ZEND_AST_ZVAL / (ZEND_AST_ZNODE)
– 定義ノード ZEND_AST_CLASS など
– リストノード ZEND_AST_STMT_LIST など
– 通常ノード ZEND_AST_VAR など
ZEND_AST_ZVAL (特殊ノード)
• zval を包含するノード(行はzval に)
• zval := php スクリプトにおける変数
• リテラル や 変数名、呼び出し関数名等
• 常にリーフ(末端)
• zend_ast_create_zval / zend_ast_create_zval /
zend_ast_create_zval_from_str /
zend_ast_create_zval_from_long によって作成
• (余談) astを保持する zval(定数式)もある
typedef struct _zend_ast_zval {
zend_ast_kind kind;
zend_ast_attr attr;
zval val; /* Lineno is stored in val.u2.lineno */
} zend_ast_zval;
定義ノード
• ZEND_AST_FUNC_DECL / ZEND_AST_CLOSURE /
ZEND_AST_METHOD / ZEND_AST_CLASS のみ
• 常に4つ分の子要素を確保(NULL の場合も)
• zend_ast_create_decl によって作成
typedef struct _zend_ast_decl {
zend_ast_kind kind;
zend_ast_attr attr; /* Unused */
uint32_t start_lineno;
uint32_t end_lineno;
uint32_t flags;
unsigned char *lex_pos;
zend_string *doc_comment;
zend_string *name;
zend_ast *child[4];
} zend_ast_decl;
定義ノード
• AST_FUNC_DECL 関数定義
– 1:AST_PARAM_LIST(仮引数), 2:未使用,
3:AST_STMT_LIST (内部), 4: AST_ZVAL(戻り値型)
• AST_CLOSURE 無名関数定義
– 1:AST_PARAM_LIST(仮引数), 2:AST_CLOSURE_USES (use),
3:AST_STMT_LIST (内部), 4: AST_ZVAL(戻り値型)
• AST_METHOD メソッド定義
– 1:AST_PARAM_LIST(仮引数), 2:未使用,
3:AST_STMT_LIST (内部), 4: AST_ZVAL(戻り値型)
• AST_CLASS クラス,無名クラス,trait,interface 定義
– 1:AST_ZVAL(継承元), 2:AST_NAME_LIST (implements),
3:AST_STMT_LIST (内部), 4:未使用
リストノード
• 可変長の子を持つノード
• zend_ast_create_list によって作成 /
zend_ast_list_add で子を追加
• ex) ZEND_AST_STMT
– ZEND_AST_STMTは乱暴に言えば、ほぼ行。
– 子に ZEND_AST_STMT を含む場合もある
typedef struct _zend_ast_list {
zend_ast_kind kind;
zend_ast_attr attr;
uint32_t lineno;
uint32_t children;
zend_ast *child[1];
} zend_ast_list;
子の数
リストノード
• AST_ARG_LIST
– 関数、メソッド呼び出しの引数群
• AST_LIST (7.0まで)
–
• AST_ARRAY
– array 定義(全体/個々の要素は
AST_ARRAY_ELEM)
• AST_ENCAPS_LIST
– 変数を包含する文字列 (ダブルクォテーション文
字列、HEREDOC、バッククォテーション文字列)
• AST_EXPR_LIST
– for文の (x;x;x)
• AST_STMT_LIST
– ステートメント (; で終わる行すべて)
• AST_IF
– if 文全体 (子は AST_IF_ELEM)
• AST_SWITCH_LIST
– switch 文全体
• AST_CATCH_LIST
– catch 節 (子は AST_CATCH)
• AST_PARAM_LIST
– 関数、メソッド定義の引数群 (子は AST_PARAM)
• AST_CLOSURE_USES
– 無名関数の use 変数リスト (子は AST_ZVAL)
• AST_PROP_DECL
– プロパティ定義 (子は AST_PROP_ELEM)
• AST_CONST_DECL
– クラス外で定義される定数 (子は
AST_CONST_ELEM)
• AST_CLASS_CONST_DECL
– クラス外で定義される定数(const) (子は
AST_CONST_ELEM)
• AST_NAME_LIST
– interface の extends や insteadof の後に
続くクラス名群, catch のクラス名群(7.1) (子
は AST_ZVAL)
• AST_TRAIT_ADAPTATIONS
– trait の use のブレース内 (子は
AST_TRAIT_PRECEDENCE (insteadof) または
AST_TRAIT_ALIAS (as) )
• AST_USE
– 名前空間のuse (子は AST_USE_ELEM)
通常ノード
• 特殊,定義,リスト 以外のすべてのノード
• zend_ast_create (attr なし) / zend_ast_create_ex /
zend_ast_create_binary_op / zend_ast_create_assign_op /
zend_ast_create_cast によって作成
AST_VAR
AST_ZVAL
‘a‘
isset($a)
AST_ISSET
AST_ZVAL
‘func‘
AST_
ARG_LIST
func()
AST_CALL
リストノード
AST_ZVAL
‘A‘
AST_ZVAL
‘X‘
echo A::X
AST_CLASS
_CONST
AST_ECHO
通常ノード(1)
/* 0 child nodes */
• AST_MAGIC_CONST
– __LINE__, __FILE__ 等のマジック定数 / attr:=マ
ジック定数種別
• AST_TYPE
– 引数型指定 / attr=T_ARRAY,T_CALLABLE
/* 1 child node */
• AST_VAR
– 変数参照 / attr 未使用
• AST_CONST
– 定義済み定数(null,true,false,NAN などが子の
ZVALの値) / attr 未使用
• AST_UNPACK
– 関数呼び出し時の ...expr
• AST_UNARY_PLUS
– +expr / attr 未使用
• AST_UNARY_MINUS
– -expr / attr 未使用
• AST_CAST
– (int) とか./ attr := IS_LONG, IS_DOUBLE,
IS_STRING, IS_ARRAY, IS_OBJECT, _IS_BOOL,
IS_NULL
• AST_EMPTY
– empty(expr) / attr 未使用
• AST_ISSET
– isset(expr) / attr 未使用
• AST_SILENCE
– @expr (エラー抑制) / attr 未使用
• AST_SHELL_EXEC
– `backticks_expr` / attr 未使用
• AST_CLONE
– clone expr / attr 未使用
• AST_EXIT
– exit(expr) / attr 未使用
• AST_PRINT
– print expr / attr 未使用
• AST_INCLUDE_OR_EVAL
– include,require,eval / attr := 1:ZEND_EVAL,
2:ZEND_INCLUDE, 4:ZEND_INCLUDE_ONCE,
8:ZEND_REQUIRE ,16:ZEND_REQUIRE_ONCE
• AST_UNARY_OP
– !expr または ~expr / attr := ZEND_BOOL_NOT,
ZEND_BW_NOT
• AST_PRE_INC
– ++variable / attr 未使用
• AST_PRE_DEC
– --variable / attr 未使用
• AST_POST_INC
– variable++ / attr 未使用
• AST_POST_DEC
– variable-- / attr 未使用
• AST_YIELD_FROM
– yield from expr / attr 未使用
通常ノード(2)
• AST_GLOBAL
– global simple_variable / attr 未使用
• AST_UNSET
– unset(variable) / attr 未使用
• AST_RETURN
– return expr / attr 未使用
• AST_LABEL
– LABEL: / attr 未使用
• AST_REF
– &variable / attr 未使用
• AST_HALT_COMPILER
– __halt_compiler() 子には
__COMPILER_HALT_OFFSET__ にセットされる値
/ attr 未使用
• AST_ECHO
– echo expr / attr 未使用
• AST_THROW
– throw expr / attr 未使用
• AST_GOTO
– goto LABEL / attr 未使用
• AST_BREAK
– break expr / attr 未使用
• AST_CONTINUE
– continue expr / attr 未使用
/* 2 child nodes */
• AST_DIM
– 配列要素の参照 variable[N],variable{N}, 1:
参照対象配列, 2:指定要素 / attr 未使用
• AST_PROP
– プロパティ参照 variable->variable,
variable->{expr}, 1:参照対象オブジェクト,
2:指定プロパティ / attr 未使用
• AST_STATIC_PROP
– 静的プロパティ参照 variable::variable, 1:
参照対象クラス指定, 2:指定プロパティ / attr
未使用
• AST_CALL
– 関数呼び出し LABEL(), variable(), 1:呼び出
し関数指定, 2: AST_ARG_LIST / attr 未使用
• AST_CLASS_CONST
– クラス定数参照 class::const, 1:参照対象クラ
ス指定, 2:定数指定
• AST_ASSIGN
– 代入
• AST_ASSIGN_REF
– 参照代入
• AST_ASSIGN_OP
– 演算代入
• AST_BINARY_OP
– 四則演算
通常ノード(3)
• AST_GREATER
– >
• AST_GREATER_EQUAL
– >=
• AST_AND
– &&
• AST_OR
– ||
• AST_ARRAY_ELEM
– 配列リテラルの要素 1:value または key 2:key /
attr 0:通常 1:リファレンス
• AST_NEW
– new
• AST_INSTANCEOF
– instanceof
• AST_YIELD
– yield
• AST_COALESCE
– ??
• AST_STATIC
– 静的変数(非クラス)
• AST_WHILE
– while
• AST_DO_WHILE
– do-while
• AST_IF_ELEM
– if および elseif の条件(0)と ブロック(1) /
else は 条件なしでブロックのみ
• AST_SWITCH
– switch
• AST_SWITCH_CASE
– case
• AST_DECLARE
– declare
• AST_USE_TRAIT
– use (クラス内)
• AST_TRAIT_PRECEDENCE
– 0:AST_METHOD_REFERENCE(instead 指定の左側選ば
れるほう) 1:AST_NAME_LIST(instead 指定の右側ク
ラス名群)
• AST_METHOD_REFERENCE
– 0:AST_ZVAL(クラス名) 1:AST_ZVAL(メソッド名)
• AST_NAMESPACE
– namespace
• AST_USE_ELEM
– 0:AST_ZVAL(use で指定された本体) 1:AST_ZVAL(AS
以降)|NULL
• AST_TRAIT_ALIAS
– 0:AST_METHOD_REFERENCE
• AST_GROUP_USE
– グループ化されたuse (use A{B,C AS D})
0:AST_ZVAL(ブレース前) 1:AST_USE(ブレース内)
通常ノード(4)
/* 3 child nodes */
• AST_METHOD_CALL
– メソッド呼び出し
• AST_STATIC_CALL
– 静的メソッド呼び出し
• AST_CONDITIONAL
– 三項演算子 および ?:
• AST_TRY
– try 0:AST_STMT_LIST(try ブロック)
1:AST_CATCH_LIST
2:AST_STMT_LIST(finally ブロッ
ク)|NULL
• AST_CATCH
– catch 0:AST_ZVAL(catch するクラス
名) 1:AST_ZVAL(変数名)
2:AST_STMT_LIST(catch ブロック) /
7.1 になって、 0 は AST_NAME_LIST
に変更 (複数指定できるようになったの
で)
• AST_PARAM
– 引数定義 0:AST_ZVAL(型)|NULL
1:AST_ZVAL(変数名) 2:AST_ZVAL(デ
フォルト値)|NULL ref_flag?
• AST_PROP_ELEM
– プロパティ定義 0:AST_ZVAL(プロパティ
名) 1:expr(値)
2:AST_ZVAL(doc_comment)
• AST_CONST_ELEM
– 定数定義 0:AST_ZVAL(定数名)
2:expr(定数値)
/* 4 child nodes */
• AST_FOR
– for(0;1;2) {3}
• AST_FOREACH
– foreach(0 as 1=>2){3} あるいは
foreach(0 as 1){3} (2未使用)
コードとASTの対比
function hello($name){
echo "Hello $name";
}
hello('php');
型 デフォルト引数
戻り値の型
未使用
HHVM における AST
• AST ノードの基底クラスである
HPHP::Construct があり、Statement と
Expression に分かれる
• HPHP::Compiler::Parser::parseImpl が、
parseImpl7 あるいは parseImpl5 を呼び
出し、HPHP::Compiler::Parser::m_tree
に StatementList が作られる
• zend_ast_kind のそれぞれに対応するクラ
スがある感じ
HPHP::Statement
• 構造を表すノードの
基本クラス
• HPHP::StatementList が
ZEND_STATEMENT_LIST に
相当
HPHP::Expression
• 評価式や値を表す
ノードの基本クラス
• AwaitExpression
あたりは hhvm なら
では
PHP AST の特徴
構文が変化すれば構造が変わる
• Short List Syntax
– (ZEND_)AST_LIST 廃止
– AST_ARRAY: attr に array 形式を保持
• Class Constant Visibility
– AST_CONST_DECL: attr にアクセス権を保持
– AST_CONST_ELEM: 2child から 3childに
• Catching Multiple Exception
– catch (E $e) -> catch (E1|E2 $e)
– AST_CATCH の 1番目の子要素が ZVAL 単体だったが、
7.1 で AST_NAME_LIST -> AST_ZVAL に
ZEND_ARRAY_SYNTAX_LIST (list)
ZEND_ARRAY_SYNTAX_LONG (array)
ZEND_ARRAY_SYNTAX_SHORT ([])
逆変換可能
• 元のコードと等価なコードを作れる
– 括弧やインデントは最低限必要なものだけ
• zend_ast_export 関数
– assert に利用されている
– 後述する Astkit::export で利用可能
• 自前で用意すれば別のコードにもできる
専用のメモリ空間
• CG(ast_arena)
– zend_arena_createによって確保
– 初期サイズは 32MB / 必要に応じて拡張
• zend_ast_alloc 関数
– AST生成用のメモリを割り当てるための関数
– zend_arena_alloc を利用し、 CG(ast_arena) か
らメモリを割り当てる(足りなければ拡張する)
• Opcode生成後に解放
– zend_arena_destroyにて解放
短命
• parse時に作られてOpcodeを生成したら破棄
される
• 現存の拡張は、parse後にzval(phpから扱え
る変数)に変換することで php スクリプト
から利用可能にしている
• zend_ast_process 関数ポインタを使って
フック可能
– AST構築直後 (Opcode 生成前)に呼ばれる
– 拡張を書けば AST 改変も可能
この章のまとめ
• ASTノードの主な構成要素は種別、付属情報、
行番号、子ノード
• 4つに大別 特殊,定義,リスト,通常
• バージョン間での互換性は考えられていない
• 拡張からであればいろいろといじれる
– 現存する拡張だけではできないこともある
第4章
ASTの利用法
既存の拡張を利用する
php-ast
• https://github.com/nikic/php-ast
• astparse_file あるいは
astparse_code で AST 構築
• astNode をベースクラスとした astDecl
• リスト型のノード は Node に統合
• Zval型のノードは Node の exprプロパティ
• STMT_LIST(A) の子要素に STMT_LIST(B) が含
まれる場合は、B の子を A の子として併合
astkit
• https://github.com/sgolemon/astkit
• AstKit::parseString あるいは
AstKit::parseFile で AST構築
• AstKit をベースクラスとした AstKitList,
AstKitDecl, AstKitZval にマッピングさ
れる
• $AstKit->export でコードに変換
astparse_code('<?php 1 + 2;')
全ノードをphp スクリプトで扱える構造に変換 (CG(ast) は破棄)
C言語 (CG(ast))
array
astnode
kind: 520
flags: 1
lineno: 1
left: 1
right: 2
astNode
kind: 133
flags:0
lineno: 0
children:
AST_ZVAL
1
AST_ZVAL
2
AST_BINA
RY_OP +
AST_STMT
_LIST
ast_to_zval
php スクリプト (zval)
Astkit::parseString('1+2;')
先頭のノードのみ生成。操作により子の AstKit が生成される
C language (CG(ast) =
astkit_tree->tree)
AstKitList
AST_ZVAL
1
AST_ZVAL
2
AST_BINA
RY_OP +
AST_STMT
_LIST
php script (zval)
AstKit
AstKitZval
getChild(0) で生成
getChild(0,false) で生成
getChild(0) ならば int(1)
それぞれの特徴
• php-ast
– php スクリプトから扱いやすい
– 初期のコストが大きめ
– 異なるバージョンでの変換処理を拡張側で頑張っ
てる部分もある
• astkit
– C の ast そのままのメモリを操作
– 利用する箇所が部分的ならば低コストか
– ast 構造の変化によって php 側での操作が大き
く変わる
利用例
• https://github.com/etsy/phan
• php-ast を利用した静的解析ツール
• 詳細は別セッションでされてるんじゃな
いかな
新たに拡張を作る
phpast
• https://github.com/do-aki/phpast
• 勉強目的で作った
• ASTの可視化: phpast + graphviz
https://dooakitestapp.herokuapp.com/phpa
st/webapp/
• php のコードで ast を操作して、実行する予定
のコードを改変することができたら面白いよなぁ
という妄想をしつつ、いまだ妄想のまま
今後考えられる AST 利用例
• さらなる最適化
– まだまだ静的に解決できる個所はある
– この拡張を導入するだけで速くなる! なんてことも
• コンバータ(トランスパイラ) (php7 -> hack)
– php7 のコードから型推論できれば、あるいは
– cf: https://speakerdeck.com/anatoo/type-
inference-on-php
• syntax grep (一致する構文を探索,置換)
• power assert (実行過程をひとつひとつ出力)
AST導入により 今後考えられる
php の進化
• コード(AST)を受け取ることができる関数
– 今は assert のみ
– ユーザランドでこれができると面白い
– php のコードがそのままSQLになったり
• ソースコードフィルタ
– ルールに従って AST を入れ替える
– 難読化に使える?
※ただの妄想です
まとめ
• ASTそのものは複雑なものではない
• 拡張を使ってASTを操作することもできる
けど、拡張を書けばもっといろいろでき
る
• 興味を持ったら拡張書いてみよう!
以上
• もっと深く知りたい人は闇php勉強会へ
(blank)

More Related Content

PHP AST 徹底解説

  • 1. 2016/11/03 PHP Conference Japan 2016 do_aki 1 updated 2016-12-13
  • 5. PHP Compiler in PHP PHP Script Opcode Request Output Compiler Lexing Parsing Compilation VM Execution INCLUDE_OR_EVAL
  • 7. Lexical Analysis 字句解析 • ソースコードをトークン(意味を持つ最 小の単位)に分解 • token_get_all 関数 で確認できる • 余談: token_get_all の中では実際にコ ンパイル処理が行われる(ただし、 Opcode 生成は省略)
  • 8. ソースコード(php スクリプト) <?php function hello ( $name ) { echo “HELLO $name“ ; } hello ( “php“ ) ;
  • 9. 字句解析 <?php function hello ( $name ) { echo “HELLO $name“ ; } hello ( “php“ ) ; T_FUNCTION T_STRING ( ) { } T_ECHO T_ENCAPSED_AND _WHITESPACE ; T_STRING ( ) ; T_OPEN_TAG T_VARIABLE T_VARIABLE T_VARIABLE“ “ ひとつひとつがトークン (意味を持つ最小の単位)
  • 10. Syntax Analysis 構文解析 • トークンの並びから構文を導く • 構文に応じたASTを構築する • 該当する構文が見つからないときは Parse Error (7.0 から Exception)
  • 11. 構文解析 T_FUNCTION T_STRING ( ) { } T_ECHO T_ENCAPSED_AND_WHITESPACE ; T_STRING ( ) ; T_OPEN_TAG T_VARIABLE T_VARIABLE function declaration function call
  • 13. Bytecode Generation Opcode生成 • AST を解析して Opcode を生成 • 狭義のコンパイル(zend_compile.c) • いくつかの最適化が施される(後述) • 構文としては正しいが不正なコードは Compile Error (Fatal error) ex: const A = 1 + f();
  • 14. Opcode (vld) line #* E I O op fetch ext return operands ---------------------------------------------------------------- 2 0 E > RECV !0 3 1 NOP 2 FAST_CONCAT ~1 'HELLO+', !0 3 ECHO ~1 4 4 > RETURN null line #* E I O op fetch ext return operands ---------------------------------------------------------------- 2 0 E > NOP 6 1 INIT_FCALL 'hello' 2 SEND_VAL 'php' 3 DO_FCALL 0 4 > RETURN 1 function hello() call hello()
  • 15. この章のまとめ • php はコンパイラを持っている • 基本的には php スクリプトを読み込む度 にコンパイルが行われる • 字句解析、構文解析、Opcode生成 構文解析の結果 AST が生成される
  • 18. php5 (1 pass / 151構文(5.6)) 字句解析 + 構文解析 + Opcode生成 php7 (2 pass / 127構文(7.0)) 字句解析+構文解析 Opcode生成
  • 19. php5 (1 pass / 151構文(5.6)) 字句解析 + 構文解析 + Opcode生成 php7 (2 pass / 127構文(7.0)) 字句解析+構文解析 Opcode生成 最適化の余地
  • 21. 定数の畳み込み $sec_in_day = 60 * 60 * 24; $sec_in_day = 86400; ※実は OpCache でも行われている class A { const HOGE = ‘hoge‘; } echo A::HOGE; echo ‘hoge‘; コンパイル時点で定義済みの定数に対してのみ有効 (autoload より pre include のほうが効きやすい)
  • 22. 静的関数展開(定数化) • 関数呼び出しコストの削減 • 定数畳み込みとの組み合わせも有効 ex: strlen(’hoge’) + 1 -> 5 strlen(’hoge’) -> 4 ord(’A’) -> 65 / 7.1~ chr(65) -> ‘A‘ / 7.1~
  • 23. 静的関数展開(call) • defined_funcが定義済みの場合のみ展開 • コンパイル時点で定義済みの関数に対し てのみ発生 (定数と同様) function func() {} call_user_func(’func’); func(); 実際には、ほぼ等価であるものの若干異なり、 EXT_FCALL_BEGIN / EXT_FCALL_END が発行されない
  • 24. 静的関数展開(cast) / 7.1~ boolval($var) -> (bool)$var intval($var) -> (int)$var floatval($var) -> (float)$var doubleval($var) -> (float)$var strval($var) -> (string)$var
  • 26. 静的関数展開の無効化 • 静的関数展開は、 CG(compiler_options) に ZEND_COMPILE_NO_BUILTINS をセット することで無効にできる • CG(compiler_options) に ZEND_COMPILE_NO_BUILTINS ビットをセッ トすることで静的関数展開を無効にできる • 拡張ならば、 CG(compiler_options) を制 御可能
  • 27. 静的zval構築 INIT_ARRAY(1) -> temp ADD_ARRAY_ELEMENT(2) -> temp ADD_ARRAY_ELEMENT(3) -> temp ASSIGN(temp->$a) ASSIGN([1,2,3]->$a) $a = [1,2,3]; ~5.6 7.0~
  • 28. 静的ショートサーキット • condition 部分で行っている変数参照や関 数呼び出しに関する Opcode が生成されな くなる • JMPZ および 実行されることがないブロッ クのOpcode は(無駄に)生成されてしまう if (1 || condition) -> if (true) if (0 && condition) -> if (false)
  • 29. print の echo 化 • ZEND_PRINT 廃止 -> ZEND_ECHO に統一 • echo も print も同じ Opcode に • print の戻り値が利用される場合のみ、 それを常に 1 で置き換え return print(’hello’); echo ’hello’; return 1;
  • 30. 条件コンパイル “assert() は PHP 7 で言語構造となり、” (http://php.net/manual/ja/function.assert.php) とあるが、構文解析においては関数呼び出しで、 引数部のコード(AST) を逆変換している assert($v === 0); [zend.assertions >= 0] assert(‘assert($v === 0)‘); [zend.assertions < 0] (assert の呼び出しがなったことに)
  • 31. コンパイルタイミングによって Opcode が変化する例 class A { const X = 1; } a.php require_once ‘a.php‘ echo A::X; echo.php require_once ‘a.php‘ require_once ‘echo.php‘ require.php > php echo.php echo.php をコンパイルする時点 では a.php はコンパイルされて いない > php require.php echo.php をコンパイルする時点 で a.php はコンパイル済み
  • 32. line #* E I O op fetch ext return operands ----------------------------------------------------------- 2 0 E > INCLUDE_OR_EVAL 'a.php', REQUIRE_ONCE 3 1 FETCH_CONSTANT ~1 'A', 'X' 2 ECHO ~1 3 > RETURN 1 line #* E I O op fetch ext return operands ----------------------------------------------------------- 0 E > INCLUDE_OR_EVAL 'a.php', REQUIRE_ONCE 3 1 ECHO 1 2 > RETURN 1 > php echo.php > php require.php (の時の echo.php)
  • 33. あらかじめ require しておくことで早くなる可能性も……?(未検証) line #* E I O op fetch ext return operands ----------------------------------------------------------- 2 0 E > INCLUDE_OR_EVAL 'a.php', REQUIRE_ONCE 3 1 FETCH_CONSTANT ~1 'A', 'X' 2 ECHO ~1 3 > RETURN 1 line #* E I O op fetch ext return operands ----------------------------------------------------------- 0 E > INCLUDE_OR_EVAL 'a.php', REQUIRE_ONCE 3 1 ECHO 1 2 > RETURN 1 > php echo.php > php require.php (の時の echo.php)
  • 34. この章のまとめ • AST 導入そのものによる影響は少ない • コンパイルに関するコードがシンプルに なり、最適化の余地が生まれた • 小手先の最適化が不要になった
  • 36. Syntax tree(Parse tree) 構文木(解析木) ex: 1 / (2 + 3) 1 2 3/ ( )+ 解析木 :=トークンを葉として、 構成を木構造で表現したもの
  • 37. Abstract syntax tree 抽象構文木 ex: 1 / (2 + 3) 1 2 3 + / 抽象構文木 := 構文木から、 その後の処理に不要なデータ をそぎ落としたもの
  • 39. zend_ast (基本形) • Zend/zend_ast.h / Zend/zend_ast.c typedef uint16_t zend_ast_kind; typedef uint16_t zend_ast_attr; struct _zend_ast { zend_ast_kind kind; /* Type of the node (ZEND_AST_* enum constant) */ zend_ast_attr attr; /* Additional attribute, use depending on node type */ uint32_t lineno; /* Line number */ zend_ast *child[1]; /* Array of children (using struct hack) */ }; typedef struct _zend_ast zend_ast; // <- Zend/zend_types.h Zend/zend_ast.h より 一部見やすさのために改変
  • 40. zend_ast (基本形) • Zend/zend_ast.h / Zend/zend_ast.c typedef uint16_t zend_ast_kind; typedef uint16_t zend_ast_attr; struct _zend_ast { zend_ast_kind kind; /* Type of the node (ZEND_AST_* enum constant) */ zend_ast_attr attr; /* Additional attribute, use depending on node type */ uint32_t lineno; /* Line number */ zend_ast *child[1]; /* Array of children (using struct hack) */ }; typedef struct _zend_ast zend_ast; // <- Zend/zend_types.h Zend/zend_ast.h より 一部見やすさのために改変 種別 行番号 子ノード 付属情報
  • 42. zend_ast_kind • ZEND_AST_* • 全98種 (7.0) / 7.1は97種 • 大まかに分類して4系統 – 特殊ノード ZEND_AST_ZVAL / (ZEND_AST_ZNODE) – 定義ノード ZEND_AST_CLASS など – リストノード ZEND_AST_STMT_LIST など – 通常ノード ZEND_AST_VAR など
  • 43. ZEND_AST_ZVAL (特殊ノード) • zval を包含するノード(行はzval に) • zval := php スクリプトにおける変数 • リテラル や 変数名、呼び出し関数名等 • 常にリーフ(末端) • zend_ast_create_zval / zend_ast_create_zval / zend_ast_create_zval_from_str / zend_ast_create_zval_from_long によって作成 • (余談) astを保持する zval(定数式)もある typedef struct _zend_ast_zval { zend_ast_kind kind; zend_ast_attr attr; zval val; /* Lineno is stored in val.u2.lineno */ } zend_ast_zval;
  • 44. 定義ノード • ZEND_AST_FUNC_DECL / ZEND_AST_CLOSURE / ZEND_AST_METHOD / ZEND_AST_CLASS のみ • 常に4つ分の子要素を確保(NULL の場合も) • zend_ast_create_decl によって作成 typedef struct _zend_ast_decl { zend_ast_kind kind; zend_ast_attr attr; /* Unused */ uint32_t start_lineno; uint32_t end_lineno; uint32_t flags; unsigned char *lex_pos; zend_string *doc_comment; zend_string *name; zend_ast *child[4]; } zend_ast_decl;
  • 45. 定義ノード • AST_FUNC_DECL 関数定義 – 1:AST_PARAM_LIST(仮引数), 2:未使用, 3:AST_STMT_LIST (内部), 4: AST_ZVAL(戻り値型) • AST_CLOSURE 無名関数定義 – 1:AST_PARAM_LIST(仮引数), 2:AST_CLOSURE_USES (use), 3:AST_STMT_LIST (内部), 4: AST_ZVAL(戻り値型) • AST_METHOD メソッド定義 – 1:AST_PARAM_LIST(仮引数), 2:未使用, 3:AST_STMT_LIST (内部), 4: AST_ZVAL(戻り値型) • AST_CLASS クラス,無名クラス,trait,interface 定義 – 1:AST_ZVAL(継承元), 2:AST_NAME_LIST (implements), 3:AST_STMT_LIST (内部), 4:未使用
  • 46. リストノード • 可変長の子を持つノード • zend_ast_create_list によって作成 / zend_ast_list_add で子を追加 • ex) ZEND_AST_STMT – ZEND_AST_STMTは乱暴に言えば、ほぼ行。 – 子に ZEND_AST_STMT を含む場合もある typedef struct _zend_ast_list { zend_ast_kind kind; zend_ast_attr attr; uint32_t lineno; uint32_t children; zend_ast *child[1]; } zend_ast_list; 子の数
  • 47. リストノード • AST_ARG_LIST – 関数、メソッド呼び出しの引数群 • AST_LIST (7.0まで) – • AST_ARRAY – array 定義(全体/個々の要素は AST_ARRAY_ELEM) • AST_ENCAPS_LIST – 変数を包含する文字列 (ダブルクォテーション文 字列、HEREDOC、バッククォテーション文字列) • AST_EXPR_LIST – for文の (x;x;x) • AST_STMT_LIST – ステートメント (; で終わる行すべて) • AST_IF – if 文全体 (子は AST_IF_ELEM) • AST_SWITCH_LIST – switch 文全体 • AST_CATCH_LIST – catch 節 (子は AST_CATCH) • AST_PARAM_LIST – 関数、メソッド定義の引数群 (子は AST_PARAM) • AST_CLOSURE_USES – 無名関数の use 変数リスト (子は AST_ZVAL) • AST_PROP_DECL – プロパティ定義 (子は AST_PROP_ELEM) • AST_CONST_DECL – クラス外で定義される定数 (子は AST_CONST_ELEM) • AST_CLASS_CONST_DECL – クラス外で定義される定数(const) (子は AST_CONST_ELEM) • AST_NAME_LIST – interface の extends や insteadof の後に 続くクラス名群, catch のクラス名群(7.1) (子 は AST_ZVAL) • AST_TRAIT_ADAPTATIONS – trait の use のブレース内 (子は AST_TRAIT_PRECEDENCE (insteadof) または AST_TRAIT_ALIAS (as) ) • AST_USE – 名前空間のuse (子は AST_USE_ELEM)
  • 48. 通常ノード • 特殊,定義,リスト 以外のすべてのノード • zend_ast_create (attr なし) / zend_ast_create_ex / zend_ast_create_binary_op / zend_ast_create_assign_op / zend_ast_create_cast によって作成 AST_VAR AST_ZVAL ‘a‘ isset($a) AST_ISSET AST_ZVAL ‘func‘ AST_ ARG_LIST func() AST_CALL リストノード AST_ZVAL ‘A‘ AST_ZVAL ‘X‘ echo A::X AST_CLASS _CONST AST_ECHO
  • 49. 通常ノード(1) /* 0 child nodes */ • AST_MAGIC_CONST – __LINE__, __FILE__ 等のマジック定数 / attr:=マ ジック定数種別 • AST_TYPE – 引数型指定 / attr=T_ARRAY,T_CALLABLE /* 1 child node */ • AST_VAR – 変数参照 / attr 未使用 • AST_CONST – 定義済み定数(null,true,false,NAN などが子の ZVALの値) / attr 未使用 • AST_UNPACK – 関数呼び出し時の ...expr • AST_UNARY_PLUS – +expr / attr 未使用 • AST_UNARY_MINUS – -expr / attr 未使用 • AST_CAST – (int) とか./ attr := IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, _IS_BOOL, IS_NULL • AST_EMPTY – empty(expr) / attr 未使用 • AST_ISSET – isset(expr) / attr 未使用 • AST_SILENCE – @expr (エラー抑制) / attr 未使用 • AST_SHELL_EXEC – `backticks_expr` / attr 未使用 • AST_CLONE – clone expr / attr 未使用 • AST_EXIT – exit(expr) / attr 未使用 • AST_PRINT – print expr / attr 未使用 • AST_INCLUDE_OR_EVAL – include,require,eval / attr := 1:ZEND_EVAL, 2:ZEND_INCLUDE, 4:ZEND_INCLUDE_ONCE, 8:ZEND_REQUIRE ,16:ZEND_REQUIRE_ONCE • AST_UNARY_OP – !expr または ~expr / attr := ZEND_BOOL_NOT, ZEND_BW_NOT • AST_PRE_INC – ++variable / attr 未使用 • AST_PRE_DEC – --variable / attr 未使用 • AST_POST_INC – variable++ / attr 未使用 • AST_POST_DEC – variable-- / attr 未使用 • AST_YIELD_FROM – yield from expr / attr 未使用
  • 50. 通常ノード(2) • AST_GLOBAL – global simple_variable / attr 未使用 • AST_UNSET – unset(variable) / attr 未使用 • AST_RETURN – return expr / attr 未使用 • AST_LABEL – LABEL: / attr 未使用 • AST_REF – &variable / attr 未使用 • AST_HALT_COMPILER – __halt_compiler() 子には __COMPILER_HALT_OFFSET__ にセットされる値 / attr 未使用 • AST_ECHO – echo expr / attr 未使用 • AST_THROW – throw expr / attr 未使用 • AST_GOTO – goto LABEL / attr 未使用 • AST_BREAK – break expr / attr 未使用 • AST_CONTINUE – continue expr / attr 未使用 /* 2 child nodes */ • AST_DIM – 配列要素の参照 variable[N],variable{N}, 1: 参照対象配列, 2:指定要素 / attr 未使用 • AST_PROP – プロパティ参照 variable->variable, variable->{expr}, 1:参照対象オブジェクト, 2:指定プロパティ / attr 未使用 • AST_STATIC_PROP – 静的プロパティ参照 variable::variable, 1: 参照対象クラス指定, 2:指定プロパティ / attr 未使用 • AST_CALL – 関数呼び出し LABEL(), variable(), 1:呼び出 し関数指定, 2: AST_ARG_LIST / attr 未使用 • AST_CLASS_CONST – クラス定数参照 class::const, 1:参照対象クラ ス指定, 2:定数指定 • AST_ASSIGN – 代入 • AST_ASSIGN_REF – 参照代入 • AST_ASSIGN_OP – 演算代入 • AST_BINARY_OP – 四則演算
  • 51. 通常ノード(3) • AST_GREATER – > • AST_GREATER_EQUAL – >= • AST_AND – && • AST_OR – || • AST_ARRAY_ELEM – 配列リテラルの要素 1:value または key 2:key / attr 0:通常 1:リファレンス • AST_NEW – new • AST_INSTANCEOF – instanceof • AST_YIELD – yield • AST_COALESCE – ?? • AST_STATIC – 静的変数(非クラス) • AST_WHILE – while • AST_DO_WHILE – do-while • AST_IF_ELEM – if および elseif の条件(0)と ブロック(1) / else は 条件なしでブロックのみ • AST_SWITCH – switch • AST_SWITCH_CASE – case • AST_DECLARE – declare • AST_USE_TRAIT – use (クラス内) • AST_TRAIT_PRECEDENCE – 0:AST_METHOD_REFERENCE(instead 指定の左側選ば れるほう) 1:AST_NAME_LIST(instead 指定の右側ク ラス名群) • AST_METHOD_REFERENCE – 0:AST_ZVAL(クラス名) 1:AST_ZVAL(メソッド名) • AST_NAMESPACE – namespace • AST_USE_ELEM – 0:AST_ZVAL(use で指定された本体) 1:AST_ZVAL(AS 以降)|NULL • AST_TRAIT_ALIAS – 0:AST_METHOD_REFERENCE • AST_GROUP_USE – グループ化されたuse (use A{B,C AS D}) 0:AST_ZVAL(ブレース前) 1:AST_USE(ブレース内)
  • 52. 通常ノード(4) /* 3 child nodes */ • AST_METHOD_CALL – メソッド呼び出し • AST_STATIC_CALL – 静的メソッド呼び出し • AST_CONDITIONAL – 三項演算子 および ?: • AST_TRY – try 0:AST_STMT_LIST(try ブロック) 1:AST_CATCH_LIST 2:AST_STMT_LIST(finally ブロッ ク)|NULL • AST_CATCH – catch 0:AST_ZVAL(catch するクラス 名) 1:AST_ZVAL(変数名) 2:AST_STMT_LIST(catch ブロック) / 7.1 になって、 0 は AST_NAME_LIST に変更 (複数指定できるようになったの で) • AST_PARAM – 引数定義 0:AST_ZVAL(型)|NULL 1:AST_ZVAL(変数名) 2:AST_ZVAL(デ フォルト値)|NULL ref_flag? • AST_PROP_ELEM – プロパティ定義 0:AST_ZVAL(プロパティ 名) 1:expr(値) 2:AST_ZVAL(doc_comment) • AST_CONST_ELEM – 定数定義 0:AST_ZVAL(定数名) 2:expr(定数値) /* 4 child nodes */ • AST_FOR – for(0;1;2) {3} • AST_FOREACH – foreach(0 as 1=>2){3} あるいは foreach(0 as 1){3} (2未使用)
  • 53. コードとASTの対比 function hello($name){ echo "Hello $name"; } hello('php'); 型 デフォルト引数 戻り値の型 未使用
  • 54. HHVM における AST • AST ノードの基底クラスである HPHP::Construct があり、Statement と Expression に分かれる • HPHP::Compiler::Parser::parseImpl が、 parseImpl7 あるいは parseImpl5 を呼び 出し、HPHP::Compiler::Parser::m_tree に StatementList が作られる • zend_ast_kind のそれぞれに対応するクラ スがある感じ
  • 58. 構文が変化すれば構造が変わる • Short List Syntax – (ZEND_)AST_LIST 廃止 – AST_ARRAY: attr に array 形式を保持 • Class Constant Visibility – AST_CONST_DECL: attr にアクセス権を保持 – AST_CONST_ELEM: 2child から 3childに • Catching Multiple Exception – catch (E $e) -> catch (E1|E2 $e) – AST_CATCH の 1番目の子要素が ZVAL 単体だったが、 7.1 で AST_NAME_LIST -> AST_ZVAL に ZEND_ARRAY_SYNTAX_LIST (list) ZEND_ARRAY_SYNTAX_LONG (array) ZEND_ARRAY_SYNTAX_SHORT ([])
  • 59. 逆変換可能 • 元のコードと等価なコードを作れる – 括弧やインデントは最低限必要なものだけ • zend_ast_export 関数 – assert に利用されている – 後述する Astkit::export で利用可能 • 自前で用意すれば別のコードにもできる
  • 60. 専用のメモリ空間 • CG(ast_arena) – zend_arena_createによって確保 – 初期サイズは 32MB / 必要に応じて拡張 • zend_ast_alloc 関数 – AST生成用のメモリを割り当てるための関数 – zend_arena_alloc を利用し、 CG(ast_arena) か らメモリを割り当てる(足りなければ拡張する) • Opcode生成後に解放 – zend_arena_destroyにて解放
  • 61. 短命 • parse時に作られてOpcodeを生成したら破棄 される • 現存の拡張は、parse後にzval(phpから扱え る変数)に変換することで php スクリプト から利用可能にしている • zend_ast_process 関数ポインタを使って フック可能 – AST構築直後 (Opcode 生成前)に呼ばれる – 拡張を書けば AST 改変も可能
  • 62. この章のまとめ • ASTノードの主な構成要素は種別、付属情報、 行番号、子ノード • 4つに大別 特殊,定義,リスト,通常 • バージョン間での互換性は考えられていない • 拡張からであればいろいろといじれる – 現存する拡張だけではできないこともある
  • 65. php-ast • https://github.com/nikic/php-ast • astparse_file あるいは astparse_code で AST 構築 • astNode をベースクラスとした astDecl • リスト型のノード は Node に統合 • Zval型のノードは Node の exprプロパティ • STMT_LIST(A) の子要素に STMT_LIST(B) が含 まれる場合は、B の子を A の子として併合
  • 66. astkit • https://github.com/sgolemon/astkit • AstKit::parseString あるいは AstKit::parseFile で AST構築 • AstKit をベースクラスとした AstKitList, AstKitDecl, AstKitZval にマッピングさ れる • $AstKit->export でコードに変換
  • 67. astparse_code('<?php 1 + 2;') 全ノードをphp スクリプトで扱える構造に変換 (CG(ast) は破棄) C言語 (CG(ast)) array astnode kind: 520 flags: 1 lineno: 1 left: 1 right: 2 astNode kind: 133 flags:0 lineno: 0 children: AST_ZVAL 1 AST_ZVAL 2 AST_BINA RY_OP + AST_STMT _LIST ast_to_zval php スクリプト (zval)
  • 68. Astkit::parseString('1+2;') 先頭のノードのみ生成。操作により子の AstKit が生成される C language (CG(ast) = astkit_tree->tree) AstKitList AST_ZVAL 1 AST_ZVAL 2 AST_BINA RY_OP + AST_STMT _LIST php script (zval) AstKit AstKitZval getChild(0) で生成 getChild(0,false) で生成 getChild(0) ならば int(1)
  • 69. それぞれの特徴 • php-ast – php スクリプトから扱いやすい – 初期のコストが大きめ – 異なるバージョンでの変換処理を拡張側で頑張っ てる部分もある • astkit – C の ast そのままのメモリを操作 – 利用する箇所が部分的ならば低コストか – ast 構造の変化によって php 側での操作が大き く変わる
  • 70. 利用例 • https://github.com/etsy/phan • php-ast を利用した静的解析ツール • 詳細は別セッションでされてるんじゃな いかな
  • 72. phpast • https://github.com/do-aki/phpast • 勉強目的で作った • ASTの可視化: phpast + graphviz https://dooakitestapp.herokuapp.com/phpa st/webapp/ • php のコードで ast を操作して、実行する予定 のコードを改変することができたら面白いよなぁ という妄想をしつつ、いまだ妄想のまま
  • 73. 今後考えられる AST 利用例 • さらなる最適化 – まだまだ静的に解決できる個所はある – この拡張を導入するだけで速くなる! なんてことも • コンバータ(トランスパイラ) (php7 -> hack) – php7 のコードから型推論できれば、あるいは – cf: https://speakerdeck.com/anatoo/type- inference-on-php • syntax grep (一致する構文を探索,置換) • power assert (実行過程をひとつひとつ出力)
  • 74. AST導入により 今後考えられる php の進化 • コード(AST)を受け取ることができる関数 – 今は assert のみ – ユーザランドでこれができると面白い – php のコードがそのままSQLになったり • ソースコードフィルタ – ルールに従って AST を入れ替える – 難読化に使える? ※ただの妄想です

Editor's Notes

  • #40: ほかにも zval, list, decl といった構造体もあるが、 kind, attr を持ち、lineno を包含し、 0以上の子ノードを持つ というのは同じ
  • #43: ruby は 105種