Slide 1

Slide 1 text

𝕏 ファミコンでPHPを動かす 長谷川智希 2026/07/20 PHPカンファレンス2026 @tomzoh

Slide 2

Slide 2 text

𝕏 はせがわ ともき 長谷川 智希 @tomzoh 所属 http://www.dgcircus.com デジタルサーカス株式会社 来たれ!PHPer!We are hiring! 寄稿・著書 技術カンファレンス主宰

Slide 3

Slide 3 text

𝕏 はせがわ ともき 長谷川 智希 @tomzoh ライフワーク 技術カンファレンス運営/参加, 各種プログラム開発, , CPU, レトロゲーム機, 電子工作, ビール, サッカー観戦, レンタルカートレース, キャンピングカー, 宝塚観劇…

Slide 4

Slide 4 text

ファミコンでPHPを動かす ファミコンでPHPを動かす 長谷川智希 @tomzoh 4

Slide 5

Slide 5 text

https://ja.wikipedia.org/wiki/ファミリーコンピュータ ファミコンでPHPを動かす 長谷川智希 @tomzoh 5

Slide 6

Slide 6 text

どういうこと…? • 2018年にPHPでファミコンエミュレータを書いた • つまり、PHPでファミコンを動かした • 逆もやってみたい • ファミコンでPHPを動かす • ファミコンは正式名称 ファミリーコンピュータ • つまりコンピュータでありプログラムを動かせる • nesphp: ファミコン(海外での名前: NES)で動くPHP ファミコンでPHPを動かす 長谷川智希 @tomzoh 6

Slide 7

Slide 7 text

まずは echo() から… ファミコンでPHPを動かす 長谷川智希 @tomzoh 7

Slide 8

Slide 8 text

HELLO, WORLD デモ ファミコンでPHPを動かす 長谷川智希 @tomzoh 8

Slide 9

Slide 9 text

この調子でPHPを作る ファミコンでPHPを動かす 長谷川智希 @tomzoh 9

Slide 10

Slide 10 text

そもそもPHPとは…? • PHPのソースコードをダウンロードして ./con gure → make とかやって出 てきたモノは間違いなくPHPであろう • macOS版 php 実行ファイルは20MB • 我々が持っているのは32KBのROMと2KBのRAM • 単純比較はできないけど、まあ普通に考えてファミコンには載せられない • PHPすべての機能をサポートしなくても良かろう • str_rot13() とか… • 本気の実用じゃないし、最低限の機能を実装してみるか… 長谷川智希 @tomzoh fi ファミコンでPHPを動かす 10

Slide 11

Slide 11 text

PHPらしさ • ただ、そうは言っても、「これはPHPです」と言える感じにしたい • PHPらしさ: • PHPソース → opcode → VMで実行の流れ • opcodeのフォーマットがPHPと同じ • 変数やリテラルがzvalで表現される ファミコンでPHPを動かす 長谷川智希 @tomzoh 11

Slide 12

Slide 12 text

opcode • PHPはソースコードをopcodeにコンパイ

Slide 13

Slide 13 text

つくってみた nesphpの組込関数 分類 画面(BG) • Claude Codeと相談しながら開発 • プログラムサイズを見ながら徐々に実装 スプライト • 変数・配列代入, 各種演算, 比較, while, if, for(;;), 組込関数, … 同期・入力 • できるだけシンプルに… • 1パスコンパイラ • ソースを先頭から1回だけ走査する • function(), break/continue非対応 ファミコンでPHPを動かす 長谷川智希 @tomzoh 関数 nes_put() nes_puts() nes_putint() nes_cls() nes_attr() nes_palette() nes_bg_color() nes_chr_bg() nes_sprite_at() nes_sprite_attr() nes_chr_spr() nes_vsync() nes_btn() fgets(STDIN) nes_srand() nes_rand() nes_peek() nes_poke() nes_peek16() nes_pokestr() 乱数 メモリ user RAM 256B メモリ nes_peek_ext() ext RAM 8KB nes_poke_ext() nes_peek16_ext() nes_pokestr_ext() 配列 count() 13

