「10日くらいでできる!プログラミング言語自作入門」の続編#1-2a

  • (by K, 2021.04.15)

(1) HL-12a

  • 今回のテーマは、本当にちゃんとコード生成できているのかの確認、どこまで速くできそうかをちょっと確認、の2つです。
(前略)

int phrCmpPutIcX86(int pid, String phr, int pc, int *pi, int lenExpr, void *sub, int *err) → HL-11と同じなので省略

int codedump; [この行追加]

///////////////////////////////////////////////////////////////////////////////

int compile(String s)
{
    (中略)
    jp = 0;
    putIcX86("60; 83_ec_7c;", 0, 0, 0, 0); // PUSHAD(); SUB(ESP,124);
+   dump0 = icq;
    for (i = 0; i < 10; i++) {
        tmp_flag[i] = 0;
    }
    tmpLabelNo = 0;
    bd = lbd = 0;
    for (pc = 0; pc < pc1; ) { // コンパイル開始.
        (中略)
+       } else if (phrCmp(35, "code", pc)) {
+           for (pc++; tc[pc] != TcSemi; pc++) {
+               if (tc[pc] == TcComma) continue;
+               *icq = var[tc[pc]];
+               icq++;
+           }
+           ppc1 = pc + 1;
+       } else if (phrCmp(36, "codedump !!*0;", pc)) {
+           codedump = var[tc[wpc[0]]];
        } else if (phrCmp( 8, "!!***0;", pc)) {	// これはかなりマッチしやすいので最後にする.
            e0 = expr(0);
        } else
            goto err;
        tmpFree(e0);
        tmpFree(e2);
        if (e0 < 0 || e2 < 0) goto err;
        pc = ppc1;
    }
    if (bd > 0) {
        printf("block nesting error (bd=%d, lbd=%d, pc=%d, pc1=%d\n", bd, lbd, pc, pc1);
        return -1;
    }
+   dump1 = icq;
    putIcX86("83_c4_7c; 61; c3;", 0, 0, 0, 0); // ADD(ESP,124); POPAD(); RET();
    icq1 = icq;
    (中略)
}

int run(String s)
{
    if (compile(s) < 0)
        return 1;
+   if (codedump == 0) {
        void (*func)() = (void (*)()) ic;
        t0 = clock();
        func();
+   } else {
+       int i, i1 = dump1 - dump0;
+       for (i = 0; i < i1; i++) {
+           printf("%02x ", dump0[i]);
+       }
+       printf("\n(len=%d)\n", i1);
+   }
    return 0;
}

(後略)
  • プログラムは766行になりました。
  • 今回コマンドを2つ作ったのですが、1つめのcodedumpは「生成した機械語を実行する」「生成した機械語を表示する」を選択するためのコマンドです。
    >codedump 1
    
    (len=0)
    
    >a=1
    8b 05 d0 f9 41 00 89 05 d8 fa 41 00
    (len=12)
  • codedump 1 のときは、機械語が表示されるだけで実行はされません。上記の例でも機械語が出るだけで、aに1は代入されません。
  • codedump 0 を実行すれば普通の実行モードに戻ります。
  • 2つめのcode命令は、かなり強烈な命令です。16進数を並べると、それをそのまま機械語生成に加えます。ちょっと間違えただけでプログラムが暴走します。
  • 私はこの命令を使って、「こういう機械語にしたら何秒で実行できるかなあ」みたいなことを試します。
  • どちらも具体的な例を、(2)や(3)で説明します。

(2) codedumpの結果を見てみる

>codedump 1

(len=0)

>run hl3.txt
8b 05 cc f9 41 00 89 05 d8 fa 41 00 8b 05 d8 fa
41 00 40 89 05 d8 fa 41 00 3b 05 e0 fa 41 00 0f
8c e7 ff ff ff e8 99 1c d9 ff
(len=42)
  • さてこの結果はあっているでしょうか?このままだとみにくいので、編集してみます。
    8b 05 cc f9 41 00; 89 05 d8 fa 41 00;     // EAX = [0]; [i] = EAX;
    8b 05 d8 fa 41 00; 40; 89 05 d8 fa 41 00; // EAX = [i]; EAX++; [i] = EAX;
    3b 05 e0 fa 41 00; 0f 8c e7 ff ff ff;     // CMP(EAX,[100000000]); JL EIP-25;
    e8 99 1c d9 ff;                           // CALL sub_time;
  • おお、ちゃんとできている!・・・とりあえずJITコンパイラとしては十分に機能しているようです。

(3) code命令を使ってみる

  • gcc-O3が生成した10億回ループのコードを実行してみます。
    >code 0xb8 0xff 0xc9 0x9a 0x3b 0x48 0x79 0xfd; time;
  • おお、やっぱり速い。さすがです。これと同じコードを作れるようになるのは大変そうなので、少し妥協して以下のコードが生成できるように頑張る予定です(HL-15a)。
    >code 0x31 0xdb 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x43 0x81 0xfb 0x00 0xca 0x9a 0x3b 0x0f 0x8c 0xf3 0xff 0xff 0xff; time;
  • よし、同じ速さが出ています。コードの長さは負けていますが、ここを頑張るためにはたくさん改造しなければいけなくなるので、これくらいで妥協します。
    • なお、上記のたくさんの0x90(NOP)は分岐先アドレスのアラインのために入れています。
  • 私は最適化の方法を検討するとき、この例のようにcode命令をよく使います。だって、すごく簡単に試せますからね。
  • また「CPUにはこういう命令があるらしい」みたいなときに試したくなることがありますが、そのときもcode命令を使います。
    • 「アセンブラのソースコードを書いてアセンブルして実行する」と比べると、手軽さが全然違います!

次回に続く

こめんと欄


コメントお名前NameLink

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2021-04-21 (水) 08:53:48 (186d)