MJHD

エモさ駆動開発

Rustでファミコンエミュレータを自作した話

前回、ゲームボーイエミュレータを開発してから2ヶ月ほど、今度はファミコンエミュレータを開発したのでゲームボーイとの開発の比較をしたいと思います。 ちなみに、ファミコンエミュレータの作成はこれで二回目。

f:id:wait0000:20210612115808p:plain
スーパーマリオを動かした様子

前回の記事: mjhd.hatenablog.com

命令がシンプル

CPUの実装の大半は数多い命令をデバッグしながらチマチマと実装していく部分なのですが、ファミコンで使用されているMOS 6502の命令セットはゲームボーイZ80, 8080と比べても命令数が少ないです。 ファミコンは8bitを素直にデコードすれば命令と一対一に対応するため、たかだか250個程度の命令しか存在しません。 比べて、ゲームボーイにはプレフィックス命令が存在して、特定のバイトが現れた時は、次の1バイトも考慮してデコードします。なので倍の500個ほど命令が存在します。

また、各命令も命令種別、アドレッシングモード、命令のカテゴリ(ロード命令、算術命令…など)をビット列として組み合わせたものなので、デコードも工夫をすればとてもシンプルになります。(CPU unofficial opcodes - Nesdev wiki

割り込みもゲームボーイに比べてかなり少なく、全体的にCPUの実装はサクッと終えることができました。

PPUはちょっとややこしい

比べて、PPUはゲームボーイより少しややこしいです。 ゲームボーイのPPUは、BG、スプライト、ネームテーブル、パターンテーブルがあれば描画できたのですが、ファミコンではここに属性テーブルが追加されます。 白黒ではなくてカラーなのでしょうがないですが、実装量は増えました。

また、ゲームボーイの時はCPUから直接VRAMを触れましたが、ファミコンレジスタ経由か、DMAが主になります。メモリ空間も共有していないので、バスの実装も倍になります。

他にもゼロスプライトヒットなどの機能や、スクロールもマッパーによってミラーリングが切り替わるなど、細かな部分で実装が増えます。

あと罠として、パターンテーブルを読むときに、ゲームボーイファミコンで2BPPの順序が逆です。今回見事にハマったため注意が必要。

全体的に、PPUはゲームボーイと比較して複雑です。

マッパーがややこしい

ゲームボーイのマッパーはバンクの切り替えだけ実装しておけば良かったのですが、ファミコンの場合はここにミラーリングの切り替えや、割り込みの実装などが追加になります。 メモリ空間もCPUのものとPPUのもので二つ存在するため、マッパーの実装も倍です。 また、MMC1はバンクの切り替えがシフトレジスタ経由なので、内部状態も複雑。

コントローラーはちょっとだけ複雑になった

ゲームボーイの時は8bitのセット/リセットでボタンの状態をレジスタに反映すれば良かっただけなのでシンプルなコントローラーの実装ができましたが、ファミコンはシフトレジスタのように読み込むたびに状態が変わり、各ボタンの状態がクルクルと切り替わりながら返ってくる仕様になってます。

全体的に

レトロゲーのエミュレータ作りで一番頭を使うのはPPUだと思っているのですが、その点でファミコンはPPUが比較的複雑でハマりポイントも多い印象です。 入門としてはゲームボーイが一番で、発展としてファミコンを作る、という順番が良いなと感じました。

今回作ったモノ(第二作目): github.com

第一作目: github.com

Rustでゲームボーイエミュレータを自作した話

ここ2ヶ月ぐらいゲームボーイエミュレータを自作してました。結果として「物量も少なくエミュレータ自作入門にオススメ」と思ったので、時系列で完成までの流れをまとめつつ、エミュレータ開発は楽しいぞということ伝えたいと思います。 

f:id:wait0000:20210414200924g:plain

星のカービィをプレイする様子

全体像を把握する

ゲームボーイエミュレータをRustで自作する記事が目に止まり、ちょうどRustの入門題材を探していたので作ることに。

記事で紹介されている動画をみるだけで全体像が把握でき、なんとなく作れそうな雰囲気を感じられます。↓

 

www.youtube.com

 

まずはROMをデコード

 何はともあれ、ROMをメモリに読み込めないことには実行もできないので、 PanDocs: The Cardridge Header を参考に、struct 定義と enum 定義をひたすら写経します。

ROMヘッダの取り込みとチェックサムの検証を実装したらデコード処理は完了なので、あとはMBC(Memory Bank Controller)というカードリッジの内部に埋め込まれているチップを模したプログラムを書きます。

カードリッジタイプのゲームは内部にチップが入っており、特定のアドレスに書き込みを行うことでバンクと言われるデータ領域を切り替えて、大容量なデータを限られたメモリマップに展開します。

この処理を再現するわけですが、まずは一番簡単な RomOnly というMBCを書きました。単純に先程読み込んだROMの配列にアクセスするだけなので5秒ぐらいで書けます楽ちん。

 

github.com

 

CPUの実装

ここがエミュレータ自作の本番です。

CPUの命令サイクルと呼ばれる、

1. フェッチ(PC番地のデータをとってくる)

2. 命令デコード(とってきたデータで処理を分岐する)

3. 命令実行

4. フラグ反映(Z, N, H, Cの4つのフラグを更新する)

5. PC更新(PC += 1)

を行ってくループ文を書きます。

次に Gameboy CPU Manual (PDF) にCPUの命令一覧と処理内容、影響を受けるフラグの一覧が載ってるので、これをひたすらに実装していきます。

だいたい 500 命令あるらしいですが、対象となるレジスタが違うだけで共通の物が多いです。

1. ロード命令(読み込み、書き込み)

2. ジャンプ命令(PCを書き換える系)

3. 加算減算、ビット演算

4. ビットシフト・ローテート

5. スタック操作

6. 特殊系

ぐらいで大まかにまとめられます。

 

ここで踏んだ罠:

- CBプレフィックス命令に気をつける(0xCB を見つけたら次の 1byte もデコードする)

- `ADD HL, rr` だけハーフキャリーの計算が違う(11bit が対象)

- `ADD SP, n` は16bit命令でも8bitだと思ってキャリー、ハーフキャリーの計算をする

- 16bit 命令は 0x0X ~ 0x3X 系と 0xCX ~ 0xFX 系で、レジスタの規則が違う(0x0X ~ 0x3X は BC, DE, HL, SP、0xCX ~ 0xFX は BC, DE, HL, AF

 

github.com

 

PPUの実装(BGだけ)

ひたすら黒い画面と睨めっこしてCPUがある程度形になったら、モチベを保つためにも画像処理を書きます。Hello World ROMが動き、画面が表示されることが目標です。

ここで実装するのは PPU(Picture Processing Unit)で、今で言うところのGPUでしょうか。

スプライト、ウィンドウ、スクロール…などPPUには色々な機能がありますが、一旦今必要なのはBGだけなので実装していきます。

GBEDG: The PPU にPPUの挙動が細かく記載されているので読みつつ、特にパレット、カラー、タイル、マップの関係性を紙などに図示すると理解しやすいです。

正常に実装できれば、Github - dusterherz/gb-hello-world に置いてあるROMを今まで書いた処理に食わせれば「Hello World」という文字が表示されるはずです。

 

ここで踏んだ罠:

- タイルのアドレス計算には二種類のモードがあり、それぞれ「符合なしでベースアドレスは 0x8000」、「符合ありでベースアドレスは 0x9000」

 

github.com

 

デバッガの実装

ここがキモです。この後の戦いに必須です。以下の機能があると便利です:

- ブレークポイント(指定のPCに到達したら、実行中断してステップ実行)

- トレース(CPUの状態とニーモニックのログを出力できる機能。別のエミュレータと出力フォーマットを合わせておくと diff とってデバッグできる)

- メモリダンプ(特定範囲のメモリを出力する機能)

 

CPUテストROMの実行

ラスボス戦です。辛かったです、地獄かな。

https://github.com/retrio/gb-test-roms に「cpu_instrs」というCPUの各種命令の実行結果をテストしてくれるROMがあります。エラーが起こると、「#1 failed」とどこでテストが落ちたのか教えてくれる便利ROMです。

前回まででBGは実装したので、テスト結果も画面表示で確認できますね。

 

これがだいたい通ると、急に市販のROMも動くようになる大事な局面なのですが…まあとにかくデバッグがしんどい。

テストROMが複雑で分かりづらいことが原因と思われるので、デバッグで大事なポイントを書いておきます。

 

1. MBC1を実装する

テストROMは MBC1 です。https://gbdev.gg8.se/wiki/articles/MBC1 を参考にMBC1を実装しましょう。

書き込み仕様で 0x0000-0x1FFF など範囲指定されている部分は、「指定範囲のどこに書き込んでも動作は一緒」です。

 

2. 信頼できる既成のエミュレータを一つ見つける

ブレークポイント、ステップ実行、トレース、メモリダンプなど一通りの機能が揃っていて、かつ安定しているエミュレータを見つけます。

macOSだと mGBA と言うエミュレータが安定していて良かったです。

ここで自作したトレース機能の出力フォーマットが統一されていると、テキスト diff ツールを使ってside-by-side で動作を検証できるので、抜群にデバッグが楽です。 

 

3. テストROMを逆アセンブルする

テストROMにもソースコードがもちろんあるのですが、マクロを多用していたり一度命令をメモリ上にコピーしていたりして追うのが大変なので、一度逆アセンブルすると吉です。

今回は GitHub - mattcurrie/mgbdis を使用しました。

基本的に 0x4XXX に存在するニーモニックは、実行時に 0xCXXX へコピーされて実行されます。なので、確認したいニーモニックのROM上の位置が 0x41B9 であれば、0xC1B9 へブレークポイントを張れば確認できます。

 

残り部分の実装

もうここからはずっと楽しい時間です。市販のROMも動き、見た目周りの実装を充実させたり、タイマー、ジョイパッドなどゲームプレイに必要なものを実装していきます。

 

完成!

RomOnly な Dr. MARIOテトリス、MBC1 の星のカービィ、マリオランドなどが動くようになります。めでたしめでたし。

f:id:wait0000:20210414214551p:plain

マリオランド

 

ROM吸い出し(番外編)

エミュレータ開発は実際のROMデータがあるとモチベが保てます。カードリッジ自体はブックオフなどで300円ぐらいで買えますが、ROM吸い出し機はアマゾンなどで6000円ぐらい。ちょっと高い。

なので今回は CUBIC STYLE さんが同人ハードとして開発している、「Raspberry PiにつないでGBAのROM読み書きできる拡張ボード」を少しいじり、GBのROMを吸い出しました。

※ 公式にGBへ対応しているわけではないので、自己責任です

cubic-style.booth.pm

 

読み出しプログラムをいじるだけでGB対応ができちゃいました。

GBAとGBでカードリッジ端子の互換性はあるのですが、ピンアサインが違います。(あと電圧も 3.3V と 5V で違うけど、一応そのまま動く)

GBのピンアサインは Arduinoを使ったgameboyカードリッジのdump が分かりやすく、読んだ結果アドレスが16ピン、データが8ピンで、RDをLowにしたままアドレスを書き込むと、データが流れてくると判明しました。

なので、ROMヘッダを読んで RomOnlyであればただメモリ番地をインクリメントしながらデータを読んでいくだけ、MBC1であればバンク番号をインクリメントしながらROM Sizeまで読み出していくだけですね。

 

追記: ROM読み出しツールをRustで書きました。興味のある方はつかってみてください。PRも歓迎しています。

github.com

 

今後

github.com

 

今後の仕事としては、実際のカードリッジから直接ゲームを起動する実験をしてみようかと思っています。

ROM吸い出しでカードリッジの低レベルな知識もついたので、MBCの実装を拡張ボードのGPIOとSPIに接続するプログラムを書きました。

(ARM 32bitへのクロスコンパイルが辛く、動かせていないです…動作速度など気になる)

これができればMBCの実装も必要なく、いろんなカードリッジが動かせますね!

 

エミュレータとしては、今まで 4bit(習作), 8bit(NES), 8bit(GB) と書いてきたので…次は 16bit の何かでしょうか…?GBが楽しかったのでまた何か書きたい…

 

 

github.com

 

2020年 振り返りと抱負

どんな年だったか

今年は引き籠りが合法となったことにより、プライベートが充実した年だった。

相対的に仕事の記憶があまりない。めちゃ頑張った気がするけど。

プライベートは年始から急にハマった現代思想に始まり放送大学に入学したり、UberEatsが高いので自炊をしたり、引っ越したり猫を飼ったりした。

 

現代思想

もともと大学の時に教養科目として現代思想の概論のような授業を受講し、毎回板書を三回読み直し、図書館へ通い原著を読む(当然何も理解できない)生活をしていたが、大学卒業後はしばらく触れていなかった。 

社内の技術交流会でひょんなことから現代思想の話が始まり、同時期にOOUIの勉強会などにも参加し、グレアムハーマンの四方対象を読み始めたこともあり、急にマイブームが訪れた。

おかげで今年は自分史上稀にみる読書の年になった。

読んだ本

自分の知識レベルとしては、一般教養として哲学者・思想家の名前を知っている程度だったので、ひたすら入門書を読んだ。

はじめての構造主義 (講談社現代新書)

はじめての構造主義 (講談社現代新書)

 

構造主義のお話。初心者でもとても読みやすく、現代思想でも大事な部分なので取り掛かりとしてとても良かった。

 

資本主義に出口はあるか (講談社現代新書)

資本主義に出口はあるか (講談社現代新書)

 

そういえば今の経済の歴史を何も知らないな?資本主義という言葉に苦手意識があったため、この本を読んだ。これも非常に読みやすく、分かりやすく思想をカテゴリ分けしてくれている。

歴史的な出来事や当時主流だった思想を、右と左、ロックとルソー、保守とリベラル、ルーズベルトとウィルソン…など対立関係で説明している。

現代思想の歴史としても、当時の世界情勢が理解できると思想の背景が理解できるため、学びがあった。

 

資本主義リアリズム

資本主義リアリズム

 

資本主義つながりでもう一つ、気になっていた本。資本主義が唯一の思想となっている現代において、資本主義の終わりを想像するよりも世界の終わりを想像する方がたやすい。資本主義の思想を内面化してしまっている現代人に、本当にこれでいいのか?と呼びかける内容だった。

上の「資本主義に出口はあるか」と一緒に読んだため、歴史的に現代がどのような状態に置かれているのか、理解がはかどった。

 

四方対象: オブジェクト指向存在論入門

四方対象: オブジェクト指向存在論入門

 

OOUI つながりで興味のあった、オブジェクト指向存在論の原著。ド素人が読んでも分かるものではないので、逆に「何を学ばなければいけないのか」をリストアップする用途になった。四方対象はライプニッツフッサールハイデガーホワイトヘッドなどなどの思想を引用して説明されているため、こいつらを学ばなければいけない。

ひとまずハイデガーホワイトヘッドを次に学ぶことに決めた。

 

ホワイトヘッドの哲学 (講談社選書メチエ)

ホワイトヘッドの哲学 (講談社選書メチエ)

 

オブジェクト指向存在論で度々言及される、ホワイトヘッドという思想家の入門書。

世界は連続する有機体である、物と物の間に厳密な区別はなく、我々は世界を抱握によって認識している…。

という有機体の哲学を分かりやすく説明してくれている。

合わせて、

1267夜『ホワイトヘッドの哲学』中村昇|松岡正剛の千夜千冊

を読むと理解がはかどる。

 

 神は実在するのか。

アンセルムスという「神」の定義をした司祭・思想家の解説から始まり、ルイスのそれに対する論理学的な批判を通して、分析哲学という一つの流派を学べる本。最終的に可能世界、様相論理などを解説している。

大学の卒論で様相論理、可能世界意味論を学んだのだが、後半、論理式がとても多くなるため、かなり読むのに苦労した…。

こういう哲学があるんだな、という学びが得られた。

 

 ここまで色々本を読み、これは外せないなと感じた思想家三人、ベルクソンサルトルフーコーを解説した入門書。

読みやすかった。ベルクソンは特に理解しやすく説明されていた。

 

現代フランス哲学に学ぶ (放送大学教材)

現代フランス哲学に学ぶ (放送大学教材)

 

放送大学でテキストとして使っている本。正直、放送大学の授業内容は、この本を音読した音声なので、実はこの本を買うだけで事足りる。

ベルクソンメルロ=ポンティ構造主義フーコー、ポールリクールを軸に、ハイデガー、マルセル、アラン、サルトルフッサール…と一通り概要を説明してくれている。

内容もとても分かりやすいし、都度他の思想家と対比をしながら説明をしているため、脳内マップの作製にとても役立つ。

ここでベルクソンの理解がかなり進み、「あーなるほどー!」と声に出しながら受講できた。

これから期末試験。

抱負

来年も、まだまだ穴埋めできていない思想家が沢山いるため、入門書を漁っていきたい。

特にカント、マルクスハイデガーについてまだ概要しかわかっていないがかなり重要人物なので、きちんと学びたい所存。

そして、準備万端の状態で四方対象を読み切りたい。また、フーコーの系譜学ももう一度読み直したい(今ならもう少し内容が入ってくる気がする)。

エンジニア

今年は、新しい技術や言語の習得ができていない年だった。(マズイ)

Rust を少し齧ったが、まだまだ「Rust らしいコード」を書けていない。

抱負としては「低レイヤーに関する知識を深める」。CPU/GPU、ドライバ、カーネル、OS、コンテナ…などなど。

ひとまず、下記の記事に取り組みたい。

kaminashi-developer.hatenablog.jp

また、Rust についても作成中の作品を作り切るところまでは最低限やっていきたい。

人生

そろそろ彼女作らないとまずくない…?まずくない?

20代後半戦に入って、ライフステージが周囲に比べて低いのが気になるところ。

総じて

来年は自発的に動いていく年になると良いなと思います。

2020年 買ってよかったモノ

一昨年のエントリ: mjhd.hatenablog.com

気がついたら「もうそんな未来?」感のあった2020年も終わりを向かえそうな日頃になってきましたがみなさんいかがお過ごしでしょうか?
僕はと言えば家から出ず会話をするのはコンビニのレジぐらい。「レジ袋要りません」「レシート要りません」「iDでお願いします」「あ、温め要りません、割り箸で」をランダムな順序で店員に伝え、どういう挙動をするのかデバッグするときしか声を使いません。そう言えば最近、上司からも声が小さいとよく言われますが、きっとこの生活で声帯が退化したのだと説明しています。新種の脊椎動物です。毎回ツッコミが入りますが。

そんな今年は家で1人で楽しめる調理器具やオーディオビジュアルをよく買った印象があります。
今年買ったモノを選りすぐり、上から5つご紹介したいと思います。

1位. 月の土地

そろそろ良い歳にもなってきたので、不動産を持ちたいという気持ちが湧いてきました。
でも日本、狭くない?そもそも地球、狭くない?せっかく買うなら最先端の不動産を買いたい。未来に生きたい。

まだ地球で消耗してるの?

ということで月に土地を買いました。場所は「ロット 189/1070 エリア J-05 QUADRANT B」。

f:id:wait0000:20201219133658j:plain:w500
月の土地権利カード

地球に向いた面でいうと、左下の方の盆地(クレーター)の内側です。日当たりも良好らしく、平坦で基地を建てるにもちょうどいいです。一等地に違いありませんね。

f:id:wait0000:20201219133511j:plain:w500
月の住所

広さは約1200坪(1エーカー)、老後は広大な土地を活用して巨大な自動プランターと代替肉工場を建て、余った土地は駐船場として運用しながら相棒の宇宙ネコとともに悠々自適な生活を送ろうと思います。
重力ないので老後の腰に優しい点もオススメです。

f:id:wait0000:20201219133456j:plain
月の土地権利書


真面目な話、飲み会などでこのカードをサッと出すだけで話のタネになるので、値段(3000円)以上の価値がありました。
贈り物などに是非。

www.lunarembassy.jp

2位. なんちゃってホームシアター

地上波は見ないしFPSなどのゲームしかしないため、テレビと悩んだ末にプロジェクターを買いました。
使ってないときはテレビより幅を取らないし、部屋の圧迫感もなく、それでいて大画面(80~100インチ)で好きな動画やゲームができる体験はとても素晴らしいです。

住んでいる部屋がそんなに広くないため、壁から離せる距離は2mぐらい、短焦点なら1~2メートルで100インチぐらいの大きさになります。
解像度は最低 Full HD、ゲームができる低遅延なものを選びました。

f:id:wait0000:20201219174309j:plain:w500
音楽イベントの中継など流すと楽しい


プロジェクタを買って気づいたのは、画面が大きい分ゲームをするにも動画を見るにも体力を使う点。
なので、軽めに動画を楽しみたいときはPCで見たりPS4 Remote Playでゲームをしてます…短焦点じゃないければこの辺り調整効いたのかも?

合わせて↓の周辺機器を買って光デジタルで音声を取り出して、Bluetoothで接続できるようにしてます:

言わずもがな、ブラウザを通じてデスクトップ共有もできます

Google Chromecast 正規品 第三世代 2K対応 チャコール GA00439-JP

Google Chromecast 正規品 第三世代 2K対応 チャコール GA00439-JP

  • 発売日: 2020/03/01
  • メディア: エレクトロニクス

HDMI切り替え機。リモコン付きがおすすめポイント、Nature Remoで切り替えも自動化できる

HDMI → 光デジタルを取り出す機械

手ごろなサウンドバー。音質はそこそこ、音に立体感あるのでFPSゲームで敵の位置が分かる

光デジタルをパススルーしてサウンドバーにつなぎつつ、AAC対応(本当か少し怪しい)なのでFPSゲームできるぐらいの低遅延でBluetooth接続。夜中など音を出せないときや大音量でプレイしたいとき用

3位. MTG Body Make Seat Style

リモートワークが始まって一番初めに躓いたのが、「腰が爆発しそう」
家系的にも腰が大爆発して金具を入れる手術をしてきた系譜を踏んでるので、いつダメになるのかとても怖い。
姿勢も悪い自信があるのでこれはとてもマズイ。

そこで良さげなワーキングチェアを購入したのですが、全く腰に合わない
逆に腰が痛くなる始末で、仕事に集中できない。上司との1on1をしても「腰が悩みです」しか言えない。

一時期はコルセットを巻いて生活をしていたのですが、オススメされて MTG Body Make Seat Style を半信半疑で買ってみたところ、腰がとても楽に。
今では部屋中持ち歩いて、ソファやイス、布団の上に座るときにも愛用してます。

座ってない時の姿勢も矯正されてるような気もする?良い買い物でした。

会社にも持っていきたい…

4位. 電気圧力鍋

リモートが増えて自炊をする機会も増えました。そして以前から抱えていた欲望を抑えきれなくなりました。

「低温調理したい…」

男という生き物は、20代前半はパスタ料理に、20代後半〜30代前半は低温調理に、30代後半はきっと蕎麦打ちにハマる生き物なのでしょう。生物の根源的欲求です、逆らうと体に毒なのです。

ですが低温調理機、用途が限定されるわりにそこそこのお値段する&もしかしたら一回使って満足してしまうかもしれない…という不安もあり、代替案を探しました。

そこで見つけたのが電気圧力鍋

普段は材料とカレールーをぶち込んで自動メニューを選ぶだけで圧力調理で柔らかい「カレー」を作ってくれるカレー製造機ですが、「低温・発酵調理」を選ぶと鶏ハムやローストビーフなどの低温調理ができます。
容量は少なくなりますが低温調理機ほど手間もかからず、美味しい鶏ハムを作ることができます。…鶏肉ってこんなにうまいんだ…

f:id:wait0000:20201122225611j:plain:w500
塩胡椒でシンプルな鶏ハム

「なべモード」を選ぶと、通常のIH調理器具のように電熱調理することができ、炒め物やスープも作れちゃうのでガスコンロ要らないのでは?といった感じです。もちろん圧力調理で煮物なども美味しいです。

上記リンクは一人暮らし向けの小容量版ですが、ご家族がいらっしゃる方は 4.0L のものをオススメします。

5位. Aftershockz AEROPEX

社内の某 times チャンネルで気になってた骨伝導イヤホンですが、ついに買ってみました。

僕はランニングが好きなのですが、普段使ってる AirPods Pro は走っていると耳が蒸れ気味になったり、外音取り込みモードの音がそんなに好きじゃない(うまく言葉にできないですが、現実の音じゃない感)、何かある度に取り外さなきゃいけない&紐もないので失くしがちと難点がありました。

骨伝導初心者なのですが、想像していた骨伝導の音とは全く違い、下手なイヤホンより音質良いのでは?というクオリティながら、外には漏れるのは軽いシャカシャカ程度。
耳が完全に開放されているのが何よりも新体験です。

音としては、低音は全く出ない(代わりにバイブレーションは感じる)のですが、特に中音〜高音の質が良いです。
耳を開放しているからといって、スピーカーから聞こえる音とも違い、ちゃんと近くで鳴ってる感はある。なんでしょう、耳が4つある感覚です(?)。

以上

今年買った5つのオススメ商品を書きました。
来年はコロナが解消しつつ、新しい生活様式が定着してることを願います。

2020年最高のWSL環境を求めて

前回: mjhd.hatenablog.com

熱狂的なWSL信者の皆さん、お元気でしょうか。
前回WSL環境について記事を書いてから1年が経ち、WSLを取り巻く状況も様変わりしました。Windowsのアップデートもあり、手元の環境もスクラップ&ビルドを繰り返したため、開発環境としてかなり完成度が高くなりました。安寧です。もう我々は花びらを散らす必要はないのです。

WSL2

まもなくの正式リリースが予定されている Windows10 version 2004 に含まれるWSL2は、今年一番の変化です。
WindowsのプロセスとしてELFをロードしLinuxシステムコール互換レイヤを通じて実行されていたWSLの仕組みが大きく変わり、軽量仮想マシン上で動くLinuxカーネルのプロセスとして実行されるようになりました。
これにより、システムコールの互換性が100%になったり、付随してext4ファイルシステムを直接使うようになったためIOのパフォーマンスがかなり向上するなど様々なメリットが得られました。
大幅な方針転換ですが、軽量VMの起動速度が1秒ほどという特徴もあり、デメリットをほぼ感じません。
以前のWSLからWSL2へ変換ができるため、開発環境として使いたい人はぜひ乗り換えましょう。
(WSL2の初期にあったlocalhost問題も解決しています)

WSL, WSL2の仕組みについては師匠のスライドが分かりやすいです:

www.slideshare.net

Docker for Desktop

Docker for DesktopがWSL2をバックエンドとして利用できる機能を公開しました。(まだ実験的ですが)
WSL1では様々な問題でdockerdを起動することができませんでしたが、WSL2はそもそも仮想マシンなのでなんでもできます。 そこで、WSL2のディストリビューションの一つとしてdockerd入りのLinuxを起動することで、オーバーヘッドも小さく高速に起動するコンテナ環境が実現できました。
起動したホストには他のディストリビューションWindowsからアクセスすることができます。

f:id:wait0000:20200509143313p:plain
WSL2 backendの設定項目

f:id:wait0000:20200509143944p:plain
docker-desktopがWSLディストリとして作成されます

Windows Terminal

今まで、WSLで使用するターミナルは

  • Xserverを通じてgnome-terminal, terminatorなどのLinux系ターミナルを動かす(少し不安定&起動に時間がかかる&依存がデカい)
  • Hyper, alacrittyなど、マルチプラットフォーム対応のターミナルを使う(微妙に不安定, 日本語入力がバギー)
  • FluentTerminal, TerminusなどモダンなWindows用ターミナルを使う(発展途上)
  • wsltty(レガシー)

など選択肢がありましたが、どれも日常的に使用するには不満が大きかったです。

今は Microsoft 謹製の Windows Terminal があります。

www.microsoft.com

初期のころは機能が十分でなかったり重たかったりという不満がありましたが、最新版は軽く安定していて、設定項目も十分です。
useAcrylicを有効にすれば、FluentDesignのアクリル素材効果が適用されてWindows10っぽさが増します。
iTerm2のカラースキームを取り込むこともできます。 もはやターミナルエミュレータはこれ一択かな、といった状況です。

f:id:wait0000:20200509141719p:plain
WIndowsTerminalの様子

f:id:wait0000:20200509142857p:plain
vimもかなりサクサク動くよ

ディストリビューション

何を選ぶかは信仰の自由もとい個人の自由ですが、僕はArchLinuxを選びます。
GUI方面はWindowsに任せ、主にターミナルを用いた開発環境としてのLinuxをメインに使う場合は、ArchLinuxがミニマルで良いのではと思います。
(そういえばxserverも消しちゃった)

もともとArchLinuxはWindows Store上で配布されていたのですが、偽物騒動などもあり現在は配布されていません。

以下のリンクからArchLinuxのappxを入手できます。これを使うのが現状一番手っ取り早いです。 github.com

Windows<->Linuxのファイル共有

もともとの課題だった、WSLのファイルをWindowsから読めない問題は、(多分)Hyper-Vソケットを通じた9pプロトコルWindows実装がされたことで解決しました。
逆に、9pプロトコルとDrvFSを通じて従来通りLinuxからWindowsファイルを読み書きすることもできます。
以前よりパフォーマンスが悪いご様子ですが、LinuxのFS内でWeb開発をする分には問題ないです。

f:id:wait0000:20200509145305p:plain
WSLのファイルを閲覧する様子

ascii.jp

おわりに

もう今までのようなハッキーなことをしなくても簡単にWindowsPOSIX標準環境が手に入ります。
今のところ開発環境として不満はなく、完成したかに思えます。
スリープ可能なLinux(Unix)としてのMacではなく、スリープ可能なLinuxとしてのWindowsが実現できそうです。

チーム異動後のターミナル周り

半年前にチームの移動をした。プロダクトは同じだが、サーバからWebフロントへポジションチェンジした。

以前からvimを使っていたが、Go言語はlspへの過渡期があったためIntelliJvimバインドを使用するなどCLI離れをしていた。 チームの異動後はTypeScriptを書くことが多く、vimをメインに使用するようになったため、CLI周りの見直しをよく行う様になった。

dotfiles

dotbotというdotfilesを管理するOSSを入れた。 デフォルトでは、dotfilesへのシンボリックリンクを作成してくれる機能ぐらいしかないが、gitのサブモジュールとしてdotbot-brewなどを追加することで、brewを使ったパッケージの自動インストールなどもできるようになる。 macOSとWSLを触ることが多いため、brewaptyayの三つに対応し、普段の開発に必要なツールのインストールを行うようにした。 今まではシェルスクリプトでこのあたりの処理を書いていたが、メンテナンスするのが面倒になってきていた。dotbotyamlだけメンテナンスすれば良いので楽。

vim

設定ファイルをシンプルに

今までは.vimrcファイルをかろうじてneovim以外でも動くよう、ifなどで分岐をしていたが、これを一切やめた。 また、deinautoloadを使って、プラグインが読み込まれた時に設定も読み込まれるようにした。設定ファイルも自然と分割する様になるため、かなり見た目もスッキリした。 副作用として、vimscriptちょっとかけるようになってきた。

ちゃんと操作できるように

正直今までのvimの使い方はvimmerとして酷いものだった。十字キー万歳、テキストオブジェクトをほぼ知らない、プラグインキーバインドはデフォルト。 これをかなり意識して直すようになった。 実践vimを読み進めながら矯正中。

yabai

ターミナルからは少し離れるが、yabaiというOSSを使っている。 タイル型ウィンドウマネージャなのだが、割とマウス操作もサポートしているのが特徴。 今で、Macのタイル型ウィンドウマネージャAmethstなどを使用していたが、バグが多く挙動が不安定なため、使用を諦めていた。 yabaiはその点、割と安定して動作している。(もちろんバグもあるが、設定でカバーできる)

yabaiを使っている様子

動画をPiPっぽく表示したり、Fn+マウスでリサイズ、移動、入れ替えなど様々な挙動ができる。

skhd

ホットキー管理のOSSであるskhdは上記yabaiとセットで使うと便利で、yabaiのウィンドウレイアウトの変更やPiPの切り替え、ターミナルの起動など、様々な連携をホットキーにより設定できる様になる。 Ctrl+Shift+Enterでターミナルが立ち上がり、ウィンドウの配置も自動で行ってくれるため、すぐにコマンドを打ち始めることができる。 動画などはAlt+PでPiP表示にしたり、Alt+Eで分割の方向を変更したり、ウィンドウを半透明にしたりできる。

yabaiとskhdの組み合わせを使い始めたことは今年一番実りのある行為だったかもしれない。

ターミナルエミュレータ

最近はkittyhyperalacrittyなど、クロスプラットフォームGPUレンダリング可能なターミナルをいくつか試していた。(WSLでも同じものを使用したいため、Windowsバイナリが配布されてる必要がある) が、どれも顕著なバグがあり(kittyはコピペ周りでバグるし、日本語ダメ。hyperは描画が崩れる&コピペバグる)またiTerm2に戻ってきてしまった。 WSLで使えないため、まだまだ彷徨う必要がありそう。

Golangメモリ周りのメモ (goroutine割り込み)

前回

mjhd.hatenablog.com

最近、面白いツイートを見つけた。

内容は、ゴルーチンのデッドロックなのだが、原因が「協調割り込み」というゴルーチンが動作するための仕組みと、前回の記事で紹介したmid-stack inliningの複合技により引き起こされていると言う。 今回は、ゴルーチンの内部動作から、なぜこのバグが引き起こされたかをまとめたいと思う。

そもそもゴルーチンってどうやって動いてるんだっけ?

GolangではOSの管理するスレッドとは別に、論理プロセッサと独自のスケジューラを持ち、ランタイムが管理をしている。
コンポーネントは以下のように説明される。

M(Machine): OSの管理するスレッド

G(Goroutine): ゴルーチン

P(Processor): 論理プロセッサ

よく見かけるGOMAXPROCS変数はこのうちPに該当し、論理プロセッサの数を表す。

golangのスケジューリングは二つの階層に分けて説明でき、一つ目は、M(スレッド)とP(論理プロセッサ)の割り当てと、もう一つがP(論理プロセッサ)とG(ゴルーチン)の割り当てである。

work-stealing

グローバルとして、キューを持ちPに割り当てされていないゴルーチンを保持する。

また、各P(論理プロセッサ)もそれぞれキューを持ち、割り当てられたゴルーチンを保持している。

Pはもし自分のキューにゴルーチンが存在しなければ、他のPからゴルーチンを奪い、実行する。 これをwork stealingという。 もし奪えるゴルーチンが存在しなければ、グローバルなキューからゴルーチンを割り当て、実行する。

ゴルーチンの実行が終わると、Pは別のゴルーチンへコンテキストスイッチをし、実行を続ける。

これを繰り返しゴルーチンは処理されている。

協調割り込み

ここで一つ問題になるのが、もし一つのゴルーチンがとても重たく、容易に終了しないものだった場合、このままでは論理プロセッサ P が占有されてしまう。 現在の定義だと、Pがゴルーチンが終わるまで次のゴルーチンへコンテキストスイッチできないのである。

ここで必要になる機能が、ゴルーチンの割り込みである。ゴルーチンの割り込みとは、ゴルーチンの実行中でも他のゴルーチンに処理を譲る(または奪う)ことで、平等に平行に処理をするための機能のことである。

現在のGoの実装では、「協調割り込み(co-operative preemption)」という方式が実装されている。 これは、コンパイル時に任意の箇所に割り込みコード(他のゴルーチンに処理を譲る処理)を埋め込むことにより、ゴルーチンが他のゴルーチンに処理を譲りながら実行できるようにしたものである。 基本的に、この割り込みコードはインライン化されていない関数呼び出しなど周りにコンパイル時に挿入される。つまり、関数呼び出しが合図になる。

逆に言うと、関数呼び出したり、その他割り込みコードが挿入される余地のない強めのループなどを書いてしまった場合、その P を占有してしまう。

非協調割り込み

これは proposal として掲げられている機能なのだが、上記の協調割り込みには強めのループなどのようにエッジケースが存在するため、非協調割り込みという、強制的にゴルーチンの処理を奪う方式を採用しようという動きがある。

proposal/24543-non-cooperative-preemption.md at master · golang/proposal · GitHub

バグの原因は

前回紹介したmid-stack inliningに関連して、より積極的に関数のインライン化が可能になったため、先日 RWMutex のインライン化対応が行われた。

https://go-review.googlesource.com/c/go/+/148958

これにより、RWMutex.RLock, Unlockなどがインライン化されてしまい、割り込みチェックが入らなくなってしまった。 よって、無限にブロックし続けるプログラムが誕生した。

参考文献 

Golangのスケジューラあたりの話 - Qiita Scheduling In Go : Part II - Go Scheduler

次回

多分SSA最適化あたり?