まず、あなたが主に (排他的ではないにしても) バイトコード インタープリターまたは同様のものに関心があると仮定することから始めます (あなたの質問はそれを前提としているようです)。ソース コードから (生またはトークン化された形式で) 直接動作するインタープリターは、かなり異なります。
典型的なバイトコード インタープリターの場合、基本的に理想化されたマシンを設計します。スタックベース (または少なくともスタック指向) の設計は、この目的ではかなり一般的であるため、それを想定してみましょう。
そこで、まずオペコードに 4 ビットを選択することを考えてみましょう。ここでの多くは、サポートしたいデータ形式の数と、それを op コードの 4 ビットに含めるかどうかによって異なります。議論のために、仮想マシンが適切にサポートする基本的なデータ型が 8 ビットおよび 64 ビットの整数 (アドレス指定にも使用可能)、および 32 ビットおよび 64 ビットの浮動小数点であると仮定しましょう。
整数については、少なくともサポートする必要があるものはほとんどありません。読み込み、保存します。浮動小数点は同じ算術演算をサポートしますが、論理/ビット演算を削除します。また、いくつかの分岐/ジャンプ操作 (無条件ジャンプ、ゼロの場合のジャンプ、ゼロでない場合のジャンプなど) も必要になります。スタック マシンの場合、少なくともいくつかのスタック指向の命令 (push、pop、dupe、おそらく回転など)
これにより、データ型の 2 ビット フィールドと、オペコード フィールドの少なくとも 5 (おそらく 6) ビットが得られます。特別な命令である条件付きジャンプの代わりに、1 つのジャンプ命令と、任意の命令に適用できる条件付き実行を指定するための数ビットが必要になる場合があります。また、少なくともいくつかのアドレッシング モードを指定する必要があります。
- オプション: 小さい即値 (命令自体の N ビットのデータ)
- ラージ・イミディエート (命令に続く 64 ビット・ワードのデータ)
- 暗黙的 (スタック上のオペランド)
- 絶対(命令に続く64ビットで指定されるアドレス)
- 相対 (命令または命令の後に指定されたオフセット)
ここでは、すべてを最小限に抑えるために最善を尽くしましたが、効率を向上させるためにもっと多くのことを望むかもしれません。
とにかく、このようなモデルでは、オブジェクトの値はメモリ内のいくつかの場所にすぎません。同様に、文字列はメモリ内の 8 ビット整数のシーケンスです。オブジェクト/文字列のほぼすべての操作は、スタックを介して行われます。たとえば、クラス A と B が次のように定義されているとします。
class A {
int x;
int y;
};
class B {
int a;
int b;
};
...そして次のようなコード:
A a {1, 2};
B b {3, 4};
a.x += b.a;
初期化は、a と b に割り当てられたメモリ位置にロードされた実行可能ファイル内の値を意味します。追加すると、次のようなコードが生成されます。
push immediate a.x // put &a.x on top of stack
dupe // copy address to next lower stack position
load // load value from a.x
push immediate b.a // put &b.a on top of stack
load // load value from b.a
add // add two values
store // store back to a.x using address placed on stack with `dupe`
適切な命令ごとに 1 バイトを想定すると、シーケンス全体で約 23 バイトになり、そのうちの 16 バイトがアドレスになります。64 ビットの代わりに 32 ビットのアドレッシングを使用すると、それを 8 バイト削減できます (つまり、合計 15 バイト)。
心に留めておくべき最も明白なことは、典型的なバイトコード インタープリター (または同様のもの) によって実装された仮想マシンは、ハードウェアに実装された "実際の" マシンとそれほど変わらないということです。実装しようとしているモデルにとって重要な命令をいくつか追加することもできます (たとえば、JVM にはセキュリティ モデルを直接サポートするための命令が含まれています)。それらを含めます(たとえば、xor
本当にしたいのであれば、いくつかのようなものを除外できると思います)。また、サポートする仮想マシンの種類を決定する必要もあります。上で説明したのはスタック指向ですが、必要に応じてレジスタ指向のマシンを実行することもできます。
いずれにせよ、オブジェクト アクセス、文字列ストレージなどのほとんどは、それらがメモリ内の場所であることに帰着します。マシンは、これらの場所からスタック/レジスタにデータを取得し、必要に応じて操作して、目的のオブジェクトの場所に保存します。