[Subject Prev][Subject Next][Thread Prev][Thread Next][Subject Index][Thread Index]

[OSASK 1362] for more compact(Re: s_world.bin).



  こんばんは、川合です。


Koyanagi Masaaki さんは 2001/01/15 12:58:59 の「[OSASK 1361] Re:
 s_world.bin.」で書きました:

>しまった間違えました。INT 0xOD General Protect です。
>何故か 0x0D = 10 と変換してしまいました。

  はい、これなら納得です。

  今のOSASKは例外が起きたら、システム全体が停止してしまうか、そ
うでなくても表示が乱れてしまいます。これは手抜きのせいで、これは
近いうちに手を付ける予定です。

  で、せっかく表示された情報を生かさないのはもったいないので、そ
の読み方を少し説明します。

  一般保護例外(INT 0x0D)は、いろんな要因で起こりますが、最も多
いのは、ポインタが狂って妙なところをアクセスしようとした時です。
妙なところというのは、与えられたセグメントサイズ以上の領域をアク
セスしようとした時です。

  で、まず、どの命令がそんな妙なことをやろうとしていたのかは、CS
:EIPの値で分かります。CSが0x0007でしたら、例外を起こしたのはアプ
リの中です。それ以外の値の場合は、[OSASK 1007]で少し説明してあり
ます。

  さて、CSの値が0x0007でしたら、このエラー表示はデバッグの役にた
ちます(主にASKAで開発している場合に限る)。EIPの表示を見てくだ
さい。それが、例外を発生した命令のアドレス(オフセット)です。MA
SMの時に出力された.LSTファイルで該当の命令が判明するはずです。で
、そのときのレジスタの値も表示されているはずですから、どうしてそ
の命令が例外を起こしたのか、察しがつくのではないでしょうか。

>>   ソースは全体的にシンプルで素直な印象を持ちました。まあ、アセン
>> ブラをやるとこうなるのが普通で、だから誰にでもコンパクトで速いプ
>> ログラムが書けるのですが。
>私のアセンブラレベルが初級だというのもあると思いますが(笑)

  いえいえ、あれだけ書ければたいしたものです。

>>   これは小柳さんへの私信ですが、
>>     http://homepage1.nifty.com/dreaming/osask/index.html
>> のページをうちの「OSASKアプリケーションのダウンロードページ」か
>> らリンクしてもよろしいでしょうか?
>はい。よろしくお願いします。

  ありがとうございます。

---

  さて、せっかく小柳さんがASKAにチャレンジして成功して下さったの
で、これを記念して少しだけASKAのコツみたいなものを書きます。テー
マは「もっとコンパクトにしよう!」です(笑)。

  ASKAはわざと変なことをやらない限りC言語で書くよりも実行ファイ
ルがコンパクトになる傾向があります。しかし、少し意識してやると、
さらに小さくできます(これをやると場合によってはソースが読みにく
くなるかもしれませんが)。

  実行ファイルの大きさに関しては、小さいことは大きいことよりもい
いことです。今から紹介するテクニックの中には実行スピードが落ちて
しまうような「改良」もあります。そういうものは、速度を必要とする
ところのコードには適しません。しかし、速度を要しないコードが小さ
くなることはそれだけキャッシュの消費を抑え、速度を必要とするコー
ドやデーターをキャッシュできるようになるので間接的に高速化に貢献
できます。

  まずは、改良のための基礎知識の説明です。

・レジスタへの即値単純代入

  EAX = 2;

のような、reg32 = imm;のタイプに分類される命令は、immが0でない限
りにおいて、5バイトの命令にコンパイルされます。immが0の場合は、2
バイトです。reg16やreg8の場合も含めて書くと以下の通りです。

    reg32 = imm; /* 5バイト */      reg32 = 0; /* 2バイト */
    reg16 = imm; /* 4バイト */      reg16 = 0; /* 3バイト */
    reg8  = imm; /* 2バイト */      reg8  = 0; /* 2バイト */

・レジスタ間の単純代入

  ECX = EDX;

のような、reg32 = reg32;のタイプに分類される命令は、2バイトの命
令にコンパイルされます。reg16やreg8の場合も含めて書くと以下の通
りです。

    reg32 = reg32; /* 2バイト */
    reg16 = reg16; /* 3バイト */
    reg8  = reg8;  /* 2バイト */

・メモリへの即値単純代入

  (int) [DS:EBX] = 0x1234;

