* 「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("8b_%1m0; 89_%0m0;", &var[tc[wpc[0]]], &var[tc[wpc[1]]], 0, 0);
             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_c0|RAXの上位56bitを0クリアする。|アセンブラではMOVZX命令|
|%R_03_%m|加算命令。|アセンブラではADD命令|
|%R_2b_%m|減算命令。|アセンブラではSUB命令|
|%R_0f_af_%m|符号付き乗算命令。|アセンブラではIMUL命令|
|%R_99|RAXをRDX:RAXに符号拡張する(IDIVの直前によく用いられる)。|アセンブラではCDQ命令|
|%R_f7_%m/7|符号付き除算・剰余演算命令。商はRAX、剰余がRDXに入る。|アセンブラではIDIV命令|
|%R_23_%m|AND計算(ビット演算)。|アセンブラではAND命令|
|%R_d3_f8|RAX = RAX >> RCX; を計算。|アセンブラではSAR命令|
|%R_ff_c0|RAX++; を計算。|アセンブラではINC命令|
|%R_f7_d8|RAX = - 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|使用しない|NOT|NEG|MUL|IMUL|DIV|IDIV|
--類似のテクニックは命令83や命令d3でも見られます。


** 次回に続く
-次回: [[a21_txt02_8]]

*こめんと欄
#comment

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS