Share
Embed
ソースコード→AST→オペコード、の旅を覗いてみる
PHPカンファレンス香川2026 での登壇資料です
https://fortee.jp/phpconkagawa-2026/proposal/e26dff71-551f-4be5-b1af-95f425fbae10
hideki kinjyo PRO
May 07, 2026
Transcript
211 slides
-
発表に入る前に: 環境テスト 本資料の文字サイズ・オブジェクトのサンプルです。 最大表示領域とあわせて、ご確認ください。 [見えにくい場合] 資料を公開済みなので、ぜひお手元にご用意ください => https://speakerdeck.com/o0h/phpcon-kagawa-2026 or Fortee
から 縦 幅 は 最 大 で こ こ ま で 使 い ま す ( 下 ま で 見 切 れ ず に 見 え ま す か ? ) 利用しているテキスト・太字もあります ・カラー1・カラー2・カラー3 #phpconkagawa #maki スライド番号この辺 Name substr 吹き出しも あります codecodecode codecodecode codecodecode 矢印 -
自己紹介 • 金城秀樹 / きんじょうひでき • GitHub: @o0h / 𝕏
: @o0h_ • 好きなFWはCakePHP • アイコンは美味しい鮭親子丼の写真です • 最近はPodcastをやっています • ハッシュタグ: #readlinefm #phpconkagawa #maki -
プログラムの実行 [In PHP…] (今日の話題である) ASTやオペコードも、この過程で出てくる話です! #phpconkagawa #maki S E
E val var Op ソースコード オペコード <?php class A { ————— ————— AST 0 INIT_FCALL 'hoge' 1 SEND_VAL 1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 機械語 00110000 00100000 00100000 01001001 01001110 01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 <?php T_OPEN_TAG if T_IF \s T_WHITESPACE $a T_VARIABLE \s T_WHITESPACE echo T_ECHO $x T_VARIABLE トークン -
変換: なに to なに? 人間に分かりやすい CPUが分かる ソースコード 機械語 抽象度が高い
単純で具体的 (形式は違えど) 内容は同じ #phpconkagawa #maki -
誰に・どのように分かりやすいか 人間に分かりやすい CPUが分かる ソースコード 機械語 抽象度が高い 単純で具体的 (形式は違えど) 内容は同じ
豊かな語彙 柔軟な文法 #phpconkagawa #maki -
誰に・どのように分かりやすいか 人間に分かりやすい CPUが分かる ソースコード 機械語 抽象度が高い 単純で具体的 (形式は違えど) 内容は同じ
使える命令が 限定的 引数が少ない #phpconkagawa #maki -
意味を崩さずに 変換する過程が必要 (形式は違えど) 内容は同じ 人間に分かりやすい CPUが分かる プログラムの実行 #phpconkagawa #maki
意味 コイツについて ちょっと考えてみましょう -
頭の体操: 「意味」について考える 文章を読み取って ↓ 意味を理解して ↓ 描画の実行 ↓ 期待される結果!
② 私です #phpconkagawa #maki -
文章→意味、の流れ 1文字ずつ読み取っていって #phpconkagawa #maki ① ド ② ドア ③
ドアの ④ ドアの前 ⑤ ドアの前に… -
文章→意味、の流れ 読み取りつつ、単語に分解して #phpconkagawa #maki ① ド → ??? ②
ドア → ??? ③ ドアの → ドア / ??? ④ ドアの前 → ドア / の / ??? ⑤ ドアの前に… → ドア / の / 前 / ??? 分割位置は 「次の文字」で 決まる -
文章→意味、の流れ 単語同士を、文中における役割を踏まえて 構造化(結合や再配置)する #phpconkagawa #maki ねこが ドアの前に います ねこ
が ドアの 前 い ます ドア の に 前に ねこが ドアの前にいます -
単語 is 大事 単語の分割が 一定しないと 意味が定まらない #phpconkagawa #maki ドアの前に猫がいます
ドアの前にねこがいます ドアの前にね、子がいます ドアの前、NINECOがいます -
1. 字句解析: 文字同士を繋げる S E E val var Op ソースコード
オペコード <?php class A { ————— ————— AST 0 INIT_FCALL 'hoge' 1 SEND_VAL 1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 機械語 00110000 00100000 00100000 01001001 01001110 01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 <?php T_OPEN_TAG if T_IF \s T_WHITESPACE $a T_VARIABLE \s T_WHITESPACE echo T_ECHO $x T_VARIABLE トークン -
簡単な分割 ① 変数 "$a" ② 加算記号 "+" ③ 変数 "$b"
#phpconkagawa #maki $a + $b 型(意味) -
簡単な分割 ① 変数 "$a" ② 加算記号 "+" ③ 変数 "$b"
#phpconkagawa #maki $a + $b 中身 -
#phpconkagawa #maki 加算式のトークン <?php $a + $b; T_OPEN_TAG <?php
T_VARIABLE $a T_VARIABLE $b ; ; T_WHITESPACE \s + + T_WHITESPACE \s -
#phpconkagawa #maki 加算式のトークン <?php $a + $b; T_OPEN_TAG <?php
T_VARIABLE $a T_VARIABLE $b ; ; T_WHITESPACE \s + + T_WHITESPACE \s 1文字トークンは そのままの文字を トークン名にすることもある -
#phpconkagawa #maki 関数コールのトークン <?php strlen('hello'); T_OPEN_TAG <?php T_STRING strlen
( ( T_CONSTANT_ENCAPSED_STRING 'hello' ; ; ) ) -
#phpconkagawa #maki 関数コールのトークン <?php strlen('hello'); T_OPEN_TAG <?php T_STRING strlen
( ( T_CONSTANT_ENCAPSED_STRING 'hello' 引用符がついていない 文字列 ; ; ) ) -
#phpconkagawa #maki 関数コールのトークン <?php strlen('hello'); T_OPEN_TAG <?php T_STRING strlen
( ( ; ; ) ) T_CONSTANT_ENCAPSED_STRING 'hello' 引用符で囲まれている 文字列 -
#phpconkagawa #maki if文のトークン <?php if(!$a){return;} hoge(); T_OPEN_TAG <?php T_IF
if return return ; ; ( ( ! ! T_VARIABLE $a ) ) { { } } T_STRING hoge ; ; ( ( ) ) T_WHITESPACE \n -
1. 字句解析: 文字同士を繋げる S E E val var Op ソースコード
オペコード <?php class A { ————— ————— AST 0 INIT_FCALL 'hoge' 1 SEND_VAL 1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 機械語 00110000 00100000 00100000 01001001 01001110 01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 <?php T_OPEN_TAG if T_IF \s T_WHITESPACE $a T_VARIABLE \s T_WHITESPACE echo T_ECHO $x T_VARIABLE トークン DONE! -
2. 構文解析: 要素を木構造に組み立てる S E E val var Op ソースコード
オペコード <?php class A { ————— ————— AST 0 INIT_FCALL 'hoge' 1 SEND_VAL 1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 機械語 00110000 00100000 00100000 01001001 01001110 01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 <?php T_OPEN_TAG if T_IF \s T_WHITESPACE $a T_VARIABLE \s T_WHITESPACE echo T_ECHO $x T_VARIABLE トークン -
抽象構文木(AST)って? 構文木: トークン同士の繋がりを木構造化(階層化) #phpconkagawa #maki 猫がドアの前にいます ドアの 前 ドア
の に 前に 前 ド の に ア 自然言語でも 木構造っぽい感じに 表せましたよね -
#phpconkagawa #maki プログラムの「木」 概念的に木を図示すると こんな感じ。 「繋がり」と「階層」が 明らかになる <?php $x
= $a + $b; $x = $a + $b; $x = $a + $b $x $a + $b $a $b -
#phpconkagawa #maki プログラムの「木」 <?php $x = $a + $b;
1つの「文」 $x = $a + $b; -
#phpconkagawa #maki プログラムの「木」 <?php $x = $a + $b;
1つの「式」 $x = $a + $b; $x = $a + $b -
#phpconkagawa #maki プログラムの「木」 <?php $x = $a + $b;
$x = $a + $b; $x = $a + $b $x $a + $b 式(代入式)の 左辺、右辺それぞれで ノードを分ける -
#phpconkagawa #maki プログラムの「木」 <?php $x = $a + $b;
$x = $a + $b; $x = $a + $b $x $a + $b 右辺の式(加算演算)が 更に式(値)に分かれる $a $b -
#phpconkagawa #maki 代入式のAST <?php $x = $a + 9;
Expression Statement Other Expr Stmt Program Assign Op_Plus Var a Scalar_Int 9 Var x -
#phpconkagawa #maki 関数コール式のAST <?php substr('abcd',1,$x); Program Expr Stmt Func
Call Name substr Arg List Var x Scalar_Int 1 Scalar_Str abcd -
#phpconkagawa #maki 関数コール式のAST Program Expr Stmt Func Call Name substr
Arg List Var x Scalar_Int 1 Scalar_Str abcd 関数コールは、 「関数名」「引数リスト」で 構築される -
#phpconkagawa #maki IF文のAST <?php if ($a < $b) { return
$a; } else { return null; } Program If Op_Smaller Var a Var b return Var a Else return Const Null -
<?php if ($a < $b) { return $a; } else
{ return null; } IF文のAST Program If Op_Smaller Var a Var b return Var a Else return Const Null if IFは、 子ノードに複数の文を持つ 文として構造化できる #phpconkagawa #maki -
<?php if ($a < $b) { return $a; } else
{ return null; } IF文のAST Program If Op_Smaller Var a Var b return Var a Else return Const Null < #phpconkagawa #maki -
<?php if ($a < $b) { return $a; } else
{ return null; } IF文のAST Program If Op_Smaller Var a Var b return Var a Else return Const Null $a $b #phpconkagawa #maki -
<?php if ($a < $b) { return $a; } else
{ return null; } IF文のAST Program If Op_Smaller Var a Var b return Var a Else return Const Null return $a #phpconkagawa #maki -
<?php if ($a < $b) { return $a; } else
{ return null; } IF文のAST Program If Op_Smaller Var a Var b return Var a Else return Const Null } else { return null; } #phpconkagawa #maki -
<?php function hoge( $x, $y ): int { $ret =
$x + $y; return $ret; } 関数定義のAST Program Func Stmts Params Identifier hoge Identifier int 次のスライドへ #phpconkagawa #maki -
<?php function hoge( $x, $y ): int { $ret =
$x + $y; return $ret; } 関数定義のAST Assign Op_Plus Var x Var ret Var y Return Var ret Param X Param y Stmts Params Expr Stmt #phpconkagawa #maki -
構文解析? 構文解析: トークンのリストを、つなげて組み立ててASTに • トークン同士を、どう結合する?というルール • OKな並び方: 「===(IS_IDENTICAL)」の後には「式が来てOK」とか • 優先:
四則演算の「+と÷は、先に÷を処理」に似たルール ※ ルールに関してもう少し詳しい話はAppendixにて #phpconkagawa #maki -
構文解析? 構文解析: トークンのリストを、つなげて組み立ててASTに • リストから順番に1つずつ取り出して、 「これは何」 + 「次は何?」を読み取って判断する #phpconkagawa
#maki さっき見たコードを例に 処理の様子を見ていきましょう -
#phpconkagawa #maki トークンリスト→AST <?php substr('abcd',1,$x); Program Expr Stmt Func
Call Name substr Arg List Var x Scalar_Int 1 Scalar_Str abcd さっき見たコード -
#phpconkagawa #maki トークンリスト→AST 1.T_OPEN_TAG 2.T_STRING(substr) 3.( 4.T_CONSTANT_ENCAPSE D_STRING(abcd) 5., 6.T_LNUMBER(1)
7., 8.T_VARIABLE($x) 9.) 10.; Program Expr Stmt Func Call Name substr Arg List Var x Scalar_Int 1 Scalar_Str abcd -
次に来るものを「先読み」 して、情報を仕入れる #phpconkagawa #maki トークンリスト→AST 2.T_STRING(substr) 3.( [status: ؔίʔϧ४උத] 文字列
+ 開きカッコ ؔίʔϧ 必要なパーツが揃ったら 「関数コール」ノードを作ろう、 という気持ちだけ準備する ってことは関数名! -
#phpconkagawa #maki トークンリスト→AST 2.T_STRING(substr) 3.( Name substr [status: ؔίʔϧ४උத] 「この名前の関数コールだ!」
が確定できる 文字列 + 開きカッコ ؔίʔϧ -
#phpconkagawa #maki トークンリスト→AST 3.( 関数コールノードにいて、 開きカッコ Name substr [status: ҾϦετ४උத]
[status: ؔίʔϧ४උத] 「引数リストが来るな!」 が分かる ؔίʔϧ ҾϦετ -
#phpconkagawa #maki トークンリスト→AST 4.T_CONSTANT_ENCAPSE D_STRING(abcd) 5., 引用符で囲まれた文字列 Name substr Scalar_Str
abcd [status: ҾϦετ४උத] [status: ؔίʔϧ४උத] 引数として渡す値として扱う ؔίʔϧ ҾϦετ -
#phpconkagawa #maki トークンリスト→AST 6.T_LNUMBER(1) 7., Name substr Scalar_Int 1 Scalar_Str
abcd [status: ҾϦετ४උத] [status: ؔίʔϧ४උத] 同様に、 Integerの引数を作る ؔίʔϧ ҾϦετ -
#phpconkagawa #maki トークンリスト→AST 8.T_VARIABLE($x) Name substr Var x Scalar_Int 1
Scalar_Str abcd [status: ҾϦετ४උத] [status: ؔίʔϧ४උத] 同様に、 変数読み込みの引数を作る ؔίʔϧ ҾϦετ -
#phpconkagawa #maki トークンリスト→AST 9.) Name substr Arg List Var x
Scalar_Int 1 Scalar_Str abcd [status: ҾϦετ४උྃʂ] [status: ؔίʔϧ४උத] 閉じカッコが来たので、 引数リストの内容が確定 ؔίʔϧ ノードを生成する -
#phpconkagawa #maki トークンリスト→AST [status: ؔίʔϧ४උྃʂ] Func Call Name substr Arg
List Var x Scalar_Int 1 Scalar_Str abcd Name + Arg Listが揃った これで関数コールの内容が確定 -
#phpconkagawa #maki トークンリスト→AST 10.; Expr Stmt Func Call Name substr
Arg List Var x Scalar_Int 1 Scalar_Str abcd 「文」が確定する デリミタが来たので -
#phpconkagawa #maki トークンリスト→AST ʂ Program Expr Stmt Func Call Name
substr Arg List Var x Scalar_Int 1 Scalar_Str abcd -
2. 構文解析: 要素を木構造に組み立てる S E E val var Op ソースコード
オペコード <?php class A { ————— ————— AST 0 INIT_FCALL 'hoge' 1 SEND_VAL 1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 機械語 00110000 00100000 00100000 01001001 01001110 01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 <?php T_OPEN_TAG if T_IF \s T_WHITESPACE $a T_VARIABLE \s T_WHITESPACE echo T_ECHO $x T_VARIABLE トークン DONE! -
3. コード生成: 木構造から別の表現に翻訳する S E E val var Op ソースコード
オペコード <?php class A { ————— ————— AST 0 INIT_FCALL 'hoge' 1 SEND_VAL 1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 機械語 00110000 00100000 00100000 01001001 01001110 01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 <?php T_OPEN_TAG if T_IF \s T_WHITESPACE $a T_VARIABLE \s T_WHITESPACE echo T_ECHO $x T_VARIABLE トークン -
そもそもオペコードとは? • 例: 「代入」を見てみる #phpconkagawa #maki boolのtrue ローカル変数 (CV0
= $iikanji) ASSIGN: 引数1に引数2を代入する -
そもそもオペコードとは? • みたいな感じで!記号や記法が減り、表現方法が集約される • `= ! { } [ ]
' "` などの記号が無くなったり • `for foreach if switch match` などの制御構文が、 全て単純な「ジャンプ命令(`JMPZ JMPNZ` など)」になったり • アセンブリに近い形になる • 1命令ごとに「オペコード(命令) + オペランド(引数)」の形をとる • レジスタ/一時変数に値を置きながら処理を進める • ジャンプ命令で制御フローを表現する #phpconkagawa #maki -
• みたいな感じで!記号や記法が減り、表現方法が集約される • `= ! { } [ ] '
"` などの記号が無くなったり • `for foreach if switch match` などの制御構文が、 全て単純な「ジャンプ命令(`JMPZ JMPNZ` など)」になったり • アセンブリに近い形になる • 1命令ごとに「オペコード(命令) + オペランド(引数)」の形をとる • レジスタ/一時変数に値を置きながら処理を進める • ジャンプ命令で制御フローを表現する そもそもオペコードとは? • アセンブリに近い形になる #phpconkagawa #maki これが 機械語の一歩前として 都合がいい -
オペコード生成の流れ • ASTのノードを1つずつ辿って • 原則、深さ優先・左から(行きがけ順)ノードを処理 • 変換するための情報が揃った!!と思ったら、 オペコードを書き出し(エミット)していく • もし、「子ノードの必要が先に必要だ」となったら
自身のエミットの前に、先に子の処理を行う #phpconkagawa #maki -
#phpconkagawa #maki 代入式のオペコード <?php $x = $a + 9;
Expr Stmt Program Assign Op_Plus Var a Scalar_Int 9 Var x さっき見たコード -
代入式のオペコード Expr Stmt Assign Op_Plus Var a Scalar_Int 9 Var
x #phpconkagawa #maki 順番に見ていきましょう -
代入式のオペコード Expr Stmt Assign Op_Plus Var a Scalar_Int 9 Var
x [Կ͔ͷจ] #phpconkagawa #maki Expr Stmt =「式文」。 子ノードに「式」を持つ文。 含まれる要素を知る必要があるので、 子ノードに処理を進める -
代入式のオペコード Expr Stmt Assign Op_Plus Var a Scalar_Int 9 Var
x [{(Կ͔) = (Կ͔)ͷೖࣜ}จ] #phpconkagawa #maki Asign =「代入式」。 左辺・右辺を子ノードに持つ。 左辺→右辺→自分自身、という順番で解決する -
代入式のオペコード Expr Stmt Assign Op_Plus Var a Scalar_Int 9 Var
x [{PHPม`CV0($x)`=(Կ͔)ͷೖࣜ}จ] #phpconkagawa #maki Var =「代入先の変数」。 PHPにわたす変数(x)を用意しつつ、 まだオペコードに出力できる内容はない -
代入式のオペコード Expr Stmt Assign Op_Plus Var a Scalar_Int 9 Var
x [(ͳʹ͔)ɾ(ͳʹ͔)ΛՃࢉ͢Δ] [{PHPม`CV0($x)`=(↑)ͷೖࣜ}จ] #phpconkagawa #maki Binary Operation Plus =「加算」。 左辺・右辺を持つノード。 左辺→右辺→自分自身、という順番で解決する -
代入式のオペコード Expr Stmt Assign Op_Plus Var a Scalar_Int 9 Var
x [PHPม`CV1($a)`ɾ(ͳʹ͔)ΛՃࢉ͢Δ] [{PHPม`CV0($x)`=(↑)ͷೖࣜ}จ] #phpconkagawa #maki Expression Variable =「変数読み取り」。 PHPの変数 `$a` を、 Op_Plusの左辺としてセット -
代入式のオペコード Expr Stmt Assign Op_Plus Var a Scalar_Int 9 Var
x [PHPม`CV1($a)`ɾ`9`ΛՃࢉ͢Δ] [{PHPม`CV0($x)`=(↑)ͷೖࣜ}จ] #phpconkagawa #maki Scalar Int・・は名前の通り、整数リテラル。 (int)9を、Op_Plusの右辺としてセット -
代入式のオペコード Expr Stmt Assign Op_Plus Var a Scalar_Int 9 Var
x [PHPม`CV1($a)`ɾ`9`ΛՃࢉ͢Δ] [{PHPม`CV0($x)`=(↑)ͷೖࣜ}จ] #phpconkagawa #maki 式「Op_Plus」に必要な情報が揃った! リデュース(還元)・エミット(出力)を行う -
代入式のオペコード Expr Stmt Assign Op_Plus Var a Scalar_Int 9 Var
x [PHPม`CV1($a)`ɾ`9`ΛՃࢉ͢Δ] [{PHPม`CV0($x)`=(↑)ͷೖࣜ}จ] #phpconkagawa #maki 加算(命令) = オペコード`ADD` 引数(オペランド)1 = `CV1($a)` 引数(オペランド)2 = `9` -
代入式のオペコード Expr Stmt Assign [0000] Var x 0000 T2 =
ADD CV1($a) int(9) ----- [{PHPม`CV0($x)`=(↑)ͷೖࣜ}จ] #phpconkagawa #maki 加算(命令) = オペコード`ADD` 引数(オペランド)1 = `CV1($a)` 引数(オペランド)2 = `9` -
代入式のオペコード Expr Stmt Assign [0000] Var x 0000 T2 =
ADD CV1($a) int(9) ----- [{PHPม`CV0($x)`=(↑)ͷೖࣜ}จ] #phpconkagawa #maki (PHPには露出しない) 一時的な変数 CV0($x), CV1($a)の 次に出てきたので、 「2」になっている -
代入式のオペコード Expr Stmt Assign [0000] Var x 0000 T2 =
ADD CV1($a) int(9) ----- [{PHPม`CV0($x)`=(↑)ͷೖࣜ}จ] #phpconkagawa #maki Assignの右辺が、`T2` にメモされている 還元するために必要な情報 = 左辺・右辺が揃った! -
代入式のオペコード Expr Stmt [0001] 0000 T2 = ADD CV1($a) int(9)
0001 ASSIGN CV0($x) T2 #phpconkagawa #maki 代入(命令) = オペコード`ASSIGN` 引数(オペランド)1 = `CV0($x)` 引数(オペランド)2 = `T2` -
代入式のオペコード Expr Stmt [0001] 0000 T2 = ADD CV1($a) int(9)
0001 ASSIGN CV0($x) T2 #phpconkagawa #maki 式文に必要な情報もすべて揃っている 今回は、子ノードの情報を使わないので そのまま破棄 消えてヨシ -
代入式のオペコード Expr Stmt 0000 T2 = ADD CV1($a) int(9) 0001
ASSIGN CV0($x) T2 #phpconkagawa #maki このノード自体も、 エミットするべき内容が無いので、 そのまま破棄 -
代入式のオペコード 0000 T2 = ADD CV1($a) int(9) 0001 ASSIGN CV0($x)
T2 0002 RETURN int(1) #phpconkagawa #maki メインループ終了時(など)には、 自動的に「リターンを付与」して 終わらせる -
代入式のオペコード Expr Stmt Assign Op_Plus Var a Scalar_Int 9 Var
x 0000 T2 = ADD CV1($a) int(9) 0001 ASSIGN CV0($x) T2 0002 RETURN int(1) #phpconkagawa #maki できあがり!👏 <?php $x = $a + 9; -
余談: Stmtノードのオペコード Expr Stmt Assign Op_Plus Var a Scalar_Int 9
Var x 0000 EXT_STMT 0001 T2 = ADD CV1($a) int(9) 0002 ASSIGN CV0($x) T2 0003 RETURN int(1) #phpconkagawa #maki 一部のPHP拡張を利用した場合など、 文ごとの処理に入る時に `EXT_STMT`を出力する XDebugなど -
IF文のオペコード 0000 T2 = IS_SMALLER CV0($a) CV1($b) 0001 JMPZ T2
0005 0002 RETURN CV0($a) 0003 JMP 0008 0004 RETURN null 0005 RETURN int(1) #phpconkagawa #maki <?php if ($a < $b) { return $a; } else { return null; } If Op_Smaller Var a Var b return Var a Else return Const Null -
IF文のオペコード 0000 T2 = IS_SMALLER CV0($a) CV1($b) 0001 JMPZ T2
0005 0002 RETURN CV0($a) 0003 JMP 0008 0004 RETURN null 0005 RETURN int(1) #phpconkagawa #maki <?php if ($a < $b) { return $a; } else { return null; } If Op_Smaller Var a Var b return Var a Else return Const Null -
IF文のオペコード 0000 T2 = IS_SMALLER CV0($a) CV1($b) 0001 JMPZ T2
0005 0002 RETURN CV0($a) 0003 JMP 0008 0004 RETURN null 0005 RETURN int(1) #phpconkagawa #maki <?php if ($a < $b) { return $a; } else { return null; } If Op_Smaller Var a Var b return Var a Else return Const Null -
IF文のオペコード 0000 T2 = IS_SMALLER CV0($a) CV1($b) 0001 JMPZ T2
0005 0002 RETURN CV0($a) 0003 JMP 0008 0004 RETURN null 0005 RETURN int(1) #phpconkagawa #maki <?php if ($a < $b) { return $a; } else { return null; } If Op_Smaller Var a Var b return Var a Else return Const Null -
関数コールのオペコード 0000 INIT_FCALL_BY_NAME 3 string("hoge") 0001 SEND_VAL_EX string("abcd") 1 0002 SEND_VAL_EX int(1) 2
0003 SEND_VAR_EX CV0($x) 3 0004 DO_FCALL 0005 RETURN int(1) #phpconkagawa #maki <?php hoge('abcd',1,$x); Expr Stmt Func Call Name hoge Arg List Var x Scalar_Int 1 Scalar_Str abcd -
3. コード生成: 木構造から別の表現に翻訳する S E E val var Op ソースコード
オペコード <?php class A { ————— ————— AST 0 INIT_FCALL 'hoge' 1 SEND_VAL 1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 機械語 00110000 00100000 00100000 01001001 01001110 01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 <?php T_OPEN_TAG if T_IF \s T_WHITESPACE $a T_VARIABLE \s T_WHITESPACE echo T_ECHO $x T_VARIABLE トークン DONE! -
立ち位置とか目的とか #phpconkagawa #maki ソースコード オペコード <?php class A {
————— ————— 0 INIT_FCALL 'hoge' 1 SEND_VAL 1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 機械語 00110000 00100000 00100000 01001001 01001110 01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 <?php T_OPEN_TAG if T_IF \s T_WHITESPACE $a T_VARIABLE \s T_WHITESPACE echo T_ECHO $x T_VARIABLE トークン S E E val var Op AST -
立ち位置とか目的とか #phpconkagawa #maki ソースコード <?php class A { —————
————— 人間が 読み書きしやすい 語彙が豊富で、 表現力が高い "simple"よりも "easy"さ重視 -
立ち位置とか目的とか #phpconkagawa #maki 本質的な情報が 整理されている 厳密な構造 何かしらの 解析器向け S
E E val var Op AST -
立ち位置とか目的とか #phpconkagawa #maki オペコード 0 INIT_FCALL 'hoge' 1 SEND_VAL
1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 仮想マシン向け 語彙が貧弱で、 記述が煩雑 "easy"よりも "simple"さ重視 -
立ち位置とか目的とか #phpconkagawa #maki 機械語 00110000 00100000 00100000 01001001 01001110
01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 機械(CPU)の 実行に必要 単純・厳密・明示的 ポータビリティは 損なわれる -
構造化と抽象化 • 「抽象」構文木(AST)以外に「具象」構文木(CST)なるものがある • 抽象化 = 不要な部分を削り本質的な部分を抽出すること • ASTは、「構造」を利用することで、 本質的な(=利用目的に対して十分な)情報を保持しつつ
不要なデータを削ぎ落としている • = 元のコンテンツに対して「完全なデータ」ではない #phpconkagawa #maki -
文章の抽象化 中核的なものと、 それ以外を選り分ける 前者は「ノード」が残るが、 後者は「構造」に還元できる #phpconkagawa #maki ねこ [誰?]
前 い(ます) ドア [何の?] [どこ?] -
文章の抽象化 構造を維持できれば、 違う形式の表現にも (意味を保ちながら) 変換できる #phpconkagawa #maki • ドアの前にねこがいます
• ねこ!ドアの前!!いるよ!! • ねこ is in 前 of ドア. • 猫在扉手前 ねこ<猫> [誰?] 前<手前> い(ます)<be, 在> ドア<扉> [何の?] [どこ?] -
何が「抽象」なの? 抽象構文木が持っているもの: プログラムの実行に必要な要素 • クラス名とか条件分岐とか呼び出される関数とか変数名とかとか • それらの繋がり方、階層構造 抽象構文木が持っていないもの: ↑に含まれないもの •
トークナイズや構造化が済んだら不要になる記号 • 空白、改行、; ( ) , $ などなど #phpconkagawa #maki -
#phpconkagawa #maki 何が「抽象」なの? 例えば、↓の2つは ASTにすると同じ構造になる if ($a) { echo
($x); } if ($a) echo $x; Program If Var a echo Var x -
抽象じゃない構造木 具象構文木: 具体的なコンテンツも扱う構文木 (CST: Concrete Syntax Tree) • 空白、改行、; (
) , $ などなども保持される • 平易に言うなら、 抽象構文木が「意味(の構造)を保持する」とのに対し、 具象構文木は「書き方を保持する」ようなイメージ #phpconkagawa #maki -
#phpconkagawa #maki CSTの例 ① ソースコードにある全ての テキストが残っている (行番号や文字の位置は別に 持つ) Program
php_tag <?php If Stmt "if" expr "(" var "$" name a ")" stmts "{" echo "echo" var "$" name x ";" "}" if ($a) { echo $x; } -
#phpconkagawa #maki CSTの例 ① 葉ノード(末端にあるもの) を辿っていけばコードが復 元できる Program php_tag
<?php If Stmt "if" expr "(" var "$" name a ")" stmts "{" echo "echo" var "$" name x ";" "}" if ($a) { echo $x; } -
#phpconkagawa #maki CSTの例 ② if文のbody部分の`{` `}` が ツリー上からも消えている "if"
expr "(" var "$" name a ")" stmts echo "echo" var "$" name x ";" Program php_tag <?php If Stmt if ($a) echo $x; -
#phpconkagawa #maki CSTの例 ①(比較用) ソースコードにある全ての テキストが残っている (行番号や文字の位置は別に 持つ) Program
php_tag <?php If Stmt "if" expr "(" var "$" name a ")" stmts "{" echo "echo" var "$" name x ";" "}" if ($a) { echo $x; } -
#phpconkagawa #maki If Var a echo Var x If Stmt
"if" expr "(" var "$" name a ")" stmts "{" echo "echo" var "$" name x ";" "}" if ($a) { echo $x; } 同一のコードで2種類の構造木の比較 -
それぞれに異なる利用目的があり、保持する情報が変わる • 抽象構文木(AST) • 利用者: コード解析やコンパイラ • 保持する情報: プログラムの構造 •
具象構文木(CST) • 利用者: エディタやコードハイライト • 保持する情報: 構造情報とコンテンツ(テキスト)の対応 • ※ CSTは、PHPの内部で利用されているものではない 抽象/具象構文木の比較 #phpconkagawa #maki -
PHPのtokenを見てみる 1. PHPでトークンリストを得るにはどうすればいいの 2. PHPにはどんなトークンがあるの 3. 実際にそれらはどういう風に定義されてるの 4. 最近の例: Pipe
operator #phpconkagawa #maki -
2. PHPにはどんなトークンがあるの • グローバル定数として確認が可能 • `T_` から始まるグローバル定数 • `get_defined_constants(true)['tokenizer']` というコードの実行が手軽
• 例: https://3v4l.org/ntDDN #phpconkagawa #maki -
3. 実際にそれらはどういう風に定義されてるの • 定義している正体は `Zend/zend_language_scanner.l`ファイル • re2cというツールで使うための設定ファイル • Regular Expressions
to Code の意味。まんまです!! • https://re2c.org/index.html • ざっくりと言えば「正規表現のルール」が書かれている • 「この状態の時」に「このパターン」が出てきたら「このトークンとみ なす」をルールとして定義していく • 状態 = ダブルクォテーションの中にいる時・配列のオフセットの宣言([] の中)にいる時)、など #phpconkagawa #maki -
3. 実際にそれらはどういう風に定義されてるの • 通常のスクリプト部分に居る時 (<ST_IN_SCRIPTING>) • "class" を見つけたら • T_CLASS
として扱う #phpconkagawa #maki -
3. 実際にそれらはどういう風に定義されてるの • 通常のスクリプト部分に居る時 (<ST_IN_SCRIPTING>) • "<=>" を見つけたら • T_SPACESHIP
として扱う #phpconkagawa #maki -
3. 実際にそれらはどういう風に定義されてるの • キーワードや記号以外の部分につい ては、正規表現のパターンを設定し てある • 例えば 「"$" の後に
LABEL が来た ら、変数だ」と言えるようになる #phpconkagawa #maki -
4. 最近の例:Non-Nullable Cast • 8.6に向けて提案されている新機能、「Not-Nullable」キャスト • https://wiki.php.net/rfc/nullable-not-nullable-cast-operator • まだ採択は決まっていない。 あくまで「文法追加の一例」として取り上げています
#phpconkagawa #maki try { var_dump((!int)$nullVar); } catch (TypeError $e) { echo "Caught TypeError for null\n"; } -
4. 最近の例:Non-Nullable Cast 字句解析の定義 • "(! int)" という記法で "T_NONNULL_INT_CAST" というトークンになる
#phpconkagawa #maki https://github.com/php/php-src/pull/20275/changes#diff-3e6742a9069b5717cf961c9d6b2aefbd1c730869d8a58123b1b5f3bc3e9082fc -
1. PHPでASTを得るにはどうすればいいの • nikic/php-parser • 参考になりそうな記事 • さわって慣れるPHP Parser https://zenn.dev/inoco/articles/a4758d9b862c14
• 使い方はそちらをじっくり見てもらえると! #phpconkagawa #maki -
2. どうやって「トークン→木」が決まっているの? • 「パーサー」と呼ばれる処理を行うものを実装する • ・・・のは大変なので、「パーサー作成用の定義ファイル」を使う • Bisonというツールに食わせる • 具体的な内容(ソース)
• https://github.com/php/php-src/blob/master/Zend/ zend_language_parser.y #phpconkagawa #maki -
4. 最近の例:Non-Nullable Cast • 8.6に向けて提案されている新機能、「Not-Nullable」キャスト • https://wiki.php.net/rfc/nullable-not-nullable-cast-operator • まだ採択は決まっていない。 あくまで「文法追加の一例」として取り上げています
#phpconkagawa #maki try { var_dump((!int)$nullVar); } catch (TypeError $e) { echo "Caught TypeError for null\n"; } -
4. 最近の例:Non-Nullable Cast 構文解析の定義 • "expr" の生成規則に、 新しい規則 "T_NONNULL_INT_CAST expr"
が追加されている #phpconkagawa #maki https://github.com/php/php-src/pull/20275/changes#diff-3e6742a9069b5717cf961c9d6b2aefbd1c730869d8a58123b1b5f3bc3e9082fc -
なぜASTが必要? • コンパイル過程で必ずしもASTが必要か?というと、そうでもない • 実際、PHPでのAST導入はPHP7から • 参考1: 内部的な変更の観点からのdo_akiさんの解説 • PHP
AST 徹底解説 | PPT https://www.slideshare.net/slideshow/php-ast/68091348 • 参考2: hnwさんの解説 • PHP7調査(2)コンパイル時にASTを構築するようになった #PHP - Qiita https://qiita.com/hnw/items/4dfdcc6a3eace7fe1936 • 参考2: 「ASTにしなくても処理ができる」の例として、shunsockさんの発表/本 • はじめて作るプログラミング言語 https://zenn.dev/shundeveloper/books/b224dd232e5a22 #phpconkagawa #maki -
なぜASTが必要? • それでもASTを使うのは「その方が楽だから」という側面 • 「字句解析しながら構造(つながり)も見て・・」だと大変 • 責務や仕事を分離したい! すなわち、「脱レガシー」で我々が話しているのと同じですね • フェーズごとの最適化がしやすく。
「木ができて、前後・上下関係が分かっている」段階だから容易にな る事も。 #phpconkagawa #maki -
AST → オペコード • ノードを1つずつ辿って、変換していく • コンパイラの中で、 「この種類のノードだったら、この関数を使って変換だ!」 を繰り返している
#phpconkagawa #maki if (ast->kind === ZEND_AST_hogehoge) { zend_compile_hogehoge(ast); } else if (ast->kind === ZEND_AST_fugafuga) { zend_compile_fugafuga(ast); } -
『言語実装パターン』 書籍 https://www.oreilly.co.jp/books/9784873115320/ • 構文解析の具体的な実装パターンや、構文木について紹介されて いる • 基本となる理論的な部分もおさえつつ、具体的な実装ベースで解 説されるので、空中戦っぽくならずにイメージが湧きやすかった •
良い意味で、あくまで「パターン」を紹介している本 #phpconkagawa #maki -
『遊んで仲良くなるAST』 ムック記事 https://fortee.jp/phperkaigi-2026/proposal/7818fa7f-5cf4-4c06- 8cf6-0a88995a5077 • PHPerKaigi 2026パンレフットに寄稿した記事 • この内容をWeb上に出していないのですが、もしパンフレットをお持ち でしたら是非・・
• nikic/php-parserが出力するASTをベースに、「どのようにコー ドが抽象化されているのか」に触れながら理解をしていくもの #phpconkagawa #maki -
『Deep Dive into Xdebug』登壇資料 https://slides.nsfisis.dev/slides/2026-04-11/phpcon- odawara-2026/ • @nsfisis さんの記事 •
オペコード `EXT_STMT`と周辺を含めた話が出てくる #phpconkagawa #maki -
ASTをブラウザで手軽に試せるサイト #phpconkagawa #maki αΠτ໊ URL հ AST explorer https://astexplorer.net/
PHPを含む、様々な言語やパーサーでの AST出力が可能 Play with AST | Rector https://getrector.com/ast Rectorのサイトで提供されている、ASTの プレイグラウンド PHP AST Viewer https://php-ast-viewer.com/ PHP-ParserでのAST出力を試せるプレイ グラウンド Playground - Tree-sitter https://tree-sitter.github.io/ tree-sitter/7-playground.html ASTではないですが、コードハイライトなど に用いられるTree-sitterでの解析木を確 認できるプレイグラウンド