メインループと文の処理
メモリに格納されたソースコードを順に機械語に翻訳します。
メインループ
rvtlcではソースコードを2回(2パス)読んで機械語に翻訳します。1回目ではコードを生成しながら、行番号とアドレスの対応表とラベルと行番号の対応表を作成します。分岐命令の分岐先アドレスは未確定のままにしておきます。ソースを2度目に読むときは、1回目で作成した行番号とアドレスの対応表と、ラベルと行番号の対応表を参照しながら、分岐先のアドレスも含めてコード生成します。「!=^ParseLine」で1行ずつ翻訳します。
10490 :------------------- 10500 : コード生成 10510 : 2PASS コンパイル 10520 :------------------- 10530 Q=1,2 10540 ;=D>0 / "----- PASS [" ?=Q "] -----" / 10550 P=H['C'] : コード生成先頭オフセット 10560 !=^InitCode : 初期化コード 10570 N=M : Nは行先頭ポインタを最初の行に設定 10580 ;=N[1]>=Z #=-1 : 行番号チェック、compilerは処理しない 10590 : N[0] offset,N[1] 行番号 10600 @ 10610 ;=(D>2) / / "line:" ?=N[1] " from " ??=P 10620 ;=Q=1 !=^LineTable : 行番号:アドレスの登録 10630 ;=(Q=2)&(D>2) !=^LineCheck 10640 C=N+8 : 行テキスト先頭 10650 S=N[0]-8 : 行長 10660 !=^ParseLine 10670 N=N+N[0] : 次行先頭 10680 @=((N[0]<0)+(N[1]>=Z)) : 最後の行まで処理したか? 10690 R[Q-1]=P 10700 @=Q+1 10710 :------------------------- 10720 : コード生成の終了 10730 :------------------------- 10740 : 10750 !=^ExitCode 10760 :
コード生成
配列 Obj は出力ファイルのイメージを格納していますが、この配列に機械語を直接書き込みます。次に書き込む位置は変数 P に保持しています。
Obj[0] +-------------------------------+ 0x000 | ELF Header領域 | Obj[32] +-------------------------------+ 0x080 | エントリーポイント jmp main | +-------------------------------+ | ランタイムライブラリ領域 | +-------------------------------+ H['C'] | コード領域 | Obj(P)-->| | | | +-------------------------------+ | セクションテーブル領域 | +-------------------------------+
1 バイト出力
変数スタックに積まれた32ビットの値を取り出し、下位8ビットをObj配列に書き込みます。
12650 :---------------------------------------------------------- 12660 : コード 1 バイト出力 12670 :---------------------------------------------------------- 12680 ^PutObj1 12690 E=Obj+P 12700 E(0)=; 12710 ;=D>4 / "putobj1 " ??=P " : " ?$=E(0) 12720 P=P+1 12730 ] 12740 :
2 バイト出力
連続する2バイトをObj配列に書き込みます。i386はリトルエンディアンのCPUのため、機械語を定数で指定する場合は逆順にする必要があります。数値を書く場合は逆順にする必要はありません。書き込む数値を変数スタックに積んでから呼び出します。
12 34 と書き込む場合 +=$3412 !^=^PutObj2
12750 :---------------------------------------------------------- 12760 : コード 2 バイト出力 12770 :---------------------------------------------------------- 12780 ^PutObj2 12790 E=Obj+P 12800 E{0}=; 12810 ;=D>4 / "putobj2 " ??=P " : " ?#=E{0} 12820 P=P+2 12830 ] 12840 :
4 バイト出力
連続する4バイトをObj配列に書き込みます。i386はリトルエンディアンのCPUのため、機械語を定数で指定する場合は逆順にする必要があります。数値を書く場合は逆順にする必要はありません。書き込む数値を変数スタックに積んでから呼び出します。
12 34 56 78 と書き込む場合 +=$78563412 !^=^PutObj2
12850 :---------------------------------------------------------- 12860 : コード 4 バイト出力 12870 :---------------------------------------------------------- 12880 ^PutObj4 12890 E=Obj+P 12900 E[0]=; 12910 ;=D>4 / "putobj4 " ??=P " : " ??=E[0] 12920 P=P+4 12930 ] 12940 :
相対アドレス出力
現在の書き込み位置の次の命令(書き込み先頭アドレス+4)からの相対アドレスを書き込みます。分岐命令で使用します。
12950 :---------------------------------------------------------- 12960 : コード 4 バイト出力 (相対アドレス) 12970 :---------------------------------------------------------- 12980 ^PutAddr 12990 E=Obj+P 13000 E[0]=;-(P+4+H['B']) 13010 ;=D>4 / "putaddr " ??=P " : " ??=E[0] 13020 P=P+4 13030 ] 13040 :
初期化コード
プログラム起動時にプログラムの実行に必要な初期設定を行うコードを生成します。esi レジスタは変数領域の先頭を設定し、この後 esi レジスタは変更しません。ediレジスタは変数スタック領域の最終に設定しますが、ediレジスタは変数スタックのスタックポインタとして使用します。また、コマンドライン引数と環境変数の取得、TERMIOの退避と設定、システム変数の設定を行います。
11130 :---------------------------------------------------------- 11140 : 初期化コード 11150 :---------------------------------------------------------- 11160 ^InitCode 11170 E=Obj+P 11180 +=$BE !=^PutObj1 : .bss 変数領域先頭アドレス 11190 +=H['K']+H['U'] !=^PutObj4 : MOV ESI, BSSアドレス 11200 +=0 !=^CallLib : Call Init 11210 : 0 を渡して brk の現在値を得る 11220 +=$DB31 !=^PutObj2 : XOR EBX, EBX 11230 +=$B8 !=^PutObj1 : MOV EAX, SYS_brk(45) 11240 +=45 !=^PutObj4 11250 +=$80CD !=^PutObj2 : INT 0x80 11260 +=$B3 !=^PutObj1 : MOV BL, '*' ; RAM末設定 11270 +='*' !=^PutObj1 11280 +=$89 !=^PutObj1 : MOV [ESI+EBX*4], EAX 11290 +=$9E04 !=^PutObj2 11300 +=$B8 !=^PutObj1 : MOV EAX, ヒープ先頭アドレス 11310 +=H['X'] !=^PutObj4 11320 +=$B3 !=^PutObj1 : MOV BL, '&' ; RAM先頭設定 11330 +='&' !=^PutObj1 11340 +=$89 !=^PutObj1 : MOV [ESI+EBX*4], EAX 11350 +=$9E04 !=^PutObj2 11360 +=$B3 !=^PutObj1 : MOV BL, '=' ; RAM先頭設定 11370 +='=' !=^PutObj1 11380 +=$89 !=^PutObj1 : MOV [ESI+EBX*4], EAX 11390 +=$9E04 !=^PutObj2 11400 +=$B8 !=^PutObj1 : MOV EAX, bss先頭アドレス 11410 +=H['N'] !=^PutObj4 11420 +=$B3 !=^PutObj1 : MOV BL, ',' ; RAM先頭設定 11430 +=',' !=^PutObj1 11440 +=$89 !=^PutObj1 : MOV [ESI+EBX*4], EAX 11450 +=$9E04 !=^PutObj2 11460 +=$BF !=^PutObj1 : MOV EDI,変数スタック最終 11470 +=H['X']-4 !=^PutObj4 11480 ] 11490 :
ランタイムライブラリの初期化ルーチン
ランタイムライブラリの初期化ルーチンでは ^InitCode から呼び出されて、コマンドライン引数と環境変数にアクセスする準備、TERMIOの退避と設定、乱数シードの設定をしています。ランタイムライブラリが使うデータ領域はライブラリ外から操作しない方針としています。
;--------------------------------------------------------------------- ; 初期化コード esi は変数領域先頭としておく ;--------------------------------------------------------------------- Init: mov ebp, esp add ebp, 4 ; skip return address mov edi, argc mov ebx, [ebp] ; ebx = argc mov [edi], ebx ; argc 引数の数を保存 add ebp, 4 mov [edi + 4], ebp ; argvp 引数配列先頭を保存 lea eax, [ebp+ebx*4+4] ; 環境変数アドレス取得 mov [edi + 8], eax ; envp 環境変数領域の保存 call GET_TERMIOS ; termios の保存 call SET_TERMIOS ; 端末のローカルエコーOFF mov eax, 672274774 ; 初期シード値 mov cl, '`' ; 乱数シード設定 mov [esi+ecx*4], eax call sgenrand ret
終了コード
翻訳するソースコードが最終に到達した場合には、TERMIOを起動時の設定に戻し、exitシステムコールを呼び出すコードを生成します。終了コマンド「#=-1」も同じコードを生成します。
11500 :---------------------------------------------------------- 11510 : 終了コード 11520 :---------------------------------------------------------- 11530 ^ExitCode 11540 +=29 !=^CallLib : RESTORE_TERMIOS 11550 +=1 !=^CallLib : Exit 11560 ] 11570 :
1行の処理
1文字ずつソースを読んでコマンドの処理をします。1行には空白を区切りとして複数のコマンドを記述できるので DO-UNTILのループになっています。
11850 :---------------------------------------------------------- 11860 : 1行の処理 11870 : C : 行テキスト先頭アドレス 11880 : S : 行長 (paddingを含む) 11890 :---------------------------------------------------------- 11900 : 11910 ^ParseLine 11920 I=0 11930 @ 11940 c=C(I) : 1文字を取り出す 11950 ;=(c<>' ')&(c<>0) !=^Command : 1命令の処理 11960 ;=C(I)<>0 I=I+1 : 次の文字 11970 @=((C(I)=0)|(I>S)) : 行末まで繰返す 11980 ] 11990 :
行番号:アドレステーブル
配列Vに以下のような形式で行番号と対応するアドレスを登録しています。
4byte 4byte +--------+----------+ V | 行番号 | アドレス | +--------+----------+ | 行番号 | アドレス | +--------+----------+ : : : +--------+----------+ | 行番号 | アドレス | +--------+----------+ | | | <--- W +--------+----------+
11580 :---------------------------------------------------------- 11590 : 行番号:アドレステーブルの登録 11600 :---------------------------------------------------------- 11610 ^LineTable 11620 ;=Q>1 ] : PASS1のみ 11630 V[W*2]=N[1] : 行番号 11640 V[W*2+1]=H['B']+P : コードアドレス 11650 W=W+1 11660 ] 11670 :
パス1とパス2で同じアドレスかどうかを確認します。デバッグレベル(D)が3以上のときだけ実行されます。
11680 :---------------------------------------------------------- 11690 : 行番号:アドレステーブルの整合性確認 11700 :---------------------------------------------------------- 11710 ^LineCheck 11720 +w 11730 w=0 11740 @ 11750 ;=V[w*2]<>N[1] #=^skipLC 11760 ;=V[w*2+1]=(H['B']+P) #=^skipLC 11770 ?(6)=N[1] ":" ??=V[w*2+1] "-" ??=(H['B']+P) " = " 11780 ?=V[w*2+1]-(H['B']+P) / 11790 ^skipLC 11800 w=w+1 11810 @=(w>=W) 11820 -w 11830 ] 11840 :
行番号からアドレスを線形探索で取得します。
28400 :---------------------------------------------------------- 28410 : 行番号テーブルの検索 28420 : in n:検索行番号 28430 : out r:addr 28440 :---------------------------------------------------------- 28450 ^SearchLine 28460 +wf 28470 w=0 f=0 28480 @ 28490 ;=V[w*2]>=n r=V[w*2+1] f=1 : コードアドレス 28500 w=w+1 28510 @=((w>=W)|(f=1)) 28520 -fw 28530 ] 28540 : 28550 :---------------------------------------------------------- 28560 : 行番号テーブルの表示 28570 :---------------------------------------------------------- 28580 ^PrintLine 28590 / 28600 w=0,W-1 28610 ?(8)=V[w*2] " : " : 行番号 28620 ??=V[w*2+1] / : コードアドレス 28630 @=w+1 28640 ] 28650 :
行番号:ラベルテーブル
配列Lに以下のような形式で行番号と対応するラベル文字列(0で終わるため最大11文字)を作成します。登録はコマンド処理の。 ラベル宣言で行います。
4byte 12byte +--------+---------------+ L | 行番号 | ラベル文字列,0| +--------+---------------+ | 行番号 | ラベル文字列,0| +--------+---------------+ : : : +--------+---------------+ | 行番号 | ラベル文字列,0| +--------+---------------+ | | | <-------- K +--------+---------------+
ラベルに対応する行番号をラベルテーブルから取得します。単純な線形検索です。ラベルは11文字まで比較し、それ以降は空白か行末まで読み飛ばします。
28020 :---------------------------------------------------------- 28030 : ラベルテーブルの検索 28040 : r:行番号 28050 :---------------------------------------------------------- 28060 ^SearchLabel 28070 +kifjp 28080 k=0 r=-1 28090 @ 28100 p=L+(k*16+4) : ラベル文字列先頭 28110 i=0 j=I f=0 28120 @ 28130 ;=(C(j)<>p(i))&(p(i)<>0) f=1 : 一致しない 28140 j=j+1 28150 i=i+1 28160 @=((f=1)|(p(i)=0)) 28170 ;=(f=0)&(i<11)&((C(j)<>' ')&(C(j)<>0)) f=1 28180 ;=(f=0)&(p(i)=0) r=L[k*4] : 行番号 28190 ;=(D>2)&(r>0) $*=p / 28200 k=k+1 28210 @=((k>=K)|(r>0)) 28220 I=j 28230 ;=(C(I)<>' ')&(C(I)<>0) @ I=I+1 @=((C(I)=' ')|(C(I)=0)) 28240 -pjfik 28250 ] 28260 : 28270 :---------------------------------------------------------- 28280 : ラベルテーブルの表示 28290 :---------------------------------------------------------- 28300 ^PrintLabel 28310 / ;=K=0 ] 28320 k=0,K-1 28330 " " 28340 ?(8)=L[k*4] " : " : コードアドレス 28350 p=L+(k*16+4) : ラベル文字列先頭 28360 $*=p / 28370 @=k+1 28380 ] 28390 :
[前] [目次] [次]