別の仮想マシンアーキテクチャ(パート2)

この投稿は、 別の仮想マシンアーキテクチャ(パート1)の続きです。



前回、階乗を計算するモジュールの例で停止しました。 さらに詳細に検討し、作業を開始して検証します。







議論を始める前に、階乗モジュールを作成するコードを次に示します。
  1. ModuleBuilderビルダー;
  2. VarTypeId vtype =ビルダー。 addVarType 8 ;
  3. RegId io =ビルダー。 addReg 0 、vtype ;
  4. ProcTypeId ptype = builder。 addProcType 0 、io ;
  5. ProcId proc =ビルダー。 addProc PFLAG_EXTERNAL、ptype ;
  6. ビルダー。 addProcInstr proc、JNZInstr io、 3 ;
  7. ビルダー。 addProcInstr proc、CPI8Instr 1 、io ;
  8. ビルダー。 addProcInstr proc、RETInstr ;
  9. RegId pr =ビルダー。 addReg 0 、vtype ;
  10. ビルダー。 addProcInstr proc、PUSHInstr pr ;
  11. ビルダー。 addProcInstr proc、CPI8Instr 1 、pr ;
  12. ビルダー。 addProcInstr proc、MULInstr io、pr、pr ;
  13. ビルダー。 addProcInstr proc、DECInstr io ;
  14. ビルダー。 addProcInstr proc、JNZInstr io、 -2 ;
  15. ビルダー。 addProcInstr proc、CPBInstr pr、io ;
  16. ビルダー。 addProcInstr proc、POPInstr ;
  17. ビルダー。 addProcInstr proc、RETInstr ;
  18. ビルダー。 createModule モジュール ;
コードからわかるように、モジュールはModuleBuilderクラスを使用して作成されます。 このクラスを使用すると、作成するモジュールに変数の型、レジスタ、プロシージャなどを追加できます。



行3はvtype-要素に8バイトが含まれる変数のタイプを定義します。 行4は、このタイプに基づいてioレジスタを追加します。 このようなレジスタは、1つの要素を含む変数を記述します。つまり、実際には1つの64ビットワードを格納します。 次に、5行目に、新しいタイプのptypeプロシージャを追加します。 プロシージャのタイプは、プロシージャコールのインターフェイスを定義する追加のエンティティです。 プロシージャを作成するだけでなく、プロシージャ参照を定義することも必要です。 この例では参照を使用せず、ptypeは単一のモジュールプロシージャを作成するためにのみ必要です。 このタイプの手順では、以前に作成した入出力レジスタを入出力レジスタとして指定します。 階乗手続きでは、このレジスタを入力引数(n)および戻り結果(n!)として使用します。



6行目では、以前に定義したptypeに基づいて外部プロシージャが作成されます。 次に、指示を入力する必要があります。 まず、条件分岐命令JNZを挿入します。 この命令は、ioの内容をゼロと比較します。 ioがゼロに等しくない場合、3命令先にジャンプします。 それ以外の場合は、io(命令CPI8)でユニットを作成し、終了(RET)します。 理解できるように、これは特別なケースの階乗計算の処理です:0! = 1。



JNZは64ビットの数値で動作することに注意してください。 すなわち たとえば、vtypeに7バイトが含まれている場合、7行目でModuleBuilderオブジェクトは例外をスローします。この場合、ioはこの命令の有効な引数ではないためです。 9バイトの場合、エラーが発生しなかったことを確認するのは簡単です。 さらに、ptypeの他の変数へのリンクを追加しても、エラーは発生しません。 これが一般的な原則です。各命令は、実行のために引数の十分性をチェックし、変数に実際に追加データが含まれているかどうかは気にしません。 ioが配列であったとしても、JNZは最初の要素のみを考慮し、残りは無視します。



行10は、作業を保存する新しいprレジスタを定義します。 次に、新しいprベースのスタックフレームが作成されます(PUSH命令)。 それ以降、prレジスタはスタックに割り当てられた64ビットワードに対応します。



前の投稿で述べたように、PUSH命令はスタックフレームを削除するPOP命令に対応しています。 ネストフレームは任意ですが、スタックフレームの整合性を侵害することはできません。 これは、特に、フレームにそれを超える指示を含めることができないことを意味します。 フレームの整合性は、ModuleBuilderによって監視されます。







12行目で、prを1に割り当てます。 ループの本体である次のステートメントは、pr = io * pr(MUL命令)を割り当てます。 次に、ioが減分され(DEC命令)、結果がゼロと比較されます(JNZ命令)。 ioがリセットされない場合、2つの指示に戻ります。



16行目からループ本体が終了し、io = prが割り当てられます。 すべての作業を完了したため、prは不要になったため、対応するフレームを削除し(POP命令)、最後に関数を終了します(RET命令)。



20行目で、モジュール変数が関連付けられている新しいモジュールを作成します。 ここで、階乗手順を開始するだけです。

  1. SVariable < 8、0、0 > io ;
  2. uint64_t val = * reinterpret_cast < uint64_t * > io。elts [ 0 ] .bytes )) ;
  3. モジュール。 unpack ;
  4. val = 20 ;
  5. モジュール。 callProc proc、io ;
  6. if val = 2432902008176640000LLU
  7. 例外をスロー ;
1行目では、1つの64ビットワードの変数に対応する構造体のインスタンスを作成します(ここでは、SVariable構造体の定義を省略します)。 2行目では、結果を簡単にテストできるように変数を準備します。



4行目では、モジュールを展開します。 開梱とは、マシンコードにコンパイルすることを意味します(LLVMインフラストラクチャがこれに使用されます)。 次に、n = 20の階乗を計算します。正しい結果が得られることを確認できます。



All Articles