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

  • (by K, 2021.05.08)

(1) HL-17a

  • HL-17では、JITコンパイラ化のための下準備がメインで、命令としては単純代入とprint命令だけ対応しました。
  • さすがにそれだけではさみしすぎるので、普通の演算子を全部JITコンパイラ化しようと思います。
  • 改造の方針としては、いくつかのputIc()をputIcX64()に書き換えていくだけです。
#include <acl.c>

typedef unsigned char *String;	// こう書くと String は unsigned char * の代用になる.

int loadText(String path, String t, int siz) → HL-4と同じなので省略

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

#define MAX_TC  1000 // トークンコードの最大値.
String ts[MAX_TC + 1]; // トークンの内容(文字列)を記憶.
int tl[MAX_TC + 1]; // トークンの長さ.
unsigned char tcBuf[(MAX_TC + 1) * 10]; // トークン1つ当たり平均10バイトを想定.
int tcs = 0, tcb = 0;

AInt var[MAX_TC + 1];	// 変数.

int getTc(String s, int len) → HL-8aと同じなので省略

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

int isAlphabetOrNumber(unsigned char c) → HL-2と同じなので省略

int lexer(String s, int tc[]) → HL-9aと同じなので省略

int tc[10000];	// トークンコード.

enum { TcSemi = 0, TcDot, TcWiCard, Tc0, Tc1, Tc2, Tc3, Tc4, Tc5, Tc6, Tc7, Tc8, TcBrOpn, TcBrCls, TcSqBrOpn, TcSqBrCls, TcCrBrOpn, TcCrBrCls,
    TcEEq, TcNEq, TcLt, TcGe, TcLe, TcGt, TcPlus, TcMinus, TcAster, TcSlash, TcPerce, TcAnd, TcShr, TcPlPlus, TcEqu,
    TcComma, TcExpr, TcExpr0, TcTmp0, TcTmp1, TcTmp2, TcTmp3, TcTmp4, TcTmp5, TcTmp6, TcTmp7, TcTmp8, TcTmp9,
    TcSubPrint, TcSubTime, TcSubRgb8, TcSubXorShift, TcSubGetPix, TcSubF16Sin, TcSubF16Cos, TcSubInkey, TcSubPrints, TcSubSetPix0,
    TcSubFilRct0, TcSubDrwStr0, TcSubGprDec, TcSubOpnWin, TcSubWait, TcSubBitBlt, TcSubAryNew, TcSubAryInit };

char tcInit[] = "; . !!* 0 1 2 3 4 5 6 7 8 ( ) [ ] { } == != < >= <= > + - * / % & >> ++ = , !!** !!*** _t0 _t1 _t2 _t3 _t4 _t5 _t6 _t7 _t8 _t9 "
    "sub_print sub_time sub_aRgb8 sub_xorShift sub_aGetPix sub_f16Sin sub_f16Cos sub_aInkey sub_prints sub_aSetPix0 "
    "sub_aFilRct0 sub_aDrwStr0 sub_gprDec sub_opnWin sub_aWait sub_bitblt sub_aryNew sub_aryInit";

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

int phrCmp_tc[32 * 100], ppc1, wpc[9], wpc1[9]; // ppc1:一致したフレーズの次のトークンをさす, wpc[]:ワイルドカードのトークンの場所をさす.

int phrCmp(int pid, String phr, int pc) → HL-7と同じなので省略

/////////////////////////////////////////////////////////////////////////////// → ここまではHL-17と全く同じ、ここから改造が始まる.

#define ABI_MSW64   1 // Windows.
#define ABI_SYSV64  0 // Linuxなど.

typedef AInt *IntP; // こう書くと IntP は AInt * の代わりに使えるようになる.

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 };