Slide 14

Slide 14 text

ファミコンの画面描画 • ファミコンは8x8のパターンを定義して画面に敷き詰める設計 • 8x8の単位でしか配置できない • マリオとか敵キャラとかはスプライトという機能を使って 表示する • スプライトは1ピクセル単位で好きな場所に配置できる • そのかわり横に並べられる数などに制限がある • 制限を超えると表示されないので意図的に交互に消す = キャラのチラツキ • 「BG+スプライト」 • 初代プレステまでのゲーム機はだいたいこの設計 • echo("HELLO, WORLD"); するにしても「どこに」を指定する機能が必要 • nesphp独自命令が必要 ファミコンでPHPを動かす 長谷川智希 @tomzoh 14

Slide 15

Slide 15 text

独自命令の実装 • ファミコン特有の機能を実装するために nes_*() が大量にある • Zend VMのopcodeは200個弱あり、番号がついている • ZEND_ECHO=136, ZEND_ASSIGN_DIM=147 など • opcodeは1byteで $E8〜$FF が空いている • 空きエリアに独自命令を割り当てている • ソースコードをnesphpコンパイラがパースする時に独自命令の番号を持った opcodeにコンパイルしている ファミコンでPHPを動かす 長谷川智希 @tomzoh 15

Slide 16

Slide 16 text

独自命令デモ ファミコンでPHPを動かす 長谷川智希 @tomzoh 16

Slide 17

Slide 17 text

ファミコンでPHPが動いた • ファミコンでPHPが動いたと言って良かろう • しかし、こう、何か「おお!」みたいなのを動かしたい • nand2tetris… • 自作実行環境のロマンとしてテトリスを動かしてみよう • 命令的には足りてるはず ファミコンでPHPを動かす 長谷川智希 @tomzoh 17

Slide 18

Slide 18 text

ロマンの前に立ちはだかる強敵 ファミコンでPHPを動かす 長谷川智希 @tomzoh 18

Slide 19

Slide 19 text

メモリ不足 ファミコンでPHPを動かす 長谷川智希 @tomzoh 19

Slide 20

Slide 20 text

ファミコンのメモリマップ • どのアドレスに何が接続されているかのマップを メモリマップと呼ぶ • $6000-$FFFF ゲームカートリッジ • プログラムROM 32KB 読み取り専用メモリ ゲームカートリッジ • ファミコンのCPUにはいろいろなデバイスが接続されている $FFFF $C000 $8000 • バッテリーバックアップRAM 8KB 電源を切っても内容を保持するメモリ セーブデータ的な用途がメイン $6000 • APU, PPU: 映像とかサウンドのチップ • ワークRAM 2KB プログラムROM - 16KB バッテリー バックアップRAM - 8KB I/Oポート (APU, etc) • いまどきのPCで言う「メモリ」 • キャラクタの座標とか加速度とかゲーム進行に必要なデータを保存する • 変数をイメージするとわかりやすいかも ファミコンでPHPを動かす プログラムROM - 16KB 長谷川智希 @tomzoh I/Oポート (PPU) $07FF $0000 ワークRAM - 2KB 20

Slide 21

Slide 21 text

RAM不足 • RAMが全然足りない • ざっと作ったテトリスをコンパイルしたopcodeは18,592bytes • opcode 1個あたり32bytes × 581個 • テトリスの盤面 10 × 20マス配列のzval表現は 6,480bytes • これだけで25,072bytes必要 • ファミコンのワークRAMは2,048bytes… ファミコンでPHPを動かす 長谷川智希 @tomzoh 21

Slide 22

Slide 22 text

