バイトコード
バイトコード (bytecode) は、仮想マシンによる実行のために設計された、実行可能なプログラムのバイナリ表現である。ソフトウェアによって処理されるため、大抵は実機の機械語より抽象度の高い中間コードになっている。プログラミング言語の実装において、特定のハードウェアへの依存度を下げ、またインタプリタの実装を容易にするために用いられる。コンパイラの中間コードとして用いられることもある。
バイトコードという名前は命令の構成がバイト指向であること、すなわち命令長がバイト可変長であったり、命令中のフィールドの区切りがバイト区切りに合っているといったことから来ている。特にJavaの場合オペコードが1バイトである。しかし、仮想マシンの機械語をバイトコードと呼ぶことがJavaで広く一般的になったことから、前述のようなバイト指向でなくともバイトコードと呼んでいることも多い。バイト単位でなくビット単位の場合は、ビットコードとも呼ばれる。ワード指向のためにワードコードという語を使っている例も見られる。
バイトコードで記述されたプログラムは通常、バイトコードインタプリタによって解釈・実行される。バイトコードインタプリタは機械語を解釈・実行するCPUとの類似性からバーチャルマシンとも呼ばれる。
目次
1 存在意義
2 スタックマシン vs レジスタマシン - その論点
2.1 命令の粒度
2.2 命令間でのオペランドの受け渡し
2.3 ソースコードからバイトコードへの変換
3 使用例
4 関連項目
存在意義
バイトコードを用いる方式の利点は、ソースコードを直接解釈するインタプリタと同等の移植性を確保した上での、インタプリタのパフォーマンスの高さである。人の手で読み書きされる用途のソースコードよりもバイトコードのほうが、抽象度の低さ、データサイズなどの点で優れておりコンピュータにとって扱いやすいため、パフォーマンスは上回る。このパフォーマンス上の利点から、現在の多くのインタプリタ言語は実際にはバイトコードにコンパイルされた後、バイトコードインタプリタによって実行される。またパフォーマンス以外にも、ソースコードの中身を隠蔽する目的でバイトコードが用いられる場合がある。
バイトコードを使用した初期のコンピュータにはSystem/38があり、ソフトウェアのインストール時に機械語に変換された。またJavaのプログラムは、コンパイル済みバイトコードも利用者の環境を選ばないことから、ソースコードだけではなくバイトコードにコンパイルしたものも、標準的に配布されていることが多い。
スタックマシン vs レジスタマシン - その論点
バイトコードを解釈実行するのはソースコードインタプリタと同様にソフトウェアなので、実機の機械語としては複雑過ぎるバイトコードでも原理的には実装できる。しかしその様な複雑なバイトコードを採用すると、それを処理するインタプリタのパフォーマンスもソースコードインタプリタと同等になってしまう。このためバイトコードの多くは、実機の機械語と似た命令セットを使っている。多くのインタプリタ処理系ではスタックマシン型の命令セットが一般的だが、レジスタマシン型を採用しているものもある(Luaのバージョン5やDalvik仮想マシン)。
命令の粒度
レジスタマシン型命令セットではオペランドの位置を命令内で明示する。これに対しスタックマシン型命令セットでは、暗黙のうちにスタックトップをオペランドとして使い、オペランドの位置を明示する領域が不要である。目的のオペランドがスタックトップにない場合にはオペランド移動のための命令を追加する。大まかに言えば、スタックマシン命令一個当たりの長さと機能は、レジスタマシン一命令内の個々のオペランドフィールド一個、またはコードフィールドと同等である。
このためバイトコード全体の長さには大きな差はでなさそうだが、命令の順序を並び換えることにより、オペランド移動をスタックマシンでは省略できる。レジスタマシンではオペランド指定は省略できないので、オペランド移動を省略した分だけスタックマシンの方が同等のプログラムを短く記述できる場合が多い。キャッシュへのヒット率が高まるので、プログラムサイズの削減は実効速度の増加にもなる。
しかしオペランド移動の省略を施しても、スタックマシンの方がレジスタマシンより命令数が多くなる。個々の命令が極めて単純な処理しか行わないバイトコードインタプリタでは、命令の種類を判定して分岐する処理が実行時間の大きな割合を占めるので、この点ではレジスタマシンの方が有利となる。
命令間でのオペランドの受け渡し
レジスタマシン型インタプリタでは番号で仮想レジスタを指定するが、多くの実機では実行時に物理レジスタを番号で参照することができないため、メモリ配列によって仮想レジスタが実装されている場合が多い。これに対しスタックマシン型インタプリタでは、ほとんどの命令のオペランドがスタックトップに決め打ちされ、参照すべき物理レジスタをコンパイル時に決定できるので、スタックトップ数個を物理レジスタで実装している。多くの実機ではレジスタ経由でのデータ受け渡しは1クロックでできるのに対し、メモリ経由でのデータ受け渡しは数クロックを要するので、直前の命令の結果を直後の命令が使う処理が連なった場合は、レジスタマシンは不利となる。
ソースコードからバイトコードへの変換
ALGOL以来、多くのプログラミング言語は文脈自由文法で記述でき、スタックマシンと類似したプッシュダウン・オートマトンで構文解析できる。このため、スタックマシン向けのコード生成器ならば、構文解析器と一体化させて省メモリ・高速なものにできる。
レジスタマシン向けの場合、有限のレジスタを使い回すレジスタ割り付けを行う必要がある。ただし、インタプリタの仮想レジスタはメモリ配列で実装されている場合が多い。このため、メモリ配列の大きさが許す限り、実機では非現実的な膨大な数の仮想レジスタを実装でき、その場合、レジスタの使い回しを省いてレジスタ割り付けを単純化することができる。レジスタ・ウィンドウも僅かなコストでレジスタ1本単位でスライドさせる柔軟なものが実装でき、スタックマシン向けと同様に構文解析器とコード生成器の一体化に役立つ。[1]
使用例
System/38 - TIMI(Technology Independent Machine Interface)
Java - Javaバイトコード
.NET Framework - 共通中間言語 (CIL)- Perl6
Ruby 1.9
JavaScriptの処理系の一部
関連項目
- オブジェクトファイル
- 命令セット
- Smalltalk
- Java仮想マシン
- 中間言語
- Pコードマシン
|