問題タブ [vm-implementation]
For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Note JavaScript is NOT the same as Java! Please include all relevant tags on your question; e.g., [node.js], [jquery], [json], [reactjs], [angular], [ember.js], [vue.js], [typescript], [svelte], etc.
compiler-construction - なぜ仮想マシンが必要なのですか?
この質問を読んで、Java仮想マシンと.NET CLRの違いを調べました。ベンジの答えから、そもそもなぜ仮想マシンが必要なのか疑問に思いました。
Benjiの説明を理解したところ、仮想マシンのJITコンパイラは、中間コードをCPU上で実行される実際のアセンブリコードに解釈します。これを行う必要がある理由は、CPUのレジスタ数が異なることが多く、Benjiによれば、「一部のレジスタは特殊用途であり、各命令は異なるレジスタのオペランドを期待している」ためです。これは、同じコードを任意のCPUで実行できるようにするために、仮想マシンのような中間インタープリターが必要であることを意味します。
しかし、その場合、私が理解していないのは、マシンコードにコンパイルされたCまたはC ++コードが、正しいOSである限り、どのコンピューターでも実行できる理由です。では、なぜPentiumを使用してWindowsマシンでコンパイルしたCプログラムが、AMDを使用して他のWindowsマシンで実行できるのでしょうか。
Cコードを任意のCPUで実行できる場合、仮想マシンの目的は何ですか?同じコードをどのOSでも実行できるようにするためですか?JavaにはほとんどすべてのOSでVMバージョンがあることは知っていますが、Windows以外の他のOS用のCLRはありますか?
それとも私が見逃しているものが他にありますか?OSは、特定のCPUなどに適合させるために、実行するアセンブリコードの他の解釈を行いますか?
これらすべてがどのように機能するのか非常に興味がありますので、明確な説明をいただければ幸いです。
注:JVMとCLRの質問でクエリをコメントとして投稿しなかった理由は、コメントを投稿するのに十分なポイントがまだないためです=b。
編集:すべての素晴らしい答えをありがとう!したがって、私が見逃していたのは、すべてのプロセッサに違いはありますが、共通の標準化、主にX86アーキテクチャがあり、1つのX86プロセッサでコンパイルされたCコードがほとんどの部分で機能するように十分な数の共通機能を提供することでした。別のX86プロセッサで。これにより、ガベージコレクションの重要性を忘れたことは言うまでもなく、仮想マシンの正当性がさらに高まります。
java - Squeak が Java にはない方法でプラットフォーム間で「ビット同一」に実行されるとはどういう意味ですか?
Alan Kayは、「Java とは異なり、[Squeak] はすべてのマシンで少し同じように動作します。これは 20 年前に発明されたものです」と指摘しています。ウィキペディアのページには、これについても言及されています。
Squeak は多くのプラットフォームで利用でき、1 つのプラットフォームで作成されたプログラムは、他のすべてのプラットフォームで少し同じように実行されます。
異なる命令セットを持つマシンは明らかにビット同一プログラムをネイティブに実行できないので、Squeak が異なるマシン上でビット同一プログラムを実行するのに、Java が実行しないと言うとき、それは何を意味するのでしょうか?
コンパイルされた Java クラスは、どの JVM 上のどのマシンでも同じように実行されるという印象を受けましたが、そうではありませんか?
python - Python 仮想マシン アーキテクチャの図とリファレンス
Python VM のアーキテクチャに関する入門ドキュメントを見つけることができるサイト/本を誰かが指摘できますか?
私は C 版に興味がありますが、他の実装に関するわかりやすい参考文献があれば、それも役立つかもしれません。
プレーンなソース コードよりも高レベルのあらゆる種類のリソースを見つけようとしています (ただし、UML ダイアグラムまたはそのようなものはいいでしょう) また、さまざまな要因 (パフォーマンス、安定性) 間のトレードオフを含む、関連する設計上の決定に関する情報、単純さ)。
vm-implementation - レジスタベースの仮想マシンはどのように機能しますか?
レジスタベースの仮想マシンはどのように機能しますか?レジスタベースの仮想マシンがどのように機能するかについての紹介を探しています。誰か助けてもらえますか?
ありがとうございました。
c++ - 多くのVMがC++機能を備えているように見えるのに、なぜCで記述されているのですか?
Lua、NekoVM、Potionなどのそれほど古くないVM言語がCで書かれていることに気づきました。
彼らは多くのC++機能を再実装しているように見えました。
それらをC++ではなくCで書くことには利点がありますか?
c - スタックレス VM の実装で発生する C 統合の問題は何ですか?
スタックレス VM とは、システムの「C スタック」を使用する代わりに、ヒープ上に独自のスタックを維持する実装を意味します。これには、継続やシリアライズ可能な状態などの多くの利点がありますが、C バインディングに関しては、特に C-VM-C の種類のコールバック (または VM-C-VM) に関しては、いくつかの欠点もあります。
問題は、これらの欠点が正確に何であるかです。誰かが実際の問題の良い例を挙げてもらえますか?
ruby - RubyVM の putspecialobject オペコードはどのように機能しますか?
私はRubyVMの実装に取り組んでおり、オペコードに関するいくつかのドキュメントを探しましたが、役に立ちませんでした。
オペコードがどのように機能するかを具体的に知っている人putspecialobject
、または完全なドキュメントへのリンクを知っている人がいれば、とても感謝しています!
stack - 仮想マシンはどのように機能しますか?
私はプログラミング言語がどのように機能するかを調べてきましたが、そのうちのいくつかにはいわゆる仮想マシンがあります。これは、別のプログラミング言語内でのプログラミング言語のエミュレーションの一種であり、コンパイルされた言語がスタックを使用して実行されるように機能することを理解しています。私はそれを正しく理解しましたか?
私が行った但し書きで、多くのコンパイルされていない言語が「リベラルな」型システムを持つ変数を許可するということは、私を困惑させます。たとえば、Python では、次のように記述できます。
文字列と大きな整数はまったく無関係であり、メモリ内で異なる量のスペースを占めるため、スタックベースの環境でこのコードを表現するにはどうすればよいでしょうか? ここで正確に何が起こりますか?x はスタック上の新しい場所を指していて、古い文字列データは参照されていませんか? これらの言語はスタックを使用しませんか? そうでない場合、変数は内部的にどのように表現されますか?
performance - VM 設計: オペコードを増やすか、オペコードを減らすか? 何が良いですか?
ショックを受けないでください。これは多くのテキストですが、詳細な情報を提供しないと、これが何であるかを実際に示すことはできません (そして、私の質問に実際には対応していない多くの回答が得られる可能性があります)。そして、これは間違いなく割り当てではありません(誰かが彼のコメントでばかげて主張したように)。
前提条件
この質問は、少なくともいくつかの前提条件が設定されていない限り、おそらくまったく答えられないため、前提条件は次のとおりです。
- 仮想マシン コードは解釈されます。JIT コンパイラーが存在することは禁止されていませんが、設計はインタープリターをターゲットにする必要があります。
- VM は、スタック ベースではなく、レジスタ ベースである必要があります。
- 答えは、レジスタの固定セットがあることも、それらの数に制限がないことも想定していない可能性があります。
さらに、「より良い」のより良い定義が必要です。考慮しなければならないプロパティがいくつかあります。
- ディスク上の VM コードのストレージ領域。もちろん、ここですべての最適化を破棄してコードを圧縮することもできますが、これは (2) に悪影響を及ぼします。
- デコード速度。コードを直接実行できるものに変換するのに時間がかかりすぎる場合、コードを保存する最善の方法は役に立ちません。
- メモリ内のストレージ スペース。このコードは、さらにデコードするかどうかに関係なく、直接実行できる必要がありますが、さらにデコードが必要な場合、このエンコードは実行中および命令が実行されるたびに行われます (コードのロード時に 1 回だけ行われるデコードは項目 2 にカウントされます)。
- コードの実行速度 (一般的なインタープリター手法を考慮)。
- VM の複雑さと、そのためのインタープリターを作成するのがいかに難しいか。
- VM 自体が必要とするリソースの量。(VM が実行するコードのサイズが 2 KB で、瞬きするよりも速く実行される場合、それは適切な設計ではありませんが、これを行うには 150 MB が必要であり、その起動時間はコードの実行時間よりもはるかに長くなります。実行されます)
ここで、多かれ少なかれオペコードが実際に意味することの例を示します。操作ごとに 1 つのオペコードが必要なため、オペコードの数が実際に設定されているように見える場合があります。ただし、それほど簡単ではありません。
同じ操作に対する複数のオペコード
のような操作ができます。
R1 と R2 の値を加算し、結果を R3 に書き込みます。ここで、次の特殊なケースを検討してください。
これらは、多くのアプリケーションで見られる一般的な操作です。既存のオペコードでそれらを表現できます (最後のオペコードがレジスタではなく int 値を持っているために別のオペコードが必要な場合を除く)。ただし、これらに対して特別なオペコードを作成することもできます。
前と同じ。メリットはどこ?ADD2 は 3 つではなく 2 つの引数のみを必要とし、INC は 1 つしか必要としません。したがって、これはディスク上および/またはメモリ内でよりコンパクトにエンコードできます。いずれかの形式を別の形式に変換することも簡単であるため、デコード ステップでこれらのステートメントを表現するために両方の方法の間で変換できます。ただし、どちらの形式が実行速度にどの程度影響するかはわかりません。
2 つのオペコードを 1 つに結合する
ここで、ADD_RRR (レジスターの R) とデータをレジスターにロードするための LOAD があるとします。
これら 2 つのオペコードを使用して、コード全体で常にこのような構成を使用できます...または、それらを ADD_RMR (メモリの M) という名前の単一の新しいオペコードに結合できます。
データ型とオペコード
ネイティブ型として 16 ビット整数と 32 ビット整数があるとします。レジスタは 32 ビットなので、どちらのデータ型にも適合します。2 つのレジスタを追加すると、データ型をパラメーターにすることができます。
たとえば、符号付き整数と符号なし整数についても同じことが言えます。そうすれば、ADD は 1 バイトの短いオペコードになり、別のバイト (または単に 4 ビット) が VM にレジスタの解釈方法を伝えます (レジスタは 16 ビットまたは 32 ビットの値を保持しますか)。または、型エンコーディングを破棄して、代わりに 2 つのオペコードを使用できます。
どちらもまったく同じだと言う人もいるかもしれません.16ビットのオペコードとして最初の方法を解釈するだけでうまくいきます. はい、しかし非常にナイーブなインタープリターは、かなり異なって見えるかもしれません。たとえば、オペコードごとに 1 つの関数があり、switch ステートメントを使用してディスパッチする場合 (最適な方法ではなく、関数呼び出しのオーバーヘッド、switch ステートメントも最適ではないことはわかっています)、2 つのオペコードは次のようになります。
各機能は、特定の種類の追加を中心にしています。ただし、2番目のものは次のようになります。
サブスイッチをメイン スイッチに追加するか、サブ ディスパッチ テーブルをメイン ディスパッチ テーブルに追加します。もちろん、型が明示的であるかどうかに関係なく、インタープリターはどちらの方法でも実行できますが、オペコードの設計に応じて、どちらの方法も開発者にとってよりネイティブに感じられます。
メタ オペコード
より良い名前がないので、そのように呼びます。これらのオペコードは、それ自体ではまったく意味がなく、後続のオペコードの意味を変更するだけです。有名な WIDE 演算子のように:
たとえば、2 番目のケースでは、レジスタは 16 ビット (したがって、より多くのアドレスを指定できます) で、最初のケースでは 8 ビットしかありません。あるいは、そのようなメタ オペコードを持たず、ADD および ADD_WIDE オペコードを持つことはできません。WIDE のようなメタ オペコードは、SUB_WIDE、MUL_WIDE などを持つことを避けます。これは、他のすべての通常のオペコードに常に WIDE を付加できるためです (常に 1 つのオペコードのみ)。不利な点は、オペコードだけでは無意味になることです。それがメタ オペコードであったかどうか、その前にオペコードを常にチェックする必要があります。さらに、VM はスレッドごとに余分な状態を保存し (たとえば、現在ワイド モードであるかどうかに関係なく)、次の命令の後に状態を再度削除する必要があります。CPU にもそのようなオペコードがあります (x86 LOCK オペコードなど)。
適切なトレードオフを見つける方法???
もちろん、オペコードが多いほど、スイッチ/ディスパッチテーブルが大きくなり、これらのコードをディスクまたはメモリで表現するために必要なビットが多くなります (ただし、データが保存されていないディスクにより効率的に格納できる場合があります)。 VM で直接実行できる必要があります)。また、VM はより複雑になり、より多くのコード行が必要になります。一方で、オペコードがより強力になります。複雑な式であっても、すべての式が 1 つのオペコードになるポイントに近づいています。
小さなオペコードを選択すると、VM のコーディングが容易になり、非常にコンパクトなオペコードにつながると思います。一方、単純なタスクを実行するには非常に多くのオペコードが必要になる可能性があり、あまり使用されない式はすべて、オペコードを使用できないため、ある種の(ネイティブ)関数呼び出しになります。
私はインターネット上のあらゆる種類の VM について多くのことを読みましたが、どちらの方向にも適切で公正なトレードオフを実際に行っている情報源はありませんでした。VM の設計は CPU の設計に似ています。オペコードがほとんどなく高速な CPU もありますが、これらの多くも必要です。また、多くのオペコードを持つ CPU があり、非常に遅いものもありますが、同じコードを表現するために必要なオペコードははるかに少なくなります。「オペコードが多いほど良い」CPU が消費者市場を完全に獲得し、「オペコードが少ないほど良い」CPU は、サーバー市場またはスーパー コンピューター ビジネスの一部でのみ生き残ることができるようです。VMはどうですか?