opcode/zval 縮小 • 「PHPらしさ」として内部的にopcode / zvalを使うことにしたけどファミコン の手に余るサイズ • nesphpでは使い道がないフィールドや過剰なサイズを持っているフィールドが あるので削除/縮小した • opcode • zval • 配列要素 ファミコンでPHPを動かす 32bytes → 12bytes 8bytes → 4bytes 32bytes → 4bytes 長谷川智希 @tomzoh 22

Slide 23

Slide 23 text

工夫後のテトリス • プログラム • opcode • リテラル 581個 × 12bytes = 6,972bytes 45個 × 4bytes = 180bytes 53個 × 4bytes = 212bytes 4bytes + 22 × 4bytes = 92bytes • 変数 • スカラ変数 • 配列1個22要素 • 1マス1配列要素でなく、1マス=1bitに圧縮した • 合計7,166bytes • ファミコンのワークRAMは2,048bytes • これでもぜんぜん足りない ファミコンでPHPを動かす 長谷川智希 @tomzoh 23

Slide 24

Slide 24 text

もっとRAMが無いのか ファミコンでPHPを動かす 長谷川智希 @tomzoh 24

Slide 25

Slide 25 text

もう1つのRAM領域 • ファミコンはセーブデータを保存する機能が(本体としては)無いが、 カートリッジ側に回路を積んで機能拡張できる • カートリッジ上にRAMと電池を置いてセーブデータを保存する様なのがある • ドラゴンクエストIIIとか • 各メーカー好きなように拡張してた • カートリッジを作れば好きなように拡張できる • 今回はEVERDRIVEがサポートするマッパーで実装する • メモリに関する機能拡張をマッパーと呼ぶ • ROM容量も厳しいのでいっしょに拡張できるとGood ファミコンでPHPを動かす 長谷川智希 @tomzoh 25

Slide 26

Slide 26 text

マッパー1(MMC1) SXROM $FFFF • バッテリーバックアップRAMを持つマッパー $C000 • $6000-$8000 の8KBがRAM領域 • バンク切替という方法で合計32KB使える • 特定のアドレスにデータを書き込むと $6000-$8000 の内容 が別のバンクに切り替わる • バンク0〜バンク3の4バンクある $8000 $6000 プログラムROM - 16KB プログラムROM - 16KB バッテリー バックアップRAM - 8KB I/Oポート RAM バンク 用途 0 コンパイル結果のopcodeとリテラル 1 配列 2 文字列リテラルの本体 3 拡張RAM (nes_poke_ext(), nes_peek_ext() で読み書きできる) ファミコンでPHPを動かす 長谷川智希 @tomzoh (APU, etc) I/Oポート (PPU) $07FF $0000 ワークRAM - 2KB 26

Slide 27

Slide 27 text

マッパー1(MMC1) SXROM • このマッパー、ROMも拡張できる $FFFF PHPコンパイラ + PHP VM + もろもろ $C000 • バンク4つ、合計64KB使える PHPソース or キャラクタデータ • nesphpでは3バンク使っている • もっとデカイPHPソースも置ける • がコンパイル結果をRAMに置ききれるか…問題も $8000 $6000 バンク 内容 0(切替) PHP ソース 1(切替) キャラクタデータ 2 未使用 3($C000 固定) PHPコンパイラ + PHP VM + もろもろ(割り込みベクタ) ファミコンでPHPを動かす プログラムROM - 16KB プログラムROM - 16KB バッテリー バックアップRAM - 8KB I/Oポート (APU, etc) I/Oポート (PPU) 長谷川智希 @tomzoh $07FF $0000 ワークRAM - 2KB 27

Slide 28

Slide 28 text

テトリスできた ファミコンでPHPを動かす 長谷川智希 @tomzoh 28

Slide 29

Slide 29 text

テトリスデモ ファミコンでPHPを動かす 長谷川智希 @tomzoh 29

Slide 30

Slide 30 text

