Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ファミコンでPHPを動かす / PHP on the Famicom

ファミコンでPHPを動かす / PHP on the Famicom

PHPカンファレンス2026の資料です

以前、PHPでファミコンエミュレータを作りました。ファミコンの仕様全体を理解しながらPHPでそれを実装する体験は今までのプログラマ人生で初の経験でも特別で、とても楽しいものでした。
PHPでファミコンを動かしたら、こんどはファミコンでPHP動かしたくなりました。
ということで、ファミコンで動くPHPコンパイラとPHP VMを実装してみました。
ファミコンは6502派生の8bit CPUを持ち、64KBのメモリ空間にアクセスできますが、ゲーム機として設計されているのでゲームプログラムや画面に表示されるキャラクタはカートリッジROMに格納される前提であり、RAMは2KBしか搭載していません。
このトークではファミコンでPHPを動かすために必要な設計、実装方法、実機でPHPを動かすためのノウハウなどをお話します。
Write once, run anywhere.
PHPをあらゆる環境で動かしましょう!

https://fortee.jp/phpcon-2026/proposal/5fcc1ed4-ad86-497a-b79c-b628c7bda95f

Avatar for HASEGAWA Tomoki

HASEGAWA Tomoki

July 19, 2026

More Decks by HASEGAWA Tomoki

Other Decks in Technology

