OSECPUに関するメモ(番外版) #1
機能密度追及に関するメモ
- (0) このメモをOSECPUのサイトに書かない最大の理由は、OSECPU-wikiが機能密度の話題でいっぱいになるのが少々申し訳ないため。
- (1) ver.0.39に採用予定の「P01にデータセクションの先頭アドレスが入った状態で起動する」はCLEの
r1 最初の String リソースへの参照
- の真似以外の何物でもない。この設計は秀逸だと思う。
- 情けないことにこの設計の秀逸さを理解できるようになったのはごく最近である。
- そもそもPxxレジスタはアプリ起動時にはNULLを入れておくくらいしか初期値がないわけだけど、NULLだったら何の役にも立たない。それなら何か役立つ値を入れておくほうがずっと賢い。
- (2) OSECPUのフロントエンド命令セットにはループ命令があるが、これはCLEのループ命令を参考にしたわけではない。以前から検討していたもの。CLEが結局没にしてしまったのとは異なり、OSECPUではver.0.39以降では命令コード6を割り当てるという大躍進を遂げる予定。利用頻度が想定よりも高かったから。
- 命令コード6以下は、4bitでエンコードできる。7以上は8bitでエンコードしなければいけない。
- (3) ver.0.39でのフロントエンドバイトコード一覧(一部)
- 4: プリフィクス(44はCNDプリフィクス)[旧6]
- 5: 関数呼び出し[変わらず]
- 6: forループスタート[旧3C]
- 7: forループエンド[旧3D]
- 0D: サブプリフィクス(データタイプオーバーライド用)[新設]
- 2E: データ記述[旧4]
- (4) OSECPUがうまくいった理由
- 僕はOSECPUはうまく行っていると思う。どうしてうまく行ったのかを考えてみると、まずバックエンドとフロントエンドを分離して、先にバックエンドを設計したのがよかったのではないかと思う。バックエンドの設計の際には、セキュリティに必要な仕様を優先して決定したし、JITコンパイラが書きやすくなるように仕様を決めていた。そしてその後でフロントエンドまわりを作り始めた。
- そもそもアセンブラもフロントエンドコードを直接出力することはしないで、バックエンドコードを出力している。これはバックエンドから設計・実装し始めたからこうなったわけだけど、でもこれは別のメリットがあった。それはアセンブラのコード生成部が単純にできたということだ。バックエンドコードは自由度が少なく単純になっている。
- そしてフロントエンドコードの設計・実装にあっては、用意したバックエンドコードをエンコードする気持ちで作った。デコードしたときに完全に元に戻るかという視点を持ち、つまり情報を失わないようにした。フロントエンドコードを設計すると、とにかくいろいろ削りたくなる。そうしないと小さくならないからだ。でも元に戻らないほど削ってしまうと、JITコンパイラは省略された情報を推測しなければならず、大変な負担になる。これをやらないでいたことが、結局は「ムダ」だけを削ることにつながり、システム全体のバランスのよさにつながっていると思う。
- OSECPU-ASKAはコード生成するときに、このフレーズ(命令の並び)はフロントエンドでは短い形式が使えるな、と判断したらリマークコードを挿入することになっている。リマークコードはJITコンパイラからは無視される。バックエンド→フロントエンドの変換プログラムは、このリマークコードをみて、フレーズの判定を簡素化できている。このバイトコードリマークを使った情報伝達はうまい設計だったと思う。これを思いつけたのもよかった。なお、フロントエンド→バックエンドの際には、このリマークも含めて完全に復元するようになっている。だから本当に全く情報を失わない。
- バックエンドがサポートする命令の種類やフォーマットの種類が少ないことも良かったと思う。とにかく単純な命令の組み合わせでアプリを書かせる。バックエンドコードが長くなるのは気にしない。こうすることでJITコンパイラはかなり単純化できている。
- たとえば加算命令は一つしかない。x86のように、ADDとINCがあるとか、ADDにしたってimmをとる形式とmod-r/mの形式があるとか、8bit形式と16/32bit形式があるとか、そういうことは全くない。
フロントエンドコードの仕様 (ver.0.39以降)
- 0: NOP
- 1: LB ラベル定義
- 「1」プリフィクス4が付かない場合は、引数を持たない。この場合、定義すべきラベル番号は直前のラベル定義番号+1である。ラベルの属性(im8)は0になる。
- 「4-1-s」プリフィクス4が付いていた場合、一つの符号付き整数を引数に持ち、その下位8bitはそのままim8を表す。引数を256で割った商は、ラベル番号補正量で、「直前のラベル定義番号+1」にその値を加える。この場合、この新しい値が次回で参照される「直前のラベル定義番号」となる。
- 2: LIMM Rxxへの定数代入
- 「2-u-x」プリフィクス4が付かない場合、符号なし整数でレジスタ番号を、そして拡張符号付き整数でim32を表す。
- 「4-2-...」プリフィクス4が付いていた場合の動作は未定義。
- 3: PLIMM Pxxへのラベル値代入
- 「3-s」プリフィクス4が付かない場合、P3Fが指定されたとみなし、符号なし整数によるレジスタ番号指定はない。その後、符号付き整数が後続し、「直前のラベル定義番号」を加算して、それをP3Fへの代入値とする。
- 「4-3-u-s」プリフィクス4が付いていた場合、符号なし整数によるレジスタ番号指定が後続する。その後、符号付き整数が後続し、「直前のラベル定義番号」を加算して、それをPxxへの代入値とする。
- 4: プリフィクス
- 「4-」さまざまな命令の前について、出現頻度の低い形式であることを教える。この機能はバックエンドにはなかった。
- 「4-4-u」このプリフィクスが連続した場合、CNDプリフィクスを表す。符号なし整数によるレジスタ番号指定が後続する。
- 5: 関数呼び出し
- これはバックエンドにはない機能で、頻出する関数呼び出しを短く書けるようにするための簡略表記である。
- 「5-s-...」プリフィクス4が付かない場合、sは関数番号でこれにより後続のパラメータ数が決まる。関数番号をuではなくsとしてマイナスを許しているのはユーザ定義用の余地を考えたものである。パラメータはxで並べる。関数値の受け取りレジスタ指定は、uでレジスタ番号を並べる。
- 「4-5-s-...」プリフィクス4が付いていた場合、この形式ではパラメータの全てが記載されるわけではなく、いくつかは典型値(レジスタ値)が利用される。
(例)
4-5-2-x : drawPoint(mod:x, x:R00, y:R01, c:R02);
4-5-3-x : drawLine(mod:x, x0:R00, y0:R01, x1:R02, y1:R03, c:R04)
- この「4-5-s-...」形式は、典型的な呼び出し方が頻出するのに、それでも全てのパラメータをxで記述しなければいけないところにムダを感じて導入した。パラメータを自由に指定できるのは基本的には非常に便利であるが、この自由度が必要ないときもあり、その際に短く記述できるのはアドバンテージである。
- 6: forループ開始
- これはバックエンドにはない機能で、頻出するforループを短く書けるようにするための簡略表記である。ネストも可能。
- 「6-u-x-x」プリフィクス4が付かない場合、レジスタ番号が後続し(Rxx)、初期値、終了判定値、が続く。2つのxが定数であれば、増分値はその符号から決まる(+1か-1)。そうでなければ増分値を後続のxでさらに記述する。
- 07: forループ終了
- 「07」プリフィクス4が付かない場合、単純にforループ範囲の終了を表す。これも簡略機能でバックエンドにはない。
- 関数末尾の「07」は省略してもよい。対応の不一致が解消されるまで、「07」を補って解釈される。
- 「4-07」未定義
- 08: LMEM メモリからのRxxへのロード
- 09: SMEM メモリへのRxxのストア
- 0A: PLMEM メモリからのPxxへのロード
- 0B: PSMEM メモリへのRxxのストア
- 0C: LEA
- 0D: 型推論をキャンセルするためのプリフィクス
- ポインタ系の命令にはtyp32のフィールドがあるが、これを極力省略させるためにver.0.39からは簡単な静的型推論を行う。しかし正しく推論できない場合にはこのプリフィクスをつけて型推論をキャンセルさせる。この機能はバックエンドにはない。
- typ32フィールドを持たない命令にこのプリフィクスを付けることはできない。
- 0E: PADD
- 0F: PDIF
- 10: OR
- 11: XOR
- 12: AND
- 13: ?
- 14: ADD
- 15: SUB
- 16: MUL
- 17: CP
- 18: SHL
- 19: SAR
- 1A: DIV
- 1B: MOD
- 1C:
- 1D:
- 1E: PCP
- 1F:
- 20: CMPE
- 21: CMPNE
- 22: CMPL
- 23: CMPGE
- 24: CMPLE
- 25: CMPG
- 26: TSTZ
- 27: TSTNZ
- 28: PCMPE
- 29: PCMPNE
- 2A: PCMPL
- 2B: PCMPGE
- 2C: PCMPLE
- 2D: PCMPG
- 2E: データ記述
- 2F: 拡張用
- 30: talloc(旧F4)
- 31: tfree(旧F5)
- 32: malloc(旧F6)
- 33: mfree(旧F7)
- 34: データ記述(旧F0)
- 35: セキュリティ制御
- 36: CMPR (in range)(未実装)
- 37: CMPNR (not in range)(未実装)
- 38: PALMEM
- 39: PASMEM
- 3A: PAPLMEM
- 3B: PAPSMEM
- 3C: beginFunc(ENTER)
- 3D: endFunc(LEAVE)
- 3E: CALL
- 3F: PCALL
- x:拡張符号付き整数:
- (-0xf)以上: そのままの値を意味する
- (-0x4f)から(-0x10): R3FからR00を意味する
- (-0x58)から(-0x50): 0x10000,0x8000,0x4000,0x2000,0x1000,0x800,0x400,0x200,0x100を意味する
- (-0x60)以下: -0x10以下を意味するので、+0x50をして補正する
- このようなエンコードになっているのは・・・
- 一つのフィールドで定数もしくはレジスタを表せるようにしたい
- 2のべきの定数はフラグを立てるときなどに出現頻度が高いのでそれを短い形式で表したい
- (-16)以下の定数は出現頻度が低い
- ということを配慮した結果による
|