In Rainbows
In Rainbows (hitogata.neocities.org)
要約
「rainbows」は、仮想マシン「ok」に接続されたデバイスのための仕様です。これは、基本的な計算のための命令評価とスタックを提供する「ok」に、画面やコンソールへの出力、マウスやキーボードからの入力といった機能を追加するためのものです。この仕様は、メモリマッピングという概念を用いて、標準的な入出力デバイスを定義し、「ok」を単なる命令評価器から「ファンタジーコンピュータ」へと進化させることを目指しています。現在、コンソールインターフェース、算術演算、ディスクI/O、日時情報、画面表示などのデバイスが実装または計画されています。
全文翻訳
In Rainbows
執筆日: 2026-10-06
[画像: the rainbows logo, inspired by the cover art for In Rainbows]
rainbowsは、Radioheadのアルバム『In Rainbows』にちなんで名付けられた、ok仮想マシンに接続されたデバイスのための仕様です。「ok」自体は、命令評価と基本的な計算のための「データ」スタックおよび「リターン」スタックを提供するだけですが、画面やコンソールに出力したり、マウスやキーボードから入力を受け取ったりしたい場合は、それらのデバイスをメモリに「マッピング」する必要があります。
メモリマッピング
「ok」は、RAMからバイトを読み取る必要があるときに ok_mem_read(addr) を呼び出し、RAMにバイトを書き込む必要があるときに ok_mem_write(addr) を呼び出します。これらは、ユーザー定義のメモリマッピングを可能にするために、あなた自身が定義する関数です。例えば、メモリマッピングなしの場合、ok_mem_read の定義は次のようになります。
// "ram" がどこかでバイト配列として定義されていると仮定します
uint8_t ok_mem_read(size_t address) {
return ram[address];
}
しかし、コンソールの標準入力(stdin)からデータを読み取れるようにしたいとしましょう。この場合、RAMの「マジック」な場所をマッピングして、そのアドレスからバイトを読み取ると stdin からデータが読み取られるようにすることができます。例は以下の通りです。
#define STDIN_MAGIC (3)
uint8_t ok_mem_read(size_t address) {
if (address == STDIN_MAGIC) {
char ch;
read(STDIN_FILENO, &ch, 1);
return (uint8_t)ch;
}
return ram[address];
}
これにより、このアドレス(okのアセンブリ言語では #000003 lod1 のようになるかもしれません)でRAMから値を読み取ると、stdin から1バイトが読み取られます。
rainbows のアイデアは、これらのマジック値を定義することで、ユーザーが使用できる標準的な入出力デバイスのセットを提供し、「ok」を単なるスタックベースの命令評価器から、本格的な「ファンタジーコンピュータ」へと変えることです。
デバイス
rainbows はまだ積極的に開発中であるため、サポートするデバイスの説明はすぐに古くなる可能性があります(ただし、何か変更があればこのページを更新するように努めます)。
現時点では、以下のデバイスを実装済み、または実装予定です(現在の定義に対する自信の順に並べています):
RbConsole: POSIXスタイルのCLIとのインターフェース用RbArith: 「ok」だけではうまくサポートされない、より便利な算術演算を提供RbDisk: ディスクI/Oと永続ストレージを提供RbDatetime: 日時情報を提供RbScreen: スクリーン/フレームバッファを提供
ここでは、現時点で実装済みのデバイスのメモリレイアウトと、まだ不確かなデバイスに関する考えを説明します。私が rainbows をさらに開発するにつれて、以下のメモリアドレスは変更される可能性があります!
RbConsole
RbConsole は以下のアドレスを使用します。テーブルの左側の数値はアドレス、右側のテキストはそのデバイスが使用するアドレスの「名前」(struct のフィールドのようなものと考えてください):
|---------------------|
| RbConsole |
|--------+------------|
| 000000 | stdin (R) |
|--------+------------|
| 000001 | stdout (W) |
|--------+------------|
| 000002 | argc (R) |
|--------+------------|
| 000003 | arg_index |
|--------+------------|
| 000004 | arg_char_i |
|--------+------------|
| 000005 | arg_char |
|--------+------------|
stdin からバイトを読み取ると、当然 stdin から読み取られ、stdout にバイトを書き込むと期待通りの動作をします。
argc は、提供されたコマンドライン引数の数(C言語の argc のようなもの)を格納します。
ここからが面白くなります。特定のコマンドライン引数を読み取るには、まず arg_index に値を書き込んで、どのコマンドライン引数を見ているかを指定します(もちろん0から始まります)。次に、arg_char_i に値を書き込んで、arg_index で指定された引数のどの文字を見ているかを指定します。そして、arg_char からバイトを読み取ると、その文字が読み取られ、末尾または範囲外の場合は0を読み取ります。
したがって、arg_char からバイトを読み取ることは、C言語で次のようなことをするのに似ています。
char* argument = argv[arg_index];
uint8_t arg_char = argument[arg_char_i];
少し面倒ですが、他のアイデアとしては、特定のメモリ位置を RbConsole デバイス内の場所に書き込むことで引数を格納できるようにすること(これは RbConsole が任意のメモリ位置を操作できるようになることを意味し、私はそれを好みませんでした)、または個々の引数を格納するための事前定義された長さのバッファを RbConsole に持たせること(これは適切な事前定義長がわからなかったため、やりたくありませんでした。また、引数がバッファに収まらない場合のエラー処理のようなものもしたくありませんでした)がありました。
このデバイスは、現時点では完全に実装されています!
RbArith
RbArith は、1、2、3、4バイトの符号なし整数に対する乗算、除算、剰余演算をサポートします。メモリレイアウトは以下の通りです。
|-----------------|
| RbArith |
|--------+--------|
| 000006 | op |
|--------+--------|
| 000007 | err |
|--------+--------|
| 000008 | width |
|--------+--------|
| 000009 | x |
| 00000a | |
| 00000b | |
| 00000c | |
|--------+--------|
| 00000d | y |
| 00000e | |
| 00000f | |
| 000010 | |
|--------+--------|
| 000011 | result |
| 000012 | |
| 000013 | |
| 000014 | |
|--------+--------|
op にマジック値を書き込むと、その値に関連付けられた操作が x と y に対して実行され、結果が result に格納されます。この操作が実行されるとき、x、y、result は width - 1 の幅を持つものとして扱われます(つまり、3 は4バイト値、2 は3バイト値、1 は2バイト値、0 は1バイト値として扱われます)。op が取りうる値は以下の通りです。
- 0 は no-op で、操作は実行されません。
- 1 は
x * yをresultに格納します。 - 2 は
x / yをresultに格納し、yが 0 の場合はerrを 1 に設定します。 - 3 は
x % yをresultに格納し、yが 0 の場合はerrを 2 に設定します。
将来的には、他の数学演算(または乱数生成?)を追加するかもしれません。その場合、op が取りうる値に新しい値(5、6など)を追加するだけです。
RbDisk
RbDisk は、最近書いたライブラリを使用してディスクI/Oを提供します。このライブラリについては、「Blocksをいじる」という記事で詳しく説明しています。
RbDisk のメモリレイアウトは以下の通りです。
|----------------------------|
| RbDisk |
|--------+-------------------|
| 000015 | capacity |
| 000016 | |
| 000017 | |
| 000018 | |
|--------+-------------------|
| 000019 | update |
|--------+-------------------|
| 00001a | error |
|--------+-------------------|
| 00001b | current |
| 00001c | |
| 00001d | |
| 00001e | |
|--------+-------------------|
| 00001f | data (1024 bytes) |
| ... | |
| 00041e | |
|--------+-------------------|
rainbows のエミュレータを実行する際に、BlockFile(前述の記事で詳細を説明)を提供するか、新しいファイルを作成することを選択できます。このファイルは、その中に格納されている1024バイトの「ブロック」の最大数である容量を持っています。この値は capacity に格納されます。
注意:もし私が将来的に
rainbowsを(基盤となるOSなしで)ベアメタルで実行することにした場合、BlockFileの代わりに、ディスク自体の空き領域を使用することになります。
その後、update から値を読み取ると、current のインデックスにあるブロックの内容が data にロードされます。update に値を書き込むと、data が current のインデックスにあるブロックに格納されます。
RbDatetime
このデバイスについては、まだ不確かです。RbDatetime のアイデアは、時計/日付情報を提供することですが、どの程度複雑にしたいかはまだはっきりしていません。Varvara の datetime デバイスの仕組みに触発され(つまり、ほぼ盗んで)、現在の年、日、曜日、時、分、秒、夏時間かどうかなどを格納するメモリ上のアドレスを持つことを考えました。
[画像: Varvara's datetime device]
しかし、64ビットのUnix時間(つまり、1970年1月1日UTC午前0時からの「うるう秒でない」秒数)を格納し、この情報を使って日付を決定するソフトウェアを書くことも検討しました。ただし、これは「ok」が最大32ビットの整数幅しかサポートしていないことを考えると、それほど便利ではありません。一部のシステムは依然としてUnix時間を32ビット値として格納していますが、これは2038年初頭に値がオーバーフローすることを意味し、それを使用するには少し早すぎると感じます。
さらに、ミリ秒のような時間の小さい単位を表現する問題もあります。これは、私が何をしたいかによっては便利かもしれません。
総じて、このデバイスはまだ作業中です。追加する際には、RbDisk デバイスの前に配置する可能性が高いと考えています。つまり、上記のテーブルのアドレスを更新する必要が出てきます。
RbScreen
これもまた、かなり不確かなデバイスです。
まず、サポートしたい色の数について、まだ決まっていません。Varvara は2ビットカラーを使用し、最大4色を可能にしていますが、画面の「カラーモード」を設定する方法を追加することも考えています。1、2、4、5ビットカラーのような小さい色深度の値の場合、パレットを設定できるかもしれませんが、より大きい値の場合、8ビットカラー(おそらくRGB332、赤に3ビット、緑に3ビット、青に2ビットを使用)や、16ビットの「ハイカラー」(RGB565を使用)、さらには24ビットの「トゥルーカラー」のサポートを追加することも検討しています。
しかし、より大きい色深度の値や画面解像度の場合、メモリ使用量はかなり大きくなります。例えば、24ビットカラーの1024x600の画面は、1800キビバイト(1キビバイトは1024バイト)のメモリを必要とします。これは、「ok」がアクセスできる総メモリの約11%に相当します。
画面解像度自体も別の問題です。ユーザーが画面サイズを自分で定義できるようにしたいでしょうか?これは、リサイズ可能なウィンドウを可能にし、rainbows をシステム上の単なるプログラムとして実行するのに便利ですが、その場合、RbScreen のメモリセクションに framebuffer_addr フィールドのようなものを用意して、フレームバッファがどこから始まるかを示す必要があります。そして、rainbows をターゲットとするソフトウェアは、それを考慮に入れる必要があります(つまり、計算中にそのメモリセクションを誤って上書きしないようにする)。
これは他の懸念事項も引き起こします。例えば、マルチバッファリングのサポートの可能性です。
もしリサイズ可能な画面/ウィンドウを許可しない場合、どの解像度にするか、そしてどの縦横比にするかを決定する必要があります。現在、私の主な目標の1つは、rainbows を私のzen machine(まだ書いていませんが、いずれ書く予定です)と呼ぶ小さなラップトップでベアメタルで実行できるようにすることです。このラップトップの解像度は1024x600ピクセルで、少し標準的ではありません(16:9でも4:3でもありません)。また、主に私が4:3の縦横比(例えば640x480)を非常に好むため、それを使用することも検討しています。しかし、rainbows をフルスクリーンで実行すると、16:9のディスプレイでは奇妙に見えるかもしれません。
ご覧の通り、RbScreen は私が多くの時間を費やして考えたデバイスであり、まだ具体的な解決策にはたどり着いていません。現時点では、文字通り私を悩ませており、「ok」について書き始めた頃からずっとそうです。
その他のデバイスのアイデア
将来的には、キーボード入力を読み取るための RbKeyboard デバイス(複数のキー入力を一度に追跡するための円形文字バッファのようなものを使用するかもしれません)についても少し考えています。理想的には、RbKeyboard は「コントローラー」機能もサポートするでしょう。これは、Varvara の「controller」デバイスがキーボードキーをサポートするだけでなく、特定のキー(ctrl、alt、shift、homeなど)を任天堂のデバイスのキー(つまり、A、B、Select、Start)のように扱うことができるのと似ています。私はキーボードのサブセットのみを使用することを検討しています。なぜなら、60%または75%のキーボードレイアウトを使うのが好きだからです。
[画像: a 60% keyboard layout]
もう一つ強く検討しているデバイスは、32ビット浮動小数点値を扱うための RbFloat です。ただし、理想的にはこのデバイスは rainbows 上でプログラムを実行するためにそれほど「不可欠」ではないはずです(なぜなら、32ビット浮動小数点標準に厳密に準拠していない、あるいは使用していないシステムで rainbows ソフトウェアを実行しようとするかもしれないからです)。
注意すべき点として、「マウス」デバイスについてはあまり考えていません。個人的には、マウスやトラックパッドを一切使うのが好きではないので、そのためのデバイスを作ることを優先していません。いずれは実装するでしょうが、ソフトウェアがキーボードファーストになるように奨励したいと考えています。つまり、rainbows 上で実行されるすべてのソフトウェアは、マウスやポインティングデバイスなしで完全に利用可能であるべきです。これは、zen machine で作業する上で特に重要です。なぜなら、トラックパッドはありますが、非常に小さく、左クリックボタンは修理不能なほど壊れており、交換部品や修理方法に関する情報もほとんど見つからないからです。
オーディオサポートについても、これはかなりの深淵になりそうなので、あまり考えていません。オーディオシンセシスをサポートするか?オーディオサンプル?MIDI?それらの組み合わせ?それとも独自のオーディオフォーマットを作成するのか?
同様の理由で、ネットワークデバイスも検討していません。ただし、非常に基本的なGeminiプロトコルクライアントとして機能するデバイスを追加することは考えています。Geminiを使うのが好きだからです。
結論
ご覧の通り、私が rainbows に長期間積極的に取り組んでいるにもかかわらず、まだ道のりは長いです。アイデアのスケッチを始めたのは、学部時代の卒業論文で「ok」について書いた頃で、当初は論文の後半でコンパイラを実装しようとする代わりに rainbows を実装することを考えていました。仕様を書くだけでも大変な作業であることがすぐにわかりました。ましてや実装となるとさらにそうです。
現時点での私の目標は、少なくとも RbConsole、RbDisk、RbArith を完全に実装することです。その後、それらの機能性をテストし、rainbows 用の「有用な」ソフトウェアを書き始めるために、シンプルなコンパイル言語(POP-2000、誰か?)を開発するかもしれません。
それまでの間、新しい開発があった際にはこの記事を最新の状態に保ちます。このプロジェクトが私を連れて行った深淵について、皆さんが楽しんで聞いてくれたことを願っています。