* Kの開発メモ #0002 -(by [[K]], 2013.03.26) -ここは川合の開発の進捗などをレポートするところです。 --当初はその予定だったが、今ではむしろ主たる目的は[[K]]の備忘録になってしまっている(苦笑)。 ** 2013.03.26 Tue -DIFFPTRやCOMPPEなどを追加。ついでに裏APIの仕様も少し変更してリビジョン0003へ。 --これらの命令のおかげでcas2nas00a.oseは293バイトになりました(006aでは317バイトでした)。 --(今夜アップロード予定) --http://osecpu.osask.jp/download/osecpu007a.zip -2013.03.19(Tue)から実装を開始したので、これで1週間経ったことになる。結構はかどった。しかし疲れた。体重も1kgくらい減った。これは長くは続けられないと思う。 -よくよく考えた末、軽量の関数呼び出しと重量の関数呼び出しの二種類が必要かなと思う。軽量callはセキュリティ的に防御せずに分岐。重量callはがっちり守る。リターン命令以外では帰ってこれない。 -もう少し見栄えがするものが作れるようになったら、ソースとバイナリを分離してもう少しきれいなパッケージにするべきだよな・・・。現状のままでは「お客さん」がとっつきにくすぎる。 ** 2013.03.27 Wed -裏APIに余興でほんの少しだけグラフィック機能をつけてみた(Windows限定)。osecpu.exeは7.50KBになってしまった。とりあえずapp0006a.oseというアプリを作ってみたところ、ちゃんと表示できているようだ。 --(今夜アップロード予定) --http://osecpu.osask.jp/download/osecpu008a.zip ~ http://osecpu.osask.jp/download/app0006a.png -軽量callがほしくなったので実装中。・・・よしできた! -アセンブラを必死に書いていると、結構疲れる。うーん、若いときにアセンブラでがんばったときはもっと疲れずにできたんだけどなあ。 -でもここでがんばっておけば、将来にわたって使えるわけなので(osecpuさえ移植すればx86が主流じゃない未来が来ても大丈夫、という意味)、がんばる気にはなる。 --まだcas2binが出来上がってないけど、軽量callとシフト演算を作ったからリリースするかな。 --(今夜アップロード予定) --http://osecpu.osask.jp/download/osecpu009a.zip ** 2013.03.28 Thu -裏APIの呼び出し方法が、うさんくさい隠し命令ではなくて、普通の軽量callでできるようになりました。 -ラベル番号の管理でやってられなくなったので、ローカルなラベル番号をうまい具合につけてくれるプログラムを書きました。 --150行ほどのC言語プログラムです。 --これもいつかOSECPUで書き直したいです。 -そのほかいろいろ調整して、OSECPUのアセンブラが第三世代OSASKと互換になりました。これでやる気は百倍です。だって何を書いても、将来にわたってずっと使える(と信じられる)からです。 -本日の成果: --http://osecpu.osask.jp/download/osecpu010a.zip ** 2013.03.29 Fri -(1) lbstk01にバージョンアップしました。→[[page0017]] --http://osecpu.osask.jp/download/osecpu011a.zip -(2) 多少の癖はあるものの、とにかくかなり書きやすくなったと思う。 --思い起こせば、最初はDBしか使えなかった。 --これに対してプリプロセッサを使うことで、基本的な命令は使えるようになった。 --さらに#defineを増やして、CMPJNEなどの便利な複合命令も作った。 --そしてlbstk01みたいな超簡易なプログラムを組み合わせることで、手動によるラベル管理は激減し、構造化言語みたいになってきた。 --このように徐々にステップアップできるのはとても有効だと思う。プリプロセッサのおかげだろう。 -今後、プリプロセッサをOSECPUアプリとして書きなおす日が来るだろう。プリプロセッサくらいならきっと書ける。 --lbstk01もOSECPUアプリに移植できるだろう。そうなれば、今後は小さなosecpu.exeだけを多数のCPU向けに書き直すだけで、アプリ開発環境もすぐに使えるということを意味する。 -(3) ここまで来るのに10日しかたってない。・・・そしてここまでの技術はどれもとても平凡で、なんとなれば10年前にだって十分に開発できた内容だと思う。それなのにすごく有効そうに思える。 --今まで僕は何をやっていたんだと強く思う。これがあればCPUの仕様とは無関係に自由にプログラムが書けるじゃないか。しかも大して遅くはならない。 --今まで少ないレジスタで必死にやりくりしていたのが情けない。 -OSECPUにはやがて正規のAPIをつけ始めると思うけど、そのAPIは原始的なI/Oに限られるだろう。つまり全然使いやすくはない。使いやすいAPIはその原始的なAPIをラップすることで用意する。もちろんそのコードはOSECPUで書かれる。 --そうすれば、osecpu.exeはシンプルなままだ。移植は簡単だ。なんというか、osecpu.exeはドライバを含んでいるといってもいい。つまり「原始的なI/OのAPI」=「ドライバ」というわけだ。 --このスタイルになっていれば、WindowsでもLinuxでも「はりぼてOS」でも、TownsOSでもMacOSでもゲームボーイアドバンスでも、すべての32bit環境で共通のアプリ実行基盤が提供できたはずだ。 --しかもそれはJavaなどとは違って非常に軽量で、osecpu.exeは30KBとかその程度なのだ。なんていい世界だろう。・・・おっと忘れていた、しかもセキュアだ。 -しかも第三世代OSASKの仕様も盛り込めば、仮想CPUはついに32bitという制限もなくなり、実際のCPUのビット数とは無関係に自由なbit数でアプリを記述できるようになる。 --つまり64bitマシンで16bitアプリを動かすことができるし、16bitマシンで64bitアプリを動かすこともできるようになる。 --これは必要に応じて多倍長演算を利用することで実現する。 -OSECPUでは、どんなCPUにでも対応できるように用心深く設計してある。 --たとえばページングを持たないCPUがある。そんなCPUに対してでも、メモリアクセス命令は4命令しかないので、その命令のJITの際にページングに相当する動作のコードを付与して出力すれば、なんとページングがあるかのように動作することができる。 --同様に、キャッシュメモリがなくて、単に一部のRAMだけが高速にアクセスできる、なんていう場合も、JITCでキャッシュ管理のコードをつけて出力すれば問題ない。 --レジスタが少ないCPUに対してはメモリで代用させればいい(x86版はまさにそうなっている)。 --CPUにハードウェア割り込みの機能がなくても、数命令おきに割り込みをチェックする命令をはさめば、割り込み機能をソフトウェアで提供することもできる。 -こうして考えていくと、実際のCPUに必要な機能は本当に少ない。セグメンテーションもページングもキャッシュコントローラも割り込み機構もいらない。例外もいらない(アクセス違反はすべて事前に検出されているから)。特権モードもいらない。過去のCPUとの互換性もいらない(どうせOSECPUのコードを動かすだけだから)。プロテクトモードもいらない。16bitモードとか32bitモードとか64bitモードとか切り替える必要もない。 --CPUはきっととてもシンプルにできるだろう。そうなれば速くなるだろう、安くなるだろう、消費電力が下がるだろう。いいことばかりだ。 -(4) OSECPUは第三世代OSASKの仕様を簡略化したもので、その第三世代OSASKは、ページングなどの仮想記憶支援機能を持たないCPUにどうやって仮想記憶を実現したらいいかというところから研究が始まった。 --それは結局、ポインタに細工をして、今はこのポインタ上のオブジェクトはスワップアウト中ですよというマークをつけるしかないという結論になった。メモリアクセスの際にはこのマークを見てからアクセスする。 --これを実現するために、ポインタレジスタは2つのポインタを持つことになった。一つは本来のポインタ。もう一つはマークのありかを指し示すポインタ。これによりマークは一つにまとまり、スワップアウトの際に変更するマークも一つで済む。 --こうなるとポインタレジスタは整数レジスタとはビット数が一致しなくなってしまう。それで汎用レジスタをやめることになった。 --メモリアクセスの際にはいくつかのチェックが入る(スワップアウトしていないかどうかとか)。セキュリティチェックがなくても。つまり遅い。それでメモリアクセスを減らす必要があると直感し、レジスタを増やした。こうして64+32レジスタ構成が生まれる。そしてこのおかげで、本当にメモリにおかなければいけないものだけがメモリにおかれるようになった(x86みたいなレジスタ数の少ないアーキテクチャでは、メモリにおくべきとかそういうのとは無関係に、レジスタ不足をメモリで解決している)。 --こういう背景が既にあり、その後セキュアなOSについて考えていたら、これはうってつけだと直感した。 -つまり何が言いたいのかといえば、セキュアにしようと思って考えた仕様ではなかったのに、気づいたらセキュア向きだったというわけだ。 --それでセキュア機能を少し追加して、そして簡略化のためにマルチビット対応に関する仕様を削り、OSECPUの仕様が出来上がった。 --こんなふうに本来の意図とは違う応用が可能な場合、僕は自分の設計の方針に確信を持つ(設計の際には少しでも汎用的でありたいと思っていて、予期せぬ応用が可能ということは汎用的である証だと思っているから)。 ** 2013.04.01 Mon -ADDやCMPcc命令で、即値が指定できたら便利だし速くなるかなと思ってそういう形式を追加してみたものの、速くなるどころかかえって遅くなる結果に。なんということだ・・・。 --いやちがった、僕のバグだった。・・・直した。よし、ちゃんと速くなったぞ! --app0002aは18%くらい高速化されました。 --(明日の午前中にアップロード予定) --http://osecpu.osask.jp/download/osecpu012a.zip -今日はセキュリティ関係のコードを書こうと思っていたのに、高速化だけで終わってしまった・・・。 ** 2013.04.02 Tue -OSECPUは当面の予定として、FPUをサポートしないし、MMXなどもサポートしないし、マルチスレッドもサポートしない。原始的な2Dのグラフィックはサポートするけど、ちょっとしたゲームを作る程度のことしか考えない。 -これは以下の理由による。 --この程度しかなくても相当に有用なものはいろいろ作れる。 --FPUやMMXやマルチスレッドが使える環境はそんなに多くはない。そのような環境に対してもOSECPUは有効であってほしい。だからまずは多くの機能を求めずに、多くの環境で共通にできることを増やしていく。 --やりつくしたら先へ進む、といった感じで。 -そもそもOSECPUのメインテーマはセキュリティなのであって、豊富な機能ではない。 --はじめはグラフィックもばっさり切り捨てようかとも思ったけど、それだとゲームが作りにくくなって遊べないかなと思い、それくらいはつけようと思った。 ** 2013.04.03 Wed -ついに3週目に突入。そろそろきりのいいところでペースを下げる準備をしなくては。 -今日は体調があまりよくない。そろそろ限界か・・・。 -現在、プログラムの中にデータを入れる方法について考え中。そろそろこれができてもいい時期なので。 -会社の業務が忙しかったので、今日もリリースなし。 -データのうまい入れ方はすぐには思いつけそうにないので、とりあえず保留にして、アセンブラのマクロで抽象化しておく。それで試行錯誤しよう。 ** 2013.04.04 Thu -とりあえずデータは入った。現在サンプルアプリを作ってテスト中。あと1日あればできると思う。 ** 2013.04.05 Fri -(1) サンプルアプリも動いた。サンプルアプリとしては一番大きい3759バイト。このうちの2000バイトはデータ。うーん、コード部分も結構大きい感じだなあ。まあ確かにいっぱい書いた気はする。手抜きでコピペばかりだけど。 --じゃあ手抜きしない方法も考えるかな・・・。 --(app0010a.ose:手抜き版) ~ http://osecpu.osask.jp/download/app0010a.png -とりあえず改良→app0011a.ose。osecpu.exeがまだまだバカなので、このくらいが限界かなあ。 --バイナリ全体で2765バイト、このうちデータ部は2064バイト。まあ現状ではこんなものかな。 --CLEだとコード部が半分以下になるんだろうなあ。 --(今夜アップロード予定) --http://osecpu.osask.jp/download/osecpu013a.zip -メモリアクセスをマシにした。現在サンプルアプリでテスト中。 --app0011a.oseは2623バイトに減った。 --(今夜アップロード予定) --http://osecpu.osask.jp/download/osecpu014a.zip -(2) バイトコードリビジョンアップの基準。旧アプリが新osecpu.exeで誤動作する場合はリビジョンを上げる。エラーが出て止まるのが確実なら、面倒なのでリビジョンを上げない。新アプリが旧osecpu.exeで誤動作する場合もリビジョンは上げない。 -(3) サンプルアプリを何度も作って感じたこと。フロントエンドのバイトコード体系について。 --3項演算は確かに便利だけど、使用頻度は高くない。3項演算プリフィクスがあるとよさそう。プリフィクスがない場合は、2項目は1項目と同じものが指定されたとみなす。 --ポインタレジスタのアクセス後のインクリメントがあれば重宝しそう。デクリメントはなくてもいい感じ。それは手動でやればいい。これもプリフィクス化していいんじゃないかと思う。 --比較してJMPのパターンはすごく多い。短縮形があってもいいレベル。 ---というかむしろ比較命令の前にプリフィクスをおかなければ CND(R3F); JMP(label); とみなしていいと思う。 --ループカウントを増やして比較してJMPのパターンもすごく多いので、それも短縮形があるといいと思う。 --即値は1バイト形式、2バイト形式がぜひともほしいところ。 --3項目は即値率が高い。だからむしろ即値ではないときにはプリフィクスをつける感じで。「3項目レジスタ指定プリフィクス」 -(4) プリフィクス候補を整理する。 p1: CMPcc形の命令についた場合、「比較してJMP」のパターンではないことを示す。 つかないときは CMPcc(R3F, ?, ?); CND(R3F); JMP(?); に展開されてしまう。 メモリ系の命令に付いたときは、ポインタレジスタのアクセス後のインクリメントを指示する。 それ以外の命令についた場合、3項型であることを示す。これがなければ2項型になる。 p2: 最終項がレジスタであることを示す。これがなければ、LIMM(R3F, ?);を使って即値フィールドを生成する。 p3: 即値フィールドは2バイトである。これがないときは即値フィールドは1バイトである。 p4: 即値フィールドは4バイトである。これがないときは即値フィールドは1バイトである。 この方法ではなく、p3を2度指定したら4バイトにするという方法もある。どっちがいいかは検討中。 --これはプリフィクスではないけど、インクリメント+比較+ジャンプ命令を作る(一命令で書ける)。 ---INCCMPJNE(reg0, reg1, label); ---INCCMPJNEI(reg0, imm, label); --ループ開始用に初期値セット&ラベル宣言のセットもほしい。 --ラベル番号も直近の宣言値からの相対値で指定できるようにするべき。その上で2バイトや4バイトも指定可能に。ラベル番号プリフィクス。 ---ラベル番号については、lbstk的な指定も可能にしておきたい。ここは考えどころ。 -JITコンパイラ前提っていいなー。実CPUではこんなのデコードが大変すぎるって思うものでも、気にせずにいられる。 ** 2013.04.10 Wed -(1) うーん、neriさんのCLEの記事で、分岐命令の長さをちゃんと調節している話があって、ちょっと危機感を覚えたかも!OSECPUもやっぱりそのくらいはやるべきかな・・・。 --http://nerry.hatenablog.com/entry/2013/04/10/032018 -(2) OSECPUでは、不正なポインタを一瞬でも生成するだけでエラーにしようと思っていた。これには理由がある。それは、アクセスするたびにポインタを確認していたら実行速度がかなり落ちそうだからだ。 -しかし一方で、文字列処理などを考えると、*s++みたいな方法でアクセスしていって、最後にsが文字列の外(1バイトだけオーバーランする)場合も十分にありうると思う。これをエラーにしてしまうというのは少々やりすぎな気もする。 -よし決めた。ポインタを加算、減算しているときにもチェックはするが、境界値も許すとする。チェックを省略しない理由は、変なポインタを作ってしまった時に少しでも早く気付くためである。 -(3) (2)でポインタチェックのことを書いたけど、これってチェックし忘れてもどこも破壊しないわけで、そういう意味では予防的なものだと思う。バグ探しの時に有効なだけだ。・・・こういうのは、常にONにしたいとまでは思わないので、レベルがあるべきだと思う。 --[1] 一番チェックがたくさんつくレベル。どこでミスしたのか発見するのに役立つ。 --[2] 安全を保証するものの、ミスした個所を特定しやすくはない。ここでエラーを検出したら、[1]で再実行して探すようなイメージ。 --[3] 何もチェックしないでとにかく高速に実行するレベル。 --[4] チェックしないだけではなく、チェックに必要な情報すら生成しないレベル。 ---これはリンクするすべてのコードが[3]か[4]でないと、うまくいかない。 -(4) ポインタのチェックに際して。たとえば文字列の開始アドレスとその文字長を受け取ったとする。その際に、s[0]とs[len-1]をダミーアクセスしてみて、これをアクセスできたら問題ないとわかる。 -こういうチェックは、[3]ではやらなくてもいいのだろうか。まあやらなくていいんだろうけど、何か気になる。OSECPUのチェック機構に頼らずに自前でチェックするのはいいことか?いいことだ。それをやめさせるのは良いことではない。じゃあこうしよう。 --[1] 一番チェックがたくさんつくレベル。どこでミスしたのか発見するのに役立つ。 --[2] 安全を保証するものの、ミスした個所を特定しやすくはない。ここでエラーを検出したら、[1]で再実行して探すようなイメージ。 --[3] OSECPUによる過剰なチェックは抑制されるが、プログラムが持っている賢いチェックは有効のまま。この賢いチェックではOSECPUのチェック用の情報を利用できる。 --[4] 何もチェックしないでとにかく高速に実行するレベル。 --[5] チェックしないだけではなく、チェックに必要な情報すら生成しないレベル。 -つまりこういうことだ。リリースされたプログラムはOSECPUが標準で持っているチェック機構でエラーを検出されてはいけない。これはエラーチェックの漏れを見つけるための物であって、通常の検出機構ではない。そういうことにすれば、愚直にバカ正直にチェックができる。耐えられない遅さになっても構わない。だってリリース時にはOFFにしてもいいんだから。 --うーん、OFFにしてもいいのか本当に?なんだか迷う・・・。他人が書いたコードは信用できるのか。うーん。悪意あるプログラムを考えると、通常は隠れていてテストできないから、やっぱり[2]までしか下げられないような気もする。 --他人が作ったモジュールは信用しないとしよう。だからそれについては[2]で。これはいい。自分が作ったモジュールは信用できるから、[3]にするとか?主に他人が作ったモジュールが渡してきたポインタを信用できないのでチェックするため。だから[4]や[5]にはしない。 -(5) 僕もJITコンパイルするときに、短い命令を出せるときには出すようにしてみた。 --ラベルのアラインまで対応してみた(笑)。 --たとえばapp0001aでは、JITコンパイルの結果で78バイトだったのだが、これが44バイトになった。 --(今夜アップロード予定) --http://osecpu.osask.jp/download/osecpu015a.zip --http://osecpu.osask.jp/download/osecpu016a.zip --http://osecpu.osask.jp/download/osecpu017a.zip --http://osecpu.osask.jp/download/osecpu018a.zip -(6) 明日の予定。スタックっぽいものを作る。これがないと再帰ができないから。 ** こめんと欄 #comment