String opBin[] = {  // 二項演算子のための機械語.
    "%R_8b_%1m0; %R_3b_%2m0; 0f_94_c0; 0f_b6_c0; %R_89_%0m0;",  // TcEEq   ==
    "%R_8b_%1m0; %R_3b_%2m0; 0f_95_c0; 0f_b6_c0; %R_89_%0m0;",  // TcNEq   !=
    "%R_8b_%1m0; %R_3b_%2m0; 0f_9c_c0; 0f_b6_c0; %R_89_%0m0;",  // TcLt    <
    "%R_8b_%1m0; %R_3b_%2m0; 0f_9d_c0; 0f_b6_c0; %R_89_%0m0;",  // TcGe    >=
    "%R_8b_%1m0; %R_3b_%2m0; 0f_9e_c0; 0f_b6_c0; %R_89_%0m0;",  // TcLe    <=
    "%R_8b_%1m0; %R_3b_%2m0; 0f_9f_c0; 0f_b6_c0; %R_89_%0m0;",  // TcGt    >
    "%R_8b_%1m0; %R_03_%2m0; %R_89_%0m0;",                      // TcPlus  +
    "%R_8b_%1m0; %R_2b_%2m0; %R_89_%0m0;",                      // TcMinus -
    "%R_8b_%1m0; %R_0f_af_%2m0; %R_89_%0m0;",                   // TcAster *
    "%R_8b_%1m0; %R_99; %R_f7_%2m7; %R_89_%0m0;",               // TcSlash /
    "%R_8b_%1m0; %R_99; %R_f7_%2m7; %R_89_%0m2;",               // TcPerce %
    "%R_8b_%1m0; %R_23_%2m0; %R_89_%0m0;",                      // TcAnd   &
    "%R_8b_%1m0; %R_8b_%2m1; %R_d3_f8; %R_89_%0m0;"             // TcShr   >>
};

unsigned char *ic, *icq; // ic[]:内部コード、icq:ic[]への書き込み用ポインタ.

void putIc(int op, IntP p0, IntP p1, IntP p2, IntP p3) → HL-17と同じなので省略

int getHex(int c) → HL-17と同じなので省略

int get32(unsigned char *p) → HL-17と同じなので省略

void put32(unsigned char *p, int i) → HL-17と同じなので省略

void putIcX86_sub(String s, IntP a[]) → HL-17と同じなので省略

void putIcX86(String s, IntP p0, IntP p1, IntP p2, IntP p3) → HL-17と同じなので省略

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

char tmp_flag[10]; // 一時変数の利用状況を管理.

int tmpAlloc() → HL-7と同じなので省略

void tmpFree(int i) → HL-7と同じなので省略

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

void sub_print(int i) → HL-17と同じなので省略

void initTcSub() → HL-17と同じなので省略

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

int epc, epc1;	// exprのためのpcとpc1.

int exprSub(int priority);	// exprSub1()が参照するので、プロトタイプ宣言.
int expr(int j);
int phrCmpPutIc(int pid, String phr, int pc, int *pi, int lenExpr, int op, int *err);
int phrCmpPutIcX64(int pid, String phr, int pc, int *pi, int lenExpr, void *sub, int *err); // この行を追加.

int exprSub1(int i, int priority, int op)	// 二項演算子の処理の標準形.
{
    int j, k;
    epc++;
    j = exprSub(priority);
    k = tmpAlloc();
!   putIcX64(opBin[op - TcEEq], &var[k], &var[i], &var[j], 0);
    tmpFree(i);
    tmpFree(j);
    if (i < 0 || j < 0) return -1;
    return k;
}

