Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
夢の無限スパゲッティ製造機 -実装篇- #phpstudy
Search
hideki kinjyo
PRO
March 25, 2026
Programming
300
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
夢の無限スパゲッティ製造機 -実装篇- #phpstudy
PHP勉強会@東京 第185回の発表資料です。
前段の話:
https://speakerdeck.com/o0h/phperkaigi-2026
hideki kinjyo
PRO
March 25, 2026
More Decks by hideki kinjyo
See All by hideki kinjyo
phperだってパンチカードでコーディングしたい
o0h
PRO
0
180
Symfonyが 簡単にAI agentになる
o0h
PRO
1
140
yield再入門 #phpcon
o0h
PRO
0
1.8k
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
450
Composerを使ったサプライチェーン攻撃の様子を眺めてみる #phpstudy
o0h
PRO
3
400
ソースコード→AST→オペコード、の旅を覗いてみる
o0h
PRO
1
380
PCOVから学ぶコードカバレッジ #phpcon_odawara
o0h
PRO
0
630
夢の無限スパゲッティ製造機 #phperkaigi
o0h
PRO
0
1k
PHPer Book Revue 「雑に作る」 #phperkaigi
o0h
PRO
0
450
Other Decks in Programming
See All in Programming
JAWS-UG 東京支部が始める、JAWS-UG支部コラボ / JAWS-UG lunchtime LT Collaboration
y0hgi
0
180
mrbgem 三角測量 開発
ogom
0
190
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
430
選挙速報を多くのユーザーへ 届ける Live Activities 設計
hamayokokuririn
0
240
Go × SIMDで高速化するベクトル検索 ~ルーフラインモデルでSIMDが効く境界を探れ! ~
po3rin
1
6.2k
C#の現在地 進化の歴史と、AI時代の.NET Everywhere
neuecc
5
4.9k
設計次第でAIコードの読む量は減らせる / designing-for-code-reading
minodriven
33
15k
IBM Bob Dojo #1 仕様駆動開発入門
oniak3ibm
PRO
0
340
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
5
540
FreeBSDでZabbixを動かす
kenkino
0
350
The Rails Doctrine Decade
koic
2
470
Atomic Design, Enforced: Scaling Mobile Design Systems (next.app devCon / droidCon Berlin 2026)
steliosf
PRO
0
110
Featured
See All Featured
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.3k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.7k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.9k
We Have a Design System, Now What?
morganepeng
55
8.4k
Navigating Team Friction
lara
192
16k
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
550
Documentation Writing (for coders)
carmenintech
77
5.6k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
1
420
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
490
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
480
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
550
Transcript
夢の無限スパゲッティ製造機 -実装篇- 第185回 PHP勉強会@東京 Hideki Kinjyo GitHub: o0h / X:
@o0h_ [発表用] v1.0.0
夢の無限スパゲッティ製造機 -実装篇- #phpstudy.185 Hideki Kinjyo GitHub:o0h / X:@o0h_
自己紹介 • 金城秀樹 / きんじょうひでき • GitHub: @o0h / 𝕏
: @o0h_ • 好きなスパゲッティはカルボナーラ • アイコンは美味しい鮭親子丼のChef ver.です • 何故ならスパゲッティを作っていますからね • 最近はPodcastをやっています • ハッシュタグ: #readlinefm 3
3月22日に発表したやつ 4
今日のお話 PHPerKaigiでは、生成されるコードの話がメインだったので その実装についての話をします 1. そもそも何これ 2. 全体の処理の流れ 3. 前半戦: 元PHPコードから「中間表現」まで
4. 後半戦: 「中間表現」から PHPコードへ 5
1. そもそも何これ 2. 全体の処理の流れ 3. 前半戦: 元PHPコードから「中間表現」まで 4. 後半戦: 「中間表現」から
PHPコードへ
The Spaghetti Dream #とは • PHPで書かれたコードを「gotoベースのコード」に変換するプログラム • 制御構文の排除 • クラスやそれに類する機能の排除
• もともとは、「構造化プログラミング」の説明を読んで 「何を言っているんだ、当たり前じゃない…?」と思ったのが始まり • それが無い世界を味わってみたくなった • PHPerKaigi 2025 『PHPによる"非"構造化プログラミング入門』 • 「gotoを使わなくても制御フローを実現できる」なら、 「今のコードをgotoでも作れる」のではないか 7
DEMO the-spaghetti-dream.nichiyou.be
1. PHPで書かれたソースコードをインプットとして 2. いったんオペコードに変換し 3. 再びPHPの表現に戻す! <?php $tmp0 = …
sub: ————— goto hoge; end: 何をしているのか 9 ソースコード オペコード <?php function hoge($x) { ————— } 0 INIT_FCALL 'hoge' 1 SEND_VAL 1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 オペコード スパゲッティ
オペコードってこんなもの • 通常のPHPのコードを、ZendVM(仮想マシン)が使える形に変換したもの • 制御フローは、「CPU(機械)向けに近く、単純化」された形に • ifやforが無くなり、「ジャンプ命令」に書き換えらえる 10
オペコードってこんなもの • 基本的に上から実行される • 1行 = 1ステップ 11
オペコードってこんなもの • 各行は、インデックス・命令・引数の3つのパーツで構成される • 引数の数は命令によって異なる 12 命令(オペコード) 引数(オペランド) インデックス(番号)
オペコードってこんなもの • 「CV0にtrueを代入する」 13 boolのtrue ローカル変数 (CV0 = $iikanji) ASSIGN:
引数1に引数2を代入する
オペコードってこんなもの • 「CV0をチェックして、値が `0` だったら、3行目にジャンプする」 14 JMPZ: 引数1がゼロだったら、 引数2の番号にジャンプする ローカル変数
(CV0 = $iikanji) 番号3
こんな感じのやつをアレコレする話です • これを変換していきましょう!! • 変換できると嬉しいですよね! 15 <?php $tmp0 = …
sub: ————— goto hoge; end: ソースコード オペコード <?php function hoge($x) { ————— } 0 INIT_FCALL 'hoge' 1 SEND_VAL 1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 オペコード スパゲッティ
1. そもそも何これ 2. 全体の処理の流れ 3. 前半戦: 元PHPコードから「中間表現」まで 4. 後半戦: 「中間表現」から
PHPコードへ
全体フロー 17 コマンド起動/src受け取り オペコード変換 オペコードに無い情報の補完 (依存ファイル読み込み) 🍝化 冗長なコードのクリーンアップ
None
1. そもそも何これ 2. 全体の処理の流れ 3. 前半戦: 元PHPコードから「中間表現」まで 4. 後半戦: 「中間表現」から
PHPコードへ
やっていること 1. インプットとなるソースコードを受け取る 2. オペコードの出力の実行 3. オペコードのフレームごとに `OpCodeCollection`を作成 • `\O0h\SpaghettiDream\Extractor\IR\OpcodeCollection`
4. 各ステップを`OpCode` クラスのオブジェクトに変換 • `\O0h\SpaghettiDream\Extractor\IR\Opcode` 20
オペコードの出力 • オペコードの出力には色々な方法がある • PHPをそのまま使う: php -d opcache.opt_debug_level=0x10000 • 拡張を入れる:
VLD • コマンドを使う: phpdbgコマンド • 今回はphpdbgコマンドを使っている • スクリプトの実行をせずにオペコードだけ取り出したかったので 21
オペコードの出力 • 素朴にexec() 22 $command = sprintf( '%s -n -p\\*
%s 2>&1', escapeshellcmd($this->phpdbgPath), escapeshellarg($phpFilePath), ); $output = []; $returnCode = 0; exec($command, $output, $returnCode);
オペコードのフレームごとに `OpCodeCollection`を作成 • オペコードに変換された時に、スコープごとにフレームが形成される 23 <?php function add($a, $b) {
return $a + $b; } function sub($a, $b) { return $a - $b; } var_dump( sub(100,add(10, 20)) ); add: ; (lines=7, args=2, vars=2, tmps=2) ; /private/tmp/piyo.php:3-6 L0003 0000 CV0($a) = RECV 1 L0003 0001 CV1($b) = RECV 2 ɾ ɾ sub: ; (lines=7, args=2, vars=2, t ; /private/tmp/piyo.php:8-11 L0008 0000 CV0($a) = RECV 1 L0008 0001 CV1($b) = RECV 2 ɾ ɾ $_main: ; (lines=13, args=0, ; /private/tmp/piyo. L0013 0000 EXT_STMT L0013 0001 INIT_FCALL 1 1 ɾ ɾ
Extractor\IR\OpcodeCollection • 1フレームに対して1インスタンス 24 sub: ; (lines=7, args=2, vars=2, tmps=2)
; /private/tmp/piyo.php:8-11 L0008 0000 CV0($a) = RECV 1 L0008 0001 CV1($b) = RECV 2 ɾ ɾ add: ; (lines=7, args=2, vars=2, tmps=2) ; /private/tmp/piyo.php:3-6 L0003 0000 CV0($a) = RECV 1 L0003 0001 CV1($b) = RECV 2 ɾ ɾ $_main: ; (lines=13, args=0, vars=0, tmps=4) ; /private/tmp/piyo.php:1-17 L0013 0000 EXT_STMT L0013 0001 INIT_FCALL 1 112 string("var_dump") ɾ ɾ OpcodeCollection OpcodeCollection OpcodeCollection
オペコードのパース • 1行ごとに地道に正規表現 でパースしていく • とはいえ、普通のPHPより はよっぽどシンプル 25
Extractor\IR\Opcode • オペコードの1行に対して1インスタンス • ここでは素直にパースして、難しいことは後で考える 26 new Opcode( line: 3,
offset: 0, opcode: 'ASSIGN', operands: [ new Operand(TYPE_CV, 0, 'a'), new Operand(TYPE_CONST, 1), ], ) L0003 0000 ASSIGN CV0($a) int(1)
Extractor\IR\Opcode • オペコードの1行に対して1インスタンス • ここでは素直にパースして、難しいことは後で考える 27 new Opcode( line: 5,
offset: 3, opcode: 'JMPZ', operands: [ new Operand(TYPE_TMP, 2), new Operand(TYPE_JUMP, 7), ], ) L0005 0003 JMPZ T2 ->7
Extractor\IR\Operand • オペランド1つに対して1インスタンス • ここも素直にパースして、難しいことは後で考える 28 new Operand(TYPE_TMP, 2) L0005
0003 JMPZ T2 ->7 new Operand(TYPE_JUMP, 7)
1. そもそも何これ 2. 全体の処理の流れ 3. 前半戦: 元PHPコードから「中間表現」まで 4. 後半戦: 「中間表現」から
PHPコードへ
• OpcodeCollectionをイテレーションして(-> OpCodeを取り出す)、 OpcodeTranslatorを介してPHPコードに変換していく 後半戦でやること 30 foreach ($opcodes as $opcode)
{ $label = $this->labelManager ->formatLabelDefinition($opcode->offset, $scopePrefix); try { $code = $this->translator->translate($opcode, $context); } catch (GeneratorException $e) { throw new GeneratorException(
• オペコードごとに対応する変換器を判定して、変換を実行 Generator\OpcodeTranslator 31 public function translate(Opcode $opcode, Context $context):
?string { foreach ($this->translators as $translator) { if ($translator->supports($opcode)) { return $translator->translate($opcode, $context); } } throw new GeneratorException( "Unsupported opcode: {$opcode->opcode}"); }
• Translatorはたくさんある • オペコード:Translator は、1:1関係ではない 色々なTranslator 32
例: ComparisonTranslator 33 <?php $a = time(); $b = time();
var_dump($a < $b);
例: ComparisonTranslator 34 class ComparisonTranslator implements TranslatorInterface { public function
supports(Opcode $opcode): bool { return isset(self::OPCODE_OPERATORS[$opcode->opcode]); }
例: ComparisonTranslator 35 class ComparisonTranslator implements TranslatorInterface { private const
OPCODE_OPERATORS = [ 'IS_EQUAL' => '==', 'IS_NOT_EQUAL' => '!=', 'IS_IDENTICAL' => '===', 'IS_NOT_IDENTICAL' => '!==', 'IS_SMALLER' => '<', 'IS_SMALLER_OR_EQUAL' => '<=', 'SPACESHIP' => '<=>', 'CASE_STRICT' => '===', ];
例: ComparisonTranslator 36 class ComparisonTranslator implements TranslatorInterface { public function
translate(Opcode $opcode, Context $context): ?string { $operator = self::OPCODE_OPERATORS[$opcode->opcode]; $operands = $opcode->operands; $left = $context->getValue($operands[0]); $right = $context->getValue($operands[1]); $resultVar = $context->extractResultVar($opcode); return "{$resultVar} = {$left} {$operator} {$right};"; } }
before / after 37 <?php $a = time(); $b =
time(); var_dump($a < $b); <?php // ===== Main code $_tmp2 = time(); $a = $_tmp2; $_tmp4 = time(); $b = $_tmp4; $_tmp6 = $a < $b; var_dump($_tmp6); goto __end; __end:
オペコードから取れないデータの補完
通常のオペコードには現れないものがあるんですよね • 値が決定されている配列とか • 関数の引数とか • クラス定数とか • etc 39
定数配列 • 次のコードのオペコードを生成すると、 • 通常は、このように配列の中身が現れない • 夢の無限スパゲッティ製造機だと、こうなる $config1 = ['name'
=> 'test', 'value' => 42]; L0003 0000 ASSIGN CV0($config) array(...) 3: ASSIGN CV0($config1) "[\'name\' => \'test\',\'value\' => 42]" 40
valueに 定数を使った配列
🍝
値がない配列を見つけたら
元のコードの行番号を取って
token_get_allで解析
"=", [397, " ", 2], "[", [269, "'name'", 2], [397,
" ", 2], [391, "=>", 2], [397, " ", 2], [269, "'test'", 2], ",", [397, " ", 2], [269, "'value'", 2], [397, " ", 2], [391, "=>", 2], [397, " ", 2], token_get_allの結果 (説明用に整形しています)
"=", [397, " ", 2], "[", [269, "'name'", 2], [397,
" ", 2], [391, "=>", 2], [397, " ", 2], [269, "'test'", 2], ",", [397, " ", 2], [269, "'value'", 2], [397, " ", 2], [391, "=>", 2], [397, " ", 2], 0:トークンの識別ID 1: ソース 2: 行番号
"=", [397, " ", 2], "[", [269, "'name'", 2], [397,
" ", 2], [391, "=>", 2], [397, " ", 2], [269, "'test'", 2], ",", [397, " ", 2], [269, "'value'", 2], [397, " ", 2], [391, "=>", 2], [397, " ", 2], array(…)があった 「2行目」のトークンを探して
"=", [397, " ", 2], "[", [269, "'name'", 2], [397,
" ", 2], [391, "=>", 2], [397, " ", 2], [269, "'test'", 2], ",", [397, " ", 2], [269, "'value'", 2], [397, " ", 2], [391, "=>", 2], [397, " ", 2], array の開始位置を見つけたら
"=", [397, " ", 2], "[", [269, "'name'", 2], [397,
" ", 2], [391, "=>", 2], [397, " ", 2], [269, "'test'", 2], ",", [397, " ", 2], [269, "'value'", 2], [397, " ", 2], [391, "=>", 2], [397, " ", 2], そこから「配列の中身」を抜き出す = "[" と "]" の間のトークン
"=", [397, " ", 2], "[", [269, "'name'", 2], [397,
" ", 2], [391, "=>", 2], [397, " ", 2], [269, "'test'", 2], ",", [397, " ", 2], [269, "'value'", 2], [397, " ", 2], [391, "=>", 2], [397, " ", 2], ['name' => 'test', 'value' => 42]
ちなみに: valueに変数を使った配列の場合 52
参考資料 • PHP7から定数配列がOPcacheに乗るので巨大配列が使い放題という話 - hnwの日記 https://hnw.hatenablog.com/entry/2020/08/12/212433 53
そんな感じでできあがり(まだ始まったばかりだ)
よかったら遊んでみてくださいね • https://the-spaghetti-dream.nichiyou.be/ • さくらのAppRunさんを使っています!! • 簡単!最高!! 55
おまけ • なんやかんやで、 Slimを使ったアプリケーションの スパゲッティ化に成功しました • →のコードが変換元(32行) => 53,957行 •
github.com/o0h/the-spaghetti-dream-gallery を見てみてください • 僕は怖くて中身を見ていません • ただし変換前後のコードに対して 同じ内容のE2Eテストはパス済み(thx runn!) 56
おしまい! お付き合いいただき ありがとうございました!!
おしまい! お付き合いいただき ありがとうございました!!