* Kの開発メモ #0003 -(by [[K]], 2013.04.11) -ここは川合の開発の進捗などをレポートするところです。 --当初はその予定だったが、今ではむしろ主たる目的は[[K]]の備忘録になってしまっている(苦笑)。 ** 2013.04.11 Thu -とりあえずosecpu.cを少し整理した。1255行→1188行。 -スタックを実装した。再帰を使った階乗計算をしてみた。うまくできたようだ。 --osecpu.cはまた増えて1261行に。 -そろそろセキュリティ機能をつけ始めるべきだよな・・・。 -まあまずはエラーになった箇所を報告する方法を検討中。 --まあこんな感じかなというところまではできた。 -今日の成果。 --http://osecpu.osask.jp/download/osecpu019a.zip ** 2013.04.12 Fri -(1) osecpu019aはいろいろとダメなところがいっぱいあった。まだまだ甘い。いろいろと直さなくては。 --でも方向性としては悪くない。 -ダメなところリスト: --行番号をエラーレポートに含めるアイデアはいいけど、行番号だけだと複数のファイルから作った場合は、ごっちゃになってしまうので識別する方法が必要。 --スタックの仕様がよくない。暫定にしてもよくない。ただまあスタックを作るという方向性は悪くない。 -(2) lbstkをバージョンアップすることで、行番号の問題は解決。 -(3) やっぱりASKA的なものがほしい。つまりアセンブラではなくてもうちょっとC言語っぽい文法で書きたい。現状では辛すぎる。 --作ったぞー。この程度までならできる。 R01 = 0x1234567; R02 = 2; R03 = 3; R00 = R02 + R03; --こんなのもできた。 #include "osecpu_asm.h" OSECPU_HEADER(); #define LMEMPP(reg, typ, preg) LMEM(reg, typ, preg); DB(0); PADDI(preg, typ, preg, 1) #define SMEMPP(reg, typ, preg) SMEM(reg, typ, preg); DB(0); PADDI(preg, typ, preg, 1) R04 = 0x100; R06 = 256; R07 = 256; PCALL(P1B); // open_window(256, 256); R11 = 0; // y LOOP(1, 0); // CONTINUEを使う回数, BREAKを使う回数. R10 = 0; // x LOOP(1, 0); // CONTINUEを使う回数, BREAKを使う回数. R00 = R10 << 16; R01 = R11 << 8; R00 += R01; // 00xxyy00 SMEMPP(R00, T_UINT32, P04); R10++; CMPIJNE(R10, 256, CONTINUE); ENDLOOP0(); // BREAKを使わない場合用、省略可能. R11++; CMPIJNE(R11, 256, CONTINUE); ENDLOOP0(); // BREAKを使わない場合用、省略可能. R04 = 0x101; R06 = 256; R07 = 256; R08 = 0; R09 = 0; PCALL(P1B); // flush(256, 256, 0, 0); R04 = 0x102; R05 = 10000; PCALL(P1B); // sleep(10.000sec); --この先は、識別子の処理を書いてからかなー。 int32s x == R00, y == R01; x += y; --とかがやりたい。 -(4) 今日の成果。 --http://osecpu.osask.jp/download/osecpu020a.zip --http://osecpu.osask.jp/download/osecpu021a.zip ** 2013.04.13 Sat -現在考えているとりあえずの理想形: include("osecpu_h.ask"); int32u *p:P10; int32s x:R10, y:R11; p = sys_openWin(256, 256); for (y = 0; y < 256; y++) { for (x = 0; x < 256; x++) { *p++ = (x << 16) + (y << 8); } } sys_flushWin(256, 256, 0, 0); sys_sleep(10000); // 10.000sec -これなら結構読みやすいはず。高級言語だといっても信じる?・・・まあ所詮は高級アセンブラでしかないんだけど。 -C言語みたいだけど、いろいろと微妙に違う仕様について。 --forの中身が1文しかなくても、{ }は省略できない。 --forの最初の条件比較は省略される。つまり不成立でも一度はループを回ってしまう。 ---そういう意味ではdo-whileに近い。 --for文のなかでcontinueを使うことはできるが、その場合、更新式は評価されないし、条件式も評価されない。問答無用で次のループに突入する。 --C言語との挙動の違いが気になるのなら、更新式や条件式を書かないでおいて、自分でループ内に記述することを勧める。 --複雑すぎる式は計算できない。 --&&や||は使わない。&や|で代用する。つまり|で左辺値が成立しても、右辺値も計算してしまう。&の左辺値が0でも右辺値を計算してしまう。 ---両辺値がそろってはじめてANDやOR演算をする仕様なので。 ** 2013.04.15 Mon -今日もアプリ開発環境の整備を中心に。 -askaとlbstkは統合してosectolsというツールになりました。 --共通する処理が多かったので一つにしたほうが作りやすいと気付いたのです。 --(今夜アップロード予定) --http://osecpu.osask.jp/download/osecpu022a.zip -さらにosectolsに機能を増やして、naskがなくてもアプリが作れるようになりました。 --逆ポーランド法を少しだけ書いたので、そのテストも兼ねて式評価ルーチンを書いて、naskの代わりにDB内の数式を計算しています。 --naskや第二世代OSASKの同梱をやめたので、アーカイブが一気に小さくなりました。52.8KBです。 ---osecpu.exe : 9.0KB ---osectols.exe : 8.0KB --この調子で頑張ったら、2013.04.13の理想形をサポートしても、osectolsは20KB未満なんじゃないかな?これってTCCが100KBなことを思えば、結構悪くない気がする。・・・まあ、TCCはISOC99をまじめにサポートしているから、やっぱり機能密度で比較したら負けているかな・・・。 --でもでも、C99をまともにサポートして行数が長くなるよりも、サブセットでもヘンテコ仕様でもいいから、そこそこの長さのほうが教材としてはいいんじゃないかな。そうでないと読み始めたところでうんざりするんじゃないかな・・・。 ---まあ都合のいいいいわけだけど(苦笑)。 --いや待て、僕はプリプロセッサをGCC任せにしているじゃないか・・・。プリプロセッサ入れたら20KBは無理だろう。・・・そうか、やっぱりTCCはすごいなあ。 --(今夜アップロード予定) --http://osecpu.osask.jp/download/osecpu023a.zip ** 2013.04.16 Tue -osecpu023の逆ポーランド変換は適当すぎた。反省中。以下のサイトを読んでちゃんとやろう。 --http://www.gg.e-mansion.com/~kkatoh/program/novel2/novel207.html --http://ja.wikipedia.org/wiki/%E6%93%8D%E8%BB%8A%E5%A0%B4%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0 -今はアセンブラのためにプリプロセッサを使っていますが、これは近いうちにやめます。今のレベルの変換ならosectolsにやらせても平気だと思うのです。そうなったら、GCCを用意しなくてもアプリが作れることになります。 --そしてosectolsをOSECPUアプリに移植すれば、完璧というわけです。 -今日は開発時間が10分くらいしかなかったので、リリースはなしです。 ** 2013.04.17 Wed -R00 = R10 << 16 | R11 << 8; がコンパイルできるようになったぞー。 --SHLI(R00,R10,0x10);SHLI(R01,R11,0x8);OR(R00,R00,R01); --R00とR01をテンポラリに使ってくれています。式に余計なかっこを付けたりしてもちゃんと処理してくれるし、定数式があればそこはコンパイル時に計算して一つにまとめてくれます。 --そのほかのコンパイル例: R10 = (R11 + R12 + R13); → ADD(R00,R11,R12);ADD(R10,R00,R13); R14 = 1 + 2 + 3; → LIMM(R14,0x6); R15 = 2 * R10 + 1; → MULI(R00,R10,0x2);ADDI(R15,R00,0x1); --これだけできても、osectols.exeはまだ9.5KBだぜー。いい感じだー。 -今日の成果: --http://osecpu.osask.jp/download/osecpu024a.zip ** 2013.04.18 Thu -osectolsだけで(つまりGCCのプリプロセッサを使わなくても)、アプリが作れる環境ができそう。 --しまった __LINE__ マクロのことを忘れていた・・・これも自前で何とかしないといけないのか。できそうだけどめんどいなあ。 -今日の成果: --昨日がんばりすぎて今日はゆっくりペース。 --http://osecpu.osask.jp/download/osecpu025a.zip -プリプロセッサのosectolsへの取り込みは後回しにしよう。今はGCCが必要なことが問題になっているわけじゃない。2013.04.13の理想形に近づくことが第一だ。そのためにどんな外部ツールを利用してもいいくらいだ。 -そのほか、今後やっていきたいと思っていることの一覧: --(順番は優先順位でも着手予定順序でもなく、ただの思いつき順) --セキュリティ部分を仕上げる → これは大事、OSECPUとして必須だから ---無限ループの自動停止なども --DLLのサポート → これは簡単だけど応用が利くし、セキュリティを考えるきっかけにもなる --任意のバイトコード列を受け入れてJITコンパイルして即時実行するAPIの導入 ---これがあると高速なインタプリタをOSECPU上で作れるようになる --構造体のサポート → これができないとプログラムが書きにくい --シェルを作る、OSECPUアプリとして → これでやっと単なるVM開発プロジェクトではなくてOS開発っぽくなる --osectolsなどのアプリ開発ツールをOSECPUアプリとして書き直して、osecpu.exeさえ移植すれば、シェルもアプリ開発環境も手に入るという状態にする --APIをまともにする --テキストエディタもOSECPUアプリとして開発する、ここまでくればセルフ開発環境がそろったことになる ** 2013.04.19 Fri -(1) 昨日書いたシェルについてちょっと書いておきます。 --シェルができると、たとえばプログラムの実行中に、変数の中身を見てみることができます。 ---書き換えてもいいです。 --一度停止させて再開させることもできます。状態の保存もできます。 --シェルをOSECPUアプリとして開発するのは、osecpu.exe以外に機種依存する部分を作りたくないからです。 -(2) OSECPUにはROLとかRORのような、ローテイト命令がありません。今後も作らない予定です。そのことについて少し書いておきます。 --まずローテイト命令は、いくつかのテンポラリレジスタと普通の右シフトと左シフトを組み合わせれば、同様の結果が得られます。 --次にローテイト命令は使用頻度が低いです。 --さらにローテイト命令はレジスタの幅に敏感な命令です。32bitレジスタに対してローテイト命令を実行した場合と、64bitレジスタに対してローテイト命令を実行した場合とでは、結果が違います。 ---OSECPUでは整数レジスタが少なくとも32bitあることは保証しますが、必ず32bitだというわけではなく64bitレジスタに割り当てられる可能性もあります。そのときにわざわざ32bitと結果が同じになるようにと細工をする予定はありません。だから32bitと同じ結果にならないような命令は、できれば持ちたくないのです。 -(3) OSECPUでは命令セットをできるだけ小さくしています。 --これはosecpu.exeの移植をしやすくするためです。バックエンド用だからということもあります。 --以下に、不採用になった命令の代替方法を書いておきます。 NOT(a,b); → XOR(a,b,-1); NEG(a,b); → SUB(a,0,b); or MUL(a,b,-1); SHR(a,b,1); → SAR(a,b,1); AND(a,a,0x7fffffff); SHR(a,b,c); → SAR(a,b,1); AND(a,a,0x7fffffff); SAR(a,a,c-1); 多倍長系の命令 → レジスタの下位16bitを演算に利用する、上位は桁あふれ検出用にする それで任意のビット長を16bitで分割して演算していけば、64bitでも1024bitでも一応多倍長演算できる。 符号なしの大小比較 → 32bit符号なし整数の比較は、もしそのビットをフルに使って誤判断する可能性があるのであれば、 多倍長演算で48bit符号付き整数に拡張してやってください。 --即値を演算命令で指定できずにR3Fレジスタに一度代入するという方式をとっています。 --即値も32bitしかサポートしていません。 --演算命令で2オペランド型をサポートしていません。 --使用頻度の高い、INCやDECも用意していません。 --メモリへのアクセス命令も非常に限定されています。 --これらもすべて命令セットを単純にするために選択された手法です。 -(4) for文ができた! for (R11 = 0; R11 < 256; R11++) { // y for (R10 = 0; R10 < 256; R10++) { // x R00 = R10 << 16 | R11 << 8; // 00xxyy00 SMEMPP(R00, T_UINT32, P04); } } --現在osectols.exeは12.5KB。 -今日の成果: --http://osecpu.osask.jp/download/osecpu026a.zip --http://osecpu.osask.jp/download/osecpu027a.zip -(5) 符号なしの大小比較 → わざわざ多倍長演算する必要なかった。以下のようにやればできそう。 --符号なしで a < b なのかどうかを判定する場合: --c = a ^ b; を計算して、このcの符号を確認する。 --cが負の場合、aとbはMSBが異なるということだから、符号ありの比較で a >= 0 なら、符号なしで a < b といえる。逆に a < 0 なら a > b ということになる。 --cが0以上の場合、aとbのMSBは同じ。だから (a > 0 && a < b) || (a <= 0 && a > b) が、符号なし a < b と同値になる。 -(6) 2013.04.13の理想形のための残りの作業: --break, continue, gotoへの対応。 --if文を使えるようにする。 --変数名を使えるようにする。 --メモリアクセスに対応する。 --関数呼び出しに対応する。 ** 2013.04.20 Sat -(1) 実レジスタ活用([[page0027]])を書いた。・・・少しosecpu.exeを整理して、とりあえずEAX,ECX,EBP,ESP以外は全く使わなくなった。これでEDX,EBX,ESI,EDIに限ればすぐに使えるわけで、実レジスタ化の実験ができる。 ** 2013.04.21 Sun -(1) やっと実レジスタ化の実験ができた。効果は大きい。Atom230(1.60GHz)だとこのくらい差が出た。 osecpu027a>osecpu app0002.ose time: JITC=0.000[sec], exec=53.953[sec] size: OSECPU=106, native=74 osecpu028a>osecpu app0002.ose time: JITC=0.000[sec], exec=21.593[sec] size: OSECPU=106, native=67 --2.5倍速だ!・・・なんか中では普通に実レジスタが使われるのが分かっているので、もう負ける気がしない。 ---DECしてJNZみたいな技は使えないけどね(それをやりたければフレーズ最適化(→[[page0022]])をやるしかない)。 --でもコードサイズ(native)があまり変わっていないような気もする。これでいいのかな? ---あっているみたいだ。まずosecpu028aでは、最初に仮想レジスタをEBX~EDXに取り込む命令が追加されて、終了時に書き戻す命令が追加されるから、これだけで24バイト増になっている。にもかかわらず7バイトも減っているわけで、つまりコード本体は31バイトも減っていることになる。 -(2) DECしてJNZみたいな技は使えないって書いたけど、使えることになった。 --JITCするときに、フラグを変化させうる命令を出力した場合はそれを覚えておくようにして、それで不要な0との比較を検出したときには、CMP命令を生成しないようにしてみた。 --app0015.ask: for (R02 = 1000000; R02 != 0; R02--) { // j:R02 R01 = 0; for (R00 = 10000; R00 != -1; R00--) { // sum:R01, i:R00; R01 += R00; } } time: JITC=0.000[sec], exec=21.906[sec] size: OSECPU=106, native=55 --app0016.ask: for (R02 = 1000000; R02 != 0; R02--) { // j:R02 R01 = 0; for (R00 = 10000; R00 != 0; R00--) { // sum:R01, i:R00; R01 += R00; } } time: JITC=0.000[sec], exec=15.531[sec] size: OSECPU=106, native=55 --両者の違いはループ時の比較が0との比較になっているかどうか。 --0と-1での違いは、約1.0001倍の性能差しかないはずなのに、1.4倍くらいの性能差になっている。 --両者のnativeのサイズが同じになってしまっているけど、これはアラインのせいで3バイトの差が見えなくなってしまっているのだろう。 --自分で作っておいて言うのもなんだけど、というか自画自賛だけど、OSECPUってすごいな。ここまでできるのなら、x86で直接作る必要ってほとんどないんじゃないかと思える。 ---SSEとかやりたいのならこの限りじゃないかもしれないけど。 ---でもここまでできて、かつ他のCPUにも移植可能って、かなりいい仕様じゃないかなと思う。 --ちなみにうちにはPCがいっぱいあるので(笑)、別のPCでも実験してみた。Atom_D525(1.80GHz)だとapp0016がexec=11.257[sec]になった。おおー、速いなー。 -(3) C言語と比べてどっちがいいか対決(笑): --OSECPUはosecpu.exeさえ移植すればどのCPUでも使えるというのが売りだけど、GCCだってコンパイラさえ移植すれば互換性は維持できる。じゃあGCCと比べてどうなのかという話。 --まず移植の容易さだけど、osecpu.exeの移植は結構簡単だとしてもいいだろう。なんといっても現状10KBしかない。もちろんGCCを自作CPUに対応させるのも結構容易だと聞いたことはある。ということでここは引き分けとしよう。 --OSECPUのASKAはC言語っぽい外見をしているものの、結局はアセンブラなので、どのレジスタに何を入れるかという制御が細かくできる。でもC言語ではできない。いや、C言語でもインラインアセンブラを使えば・・・という話はある。それはそのとおりだけど、C言語でインラインアセンブラを使ってしまったら、もはやアプリの移植性は地に落ちる。 ---OSECPUでは、osecpu028aのように実レジスタとの対応を決めてしまえば、実レジスタを確実に使うことができる。 --GCCには優秀な最適化があって、OSECPU-ASKAにはそれがない。だから互換性のあるプログラムを適当に書いてそこそこの速度を出すという用途であればGCCはうってつけだと思う。でもアセンブラであれこれ書いてそれ以上の速度を出したい向きには、やっぱりアセンブラに手を出すかない。そしてGCCのインラインアセンブラを使えば移植性はない。 --ということでOSECPUの勝ちだー。 -(4) 今日の成果: --http://osecpu.osask.jp/download/osecpu028a.zip ** 2013.04.22 Mon -Core_i7-2600(3.40GHz)で実験してみたら、app0016はexec=2.762[sec]でした。なんという速さだ! -P02とP03の実レジスタ化もちょっと作ったけど、app0011がバグで動かない。 --http://osecpu.osask.jp/download/osecpu029a.zip --というかapp0011に関して言えば、osecpu028aでも動かなくなっていたようだ。 -やっとバグが取れた。 --http://osecpu.osask.jp/download/osecpu030a.zip ** こめんと欄 #comment