中学受験算数・徹底攻略シリーズ(1) 数の性質・徹底攻略suguru.jp/中学受験算数・徹底攻略シリーズ(1) 数の性質・徹底攻略 この教材プリントは,「むずかしすぎず,やさしすぎもしない」ぐら
PHP AST 徹底解説(補遺)
-
Upload
doaki -
Category
Technology
-
view
1.361 -
download
0
Transcript of PHP AST 徹底解説(補遺)
PHP AST 徹底解説( 補遺 )
2016/12/11 第七回闇 PHP 勉強会
do_aki1
updated 2016-12-13
このスライドは
• PHP カンファレンス 2016 で発表した 「 PHP AST 徹底解説」 において説明しきれなかった部分を補足した際に用いたもの
• AST に関する部分については、元のスライドにマージ済みなので http://www.slideshare.net/do_aki/php-ast を参照してください
@do_aki
@do_aki
http://do-aki.net/
PHP カンファレンス 2016 で話したこと
PHP
Compiler in PHP
PHP Script
Opcode
Request
Output
Compiler
Lexing
Parsing
Compilation
VM
Execution
INCLUDE_OR_EVAL
php5 (1 pass / 151 構文 (5.6))
字句解析 + 構文解析 + Opcode 生成
php7 (2 pass / 127 構文 (7.0))
字句解析 + 構文解析 Opcode 生成
複雑
最適化の余地
PHP の 抽象構文木
<?php1/(2+3);
種別
付属情報
子ノード
子ノード
あじぇんだ
1 token_get_all の話
2 文字列結合 Opcode の話
3 strlen 静的展開 の話
4 同じコードから異なる Opcode の話
5 HHVM の話
6 ast 操作拡張 の話
token_get_all の話
token_get_all 関数
• PHP スクリプトを トークン分解して配列にする関数
• nikic/PHP-Parser で利用されてる
• tokenizer 拡張 (デフォルトで有効)
7.0 からの変更
• 7.0 から第 2引数に TOKEN_PARSE を指定できるようになった– 指定なし : 字句解析のみ / 5.6 まで同様– 指定あり : 構文解析もする / ast は破棄
• TOKEN_PARSE 指定でも Opcode 生成は省略 – zendparse を呼ぶが、 zend_compile_top_stmt
は呼ばない– Syntax Error (例外 ) は発生するが Compile Error (Fatal) は発生しない
– const A = f(); のようなコードも受け入れる
7.0.12 での実行例
token_name(319) => T_STRING
コンパイル と token_get_all の関係
字句解析 構文解析 Opcode 生成
狭義のコンパイルAST を生成トークンに分解
従来からのtoken_get_all
TOKEN_PARSE 付きの token_get_all
文字列結合 Opcode の話
ソースコード (php スクリプト )
<?php
function hello ( $name ) {
echo “HELLO $name“ ;
}
hello ( “php“ ) ;
Opcode (vld)line #* E I O op fetch ext return operands---------------------------------------------------------------- 2 0 E > EXT_NOP 1 RECV !0 3 2 EXT_STMT 3 NOP 4 FAST_CONCAT ~1 'Hello+', !0 5 ECHO ~1 4 6 EXT_STMT 7 > RETURN null
line #* E I O op fetch ext return operands---------------------------------------------------------------- 2 0 E > EXT_STMT 1 NOP 6 2 EXT_STMT 3 INIT_FCALL 'hello' 4 EXT_FCALL_BEGIN 5 SEND_VAL 'php' 6 DO_FCALL 0 7 EXT_FCALL_END 8 > RETURN 1
function
hello()
call hello()
Opcode (vld without xdebug)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()
FAST_CONCAT• encaps_list ( 変数を含む文字列 ) において、
要素が 2 つの時 (変数の前後どちらかに文字列を加える
時 ) に FAST_CONCAT になる (at zend_compile_encaps_list)
• 5.6 までは ADD_VAR + ADD_STRING
• ちなみに 3 要素以上ならば ROPE_INIT, ROPE_ADD, [ROPE_ADD,] ROPE_END
"{$a} lines"5.6
7.0
#* E I O op fetch ext return operands------------------------------------------------------0 E > ADD_VAR ~0 !01 ADD_STRING ~0 ~0, '+lines'
#* E I O op fetch ext return operands------------------------------------------------------0 E > NOP1 FAST_CONCAT ~1 !0, '+lines'
"{$a} / {$b} lines"5.6
7.0
#* E I O op fetch ext return operands------------------------------------------------------0 E > ADD_VAR ~0 !01 ADD_STRING ~0 ~0, '+%2F+'2 ADD_VAR ~0 ~0, !13 ADD_STRING ~0 ~0, '+lines'
#* E I O op fetch ext return operands------------------------------------------------------0 E > ROPE_INIT 4 ~3 !01 ROPE_ADD 1 ~3 ~3, '+%2F+'2 ROPE_ADD 2 ~3 ~3, !13 ROPE_END 3 ~2 ~3, '+lines'
parse しつつ opcode 生成していた時に、これを導入す
るのは困難だったはず
AST により、容易に導入できるようになった例かなと
strlen 静的展開の話
静的関数展開 ( 定数化 )
• 関数呼び出しコストの削減• 定数畳み込みとの組み合わせも有効ex: strlen(A::HOGE) + 1 -> 5
strlen(’hoge’) -> 4
ord(’A’) -> 65 / 7.1 ~
chr(65) -> ‘A‘ / 7.1 ~
mbstring.func_overload• strlen コールが mb_strlen に置き
換わる• EG(function_table) を操作して、上
書き
• コンパイル時に strlen が定数になってしまうと機能しないのでは? という疑問
静的関数展開の無効化• CG(compiler_options) に ZEND_COMPILE_NO_BUILTINS ビットをセットすることで静的関数展開を無効にできる
• CG(compiler_options) に ZEND_COMPILE_NO_BUILTIN_STRLENビットをセットすることで strlen の展開のみを無効にできる
• 拡張ならば、 CG(compiler_options) を制御可能
問題なかった
• mbstring 拡張の初期化 (RINIT) 時 func_overload が有効ならば ZEND_COMPILE_NO_BUILTIN_STRLEN を指定している
• func_overload は問題なく機能する
• func_overload が有効だと、 strlen 展開による恩恵を受けられない
同じコードから異なる 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 のほうが効きやすい)
コンパイルタイミングによって 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.phpecho.php をコンパイルする時点では a.php はコンパイルされていない
> php require.phpecho.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)
HHVM の話
HHVM におけるコンパイル
• 2 つの Lexer (yylex)Compiler5lex/Compiler7lex
• 2 つの Parser (yyparse)HPHP::Compiler::Parser::parseImpl5HPHP::Compiler::Parser::parseImpl7
• どちらも AST を生成する
HHVM における AST• AST ノードの基底クラスであるHPHP::Construct があり、 Statement と Expression に分かれる
• HPHP::Compiler::Parser::parseImpl が、 parseImpl7 あるいは parseImpl5 を呼び出し、 HPHP::Compiler::Parser::m_tree に StatementList が作られる
HPHP::Statement
• 構造を表すノードの基本クラス
• HPHP::StatementList が ZEND_STATEMENT_LIST に相当
HPHP::Expression
• 評価式や値を表すノードの基本クラス
• AwaitExpression あたりは hhvm ならでは
ast 操作拡張の話
php-ast• https://github.com/nikic/php-ast
• ast\parse_file あるいは ast\parse_code で AST 構築
• ast\Node をベースクラスとした ast\Decl• リスト型のノード は 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 でコードに変換
ast\parse_code('<?php 1 + 2;')
全ノードをphp スクリプトで扱える構造に変換 (CG(ast) は破棄)
C 言語 (CG(ast))
array
ast\node kind: 520 flags: 1 lineno: 1 left: 1 right: 2
ast\Node kind: 133 flags:0 lineno: 0 children:
AST_ZVAL1
AST_ZVAL2
AST_BINARY_OP +
AST_STMT_LIST
ast_to_zval
php スクリプト (zval)
Astkit::parseString('1+2;')
先頭のノードのみ生成。操作により子の AstKit が生成される
C language (CG(ast) = astkit_tree->tree)
AstKitList
AST_ZVAL1
AST_ZVAL2
AST_BINARY_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 側での操作が大
きく変わる
おしまい
(blank)