このページの目次

メインループと文の処理

メモリに格納されたソースコードを順に機械語に翻訳します。

メインループ

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 :

[前] [目次] [次]