int exprSub(int priority)
{
    int i = -1, e0 = 0, e1 = 0;
    ppc1 = 0;
    if (phrCmp(99, "( !!**0 )", epc)) {	// かっこ.
        i = expr(0);
    } else if (tc[epc] == TcPlPlus) {	// 前置インクリメント.
        epc++;
        i = exprSub(2);
!       putIcX64("%R_8b_%0m0; %R_ff_c0; %R_89_%0m0;", &var[i], 0, 0, 0);
    } else if (tc[epc] == TcMinus) {	// 単項マイナス.
        epc++;
        e0 = exprSub(2);
        i = tmpAlloc();
!       putIcX64("%R_8b_%1m0; %R_f7_d8; %R_89_%0m0;", &var[i], &var[e0], 0, 0);
    } else if (phrCmpPutIc(72, "mul64shr(!!**1, !!**2, !!**3)",          epc, &i, 4, OpM64s,     &e0)) {
    } else if (phrCmpPutIc(73, "aRgb8(!!**1, !!**2, !!**3)",             epc, &i, 4, OpRgb8,     &e0)) {
    } else if (phrCmpPutIc(74, "aOpenWin(!!**0, !!**1, !!***2, !!***8)", epc, 0,  3, OpOpnWin,   &e0)) {
        i = Tc0;
    } else if (phrCmpPutIc(75, "aXorShift32()",                          epc, &i, 1, OpXorShift, &e0)) {
    } else if (phrCmpPutIc(76, "aGetPix(!!**8, !!**1, !!**2)",           epc, &i, 3, OpGetPix,   &e0)) {
    } else if (phrCmpPutIc(77, "ff16sin(!!**1)",                         epc, &i, 2, OpF16Sin,   &e0)) {
    } else if (phrCmpPutIc(78, "ff16cos(!!**1)",                         epc, &i, 2, OpF16Cos,   &e0)) {
    } else if (phrCmpPutIc(79, "aInkey(!!***8 , !!**1)",                 epc, &i, 2, OpInkey,    &e0)) {
    } else {		// 変数もしくは定数.
        i = tc[epc];
        epc++;
    }
    if (ppc1 > 0)
        epc = ppc1;
    for (;;) {
        tmpFree(e0);
        tmpFree(e1);
        if (i < 0 || e0 < 0 || e1 < 0) return -1;	// ここまででエラーがあれば、処理を打ち切り.
        if (epc >= epc1) break;
        e0 = e1 = 0;
        if (tc[epc] == TcPlPlus) {		// 後置インクリメント.
            epc++;
            e0 = i;
            i = tmpAlloc();
!           putIcX64("%R_8b_%1m0; %R_89_%0m0; %R_ff_c0; %R_89_%1m0;", &var[i], &var[e0], 0, 0);
        } else if (phrCmp(70, "[!!**0]=", epc) && priority >= 15) {
            e1 = i;
            e0 = expr(0);
            epc = ppc1;
            i = exprSub(15);
            putIc(OpArySet, &var[e1], &var[e0], &var[i], 0);
        } else if (phrCmp(71, "[!!**0]", epc)) {
            e1 = i; 
            i = tmpAlloc();
            e0 = expr(0);
            putIc(OpAryGet, &var[e1], &var[e0], &var[i], 0);
            epc = ppc1;
        } else if (TcAster <= tc[epc] && tc[epc] <= TcPerce && priority >= 4) {	// * / %
!           i = exprSub1(i, 3, tc[epc]); // 左結合なので4より1小さくする.
        } else if (TcPlus  <= tc[epc] && tc[epc] <= TcMinus && priority >= 5) {
!           i = exprSub1(i, 4, tc[epc]); // 左結合なので5より1小さくする.
        } else if (tc[epc] == TcShr && priority >= 6) {
!           i = exprSub1(i, 5, TcShr); // 左結合なので6より1小さくする.
        } else if (TcLt    <= tc[epc] && tc[epc] <= TcGt    && priority >= 7) {
!           i = exprSub1(i, 6, tc[epc]); // 左結合なので7より1小さくする.
        } else if (TcEEq   <= tc[epc] && tc[epc] <= TcNEq   && priority >= 8) {
!           i = exprSub1(i, 7, tc[epc]); // 左結合なので8より1小さくする.
        } else if (tc[epc] == TcAnd && priority >= 9) {
!           i = exprSub1(i, 8, TcAnd); // 左結合なので9より1小さくする.
        } else if (tc[epc] == TcEqu && priority >= 15) {
            epc++;
            e0 = exprSub(15);	// 右結合なので15のまま.
!           putIcX64("%R_8b_%1m0; %R_89_%0m0;", &var[i], &var[e0], 0, 0);
        } else
            break;
    }
    return i;
}

int expr(int j) → HL-7と同じなので省略

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

(中略)

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

