「10日くらいでできる!プログラミング言語自作入門」の続編#1-6b (旧版)
(1) HL-16b
- HL-16aまでで、JITコンパイラの作り方の説明は一通りできたと思っています。だから次は、JITではない普通のコンパイラの作り方をやりたいと思います。
- 普通のコンパイラなら、JITコンパイルした機械語を出力すればいいわけなのですが、世間的にコンパイラとして期待されるのは、機械語の出力ではなくてアセンブラ用のソースコードを出力することのようです。ただ機械語を出すだけでよければ、「codedump 1」でほぼできたようなものだったのですが、しょうがないのでもうちょっと作りこむことにします。
- さてアセンブラを出力するといっても、アセンブラごとに多少の方言があるので、どれにするか決めなければいけません。今回は私の趣味で、 NASM 用の形式で出力させることにしました。ということで、 NASM のインストール方法を紹介します。
- 次に考えたのは開発方針です。普通に考えれば、putIcX86()を改造してputIcNasm86()を作るのがよさそうなのですが、それは今から作るにはちょっと面倒そうです。それで、まあ、すごく変わっているのですが、putIcX86()はそのままにして、生成した機械語を逆アセンブラでアセンブラソースコードに変換するという方法で、目的を達成しようと思います。
- 普通のコンパイラの世界では、コンパイラがアセンブラのソースコード生成してそれをアセンブルして機械語に変換するものですが、HL-16bでは、機械語を生成してからアセンブラのソースコードを作り直すというわけです。
- まあ結局、こんな変わった作り方をする人は少ないと思うので、これはやってみたら面白いかも?って思ったから、これにしただけです。たぶん素直にputIcNasm86()を作って全体を再構成するほうが総行数は少なくなるはずです。
- この変な作り方のおかげで、HL-16bは普通のコンパイラとして使えますが、HL-16aまでのJITコンパイラとしても使えるようになっています。
- ということで、「codedump 2」にしたらアセンブラ用のソースコードが出力されます。変数codedumpの初期値を2にしておけば、
prompt>hl16b mandel.c
とするだけでいきなりソースコードが出てくるようになるので、すごく普通のコンパイラっぽくなります。コマンドライン引数を指定しないで起動すれば、従来通りプロンプトが出るので、そこで「codedump 0」を実行すれば、HL-16aと同じになります。
- [1]関数compile()を以下のように改造
int aryInitList[100], ailp; // この行追加.
char varOpt[MAX_TC + 1]; // この行追加.
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 = icq0 = ic;
! jp = ailp = 0;
icq1 = icqSet = 0;
putIcX86("60; 83_ec_7c;", 0, 0, 0, 0); // PUSHAD(); SUB(ESP,124);
regVarSaveLoad(RvLoad);
dump0 = icq;
for (i = 0; i < 10; i++) {
tmp_flag[i] = 0;
}
tmpLabelNo = 0;
for (i = 0; i < MAX_TC + 1; i++) {
vc[i] = 0;
! varOpt[i] = 0;
}
bd = lbd = 0;
toExit = tmpLabelAlloc();
for (pc = 0; pc < pc1; ) { // コンパイル開始.
(中略)
} else if (phrCmp( 0, "!!*0:", pc)) { // ラベル定義命令.
defLabel(tc[wpc[0]]); // ラベルに対応するicqを記録しておく.
+ varOpt[tc[wpc[0]]] |= 1;
} else if (phrCmp( 5, "goto !!*0;", pc)) { // goto.
(中略)
} 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++;
}
}
+ aryInitList[ailp] = icq - ic; // sub_aryInitの呼び出し位置を記憶しておく.
+ ailp++;
! putIcX86("89_44_24_00; b8_%0i; 89_44_24_04; b8_%1i; 89_44_24_08; e8_%2r;", (IntP) ip, (IntP) j, (IntP) sub_aryInit, 0);
ppc1 = i + 2; // } と ; の分.
} else if (phrCmpPutIcX86(23, "aSetPix0(!!***8, !!**0, !!**1, !!**2);", pc, 0, 3, sub_aSetPix0, &e0)) {
(中略)
}
(中略)
for (i = 0; i < jp; i++) { // ジャンプ命令の最適化.
(中略)
}
+ if (codedump != 2) {
for (i = 0; i < jp; i++) { // ジャンプ先の設定(相対値にする).
icq = jmps[i] + ic;
j = *(IntP) get32(icq); // ラベル番号を取得.
icp = j + ic; // icp=飛び先の命令列の先頭.
put32(icq, icp - (icq + 4));
}
+ }
return icq1 - ic;
(中略)
}
- [2]関数run()の直前に以下の関数群を追加
String regNamX86[8] = { "EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI" };
int modRmX86(unsigned char *p, char *s)
{
int c0 = *p;
if ((c0 & 0xc0) == 0xc0) {
strcpy(s, regNamX86[c0 & 7]);
return 1;
}
if ((c0 & 0xc7) == 0x05) {
sprintf(s, "DWORD [.%s]", ts[(IntP) get32(p + 1) - var]);
return 5;
}
if ((c0 & 0xc7) == 0x04 && p[1] == 0x8a) {
sprintf(s, "DWORD [EDX+ECX*4]");
return 2;
}
if ((c0 & 0xc7) == 0x44 && p[1] == 0x24) {
sprintf(s, "DWORD [ESP+%d]", p[2]);
return 3;
}
printf("modRmX86: error: %02x\n", c0);
exit(1);
}
String asmX86_sub1(int q, String p0, String s, String p1, String t)
// (q) 1:modRmの直前の1バイトの下位3bitがレジスタを指定, 2:modRm内の中間3bitがレジスタを指定, 3:modRmを解釈した結果.
// 4:8bit即値, 5:32bit即値, 6:CL, 7:sub関数呼び出し, 8:JMP/Jccの分岐先ラベル.
{
if (q == 1) { return regNamX86[p0[-1] & 7]; }
if (q == 2) { return regNamX86[(p0[0] >> 3) & 7]; }
if (q == 3) { return s; }
if (q == 4) { sprintf(t, "%d", *((signed char *) p1)); return t; }
if (q == 6) { return "CL"; }
if (q == 8) { sprintf(t, ".%s", ts[((IntP) get32(p1)) - var]); return t; }
if (q == 5) {
int i = get32(p1), j;
sprintf(t, "%d", i);
for (j = 0; j < tcs; j++) {
if (var[j] == i && ts[j][0] == 34) {
sprintf(t, ".str%d", i);
varOpt[j] |= 2;
}
}
return t;
}
if (q == 7) {
void *sub = p1 + 4 + get32(p1);
if (sub == sub_print) { return "_sub_print"; }
if (sub == sub_time) { return "_sub_time"; }
if (sub == sub_aRgb8) { return "_sub_aRgb8"; }
if (sub == sub_XorShift) { return "_sub_XorShift"; }
if (sub == sub_aGetPix) { return "_sub_aGetPix"; }
if (sub == sub_f16Sin) { return "_sub_f16Sin"; }
if (sub == sub_f16Cos) { return "_sub_f16Cos"; }
if (sub == sub_aInkey) { return "_sub_aInkey"; }
if (sub == sub_prints) { return "_sub_prints"; }
if (sub == sub_aSetPix0) { return "_sub_aSetPix0"; }
if (sub == sub_aFilRct0) { return "_sub_aFilRct0"; }
if (sub == sub_aDrwStr0) { return "_sub_aDrwStr0"; }
if (sub == sub_gprDec) { return "_sub_gprDec"; }
if (sub == sub_OpnWin) { return "_sub_OpnWin"; }
if (sub == sub_aWait) { return "_sub_aWait"; }
if (sub == sub_bitblt) { return "_sub_bitblt"; }
if (sub == sub_aryNew) { return "_sub_aryNew"; }
if (sub == sub_aryInit) { return "_sub_aryInit"; }
}
return "?";
}
int asmX86_sub0(unsigned char *p, String op, int modRm, int q0, int q1, int q2)
// modRm: mod r/m があるかないか.
{
char s[100], t[100];
int len = 0;
String p0 = p;
if (modRm != 0) {
len = modRmX86(p, s);
p += len;
}
printf(" %-8s", op);
if (q0 > 0) {
printf("%s", asmX86_sub1(q0, p0, s, p, t));
if (q1 > 0) {
printf(",%s", asmX86_sub1(q1, p0, s, p, t));
if (q2 > 0) {
printf(",%s", asmX86_sub1(q2, p0, s, p, t));
}
}
}
printf("\n");
return len;
}
String asmX86_grp0[8] = { "ADD", "OR", "ADC", "SBB", "AND", "SUB", "XOR", "CMP" };
String asmX86_jcc[16] = { "JO", "JNO", "JB", "JAE", "JE", "JNE", "JBE", "JA", "JS", "JNS", "JP", "JNP", "JL", "JGE", "JLE", "JG" };
String asmX86_shft[8] = { "ROL", "ROR", "RCL", "RCR", "SHL", "SHR", "?", "SAR" };
void asmX86()
{
int i, j, k = 0;
if (dump0 == dump1) return;
printf("BITS 32\n");
printf("SECTION .text\n");
printf("GLOBAL _aMain\n");
printf(" ALIGNB 16\n");
printf("_aMain:\n");
for (i = 0; i < jp; i++) {
icq = jmps[i] + ic;
j = ((IntP) get32(icq)) - var;
varOpt[j] |= 1; // 利用したラベルをマークする.
}
for (i = 0;;) {
int c0 = ic[i], c1 = ic[i + 1];
for (j = 0; j < tcs; j++) {
if (var[j] == (AInt) i && (varOpt[j] & 1) != 0) {
printf(".%s:\n", ts[j]); // ラベル定義出力.
}
}
if (k < ailp && aryInitList[k] + 4 == i) {
printf(" MOV EAX,.ary%d\n", get32(&ic[i + 1]));
i += 5;
k++;
continue;
}
if (c0 == 0x0f) {
if (0x80 <= c1 && c1 <= 0x8f) { i += asmX86_sub0(&ic[i + 2], asmX86_jcc[c1 & 15], 0, 8, 0, 0) + 6; continue; }
if (0x90 <= c1 && c1 <= 0x9f) {
printf(" SET%-5sAL\n", asmX86_jcc[c1 & 15] + 1);
printf(" MOVZX EAX,AL\n");
i += 6;
continue;
}
if (c1 == 0xac) { i += asmX86_sub0(&ic[i + 2], "SHRD", 1, 3, 2, 4) + 3; continue; }
if (c1 == 0xad) { i += asmX86_sub0(&ic[i + 2], "SHRD", 1, 3, 2, 6) + 2; continue; }
if (c1 == 0xaf) { i += asmX86_sub0(&ic[i + 2], "IMUL", 1, 2, 3, 0) + 2; continue; }
}
if (c0 == 0x03) { i += asmX86_sub0(&ic[i + 1], "ADD", 1, 2, 3, 0) + 1; continue; }
if (c0 == 0x2b) { i += asmX86_sub0(&ic[i + 1], "SUB", 1, 2, 3, 0) + 1; continue; }
if (c0 == 0x31) { i += asmX86_sub0(&ic[i + 1], "XOR", 1, 3, 2, 0) + 1; continue; }
if (c0 == 0x3b) { i += asmX86_sub0(&ic[i + 1], "CMP", 1, 2, 3, 0) + 1; continue; }
if (0x40 <= c0 && c0 <= 0x47) { i += asmX86_sub0(&ic[i + 1], "INC", 0, 1, 0, 0) + 1; continue; }
if (0x48 <= c0 && c0 <= 0x4f) { i += asmX86_sub0(&ic[i + 1], "DEC", 0, 1, 0, 0) + 1; continue; }
if (c0 == 0x60) { i += asmX86_sub0(0, "PUSHAD", 0, 0, 0, 0) + 1; continue; }
if (c0 == 0x61) { i += asmX86_sub0(0, "POPAD", 0, 0, 0, 0) + 1; continue; }
if (c0 == 0x69) { i += asmX86_sub0(&ic[i + 1], "IMUL", 1, 2, 3, 5) + 5; continue; }
if (c0 == 0x6b) { i += asmX86_sub0(&ic[i + 1], "IMUL", 1, 2, 3, 4) + 2; continue; }
if (c0 == 0x81) { i += asmX86_sub0(&ic[i + 1], asmX86_grp0[(c1 >> 3) & 7], 1, 3, 5, 0) + 5; continue; }
if (c0 == 0x83) { i += asmX86_sub0(&ic[i + 1], asmX86_grp0[(c1 >> 3) & 7], 1, 3, 4, 0) + 2; continue; }
if (c0 == 0x85) { i += asmX86_sub0(&ic[i + 1], "TEST", 1, 3, 2, 0) + 1; continue; }
if (c0 == 0x89) { i += asmX86_sub0(&ic[i + 1], "MOV", 1, 3, 2, 0) + 1; continue; }
if (c0 == 0x8b) { i += asmX86_sub0(&ic[i + 1], "MOV", 1, 2, 3, 0) + 1; continue; }
if (c0 == 0x99) { i += asmX86_sub0(0, "CDQ", 0, 0, 0, 0) + 1; continue; }
if (0xb8 <= c0 && c0 <= 0xbf) { i += asmX86_sub0(&ic[i + 1], "MOV", 0, 1, 5, 0) + 5; continue; }
if (c0 == 0xd3) { i += asmX86_sub0(&ic[i + 1], asmX86_shft[(c1 >> 3) & 7], 1, 3, 6, 0) + 1; continue; }
if (c0 == 0xc3) { asmX86_sub0(0, "RET", 0, 0, 0, 0); break; }
if (c0 == 0xe8) { i += asmX86_sub0(&ic[i + 1], "CALL", 0, 7, 0, 0) + 5; continue; }
if (c0 == 0xe9) { i += asmX86_sub0(&ic[i + 1], "JMP", 0, 8, 0, 0) + 5; continue; }
if (c0 == 0xf7) {
c1 = (c1 >> 3) & 7;
if (c1 == 3) { i += asmX86_sub0(&ic[i + 1], "NEG", 1, 3, 0, 0) + 1; continue; }
if (c1 == 5) { i += asmX86_sub0(&ic[i + 1], "IMUL", 1, 3, 0, 0) + 1; continue; }
if (c1 == 7) { i += asmX86_sub0(&ic[i + 1], "IDIV", 1, 3, 0, 0) + 1; continue; }
}
printf("asmX86: error: %02x-%02x\n", c0, c1);
exit(1);
}
printf("EXTERN _sub_print, _sub_time, _sub_aRgb8, _sub_XorShift\n");
printf("EXTERN _sub_aGetPix, _sub_f16Sin, _sub_f16Cos, _sub_aInkey\n");
printf("EXTERN _sub_prints, _sub_aSetPix0, _sub_aFilRct0, _sub_aDrwStr0\n");
printf("EXTERN _sub_gprDec, _sub_OpnWin, _sub_aWait, _sub_bitblt\n");
printf("EXTERN _sub_aryNew, _sub_aryInit\n");
printf("SECTION .data\n");
printf(" ALIGNB 16\n");
for (j = 0; j < tcs; j++) { // 普通の変数の出力.
if (vc[j] > 0) {
printf(".%-6s DD %d\n", ts[j], var[j]);
}
}
for (k = 0; k < ailp; k++) { // 配列の初期値の出力.
int j1 = get32(&ic[aryInitList[k] + 13]), *ip = (IntP) get32(&ic[aryInitList[k] + 5]);
printf(".ary%d:\n", (int) ip);
for (j = 0; j < j1; j++) {
printf(" DD %d\n", ip[j]);
}
}
for (j = 0; j < tcs; j++) { // 文字列リテラルの出力.
if ((varOpt[j] & 2) != 0) {
printf(".str%d DD %s,0\n", var[j], ts[j]);
}
}
}
- [3]関数run()を以下のように改造
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 if (codedump == 1) {
int i, i1 = dump1 - dump0;
for (i = 0; i < i1; i++) {
printf("%02x ", dump0[i]);
}
printf("\n(len=%d)\n", i1);
for (i = 0; i < MAX_TC + 1; i++) {
if (vc[i] != 0) {
printf("#%04d(%08x): %06d: %s\n", i, (int) &var[i], vc[i], ts[i]);
}
}
+ } else if (codedump == 2) {
+ asmX86();
}
return 0;
}
- 以上すべての改造を終えると、プログラムは1284行になります。HL-16aが1081行だったので、203行だけ増えたことになります。
- それでは早速動かしてみます。
>codedump 2
>run maze.c
BITS 32
SECTION .text
GLOBAL _aMain
ALIGNB 16
_aMain:
PUSHAD
SUB ESP,124
MOV EAX,752
MOV DWORD [ESP+0],EAX
MOV EAX,496
MOV DWORD [ESP+4],EAX
MOV EAX,.str36703696
MOV DWORD [ESP+8],EAX
CALL _sub_OpnWin
(中略)
.d DD 0
.dd DD 0
.23 DD 23
.15 DD 15
.str36703696 DD "maze",0
- この出力部分(「BITS 32」以降の部分)をコピー&ペーストして、テキストファイルとして保存します。たとえば、「maze_asm.nas」としましょう。
- もしcodedump変数の初期を2に改造してあれば「 prompt>hl16b maze.c > maze_asm.nas 」だけでいけます。
- これを
prompt>nasm -f win32 -o maze_asm.obj maze_asm.nas
としてアセンブルします。-fはelf32とかにすることもできます(その場合の拡張子は.oのほうがいいですね)。・・・自分の使っているOSに合わせましょう。
- さらに実行ファイルを作りたいわけですが、そのためには前もって以下のファイル(sub32.c)をコンパイルしてオブジェクトファイルを作っておく必要があります。
#include <acl.c>
AWindow *win;
void sub_print(int i) { printf("%d\n", i); }
void sub_time() { printf("time: %.3f[sec]\n", clock() / (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)
{
win = aOpenWin(xsz, ysz, s, 0);
return 0;
}
int sub_aWait(int msec)
{
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;
}
}
AInt *sub_aryNew(int n)
{
int *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));
}
- これを以下の手順でコンパイルして、sub32.objを作っておきます(下記はWindowsでの例)。
prompt>gcc -m32 -Wno-unused-function -O3 -I (acl-winへのパス) -c -DAARCH_X86 -o sub32.obj sub32.c
- sub32.objとmaze_asm.objから、maze_asm.exeを生成できます。
prompt>gcc -Wl,-s -o maze_asm.exe maze_asm.obj sub32.obj -lgdi32
本当はldコマンドでリンクすべきなのですが、指定しなければいけないライブラリがたくさんあって面倒なのでgccをリンカ代わりにしています。
- こちらで試したところ、15.0KBのmaze_asm.exeが生成されました。もちろんこれをそのまま実行することもできます。
(2) プログラムの説明
- まず全体的な改造の構成を説明したいと思います。
- 基本的には、compile()が生成した機械語を逆アセンブルして表示すればいいだけです。でもそれだけでは十分ではありません。
- [1]文字列リテラルの値は、文字列へのポインタが定数として利用されるわけですが、アセンブラで出力する場合には、そこを別処理してやる必要があります。
それで、あとで文字列リテラルの内容を.dataセクション内に展開できるように varOpt[]変数にフラグを付けています。
- [2]初期値付きの配列宣言の場合、初期値テーブルを.dataセクション内に展開しなければいけません。これを可能にするために、aryInitList[]とailpを用意しています。
- [3]初期値付きの配列宣言のところで、putIcX86()の内容を少し変えました。
旧: 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);
新: putIcX86( "89_44_24_00; b8_%0i; 89_44_24_04; b8_%1i; 89_44_24_08; e8_%2r;", (IntP) ip, (IntP) j, (IntP) sub_aryInit, 0);
これは最初の8b命令が、直前の89命令の関係でoptimizerX86()関数によって必ず消されてしまうので、それなら最初からそんなものは書かないようにしただけです。
- [4]アセンブラで出力することを考えると、JMP命令やJcc命令の飛び先を相対値に変更しないでラベル番号のままにしておく方が処理が楽なので、codedump==2のときは相対値に上書きする処理をやめさせました。
- [5]asmX86()関数では、ラベル宣言に関して、プログラム内で参照されなかったものは出力しないようにしています。これはそうしないと、無意味なラベル宣言がたくさん出てきてしまってみっともないからです。一方それだけど、ユーザ定義したラベルも使われなければ消されてしまって、それだと不便かもしれないので、ユーザ定義ラベルは利用の有無にかかわらずvarOpt[]変数にフラグを付けておくようにしました。
- それ以外の部分はこんな感じです。
- regNamX86[] : レジスタ番号をレジスタ名に変換するためのテーブルです。
- modRmX86(p, s) : pに mod r/m の機械語のアドレスを渡すと、sに mod r/m をでコードした文字列を格納し、関数値としては mod r/m の命令長を返します。
- asmX86_sub1(q, p0, s, p1, t) : qで指定された情報を返します。その際には p0~t で指定された情報を利用します。
- q=1: modRmの直前の1バイトの下位3bitによるレジスタ指定(INC, DEC, PUSH, POPなど)
- q=2: modRm内の中間3bitによるレジスタ指定
- q=3: modRmを解釈した結果
- q=4: 8bit即値
- q=5: 32bit即値
- q=6: CL
- q=7: sub関数呼び出し
- q=8: JMP/Jccの分岐先ラベル
- asmX86_sub0(p, op, modRm, q0, q1, q2) : 1命令を逆アセンブルします。asmX86()の下請け関数です。
- asmX86_grp0[] : 命令81や83で使われる命令の表です。
- asmX86_jcc[] : Jcc命令の表です。
- asmX86_shft[] : シフト命令の表です。
- asmX86() : アセンブラ出力のメインルーチンです。
次回に続く
こめんと欄
|