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

  • (by K, 2021.04.20)

(1) HL-13a

  • HL-13で保留にしてきた配列の機能をJITコンパラ対応させます。これでkcube.cやinvader.cも動くようになるはずです。
  • そして、もうHL-9aの機能の中でやり残したことはなくなるので、ダミーで残してあるputIc()関数やphrCmpPutIc()関数を削除します。

  • [1]sub_bitblt()関数の宣言の下に以下を追記
    AInt *sub_aryNew(int n)
    {
        AInt *p = malloc(n * sizeof (AInt));
        memset((char *) p, 0, n * sizeof (AInt));
        return p;
    }
    
    void sub_aryInit(AInt *a, AInt *ip, int n)
    {
        memcpy((char *) a, (char *) ip, n * sizeof (AInt));
    }
  • [2]exprSub()関数の一部を書き換え
            } else if (phrCmp(70, "[!!**0]=", epc) && priority >= 15) {
                e1 = i;
                e0 = expr(0);
                epc = ppc1;
                i = exprSub(15);
    !           putIcX86("8b_%2m0; 8b_%0m2; 8b_%1m1; 89_04_8a;", &var[e1], &var[e0], &var[i], 0);
            } else if (phrCmp(71, "[!!**0]", epc)) {
                e1 = i; 
                i = tmpAlloc();
                e0 = expr(0);
    !           putIcX86("8b_%0m2; 8b_%1m1; 8b_04_8a; 89_%2m0;", &var[e1], &var[e0], &var[i], 0);
                epc = ppc1;
  • [3]compile()関数の一部を書き換え
            } else if (phrCmp(21, "int !!*0[!!**2];", pc)) {
                e2 = expr(2);
    !           putIcX86("8b_%1m0; 89_44_24_00; e8_%2r; 89_%0m0;", &var[tc[wpc[0]]], &var[e2], (IntP) sub_aryNew, 0);
            } else if (phrCmp(22, "int !!*0[!!**2] = {", pc)) {
                e2 = expr(2);
    !           putIcX86("8b_%1m0; 89_44_24_00; e8_%2r; 89_%0m0;", &var[tc[wpc[0]]], &var[e2], (IntP) sub_aryNew, 0);
                j = 0;
                for (i = ppc1; i < pc1; i++) {	// コンマ以外のトークンを数える.
                    if (tc[i] == TcCrBrCls) break;
                        if (tc[i] != TcComma) {
                            j++;
                        }
                    }
                    if (i >= pc1) goto err;
                    AInt *ip = malloc(j * sizeof (AInt));
                    j = 0;
                    for (i = ppc1; tc[i] != TcCrBrCls; i++) {
                        if (tc[i] == TcCrBrCls) break;
                        if (tc[i] != TcComma) {
                        ip[j] = var[tc[i]];
                        j++;
                    }
                }
    !           putIcX86("8b_%0m0; 89_44_24_00; b8_%1i; 89_44_24_04; b8_%2i; 89_44_24_08; e8_%3r;", &var[tc[wpc[0]]], (IntP) ip, (IntP) j, (IntP) sub_aryInit);
                ppc1 = i + 2; // } と ; の分.
  • [4]以下の記述は不要なので削除(1)
    enum { OpCpy = 0, OpCeq, OpCne, OpClt, OpCge, OpCle, OpCgt, OpAdd, OpSub, OpMul, OpDiv, OpMod, OpAnd, OpShr, 
        OpAdd1, OpNeg, OpGoto, OpJeq, OpJne, OpJlt, OpJge, OpJle, OpJgt, OpLop, OpPrint, OpTime, OpEnd,
        OpPrints, OpAryNew, OpAryInit, OpArySet, OpAryGet, OpOpnWin, OpSetPix0, OpM64s, OpRgb8, OpWait,
        OpXorShift, OpGetPix, OpFilRct0, OpPrm, OpF16Sin, OpF16Cos, OpInkey, OpDrwStr0, OpGprDec, OpBitBlt };
  • [5]以下の記述は不要なので削除(2)
    void putIc(int op, IntP p0, IntP p1, IntP p2, IntP p3)  // 移行中の間だけ、以下の形で残しておく.
    {
        printf("putIc: error\n");
        exit(1);
    }
  • [6]以下の記述は不要なので削除(3)
    int phrCmpPutIc(int pid, String phr, int pc, int *pi, int lenExpr, int op, int *err)  // 移行中の間だけ、以下の形で残しておく.
    {
        if (phrCmp(pid, phr, pc)) {
            printf("phrCmpPutIc: error\n");
            exit(1);
        }
        return 0;
    }

  • 以上すべての改造を終えると、プログラムは819行になります。
  • これでkcube.cやinvader.cが何の問題もなく動きます。

(2) 新出機械語の説明

  • 配列へ代入した場合の機械語は次のようになっています。
    • 以下、 a[i] = x; と書いた場合で説明します。
      putIcX86("8b_%2m0; 8b_%0m2; 8b_%1m1; 89_04_8a;", &var[e1], &var[e0], &var[i], 0);
      
      8b_%2m0   EAX = [x]; // EAXはレジスタ番号0
      8b_%0m2   EDX = [a]; // EDXはレジスタ番号2
      8b_%1m1   ECX = [i]; // ECXはレジスタ番号1
      89_04_8a  [EDX+ECX*4] = EAX;
  • 配列から読み込む場合の機械語は次のようになっています。
    • 以下、 x = a[i]; に相当する処理の場合で説明します。
      putIcX86("8b_%0m2; 8b_%1m1; 8b_04_8a; 89_%2m0;", &var[e1], &var[e0], &var[i], 0);
      
      8b_%0m2   EDX = [a]; // EDXはレジスタ番号2
      8b_%1m1   ECX = [i]; // ECXはレジスタ番号1
      8b_04_8a  EAX = [EDX+ECX*4];
      89_%2m0   [x] = EAX; // EAXはレジスタ番号0

(3) ここまでのまとめ(HL-9aとHL-13aを比較する)

  • ということで、とりあえずHL-9aのJITコンパイラ対応は一通りできたことになります。
  • もちろん現状では最適化をサボりまくっているので、十分な速度は出ていません。しかしそれでも例外なくHL-9aよりは速くなっています。
  • 結局この2つの違いはそれほど大きくはなくて、putIc()で内部コードを出力するか、それともputIcX86()でx86の機械語を出すかの違いでしかありません。行数は48行しか増えていません。実行ファイルサイズでもたったの1.0KBしか増えていません。
  • 機械語の知識がなければ自力で作るのは非常に難しいです。そこが唯一にして最大のハードルでしょう。でもこれは知っているかどうかであって、アルゴリズムが難しいとかそういうのじゃないです。
  • 単純にJITコンパイラ化するだけだと、最適化なしではあまり速くはなりません。しかしJITコンパイラ化しないと、速度の限界はすぐに来ます。JITコンパイラにしてしまえば、最適化を入れていくことで、どんどんスピードアップする余地ができるのです。

次回に続く

こめんと欄


コメントお名前NameLink

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