Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
2016/12/11
第七回闇PHP勉強会
do_aki
1
updated 2016-12-13
このスライドは
• PHPカンファレンス2016で発表した
「PHP AST 徹底解説」 において説明し
きれなかった部分を補足した際に用いた
もの
• AST に関する部分については、元のスラ
イドにマージ済みなので
http://www.s...
@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 の
抽象構文木
<?php
1/(2+3);
種別
付属情報
子ノード
子ノード
token_get_all の話
token_get_all 関数
• PHP スクリプトを トークン分解して配列
にする関数
• nikic/PHP-Parser で利用されてる
• tokenizer 拡張 (デフォルトで有効)
7.0 からの変更
• 7.0 から第2引数に TOKEN_PARSE を指定で
きるようになった
– 指定なし: 字句解析のみ / 5.6 まで同様
– 指定あり: 構文解析もする / ast は破棄
• TOKEN_PARSE 指定でも O...
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...
Opcode (vld without xdebug)
line #* E I O op fetch ext return operands
---------------------------------------------------...
FAST_CONCAT
• encaps_list (変数を含む文字列) において、
要素が2つの時(変数の前後どちらかに文字列を加える時)
に FAST_CONCAT になる
(at zend_compile_encaps_list)
• 5...
"{$a} lines"
5.6
7.0
#* E I O op fetch ext return operands
------------------------------------------------------
0 E > AD...
"{$a} / {$b} lines"
5.6
7.0
#* E I O op fetch ext return operands
------------------------------------------------------
0...
parse しつつ opcode 生成し
ていた時に、これを導入するの
は困難だったはず
AST により、容易に導入できる
ようになった例かなと
strlen 静的展開の話
静的関数展開(定数化)
• 関数呼び出しコストの削減
• 定数畳み込みとの組み合わせも有効
ex: strlen(A::HOGE) + 1 -> 5
strlen(’hoge’) -> 4
ord(’A’) -> 65 / 7.1~
chr(6...
mbstring.func_overload
• strlen コールが mb_strlen に置き換わ
る
• EG(function_table) を操作して、上書
き
• コンパイル時に strlen が定数になって
しまうと機能しないの...
静的関数展開の無効化
• CG(compiler_options) に
ZEND_COMPILE_NO_BUILTINS ビットをセッ
トすることで静的関数展開を無効にできる
• CG(compiler_options) に
ZEND_COMP...
問題なかった
• mbstring 拡張の初期化(RINIT)時
func_overload が有効ならば
ZEND_COMPILE_NO_BUILTIN_STRLEN を
指定している
• func_overload は問題なく機能する
• ...
同じコードから異なるOpcodeが
生成される話
定数の畳み込み
$sec_in_day = 60 * 60 * 24;
$sec_in_day = 86400;
※実は OpCache でも行われている
class A { const HOGE = ‘hoge‘; }
echo A::HOG...
コンパイルタイミングによって
Opcode が変化する例
class A { const X = 1; }
a.php
require_once 'a.php';
echo A::X;
echo.php
require_once 'a.php'...
line #* E I O op fetch ext return operands
-----------------------------------------------------------
2 0 E > INCLUDE_OR_...
HHVM の話
HHVM におけるコンパイル
• 2つのLexer (yylex)
Compiler5lex/Compiler7lex
• 2つのParser (yyparse)
HPHP::Compiler::Parser::parseImpl5
HPHP:...
HHVM における AST
• AST ノードの基底クラスである
HPHP::Construct があり、Statement
と Expression に分かれる
• HPHP::Compiler::Parser::parseImpl
が、pa...
HPHP::Statement
• 構造を表すノードの
基本クラス
• HPHP::StatementList が
ZEND_STATEMENT_LIST に
相当
HPHP::Expression
• 評価式や値を表す
ノードの基本クラス
• AwaitExpression
あたりは hhvm なら
では
ast 操作拡張の話
php-ast
• https://github.com/nikic/php-ast
• astparse_file あるいは
astparse_code で AST 構築
• astNode をベースクラスとした astDecl
• リスト型...
astkit
• https://github.com/sgolemon/astkit
• AstKit::parseString あるいは
AstKit::parseFile で AST構築
• AstKit をベースクラスとした AstKi...
astparse_code('<?php 1 + 2;')
全ノードをphp スクリプトで扱える構造に変換 (CG(ast) は破棄)
C言語 (CG(ast))
array
astnode
kind: 520
flags: 1
lineno:...
Astkit::parseString('1+2;')
先頭のノードのみ生成。操作により子の AstKit が生成される
C language (CG(ast) =
astkit_tree->tree)
AstKitList
AST_ZVAL
...
それぞれの特徴
• php-ast
– php スクリプトから扱いやすい
– 初期のコストが大きめ
– 異なるバージョンでの変換処理を拡張側で頑張っ
てる部分もある
• astkit
– C の ast そのままのメモリを操作
– 利用する箇所...
おしまい
(blank)
PHP AST 徹底解説(補遺)
Upcoming SlideShare
Loading in …5
×

PHP AST 徹底解説(補遺)

第七回闇PHP勉強会

  • Login to see the comments

  • Be the first to like this

PHP AST 徹底解説(補遺)

  1. 1. 2016/12/11 第七回闇PHP勉強会 do_aki 1 updated 2016-12-13
  2. 2. このスライドは • PHPカンファレンス2016で発表した 「PHP AST 徹底解説」 において説明し きれなかった部分を補足した際に用いた もの • AST に関する部分については、元のスラ イドにマージ済みなので http://www.slideshare.net/do_aki/p hp-ast を参照してください
  3. 3. @do_aki @do_aki http://do-aki.net/
  4. 4. PHPカンファレンス2016で 話したこと
  5. 5. PHP Compiler in PHP PHP Script Opcode Request Output Compiler Lexing Parsing Compilation VM Execution INCLUDE_OR_EVAL
  6. 6. php5 (1 pass / 151構文(5.6)) 字句解析 + 構文解析 + Opcode生成 php7 (2 pass / 127構文(7.0)) 字句解析+構文解析 Opcode生成 最適化の余地
  7. 7. PHP の 抽象構文木 <?php 1/(2+3); 種別 付属情報 子ノード 子ノード
  8. 8. token_get_all の話
  9. 9. token_get_all 関数 • PHP スクリプトを トークン分解して配列 にする関数 • nikic/PHP-Parser で利用されてる • tokenizer 拡張 (デフォルトで有効)
  10. 10. 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(); のようなコードも受け入れる
  11. 11. 7.0.12 での実行例 token_name(319) => T_STRING
  12. 12. コンパイル と token_get_all の関係 字句解析 構文解析 Opcode生成 狭義のコンパイルAST を生成トークンに分解 従来からの token_get_all TOKEN_PARSE 付きの token_get_all
  13. 13. 文字列結合 Opcode の話
  14. 14. ソースコード(php スクリプト) <?php function hello ( $name ) { echo “HELLO $name“ ; } hello ( “php“ ) ;
  15. 15. 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()
  16. 16. 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()
  17. 17. 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
  18. 18. "{$a} lines" 5.6 7.0 #* E I O op fetch ext return operands ------------------------------------------------------ 0 E > ADD_VAR ~0 !0 1 ADD_STRING ~0 ~0, '+lines' #* E I O op fetch ext return operands ------------------------------------------------------ 0 E > NOP 1 FAST_CONCAT ~1 !0, '+lines'
  19. 19. "{$a} / {$b} lines" 5.6 7.0 #* E I O op fetch ext return operands ------------------------------------------------------ 0 E > ADD_VAR ~0 !0 1 ADD_STRING ~0 ~0, '+%2F+' 2 ADD_VAR ~0 ~0, !1 3 ADD_STRING ~0 ~0, '+lines' #* E I O op fetch ext return operands ------------------------------------------------------ 0 E > ROPE_INIT 4 ~3 !0 1 ROPE_ADD 1 ~3 ~3, '+%2F+' 2 ROPE_ADD 2 ~3 ~3, !1 3 ROPE_END 3 ~2 ~3, '+lines'
  20. 20. parse しつつ opcode 生成し ていた時に、これを導入するの は困難だったはず AST により、容易に導入できる ようになった例かなと
  21. 21. strlen 静的展開の話
  22. 22. 静的関数展開(定数化) • 関数呼び出しコストの削減 • 定数畳み込みとの組み合わせも有効 ex: strlen(A::HOGE) + 1 -> 5 strlen(’hoge’) -> 4 ord(’A’) -> 65 / 7.1~ chr(65) -> ‘A‘ / 7.1~
  23. 23. mbstring.func_overload • strlen コールが mb_strlen に置き換わ る • EG(function_table) を操作して、上書 き • コンパイル時に strlen が定数になって しまうと機能しないのでは? という疑問
  24. 24. 静的関数展開の無効化 • CG(compiler_options) に ZEND_COMPILE_NO_BUILTINS ビットをセッ トすることで静的関数展開を無効にできる • CG(compiler_options) に ZEND_COMPILE_NO_BUILTIN_STRLENビット をセットすることで strlen の展開のみを 無効にできる • 拡張ならば、 CG(compiler_options) を制 御可能
  25. 25. 問題なかった • mbstring 拡張の初期化(RINIT)時 func_overload が有効ならば ZEND_COMPILE_NO_BUILTIN_STRLEN を 指定している • func_overload は問題なく機能する • func_overload が有効だと、 strlen 展開による恩恵を受けられない
  26. 26. 同じコードから異なるOpcodeが 生成される話
  27. 27. 定数の畳み込み $sec_in_day = 60 * 60 * 24; $sec_in_day = 86400; ※実は OpCache でも行われている class A { const HOGE = ‘hoge‘; } echo A::HOGE; echo ‘hoge‘; コンパイル時点で定義済みの定数に対してのみ有効 (autoload より pre include のほうが効きやすい)
  28. 28. コンパイルタイミングによって 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 はコンパイル済み
  29. 29. 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)
  30. 30. HHVM の話
  31. 31. HHVM におけるコンパイル • 2つのLexer (yylex) Compiler5lex/Compiler7lex • 2つのParser (yyparse) HPHP::Compiler::Parser::parseImpl5 HPHP::Compiler::Parser::parseImpl7 • どちらも AST を生成する
  32. 32. HHVM における AST • AST ノードの基底クラスである HPHP::Construct があり、Statement と Expression に分かれる • HPHP::Compiler::Parser::parseImpl が、parseImpl7 あるいは parseImpl5 を呼び出し、 HPHP::Compiler::Parser::m_tree に StatementList が作られる
  33. 33. HPHP::Statement • 構造を表すノードの 基本クラス • HPHP::StatementList が ZEND_STATEMENT_LIST に 相当
  34. 34. HPHP::Expression • 評価式や値を表す ノードの基本クラス • AwaitExpression あたりは hhvm なら では
  35. 35. ast 操作拡張の話
  36. 36. 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 の子として併合
  37. 37. astkit • https://github.com/sgolemon/astkit • AstKit::parseString あるいは AstKit::parseFile で AST構築 • AstKit をベースクラスとした AstKitList, AstKitDecl, AstKitZval にマッピングさ れる • $AstKit->export でコードに変換
  38. 38. 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)
  39. 39. 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)
  40. 40. それぞれの特徴 • php-ast – php スクリプトから扱いやすい – 初期のコストが大きめ – 異なるバージョンでの変換処理を拡張側で頑張っ てる部分もある • astkit – C の ast そのままのメモリを操作 – 利用する箇所が部分的ならば低コストか – ast 構造の変化によって php 側での操作が大き く変わる
  41. 41. おしまい
  42. 42. (blank)

×