Transcript

  1. 𝕏 はせがわ ともき 長谷川 智希 @tomzoh ライフワーク 技術カンファレンス運営/参加, 各種プログラム開発, ,

    CPU, レトロゲーム機, 電子工作, ビール, サッカー観戦, レンタルカートレース, キャンピングカー, 宝塚観劇…
  2. どういうこと…? • 2018年にPHPでファミコンエミュレータを書いた • つまり、PHPでファミコンを動かした • 逆もやってみたい • ファミコンでPHPを動かす •

    ファミコンは正式名称 ファミリーコンピュータ • つまりコンピュータでありプログラムを動かせる • nesphp: ファミコン(海外での名前: NES)で動くPHP ファミコンでPHPを動かす 長谷川智希 @tomzoh 6
  3. そもそもPHPとは…? • PHPのソースコードをダウンロードして ./con gure → make とかやって出 てきたモノは間違いなくPHPであろう •

    macOS版 php 実行ファイルは20MB • 我々が持っているのは32KBのROMと2KBのRAM • 単純比較はできないけど、まあ普通に考えてファミコンには載せられない • PHPすべての機能をサポートしなくても良かろう • str_rot13() とか… • 本気の実用じゃないし、最低限の機能を実装してみるか… 長谷川智希 @tomzoh fi ファミコンでPHPを動かす 10
  4. PHPらしさ • ただ、そうは言っても、「これはPHPです」と言える感じにしたい • PHPらしさ: • PHPソース → opcode →

    VMで実行の流れ • opcodeのフォーマットがPHPと同じ • 変数やリテラルがzvalで表現される ファミコンでPHPを動かす 長谷川智希 @tomzoh 11
  5. opcode • PHPはソースコードをopcodeにコンパイ <?php for ($i = 0; $i <

    10; $i++){ echo($i.PHP_EOL); } ルし、opcodeをPHP VMで実行する • opcacheはコンパイル済のopcodeをキャ ッシュしておいてコンパイルの時間を節約す る仕組み ファミコンでPHPを動かす 長谷川智希 @tomzoh 0000 ASSIGN CV0($i) int(0) 0001 JMP 0005 0002 T2 = CONCAT CV0($i) string(" ") 0003 ECHO T2 0004 PRE_INC CV0($i) 0005 T4 = IS_SMALLER CV0($i) int(10) 0006 JMPNZ T4 0002 0007 RETURN int(1) 12
  6. つくってみた 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
  7. ファミコンの画面描画 • ファミコンは8x8のパターンを定義して画面に敷き詰める設計 • 8x8の単位でしか配置できない • マリオとか敵キャラとかはスプライトという機能を使って 表示する • スプライトは1ピクセル単位で好きな場所に配置できる

    • そのかわり横に並べられる数などに制限がある • 制限を超えると表示されないので意図的に交互に消す = キャラのチラツキ • 「BG+スプライト」 • 初代プレステまでのゲーム機はだいたいこの設計 • echo("HELLO, WORLD"); するにしても「どこに」を指定する機能が必要 • nesphp独自命令が必要 ファミコンでPHPを動かす 長谷川智希 @tomzoh 14
  8. 独自命令の実装 • ファミコン特有の機能を実装するために nes_*() が大量にある • Zend VMのopcodeは200個弱あり、番号がついている • ZEND_ECHO=136,

    ZEND_ASSIGN_DIM=147 など • opcodeは1byteで $E8〜$FF が空いている • 空きエリアに独自命令を割り当てている • ソースコードをnesphpコンパイラがパースする時に独自命令の番号を持った opcodeにコンパイルしている ファミコンでPHPを動かす 長谷川智希 @tomzoh 15
  9. ファミコンのメモリマップ • どのアドレスに何が接続されているかのマップを メモリマップと呼ぶ • $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
  10. RAM不足 • RAMが全然足りない • ざっと作ったテトリスをコンパイルしたopcodeは18,592bytes • opcode 1個あたり32bytes × 581個

    • テトリスの盤面 10 × 20マス配列のzval表現は 6,480bytes • これだけで25,072bytes必要 • ファミコンのワークRAMは2,048bytes… ファミコンでPHPを動かす 長谷川智希 @tomzoh 21
  11. 工夫後のテトリス • プログラム • 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
  12. もう1つのRAM領域 • ファミコンはセーブデータを保存する機能が(本体としては)無いが、 カートリッジ側に回路を積んで機能拡張できる • カートリッジ上にRAMと電池を置いてセーブデータを保存する様なのがある • ドラゴンクエストIIIとか • 各メーカー好きなように拡張してた

    • カートリッジを作れば好きなように拡張できる • 今回はEVERDRIVEがサポートするマッパーで実装する • メモリに関する機能拡張をマッパーと呼ぶ • ROM容量も厳しいのでいっしょに拡張できるとGood ファミコンでPHPを動かす 長谷川智希 @tomzoh 25
  13. マッパー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
  14. マッパー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
  15. テトリスのメモリ使用状況 実行できるプログラムサイズの 上限が近い • 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
  16. 𝕏 まとめ 長谷川智希 • ファミコンでPHPを動かす nesphp を作った • PHPの基本文法と、ファミコン特有の独自命令を実装した •

    メモリ不足に苦しんだ • そこそこPHPらしく、テトリスを実現できるぐらいのものができた @tomzoh • はじめてClaude Codeで趣味DIYプロジェクトやってみた • AIは深い知識をもっており、試行錯誤が速いのですごい勢いで実装が進む • AIが悩んでいるところに意見して相談して良いモノが出来るとすごく楽しい • のだけど、これ、一番書いてるの日本語だし、仕事みたいだな… • 役に立つものを作る時には最高だけど役に立たないものを作るのにはどうだろうな… • でもまあファミコンでPHP動くのは楽しい • みんなもヘンなとこで動くPHP作ってみて ファミコンでPHPを動かす 長谷川智希 @tomzoh 32
  17. ファミコン • 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
  18. ファミコンで自作プログラムを動かす • EVERDRIVE • ファミコンで自作プログラムを動かせるアイテム • microSDカードにROMイメージを入れてファミコンを起動 • Amazonで売ってる •

    …けどどうもこれは本家EVERDRIVEの互換品みたい • だいたい動くけどしばしば不安定… • オリジナルを直販サイトからも買ってみた • ウクライナから2週間ぐらいかけて届いた • 安定してるので今日のデモはこっちでやってます ファミコンでPHPを動かす 長谷川智希 @tomzoh 38
  19. 時代のギャップ埋め • ファミコンは1983年発売のデバイス • 当時のテレビはアナログ • ファミコン、RF出力しか無い • アナログテレビの1ch/2chに信号を割り込ませるやつ •

    軽めの改造でAV端子(黄白赤)にできる • 電源のUSB-C化といっしょにやって背面パネルを3D プリンタで作った https://news.denfaminicogamer.jp/kikakuthetower/181226a/2 ファミコンでPHPを動かす 長谷川智希 @tomzoh 39
  20. ファミコンの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
  21. いまどきのコンピュータとの違い $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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 余談: ファミコンの拡張性 $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