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

  • (by K, 2021.04.16)

(1) HL-13

  • さてなんだかんだとJITコンパイラ対応をしているうちに、あとちょっとでmandel.cくらいなら動かせそうなところまで来ました。だから一気にやってしまいます。
  • [1]sub_関数群の宣言を以下の記述と差し替え
    clock_t t0;
    AWindow *win;
    int toExit;
    
    void sub_print(int i)                  { printf("%d\n", i); }
    void sub_time()                        { printf("time: %.3f[sec]\n", (clock() - t0) / (double) CLOCKS_PER_SEC); }
    int  sub_aRgb8(int r, int g, int b)    { return aRgb8(r, g, b); }
    int  sub_XorShift()                    { return aXorShift32(); }
    int  sub_aGetPix(int x, int y)         { return aGetPix(win, x, y); }
    int  sub_f16Sin(int x)                 { return (int) (sin(x * (2 * 3.14159265358979323 / 65536)) * 65536); }
    int  sub_f16Cos(int x)                 { return (int) (cos(x * (2 * 3.14159265358979323 / 65536)) * 65536); }
    int  sub_aInkey(int opt)               { return aInkey(win, opt); }
    void sub_prints(char *s)               { printf("%s\n", s); }
    void sub_aSetPix0(int x, int y, int c) { aSetPix0(win, x, y, c); }
    void sub_aFilRct0(int xsz, int ysz, int x0, int y0, int c)  { aFillRect0(win, xsz, ysz, x0, y0, c); }
    void sub_aDrwStr0(int x, int y, int c, int b, char *s)      { aDrawStr0(win, x, y, c, b, s); }
    void sub_gprDec(int x, int y, int w, int c, int b, int i)   { char s[100]; sprintf(s, "%*d", w, i); aDrawStr0(win, x, y, c, b, s); }
    
    int sub_OpnWin(int xsz, int ysz, char *s)
    {
        if (win != 0) {
            if (win->xsiz < xsz || win->ysiz < ysz) {
                printf("openWin error\n");
                return 1;
            }
        } else
           win = aOpenWin(xsz, ysz, s, 0);
        return 0;
    }
    
    int sub_aWait(int msec)
    {
        if (msec == -1) {
            if (win != 0) {
                aFlushAll(win);
            }
            return 1;
        }
        aWait(msec);
        return 0;
    }
    
    void sub_bitblt(int xsz, int ysz, int x0, int y0, int *a)
    {
        AInt32 *p32 = &win->buf[x0 + y0 * win->xsiz];
        int i, j;
        for (j = 0; j < ysz; j++) {
            for (i = 0; i < xsz; i++) {
                p32[i] = a[i];
            }
            a += xsz;
            p32 += win->xsiz;
        }
    }
  • [2]exprSub1()関数の直前のプロトタイプ宣言に、以下の行を追加
    int phrCmpPutIcX86(int pid, String phr, int pc, int *pi, int lenExpr, void *sub, int *err);
  • [3]exprSub()関数内の記述を差し替え
    !   } else if (phrCmp(72, "mul64shr(!!**0, !!**1, !!**2)", epc)) {
    +       e0 = expr(0);
    +       e1 = expr(1);
    +       int e2 = expr(2);
    +       i = tmpAlloc();
    +       putIcX86("8b_%2m1; 8b_%0m0; f7_%1m5; 0f_ad_d0; 89_%3m0;", &var[e0], &var[e1], &var[e2], &var[i]);
    +       tmpFree(e2);
    +       if (e2 < 0) {
    +           e0 = -1;
    +       }
    !   } else if (phrCmpPutIcX86(73, "aRgb8(!!**0, !!**1, !!**2)",             epc, &i, 3, sub_aRgb8,    &e0)) {
    !   } else if (phrCmpPutIcX86(74, "aOpenWin(!!**0, !!**1, !!***2, !!***8)", epc, 0,  3, sub_OpnWin,   &e0)) {
    +       putIcX86("85_c0; 0f_85_%0l;", &var[toExit], 0, 0, 0);
            i = Tc0;
    !   } else if (phrCmpPutIcX86(75, "aXorShift32()",                          epc, &i, 0, sub_XorShift, &e0)) {
    !   } else if (phrCmpPutIcX86(76, "aGetPix(!!**8, !!**0, !!**1)",           epc, &i, 2, sub_aGetPix,  &e0)) {
    !   } else if (phrCmpPutIcX86(77, "ff16sin(!!**0)",                         epc, &i, 1, sub_f16Sin,   &e0)) {
    !   } else if (phrCmpPutIcX86(78, "ff16cos(!!**0)",                         epc, &i, 1, sub_f16Cos,   &e0)) {
    !   } else if (phrCmpPutIcX86(79, "aInkey(!!***8 , !!**0)",                 epc, &i, 1, sub_aInkey,   &e0)) {
  • [4]ifgoto()関数を以下の記述と交換
    int condCode[6] = { 0x84, 0x85, 0x8c, 0x8d, 0x8e, 0x8f }; [この行追加]
    
    void ifgoto(int i, int not, int label)
    {
        int j = wpc[i];
        if (j + 3 == wpc1[i] && TcEEq <= tc[j + 1] && tc[j + 1] <= TcGt) { // 条件式の長さが3トークンで、真ん中が比較演算子だったら.
    !       putIcX86("8b_%2m0; 3b_%3m0; 0f_%0c_%1l;", (IntP) condCode[(tc[j + 1] - TcEEq) ^ not], &var[label], &var[tc[j]], &var[tc[j + 2]]);
        } else {
            i = expr(i);
    !       putIcX86("8b_%2m0; 85_c0; 0f_%0c_%1l;", (IntP) (0x85 - not), &var[label], &var[i], 0);
            tmpFree(i);
        }
    }
  • [5]compile()関数に1行追加(1)
        tmpLabelNo = 0;
        bd = lbd = 0;
    +   toExit = tmpLabelAlloc();
        for (pc = 0; pc < pc1; ) { // コンパイル開始.
  • [6]compile()関数内の記述を差し替え(1)
             } else if (phrCmp( 5, "goto !!*0;", pc)) { // goto.
    !           putIcX86("e9_%0l;", &var[tc[wpc[0]]], 0, 0, 0);
  • [7]compile()関数内の記述を差し替え(2)
            } else if (phrCmp(12, "} else {", pc) && binf[bd] == BlkIf) {
                binf[bd + IfLabel1] = tmpLabelAlloc(); // else節の終端.
    !           putIcX86("e9_%0l;", &var[binf[bd + IfLabel1]], 0, 0, 0);
    !           defLabel(binf[bd + IfLabel0]);	// ラベルに対応するicqを記録しておく.
            } else if (phrCmp( 13, "}", pc) && binf[bd] == BlkIf) {
                if (binf[bd + IfLabel1] == 0) {
    !               defLabel(binf[bd + IfLabel0]);	// ラベルに対応するicqを記録しておく.
                } else {
    !               defLabel(binf[bd + IfLabel1]);	// ラベルに対応するicqを記録しておく.
                }
                bd -= BInfSiz;
            } else if (phrCmp(14, "for (!!***0; !!***1; !!***2) {", pc)) {	// for文
                bd += BInfSiz;
                binf[bd] = BlkFor; // ブロックのタイプ.
                binf[bd + ForLopBgn] = tmpLabelAlloc(); // ループの頭に戻る用.
                binf[bd + ForCont  ] = tmpLabelAlloc(); // continue用.
                binf[bd + ForBrk   ] = tmpLabelAlloc(); // break用.
                binf[bd + ForLbd0  ] = lbd; // 古い値を保存.
                binf[bd + ForWpc01 ] = wpc [1];
                binf[bd + ForWpc11 ] = wpc1[1];
                binf[bd + ForWpc02 ] = wpc [2];
                binf[bd + ForWpc12 ] = wpc1[2];
                lbd = bd;
                e0 = expr(0);
                if (wpc[1] < wpc1[1]) {	// !!***1に何らかの式が書いてあった.
                    ifgoto(1, IfFalse, binf[bd + ForBrk]); // 最初から条件不成立ならbreakへ.
                }
    !           defLabel(binf[bd + ForLopBgn]);	// ラベルに対応するicqを記録しておく.
            } else if (phrCmp(15, "}", pc) && binf[bd] == BlkFor) {
    !           defLabel(binf[bd + ForCont]);	// ラベルに対応するicqを記録しておく.
                i = binf[bd + ForWpc01];
                j = binf[bd + ForWpc02];
                if (i + 3 == binf[bd + ForWpc11] && j + 2 == binf[bd + ForWpc12] && tc[i] == tc[j] && tc[i + 1] == TcLt && tc[j + 1] == TcPlPlus) {
                    // !!***1が「i < ?」かつ、!!***2が「i++」だったら(変数名はiじゃなくてもいいけど、共通である必要がある).
    !               putIcX86("8b_%1m0; 40; 89_%1m0; 3b_%2m0; 0f_8c_%0l;", &var[binf[bd + ForLopBgn]], &var[tc[i]], &var[tc[i + 2]], 0);
                } else {
                    wpc [1] = binf[bd + ForWpc01];
                    wpc1[1] = binf[bd + ForWpc11];
                    wpc [2] = binf[bd + ForWpc02];
                    wpc1[2] = binf[bd + ForWpc12];
                    e2 = expr(2);
                    if (wpc[1] < wpc1[1]) {	// !!***1に何らかの式が書いてあった.
                        ifgoto(1, 0, binf[bd + ForLopBgn]);
                    } else {
    !                   putIcX86("e9_%0l;", &var[binf[bd + ForLopBgn]], 0, 0, 0);
                    }
                }
    !           defLabel(binf[bd + ForBrk]);	// ラベルに対応するicqを記録しておく.
                lbd = binf[bd+ ForLbd0]; // 以前の値を復元.
                bd -= BInfSiz;
            } else if (phrCmp(16, "continue;", pc) && lbd > 0) {
    !           putIcX86("e9_%0l;", &var[binf[lbd + ForCont]], 0, 0, 0);
            } else if (phrCmp(17, "break;", pc) && lbd > 0) {
    !           putIcX86("e9_%0l;", &var[binf[lbd + ForBrk ]], 0, 0, 0);
  • [8]compile()関数内の記述を差し替え(3)
    !       } else if (phrCmpPutIcX86(20, "prints !!**0;", pc, 0, 1, sub_prints, &e0)) { // prints.
  • [9]compile()関数内の記述を差し替え(4)
    !       } else if (phrCmpPutIcX86(23, "aSetPix0(!!***8, !!**0, !!**1, !!**2);",                       pc, 0, 3, sub_aSetPix0, &e0)) {
    !       } else if (phrCmpPutIcX86(24, "aWait(!!**0);",                                                pc, 0, 1, sub_aWait,    &e0)) {
    +           putIcX86("85_c0; 0f_85_%0l;", &var[toExit], 0, 0, 0); 
    !       } else if (phrCmpPutIcX86(25, "aFillRect0(!!***8, !!**0, !!**1, !!**2, !!**3, !!**4);",       pc, 0, 5, sub_aFilRct0, &e0)) {
    !       } else if (phrCmpPutIcX86(26, "aDrawStr0(!!***8, !!**0, !!**1, !!**2, !!**3, !!**4);",        pc, 0, 5, sub_aDrwStr0, &e0)) {
    !       } else if (phrCmpPutIcX86(27, "gprintDec(!!***8, !!**0, !!**1, !!**2, !!**3, !!**4, !!**5);", pc, 0, 6, sub_gprDec,   &e0)) {
    !       } else if (phrCmpPutIcX86(28, "bitblt(!!***8, !!**0, !!**1, !!**2, !!**3, !!**4);",           pc, 0, 5, sub_bitblt,   &e0)) {
    !       } else if (phrCmpPutIcX86(29, "printTime();",                                                 pc, 0, 0, sub_time,     &e0)) { // time;と同じ(C言語っぽく書けるようにした)
  • [10]compile()関数に1行追加(2)
        if (bd > 0) {
            printf("block nesting error (bd=%d, lbd=%d, pc=%d, pc1=%d\n", bd, lbd, pc, pc1);
            return -1;
        }
    !   defLabel(toExit);
        dump1 = icq;
        putIcX86("83_c4_7c; 61; c3;", 0, 0, 0, 0); // ADD(ESP,124); POPAD(); RET();
        icq1 = icq;
        for (i = 0; i < jp; i++) { // ジャンプ命令の最適化.
  • [11]run()関数に3行追加
    int run(String s)
    {
        if (compile(s) < 0)
            return 1;
        if (codedump == 0) {
            void (*func)() = (void (*)()) ic;
            t0 = clock();
            func();
    +       if (win != 0) {
    +           aFlushAll(win);
    +       }
        } else {
            int i, i1 = dump1 - dump0;
            for (i = 0; i < i1; i++) {
                printf("%02x ", dump0[i]);
            }
            printf("\n(len=%d)\n", i1);
        }
        return 0;
    }

  • 以上すべての改造を終えると、プログラムは827行になります。
  • これでa21_txt01_9aで紹介した、mandel.cやmaze.cが動くようになります。
  • 特にmandel.cはすごいです。HL-9aのころと比べて3.2倍くらい高速になっています。まだJITコンパイラの最適化をやっていないのに!・・・というか、JITコンパイラ化は最適化をしなくても、これくらいの高速化ができるのが普通なのです。HL-12の結果のほうが例外なんです。

(2) 今回新出の機械語

f7_%m/5EAXに%mで指定した値を掛け算して、結果をEDX:EAXにいれる。アセンブラではIMUL命令
0f_ad_d0EDX:EAXの64bitをECXだけ右シフトする。でもEAXしか更新されない(EDXはそのまま)。アセンブラではSHRD命令
0f_80_%l ~ 0f_8f_%l条件分岐命令。アセンブラではJcc命令
85_c0EAXを0と比較。アセンブラではTEST命令

(3) HL-9aとHL-13を比較してみる

  • これがあれば、何が何に対応しているかわかりやすいでしょう。そしてJITコンパイラが大したことないということもわかってもらえると思います。
    単純代入:
      (HL-9a) putIc   (OpCpy,               &var[tc[wpc[0]]], &var[tc[wpc[1]]], 0, 0);
      (HL-13) putIcX86("8b_%1m0; 89_%0m0;", &var[tc[wpc[0]]], &var[tc[wpc[1]]], 0, 0);
    
    print命令:
      (HL-9a) phrCmpPutIc   ( 4, "print !!**0;", pc, 0, 1, OpPrint,   &e0)
      (HL-13) phrCmpPutIcX86( 4, "print !!**0;", pc, 0, 1, sub_print, &e0)
    
    +1する命令:
      (HL-9a) putIc   (OpAdd1,                  &var[tc[wpc[0]]], 0, 0, 0);
      (HL-13) putIcX86("8b_%0m0; 40; 89_%0m0;", &var[tc[wpc[0]]], 0, 0, 0);
    
    各種二項演算子:
      いずれも基本形は以下の通り
        (HL-9a) putIc   (op, &var[tc[wpc[0]]], &var[tc[wpc[1]]], &var[tc[wpc[3]]], 0);
        (HL-13) putIcX86(op, &var[tc[wpc[0]]], &var[tc[wpc[1]]], &var[tc[wpc[3]]], 0);
    
      演算子 (HL-9a)  (HL-13)
       +      OpAdd    "8b_%1m0; 03_%2m0; 89_%0m0;"
       -      OpSub    "8b_%1m0; 2b_%2m0; 89_%0m0;"
       *      OpMul    "8b_%1m0; 0f_af_%2m0; 89_%0m0;"
       /      OpDiv    "8b_%1m0; 99; f7_%2m7; 89_%0m0;"
       %      OpMod    "8b_%1m0; 99; f7_%2m7; 89_%0m2;"
       &      OpAnd    "8b_%1m0; 23_%2m0; 89_%0m0;"
       >>     OpShr    "8b_%1m0; 8b_%2m1; d3_f8; 89_%0m0;"
       ==     OpCeq    "8b_%1m0; 3b_%2m0; 0f_94_c0; 83_e0_01; 89_%0m0;"
       !=     OpCne    "8b_%1m0; 3b_%2m0; 0f_95_c0; 83_e0_01; 89_%0m0;"
       <      OpClt    "8b_%1m0; 3b_%2m0; 0f_9c_c0; 83_e0_01; 89_%0m0;"
       >=     OpCge    "8b_%1m0; 3b_%2m0; 0f_9d_c0; 83_e0_01; 89_%0m0;"
       <=     OpCle    "8b_%1m0; 3b_%2m0; 0f_9e_c0; 83_e0_01; 89_%0m0;"
       >      OpCgt    "8b_%1m0; 3b_%2m0; 0f_9f_c0; 83_e0_01; 89_%0m0;"
    
    ループ命令:
      (HL-9a) putIc   (OpLop,                                       &var[tc[wpc[4]]], &var[tc[wpc[0]]], &var[tc[wpc[3]]], 0);
      (HL-13) putIcX86("8b_%1m0; 40; 89_%1m0; 3b_%2m0; 0f_8c_%0l;", &var[tc[wpc[4]]], &var[tc[wpc[0]]], &var[tc[wpc[3]]], 0);
  • (未完成)

次回に続く

こめんと欄


コメントお名前NameLink

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2021-04-20 (火) 00:13:54 (1096d)