セキュリティを考えなくてもOSECPUの設計に至る
(0) はじめに
- OSECPUのバイトコードは、「第三世代OSASK」としてKが数年前から設計していたものでした。これがOSECPUになる際に、実は大きな仕様変更をすることなく、そのまま流用できています。つまりKは知らないうちにセキュアな設計をしていたのです。
- ではどうやってこの仕様に決めていたのか、その話を書きたいと思います。
(1) 仕様の理由(セキュアに関係ある部分)
- なぜJITコンパイル方式?
- CPUに依存しない互換性を提供したかったから。Javaがうらやましかったから真似してみた。
- (セキュアなOSを考えると、アプリを実行する前に、悪い命令が混ざっていないかをチェックすることは重要である。JITコンパイルはすべての命令を実行前にチェックできるので、これはとても理にかなっていた。)
- なぜ整数レジスタとポインタレジスタを分離したのか?
- ページングもセグメンテーションもない環境下で、(多少時間がかかってもいいから)メモリ上のある構造体を移動することを考えよう。幅広いCPUに対応するのならこのような可能性も検討しておくべきだ。・・・この時、この構造体を指していたポインタはすべて修正されなければいけない。だからメモリ内のデータのうち、どれがポインタであるかはOSが完全に把握することにした(だからmalloc時に型を明示する)。
- ではレジスタはどうか。もし汎用レジスタ方式を採用していたら、その値は整数値なのかポインタなのかをどうにかして判断できなければいけない。そのために各レジスタがポインタを扱っているのかどうかを反映するフラグレジスタを作り、それを制御する命令を付けたモデルを検討したが、結局のところ、汎用レジスタ方式をやめるほうが単純化できると分かった。
- 汎用レジスタ方式をやめれば、整数レジスタとポインタレジスタのビット幅も独立に決められる。さらにポインタレジスタの値を直接設定させなければ、ポインタレジスタのビット数に依存しないでプログラムが書けることもわかった。これはアドレス幅に無関係にアプリが書けるということなので、互換性の面で非常に良い。
- (ポインタレジスタを分離していたからこそ、ポインタレジスタ側にたくさんの属性情報を追加することができた。型情報やアクセスリミット、死活情報などである。命令セットをほとんど変えることなく、これらをサポートできた。)
- なぜメモリアクセス時に型チェックをするのか?
- エンディアンが違うシステムでも同一の動作結果を保証するにはこれがいいと思ったから。
- またポインタレジスタの値をメモリに書いて、それを整数レジスタで読み取ることを許せば、結局は整数レジスタでのポインタ操作を許していることと同じになり、構造体移動の際には整数値まで補正しなければいけなくなる危険性がある。
(2) 仕様の理由(セキュアに関係ない部分)
- なぜレジスタ数がこんなに多いのか?
- x86が8本しか汎用レジスタがなく、しかもそのうちの一つはESPで、実質7本しかない。これで長い間プログラミングをしていたら、いつの間にかレジスタがたくさんほしいと熱望するようになった。一時は256本にしようとさえ思ったほどだ。
- 64にした最大の動機は、hh4エンコードで1バイトで表現できる本数が64だったから、というだけに過ぎない。でも今でも64本というのはなかなか程よい本数だと思っている。
- なぜ「reg0=reg1+reg2;」のような三項演算形式にしたのか?
- ARMの影響。計算値を生成するたびに、元の値が必ず破壊されていくのが嫌だった。そのたびに破壊前の値をPUSHしたり別のレジスタにMOVしたりしていた。ARMがうらやましかった。
- なぜフラグレジスタがないのか?
- なぜスタックがないのか?
- レジスタが十分に豊富なら、スタックがなくても困らないはずだと直感的に思った。スタックを扱わなくてよければ、それだけ命令セットは単純になる。
- スタックには、整数値もポインタ値も積んだりおろしたりするだろう。ということは、「メモリ内データの型管理情報」を頻繁に更新する必要が出てくる。これは遅そうだ。こんなことになるくらいなら、スタックなんかなくてもいいじゃないか。それとも整数専用スタックとポインタ専用スタックを作るのか?またそうやって命令の種類が増えて、命令セットが複雑になるのか?それでいいのか?
- なぜスタックマシンにしなかったのか?Javaのまねで出発したのに。
- レジスタマシンが好きだから。スタックマシンはレジスタマシンよりも機能密度が高いという説があるらしいが、それと対決してやろうと思った。
- レジスタマシンならJITコンパイラがレジスタ割り付けに悩まなくて済む。
こめんと欄