テトリスのメモリ使用状況 実行できるプログラムサイズの 上限が近い • ROM • バンク0: テトリスのPHPソースコード 14,554bytes / 16,384bytes (88.8%) • バンク3: PHPコンパイラ, PHP VM 13,815bytes / 16,384bytes (84.3%) • RAM • 本体: スタック, PHP変数, … • バンク0: opcode, リテラル • バンク1: 配列 • バンク2: 文字列リテラル実体 • バンク3: 拡張RAM ファミコンでPHPを動かす 実装できる機能の 上限が近い (83.0%) 7,168bytes / 8,192bytes (87.5%) 92bytes / 8,192bytes (1.1%) 100bytes / 8,192bytes (1.2%) 220bytes / 8,192bytes (2.7%) 長谷川智希 @tomzoh 30

Slide 31

Slide 31 text

nesphp • 今回のコードはGitHubに置いてあります • https://github.com/hasegawa-tomoki/nesphp • macOS上でもファミコンエミュレータで動かせるので興味ある方は遊んでみてください • デモで使用した象とかテトリスは examples/ にあります ファミコンでPHPを動かす 長谷川智希 @tomzoh 31

Slide 32

Slide 32 text

𝕏 まとめ 長谷川智希 • ファミコンでPHPを動かす nesphp を作った • PHPの基本文法と、ファミコン特有の独自命令を実装した • メモリ不足に苦しんだ • そこそこPHPらしく、テトリスを実現できるぐらいのものができた @tomzoh • はじめてClaude Codeで趣味DIYプロジェクトやってみた • AIは深い知識をもっており、試行錯誤が速いのですごい勢いで実装が進む • AIが悩んでいるところに意見して相談して良いモノが出来るとすごく楽しい • のだけど、これ、一番書いてるの日本語だし、仕事みたいだな… • 役に立つものを作る時には最高だけど役に立たないものを作るのにはどうだろうな… • でもまあファミコンでPHP動くのは楽しい • みんなもヘンなとこで動くPHP作ってみて ファミコンでPHPを動かす 長谷川智希 @tomzoh 32

Slide 33

Slide 33 text

次回予告 ファミコンでPHPを動かす 長谷川智希 @tomzoh 33

Slide 34

Slide 34 text

𝕏 PHPカンファレンス愛媛2026 • PHPカンファレンス愛媛2026でも 同じ話で採択頂いています • 愛媛では今日お話できなかった 文字周りのおもしろい話をしようと思い ます • 今日の話が刺さった方はぜひ 聞きに来てください 長谷川智希 ファミコンでPHPを動かす 長谷川智希 @tomzoh @tomzoh 34

Slide 35

Slide 35 text

おまけ ファミコンでPHPを動かす 長谷川智希 @tomzoh 35

Slide 36

Slide 36 text

ファミコン • 1983年 任天堂から発売されたゲーム機 • 当時 14,800円 • 世界で1.15億台販売 • CPUはリコー RP2A03 • 6502(ほぼ)互換の派生CPU • 6502 - BCD演算 + APU(サウンド) + DMA + I/O • 6502: Apple I, Apple II • PPU(今で言うGPU)もリコーで RP2C02 ファミコンでPHPを動かす 長谷川智希 @tomzoh 36

Slide 37

Slide 37 text

はじめに ファミコンでPHPを動かす 長谷川智希 @tomzoh 37

Slide 38

Slide 38 text

ファミコンで自作プログラムを動かす • EVERDRIVE • ファミコンで自作プログラムを動かせるアイテム • microSDカードにROMイメージを入れてファミコンを起動 • Amazonで売ってる • …けどどうもこれは本家EVERDRIVEの互換品みたい • だいたい動くけどしばしば不安定… • オリジナルを直販サイトからも買ってみた • ウクライナから2週間ぐらいかけて届いた • 安定してるので今日のデモはこっちでやってます ファミコンでPHPを動かす 長谷川智希 @tomzoh 38

Slide 39

Slide 39 text

