残念ながら、あなたが説明するCPUアーキテクチャは制限が多すぎて、すべての中間ステップでこれを明確にすることはできません. 代わりに、疑似 C と疑似 x86 アセンブラを書きます。うまくいけば、C や x86 にあまり詳しくなくてもわかりやすい方法で書きます。
コンパイルされた JVM バイトコードは次のようになります。
ldc 0 # push first first constant (== 1)
ldc 1 # push the second constant (== 2)
iadd # pop two integers and push their sum
istore_0 # pop result and store in local variable
インタプリタは、配列内のこれらの命令 (のバイナリ エンコード) と、現在の命令を参照するインデックスを持っています。また、定数の配列、スタックとして使用されるメモリ領域、およびローカル変数用のメモリ領域もあります。次に、インタプリタ ループは次のようになります。
while (true) {
switch(instructions[pc]) {
case LDC:
sp += 1; // make space for constant
stack[sp] = constants[instructions[pc+1]];
pc += 2; // two-byte instruction
case IADD:
stack[sp-1] += stack[sp]; // add to first operand
sp -= 1; // pop other operand
pc += 1; // one-byte instruction
case ISTORE_0:
locals[0] = stack[sp];
sp -= 1; // pop
pc += 1; // one-byte instruction
// ... other cases ...
}
}
このC コードは、マシン コードにコンパイルされて実行されます。ご覧のとおり、非常に動的です。命令が実行されるたびに各バイトコード命令を検査し、すべての値がスタック (つまり RAM) を通過します。
実際の追加自体はおそらくレジスターで行われますが、追加を取り巻くコードは、Java からマシンへのコード コンパイラーが出力するものとはかなり異なります。以下は、C コンパイラが上記を (pseudo-x86) に変換する可能性があるものからの抜粋です。
.ldc:
incl %esi # increment the variable pc, first half of pc += 2;
movb %ecx, program(%esi) # load byte after instruction
movl %eax, constants(,%ebx,4) # load constant from pool
incl %edi # increment sp
movl %eax, stack(,%edi,4) # write constant onto stack
incl %esi # other half of pc += 2
jmp .EndOfSwitch
.addi
movl %eax, stack(,%edi,4) # load first operand
decl %edi # sp -= 1;
addl stack(,%edi,4), %eax # add
incl %esi # pc += 1;
jmp .EndOfSwitch
追加のオペランドは、Java プログラムの目的では定数ですが、ハードコードされているのではなく、メモリから取得されていることがわかります。これは、インタープリターにとって、それらが一定ではないためです。インタプリタは、一度コンパイルされると、特殊なコードを生成することなく、あらゆる種類のプログラムを実行できなければなりません。
JIT コンパイラの目的は、まさにそれを行うことです。特殊なコードを生成します。JIT は、スタックを使用してデータを転送する方法、プログラム内のさまざまな定数の実際の値、および実行される一連の計算を分析して、同じことをより効率的に行うコードを生成できます。サンプル プログラムでは、ローカル変数 0 をレジスタに割り当て、定数テーブルへのアクセスをレジスタへの定数の移動に置き換え ( movl %eax, $1
)、スタック アクセスを適切なマシン レジスタにリダイレクトします。通常行われるいくつかの最適化 (コピーの伝播、定数の折りたたみ、デッド コードの削除) を無視すると、次のようなコードになる可能性があります。
movl %ebx, $1 # ldc 0
movl %ecx, $2 # ldc 1
movl %eax, %ebx # (1/2) addi
addl %eax, %ecx # (2/2) addi
# no istore_0, local variable 0 == %eax, so we're done