したがって、たとえば、PythonとJavaにはVMがありますが、CとHaskellにはありません。(私が間違っている場合は私を訂正してください)
両側の言語が何であるかを考えると、理由がわかりません。Javaは多くの点で静的ですが、Haskellは多くの動的機能を提供します。
したがって、たとえば、PythonとJavaにはVMがありますが、CとHaskellにはありません。(私が間違っている場合は私を訂正してください)
両側の言語が何であるかを考えると、理由がわかりません。Javaは多くの点で静的ですが、Haskellは多くの動的機能を提供します。
静的と動的の関係はありません。
むしろ、基盤となるハードウェアプラットフォームから独立することです(「一度構築すれば、どこでも実行できる」-理論的には...)
実際、それは言語とも関係ありません。JVMのバイトコードを生成するCコンパイラを作成できます。x86マシンコードを生成するJavaコンパイラを作成できます。
少しの間VMのことを忘れて(以下に戻ります、約束します)、この重要な事実から始めましょう:
ガベージコレクションを提供する言語には、それを実行するある種の「ランタイム」 / runtime-environment/thingが必要です。
そのため、Python、Java、Haskellには「ランタイム」が必要であり、Cは必要ありませんが、ネイティブコードに直接コンパイルできます。
psycoはPythonコードをマシンコードにコンパイルするPythonオプティマイザーでしたが、そのマシンコードの多くは、、などのC-Pythonのランタイム関数の呼び出しで構成されていたPyImport_AddModule
ことに注意してくださいPyImport_GetModuleDict
。
Haskell / GHCは、psycoでコンパイルされたPythonと同様のボートに乗っています。Int
は単純なマシン命令として追加されますが、オブジェクトなどを割り当てるより複雑なものはランタイムを呼び出します。
ほかに何か?
Cに例外を追加する場合、生成されたマシンコードは、すべての関数およびすべての関数呼び出しに対していくつかの処理を実行する必要があります。
次に「クロージャ」も追加すると、さらに多くのものが追加されます。
これで、この定型的なマシンコードをすべての関数で繰り返す代わりに、サブプロシージャを呼び出して、のような必要な処理を実行することができますPyErr_Occurred
。
したがって、基本的に、すべての元のソース行は、いくつかの関数へのいくつかの呼び出しと、より小さな固有の部分にマップされます。
これがアイデアです(ところで、このアイデアを「仮想マシン」と呼びましょう)。
Pythonコードを表現してみましょう。例は次のとおりです。
def has_no_letters(text):
return text.upper() == text.lower()
メモリ内のデータ構造として、たとえば次のようになります。
{ 'func_name': 'has_no_letters',
'num_args': 1,
'kwargs': [],
'codez': [
('get_attr', 'tmp_a', 'arg_0', 'upper'), # tmp_a = arg_0.upper
('func_call', 'tmp_b', 'tmp_a', []), # tmp_b = tmp_a() # tmp_b = arg_0.upper()
('get_attr', 'tmp_c', 'arg_0', 'lower'),
('func_call', 'tmp_d', 'tmp_c', []),
('get_global', 'tmp_e', '=='),
('func_call', 'tmp_f', 'tmp_e', ['tmp_b', 'tmp_d']),
('return', 'tmp_f'),
]
}
それでは、このメモリ内データ構造を実行するインタプリタを作成しましょう。
テキストインタプリタからの直接変換に対するこれの利点について説明し、次にマシンコードへのコンパイルに対する利点について説明しましょう。
wt(f, d(o, e), s) <= th(i, s) + cr(a, p * d + o)
VM(仮想マシン)は、実際には、言語の実装を作成する際の複雑さを回避するための言語設計者向けのツールです。
基本的には、仮想コンピューターの仕様と、そのコンピューターの各部分が互いにどのように相互作用するかです。この仕様では、実際の言語で使用できるかどうかに関係なく、いくつかの仮定をコーディングできます。
この仕様では、通常、プロセッサ/プロセッサの動作、メモリの動作、可能な読み取り/書き込みバリアなど、およびそれと対話するためのより単純なアセンブリ言語を定義します。
最終的な言語は通常、作成しているテキストファイルからそのマシン用に作成された表現に変換(コンパイル)されます。
これにはいくつかの利点があります。
クールさの要素もあります。仮想マシンを作成したようです:)。
仮想マシンは基本的に、マシンコードに近い言語を解釈するインタプリタです。実マシンが実マシンコードを解釈するとき、仮想マシンは作成されたマシンコードを解釈します。一部のVMは、実際のコンピューターのマシンコードを解釈します。これらはエミュレーターと呼ばれます。
単純なアセンブリのような言語のインタプリタを作成してから、完全な高級言語のインタプリタを作成する方が簡単です。その上、多くの高水準コード構造は、多くの場合、いくつかの基本原則に対する単なる構文糖衣です。したがって、これらの複雑な概念をすべて単純なVM言語に変換するコンパイラーを作成する方が簡単なので、複雑なインタープリターを作成する必要はありませんが、単純なインタープリター(VM)を使用することができます。そして、VMを最適化するためのより多くの時間があります。
これが基本的に、最近のほとんどの言語(実際のマシンコードにコンパイルされない)の実装方法です。
インタプリタ(VM)とコンパイラは別々のプログラム(java
となどjavac
)にすることも、1つのプログラム(RubyやPythonなど)にすることもできます。
仮想マシンに関するウィキペディアのエントリから:
「仮想マシン(VM)は、物理マシンのようにプログラムを実行するマシン(つまりコンピューター)のソフトウェア実装です。」
仮想マシンの最大の利点は、理論的には、コードの移植性です。「一度書けば、どこでも実行できます」
おそらく、仮想マシンの最もよく知られている例は、元々Javaコードを実行するように設計されたJVMですが、現在ではClojureやScalaなどの言語にもますます使用されています。
動的言語に固有のことは何もありません。つまり、VMが必要です。ただし、VM上に構築できるインタプリタが必要です。
「必要」はありません。これらの言語はいずれも、特定のアーキテクチャで言語のセマンティクスを実装するためにマシンコードを直接出力するコンパイラを提供します。
仮想マシンのアイデアは、すべての異なるハードウェアおよびソフトウェアメーカー間のアーキテクチャの違いを抽象化して、開発者が1台のマシンに書き込むことができるようにすることです。
JavaとPythonは、プラットフォームの独立性を維持する方法でコンパイルできます。これはC#にも当てはまります。利点は、VMが、このほとんど強く型付けされたバイトコードを、比較的低いオーバーヘッドで非常に優れたプラットフォーム固有のコードに変換できることです。Javaは「一度ビルドすればどこでも実行できる」ことを目的としているため、JVMが作成されました。
プログラミング言語を作成したと想像してみてください。言語のセマンティクスを理解し、優れた構文を開発しました。
ただし、テキスト表現だけでは不十分です。プログラムの実行時にテキストを何度も解析する必要があるのは非効率的であるため、メモリ内のバイナリ表現を追加するのは自然なことです。これをカスタムメモリマネージャーと組み合わせると、基本的にVMが得られます。
ここで、追加のポイントとして、メモリ内表現とランタイムローダーをシリアル化するためのバイトコード形式を開発するか、スクリプト言語の方法を使いたい場合はeval()
関数を開発します。
グランドフィナーレには、JITを追加します。