int compile(String s)
{
    int pc, pc1, i, j;
    unsigned char *icq1, *icp;
    pc1 = lexer(s, tc);
    tc[pc1++] = TcSemi;	// 末尾に「;」を付け忘れることが多いので、付けてあげる.
    tc[pc1] = tc[pc1 + 1] = tc[pc1 + 2] = tc[pc1 + 3] = TcDot;	// エラー表示用のために末尾にピリオドを登録しておく.
    icq = ic;
    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);
    for (i = 0; i < 10; i++) {
        tmp_flag[i] = 0;
    }
    tmpLabelNo = 0;
    bd = lbd = 0;
    for (pc = 0; pc < pc1; ) { // コンパイル開始.
        int e0 = 0, e2 = 0;
        if (phrCmp( 1, "!!*0 = !!*1;", pc)) { // 単純代入.
            putIcX86("%R_8b_%1m0; %R_89_%0m0;", &var[tc[wpc[0]]], &var[tc[wpc[1]]], 0, 0);
        } else if (phrCmp(10, "!!*0 = !!*1 + 1; if (!!*2 < !!*3) goto !!*4;", pc) && tc[wpc[0]] == tc[wpc[1]] && tc[wpc[0]] == tc[wpc[2]]) {
            putIc(OpLop, &var[tc[wpc[4]]], &var[tc[wpc[0]]], &var[tc[wpc[3]]], 0);
        } else if (phrCmp( 9, "!!*0 = !!*1 + 1;", pc) && tc[wpc[0]] == tc[wpc[1]]) {  // 高速化のために+1専用の命令を用意(せこくてすみません).
!           putIcX64("%R_8b_%0m0; %R_ff_c0; %R_89_%0m0;", &var[tc[wpc[0]]], 0, 0, 0);
        } else if (phrCmp( 2, "!!*0 = !!*1 !!*2 !!*3;", pc) && TcEEq <= tc[wpc[2]] && tc[wpc[2]] <= TcShr) {  // 加算, 減算など.
!           putIcX64(opBin[tc[wpc[2]] - TcEEq], &var[tc[wpc[0]]], &var[tc[wpc[1]]], &var[tc[wpc[3]]], 0);
        } else if (phrCmpPutIcX86(4, "print !!**0;", pc, 0, 1, sub_print, &e0)) { 
        (中略)
}

int run(String s) → HL-17と同じなので省略

// 以下のmallocRWX()はWindows用の記述.

#include <windows.h>

void *mallocRWX(int siz) → HL-17と同じなので省略

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

void aMain() → HL-17と同じなので省略
  • プログラムは756行になりました。そして基本的な計算機能はすべて復活しました。
    >print (1+2)*3
    9
  • もちろん他にも変数を使った演算なども自由にできますが、例を書いているときりがないので、ここでは省略します。

(2) 機械語の説明

  • 8bや89については、もうすでに説明したので、ここでは説明を省略します。
  • 表の中の%mは、mod r/mバイトを表しています。
    %R_3b_%m比較命令。大小を比べる際には、まずこの命令を使って、どれとどれを比較するのかを明示する。アセンブラではCMP命令
    0f_94_c0 ~ 0f_9f_c0比較結果に応じて、RAXレジスタの下位8bitを0x00(偽)か0x01(真)にする。アセンブラではSETcc命令
    0f_b6_c0RAXの上位56bitを0クリアする。アセンブラではMOVZX命令
    %R_03_%m加算命令。アセンブラではADD命令
    %R_2b_%m減算命令。アセンブラではSUB命令
    %R_0f_af_%m符号付き乗算命令。アセンブラではIMUL命令
    %R_99RAXをRDX:RAXに符号拡張する(IDIVの直前によく用いられる)。アセンブラではCDQ命令
    %R_f7_%m/7符号付き除算・剰余演算命令。商はRAX、剰余がRDXに入る。アセンブラではIDIV命令
    %R_23_%mAND計算(ビット演算)。アセンブラではAND命令
    %R_d3_f8RAX = RAX >> RCX; を計算。アセンブラではSAR命令
    %R_ff_c0RAX++; を計算。アセンブラではINC命令
    %R_f7_d8RAX = - RAX; を計算。アセンブラではNEG命令
    • 除算命令のところの「/7」の意味は、mod r/mバイトの中の第二オペランドに、演算対象のレジスタ番号を書くのではなく、そこは常に定数7を指定しなさい、という意味です。
    • 除算命令は、割られる数は EDX:EAX で固定なので、mod r/m の第二オペランドでレジスタ番号を書く必要がないのです。そのかわりに、第二オペランドの部分も命令番号の一部にすることで、命令長の削減を狙っていると思われます。
    • 参考までに、命令f7の/0~/7を全部書いておきます。
      %R_f7_%m/0%R_f7_%m/1%R_f7_%m/2%R_f7_%m/3%R_f7_%m/4%R_f7_%m/5%R_f7_%m/6%R_f7_%m/7
      TEST使用しないNOTNEGMULIMULDIVIDIV
    • 類似のテクニックは命令83や命令d3でも見られます。

次回に続く

こめんと欄


コメントお名前NameLink

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2021-05-08 (土) 02:19:49 (169d)