のような、mem32 = imm;のタイプに分類される命令は、6バイトの命令
にコンパイルされます。これは、メモリへのポインタをどのように指定
するかによってより長い命令にコンパイルされることがあります。とり
あえず、最低でもこれだけの命令長にはなると理解して下さい。バリエ
ーションを書いておきます。immが0の場合に命令が短くなったりするこ
とはありません。

    mem32 = imm; /* 6バイト */
    mem16 = imm; /* 5バイト */
    mem8  = imm; /* 3バイト */

  memの指定方法によって、さらに命令長の長さが変わります。

  [DS:EBX]や[DS:ESI]のように、オフセット部がreg32の場合:
    命令長は上記バイト数のまま
    (例外1:ESPの場合は+2バイト)
    (例外2:EBPの場合は+1バイト)

  [DS:1234]のように、オフセット部がimmの場合:
    命令長は上記バイト+4バイト

  [DS:EBX + 4]のように、オフセット部がreg32 + immの場合:
    (a) -128 <= imm <= 127 : +1バイト
    (b) immが(a)の範囲におさまらない : +4バイト
    もちろん、immが0の場合は、オフセット部がreg32の場合として分
      類されます。
    また、reg32の部分がESPの場合は、さらに+1バイトです。

  他にも多くの指定方法がありますが、面倒になってきたので省略(笑
)。

・メモリへのレジスタ値単純代入

  (int) [DS:EDI] = EAX;

のような、mem32 = reg32;タイプに分類される命令は、2バイトの命令
にコンパイルされます。これも、memの指定方法によって、追加のバイ
トがあります。追加バイトの計算方法は、「・メモリへの即値単純代入
」の時と共通です。データー長によって基本命令長の長さが変化します
。

    mem32 = reg32; /* 2バイト */
    mem16 = reg16; /* 3バイト */
    mem8  = reg8;  /* 2バイト */

  ただし、例外が一つだけあります。regの部分がEAX、AX、ALのどれか
で、かつ、memのオフセット部がimmなら、基本命令長を1バイト小さく
みてから追加命令長を加えて下さい。

・レジスタへのメモリ値単純代入

  EAX = (int) [DS:EDI];

のような、reg32 = mem32;タイプに分類される命令は、2バイトの命令
にコンパイルされます。これも、memの指定方法によって、追加のバイ
トがあります。追加バイトの計算方法は、「・メモリへの即値単純代入
」の時と共通です。データー長によって基本命令長の長さが変化します
。

    reg32 = mem32; /* 2バイト */
    reg16 = mem16; /* 3バイト */
    reg8  = mem8;  /* 2バイト */

  ただし、例外が一つだけあります。regの部分がEAX、AX、ALのどれか
で、かつ、memのオフセット部がimmなら、基本命令長を1バイト小さく
みてから追加命令長を加えて下さい。

  ・・・ふう、長かったですねえ。さて、上記の知識を元に、s_world
のソースの一部が何バイトになるかを計算してみましょう。

    work->lib_putnotename.string[0] = 32;/* ' ' */  // 10バイト
    work->lib_putnotename.string[4] = 32;           // 10バイト
    work->lib_putnotename.string[8] = 32;           // 10バイト
    work->lib_putnotename.string[12] = 32;          // 10バイト

(註)

    work->lib_putnotename.string[0] = 32;

は、

    (int) [DS:0x0c20] = 32;

とみなされる。

  さて、これをこのように改良することができます。

    EAX = 32;                               // 5バイト
    work->lib_putnotename.string[ 0] = EAX; // 5バイト
    work->lib_putnotename.string[ 4] = EAX; // 5バイト
    work->lib_putnotename.string[ 8] = EAX; // 5バイト
    work->lib_putnotename.string[12] = EAX; // 5バイト

  はい、これで、40バイトの命令群が25バイトに改良されました。

  ついでに、もっと小さくしてみましょう。

    int *putnotename_string == DS:EDX;
    EAX = 32; // 5バイト
//  (offset) putnotename_string = work->lib_putnotename.string; // 5バイト
    LEA((offset) putnotename_string, (int) [work->lib_putnotename.string]);
    putnotename_string[ 0] = EAX; // 2バイト
    putnotename_string[ 4] = EAX; // 3バイト
    putnotename_string[ 8] = EAX; // 3バイト
    putnotename_string[12] = EAX; // 3バイト

  はい、これなら21バイトです。これで、元の40バイトからみれば、19
バイトだけコンパクトになりました。

  ・・・こういうことを意識することもできるのが、ASKAの特徴でしょ
う(意識しなければいけないわけではありません)。


  それでは。

--
    川合 秀実(KAWAI Hidemi)
川合堂社長 / OSASK計画総指揮 / カーネル開発班
E-mail:kawai !Atmark! imasy.or.jp
Homepage http://www.imasy.or.jp/~kawai/