ちょっと変わった質問かもしれません。
C ++コンパイラ(またはVM以外の言語)を書く人:生の機械語を読み書きできる必要がありますか?それはどのように機能しますか?
編集:私は特に、他のプログラミング言語ではなく、マシンコードにコンパイルするコンパイラについて言及しています。
ちょっと変わった質問かもしれません。
C ++コンパイラ(またはVM以外の言語)を書く人:生の機械語を読み書きできる必要がありますか?それはどのように機能しますか?
編集:私は特に、他のプログラミング言語ではなく、マシンコードにコンパイルするコンパイラについて言及しています。
回答:
いいえ、まったくありません。コンパイラが代わりにアセンブリコードを発行することは完全に可能です(多くの場合はさらに好都合です)。次に、アセンブラが実際のマシンコードの作成を処理します。
ちなみに、非VM実装とVM実装を区別することは役に立ちません。
手始めに、VMまたはプリコンパイルを使用してコードをマシン化することは、言語を実装するための単なる異なる方法です。ほとんどの場合、言語はどちらの方法でも実装できます。実際、C ++ インタープリターを 1回使用する必要がありました。
また、JVMのような多くのVMには、通常のアーキテクチャと同様に、両方にバイナリマシンコードといくつかのアセンブラがあります。
LLVM(Clangコンパイラーによって使用される)は、ここで特別な言及に値します:LLVMは、命令をバイトコード、テキストアセンブリ、またはコンパイラーからの放出を非常に簡単にするデータ構造として表すことができるVMを定義します。そのため、デバッグには役立ちますが(そして何をしているのかを理解するのに)、アセンブリ言語について知る必要はなく、LLVM APIについてのみ知る必要があります。
LLVMの良い点は、そのVMが単なる抽象化であり、バイトコードが通常は解釈されず、透過的にJITされることです。したがって、CPUの命令セットについて知る必要がなくても、効果的にコンパイルされた言語を書くことは完全に可能です。
いいえ。質問の要点は、コンパイルは非常に広義の用語であるということです。コンパイルは任意の言語から任意の言語で発生する可能性があります。また、アセンブリ/マシンコードは、コンパイルターゲットの多くの言語の1つにすぎません。たとえば、C#、F#、VB.NETなどのJavaおよび.NET言語はすべて、マシン固有のコードではなく、ある種の中間コードにコンパイルされます。それがVMで実行されるかどうかは関係ありません。言語はコンパイルされたままです。Cのような他の言語にコンパイルするオプションもあります。Cは実際には非常に人気のあるコンパイルターゲットであり、多くのツールがそれを行います。そして最後に、いくつかのツールまたはライブラリを使用して、マシンコードを生成するというハードワークを行うことができます。たとえば、スタンドアロンコンパイラの作成に必要な労力を削減できるLLVMがあります。
また、あなたの編集は意味がありません。それは、「すべてのエンジニアがエンジンの仕組みを理解する必要があるのでしょうか?そして、私はエンジンに取り組んでいるエンジニアについて尋ねているのです。」マシンコードを生成するプログラムまたはライブラリで作業している場合は、それを理解する必要があります。重要なのは、コンパイラーを作成するときにそのようなことをする必要がないということです。多くの人があなたの前にそれをしたので、あなたは再びそれをする真剣な理由が必要です。
古典的には、コンパイラーには字句解析、構文解析、コード生成の3つの部分があります。字句解析は、プログラムのテキストを言語のキーワード、名前、値に分解します。構文解析により、字句解析からのトークンが言語の構文的に正しいステートメントにどのように結合されるかがわかります。コード生成は、パーサーによって生成されたデータ構造を取得し、それらをマシンコードまたは他の何らかの表現に変換します。今日では、字句解析と構文解析を1つのステップに組み合わせることができます。
明らかに、コードジェネレータを作成する人は、命令セット、プロセッサパイプライン、キャッシュの動作など、非常に深いレベルでターゲットマシンコードを理解する必要があります。そうしないと、コンパイラーによって生成されるプログラムは遅くなり、非効率になります。彼らは8進数または16進数で表されるマシンコードを読み書きできるかもしれませんが、通常、マシン命令のテーブルを内部的に参照して、マシンコードを生成する関数を記述します。理論的には、レクサーとパーサーを書いている人は、マシンコードの生成について何も知らないかもしれません。実際、一部の最新のコンパイラでは、レクサーやパーサーの作成者が聞いたことのない一部のCPUのマシンコードを生成する可能性のある独自のコード生成ルーチンをプラグインできます。
ただし、実際には、各ステップのコンパイラー作成者はさまざまなプロセッサー・アーキテクチャーについて多くを知っているため、コード生成ステップに必要なデータ構造を設計するのに役立ちます。
入力言語と出力言語のセマンティクスの詳細な知識で開始する必要はありませんが、両方の非常に詳細な知識で終了することをお勧めします。そうしないと、コンパイラにバグが発生します。したがって、入力がC ++で、出力が特定の機械語である場合、最終的には両方のセマンティクスを知る必要があります。
C ++をマシンコードにコンパイルする際の機微の一部を以下に示します(頭の真上から、忘れていたものが他にもあると思います)。
どんなサイズになりint
ますか?ここでの「正しい」選択は、マシンの自然なポインターサイズ、さまざまなサイズの算術演算に対するALUのパフォーマンス、およびマシンの既存のコンパイラーによる選択の両方に基づく技術です。マシンには64ビット演算さえありますか?そうでない場合、32ビット整数の追加は命令に変換され、64ビット整数の追加は64ビット追加を実行する関数呼び出しに変換される必要があります。マシンには8ビットと16ビットの加算演算がありますか、それとも32ビットの演算とマスキング(DEC Alpha 21064など)でそれらをシミュレートする必要がありますか?
マシンの他のコンパイラ、ライブラリ、および言語で使用されている呼び出し規約は何ですか?パラメータはスタックに右から左または左から右にプッシュされますか?一部のパラメーターはレジスターに入り、他のパラメーターはスタックに入りますか?intとfloatは異なるレジスタスペースにありますか?レジスター割り当てパラメーターは、varargs呼び出しで特別に処理する必要がありますか?発信者が保存するレジスタと着信者が保存するレジスタはどれですか。リーフコール最適化を実行できますか?
機械の各シフト命令は何をしますか?64ビット整数を65ビットシフトするように依頼すると、結果はどうなりますか?(多くのマシンでは、結果は1ビットのシフトと同じですが、他のマシンでは "0"です。)
マシンのメモリ整合性セマンティクスは何ですか?C ++ 11には、非常に明確に定義されたメモリセマンティクスがあり、場合によっては一部の最適化に制限を課しますが、他の場合には最適化を許可します。十分に定義されたメモリセマンティクスを持たない言語(C ++ 11より前のすべてのバージョンのC / C ++や他の多くの命令型言語など)をコンパイルしている場合、通常はメモリセマンティクスを発明する必要があります。マシンのセマンティクスに最も一致するメモリセマンティクスを発明する必要があります。