時代のギャップ埋め • ファミコンは1983年発売のデバイス • 当時のテレビはアナログ • ファミコン、RF出力しか無い • アナログテレビの1ch/2chに信号を割り込ませるやつ • 軽めの改造でAV端子(黄白赤)にできる • 電源のUSB-C化といっしょにやって背面パネルを3D プリンタで作った https://news.denfaminicogamer.jp/kikakuthetower/181226a/2 ファミコンでPHPを動かす 長谷川智希 @tomzoh 39

Slide 40

Slide 40 text

ファミコンの仕様 ファミコンでPHPを動かす 長谷川智希 @tomzoh 40

Slide 41

Slide 41 text

ファミコンのCPUとメモリ • ファミコンのCPUはリコー RP2A03 • アドレス線16本、データ線8本 • アドレス 16bit, データ 8bit のメモリアクセスができる • アドレス 0x0000 〜 0xffff のどれかを指定して… • 読み込めば1byteのデータが返ってくる • 書き込めば1byteのデータを書き込める • 合計64KB https://ja.wikipedia.org/wiki/MOS_6502 https://user.xmission.com/~trevin/atari/6502_pinout.html ファミコンでPHPを動かす 長谷川智希 @tomzoh 41

Slide 42

Slide 42 text

いまどきのコンピュータとの違い $FFFF • いまどきのコンピュータはROMはほぼ使われない $C000 • 電源投入直後に動くようなプログラム(BIOS)ぐらい • プログラムはSSDなどに保存してあって、 プログラムROM - 16KB プログラムROM - 16KB それをRAMに読み込んで実行する • ファミコンではプログラムはROMに保存されて CPUに直結されている • 書換可能な領域と書換不可な領域に何を保存するかを $8000 $6000 考える必要がある バッテリー バックアップRAM - 8KB I/Oポート (APU, etc) • SwitchとかはソフトウェアがROMで供給されているけど ストレージ扱いでありCPUには直結されていない I/Oポート (PPU) $07FF $0000 ファミコンでPHPを動かす 長谷川智希 @tomzoh ワークRAM - 2KB 42

Slide 43

Slide 43 text

メモリ節約の詳細 ファミコンでPHPを動かす 長谷川智希 @tomzoh 43

Slide 44

Slide 44 text

まずopcode見てみる ファミコンでPHPを動かす 長谷川智希 @tomzoh 44

Slide 45

Slide 45 text

off. sz. field 0 8 handler opcode構造体 opcodeハンドラの C関数ポインタ 8 4 op1 constant / var / num / jmp_offset • opcodeはメモリ上には32bytesのデータとして格 納されている 12 4 op2 • つまりPHPの1命令は最低でも32bytesのメモリを必要 16 4 result 20 4 extended_value とする • for()みたいな制御構造は複数のopcodeで実現される • nesphpではROMにPHPソースコードがある 第3引数・フラグ等の 追加情報 24 PHPソース行番号 • ソースコードをコンパイルした結果のopcodeはRAM上に 置く必要がある ファミコンでPHPを動かす (デバッグ用) 28 • ファミコンのRAMは2KB = 2,048bytes • つまり64命令しか置けない 長谷川智希 @tomzoh 4 lineno 1 opcode 命令番号 29 1 op1_type IS_CONST / IS_CV など 30 1 op2_type 31 1 result_type 45

Slide 46

Slide 46 text

メモリきっつ〜 ファミコンでPHPを動かす 長谷川智希 @tomzoh 46

Slide 47

Slide 47 text

