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

  • (by K, 2021.05.10)

(1) HL-18a

  • 今回のテーマは、x86の時に付けたcodedump命令とcode命令がとても便利だったので、同じことをx64でもやることです(参考:HL-12a)。
    (前略)
    
    int phrCmpPutIcX64(int pid, String phr, int pc, int *pi, int lenExpr, int sub, int *err) → HL-17と同じなので省略
    
    int codedump; [この行追加]
    
    ///////////////////////////////////////////////////////////////////////////////
    
    int compile(String s)
    {
        (中略)
        jp = 0;
        putIcX64("41_57; 41_56; 41_55; 41_54; 41_53; 41_52;", 0, 0, 0, 0);
        putIcX64("41_51; 41_50; 57; 56; 55; 54; 53; 52; 51; 50;", 0, 0, 0, 0);
        putIcX64("%R_81_ec_f8_01_00_00; %R_bd_%0q;", var, 0, 0, 0);
    +   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;
        putIcX64("%R_81_c4_f8_01_00_00;", 0, 0, 0, 0);
        putIcX64("58; 59; 5a; 5b; 5c; 5d; 5e; 5f; 41_58; 41_59;", 0, 0, 0, 0);
        putIcX64("41_5a; 41_5b; 41_5c; 41_5d; 41_5e; 41_5f; c3;", 0, 0, 0, 0);
        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;
    }
    
    (後略)
  • プログラムは816行になりました。
  • 今回コマンドを2つ作ったのですが、1つめのcodedumpは「生成した機械語を実行する」「生成した機械語を表示する」を選択するためのコマンドです。
    >codedump 1
    
    (len=0)
    
    >a=1
    48 8b 85 20 00 00 00 48 89 85 c0 02 00 00
    (len=14)
  • codedump 1 のときは、機械語が表示されるだけで実行はされません。上記の例でも機械語が出るだけで、aに1は代入されません。
  • codedump 0 を実行すれば普通の実行モードに戻ります。
  • 2つめのcode命令は、かなり強烈な命令です。16進数を並べると、それをそのまま機械語生成に加えます。ちょっと間違えただけでプログラムが暴走します。
  • 私はこの命令を使って、「こういう機械語にしたら何秒で実行できるかなあ」みたいなことを試します。
  • どちらも具体的な例を、(2)や(3)で説明します。

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

>codedump 1

(len=0)

>run hl3.txt
48 8b 85 18 00 00 00 48 89 85 c0 02 00 00 48 8b
85 c0 02 00 00 48 ff c0 48 89 85 c0 02 00 00 48
3b 85 d0 02 00 00 0f 8c e2 ff ff ff 48 ff 95 78
01 00 00
(len=51)
  • さてこの結果はあっているでしょうか?このままだとみにくいので、編集してみます。
    48 8b 85 18 00 00 00; 48 89 85 c0 02 00 00;           // RAX = [c0]; [i] = RAX;
    48 8b 85 c0 02 00 00; 48 ff c0; 48 89 85 c0 02 00 00; // RAX = [i]; RAX++; [i] = RAX;
    48 3b 85 d0 02 00 00; 0f 8c e2 ff ff ff;              // CMP(RAX,[c100000000]); JL EIP-30;
    48 ff 95 78 01 00 00;                                 // CALL [sub_time];
  • おお、ちゃんとできている!・・・とりあえずJITコンパイラとしては十分に機能しているようです。

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

  • gcc-O3が生成した10億回ループのコードを実行してみます。
    >code 0x90 0x90 0xb8 0xff 0xc9 0x9a 0x3b 0xff 0xc8 0x79 0xfc; time;
  • おお、やっぱり速い。さすがです。
  • 私は最適化の方法を検討するとき、この例のようにcode命令をよく使います。だって、すごく簡単に試せますからね。
  • また「CPUにはこういう命令があるらしい」みたいなときに試したくなることがありますが、そのときもcode命令を使います。
    • 「アセンブラのソースコードを書いてアセンブルして実行する」と比べると、手軽さが全然違います!

次回に続く

こめんと欄


コメントお名前NameLink

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2021-05-10 (月) 23:45:00 (166d)