opcodeを小さくする off. sz. field 0 8 handler sz. opcodeハンドラの C関数ポインタ 8 • nesphpで不要なものを削り、フィールドサイズも削減する • handler • このopcodeを処理するC関数へのポインタが入っている • nesphpではswitch-case的に処理するので不要 4 op1 2 constant / var / num / jmp_offset 12 4 op2 2 16 4 result 2 20 4 extended_value 2 第3引数・フラグ等の 追加情報 • lineno • デバッグ用にPHPソースの行番号が入っている • コンパイル時はコンパイラが出せるのでいらない • 実行時エラーは行番号出せなくなる 24 4 lineno PHPソース行番号 (デバッグ用) 28 1 opcode 1 命令番号 • op1, op2, result, extended_value 29 • nesphpでは指し示す先が1byteで済む 30 1 op2_type 1 31 1 result_type 1 • 32bytes → 12bytes ファミコンでPHPを動かす 長谷川智希 @tomzoh 1 op1_type 1 IS_CONST / IS_CV など 47

Slide 48

Slide 48 text

強敵はまだいる ファミコンでPHPを動かす 長谷川智希 @tomzoh 48

Slide 49

Slide 49 text

PHPの変数表現 変数 zval off. sz. field 0 8 value • PHPの値は内部的にzvalで表現される • 変数1こ 最低16bytes 値 or 値へのポインタ 8 型情報 12 4 u2 用途に応じた補助情報 • 配列の場合、もうちょっと複雑 • zval → zend_array → Bucket × n 構造 • zend_array にはインデックスの次の値とかが入っている • テトリスを考えてみる • 縦20マス × 横10マス: 200要素 • zval 16bytes + zend_array 64bytes + Bucket 32bytes × 200 = 6,480bytes • 我々が持っているメモリは2KB, 2,480bytes ファミコンでPHPを動かす 4 u1 長谷川智希 @tomzoh 配列 zval 16 bytes zend̲array 64 bytes Bucket 32 bytes Bucket 32 bytes Bucket 32 bytes 49

Slide 50

Slide 50 text

メモリきっつ〜 ファミコンでPHPを動かす 長谷川智希 @tomzoh 50

Slide 51

Slide 51 text

nesphpでの工夫 - 変数 変数 zval off. sz. field 0 8 value sz. 3 値 or 値へのポインタ • zvalを8bytes → 4bytesに 8 • valueは2bytes + 1bytes: intの最大値が16bitになる • 玄人用: • nesphpではzend_stringを廃止している • RAM上に文字列そのものがあるのでポインタはRAMを指す • 文字長だけvalueのbyte 3に持たせている • 配列も大きく削減 • zend_arrayからGCなどパフォーマンスのための構造を全削除 4 u1 1 型情報 12 4 u2 - 用途に応じた補助情報 配列 zval 16 bytes zend̲array 64 bytes Bucket 32 bytes Bucket 32 bytes Bucket 32 bytes 64bytes → 4bytes • 連想配列をサポートしないのでBucketはzvalだけに削減 32bytes → 4bytes • テトリス配列 4 + 4 + 4 × 200 = 808bytes • 6,480bytes → 808bytes ファミコンでPHPを動かす 長谷川智希 @tomzoh 51

Slide 52

Slide 52 text

余談: ファミコンの拡張性 $FFFF • ファミコン、カートリッジとCPUがアドレス線16本つながって $C000 る • ファミコン発売時のソフトたちはROM 16KBだった プログラムROM - 16KB プログラムROM - 16KB • アドレス線14本で表現できる • 16本つなげておいたことで拡張性が得られた • SOUND IN/OUTなんかもカートリッジとつながってる • カートリッジ側に音源チップを載せてリッチな音を鳴らすなんての も $8000 $6000 I/Oポート • ファミコンはカートリッジ側にも機能がいろいろある (APU, etc) • ファミコンエミュレータはファミコン本体の機能だけエミュレートして I/Oポート いては市販されたゲームは動かせない (PPU) • ファミコンエミュレート + カートリッジ機能エミュレート + カートリ ッジ内蔵データ ファミコンでPHPを動かす バッテリー バックアップRAM - 8KB 長谷川智希 @tomzoh $07FF $0000 ワークRAM - 2KB 52