web-dev-qa-db-ja.com

コンパイラー作成者は実際にマシンコードを「理解」する必要がありますか?

ちょっと奇妙な質問かもしれません。

C++コンパイラ(またはVM以外の言語)を書く人:生の機械語を読み書きできる必要がありますか?それはどのように機能しますか?

編集:私は特に、他のプログラミング言語ではなく、マシンコードにコンパイルするコンパイラについて言及しています。

10
Aviv Cohn

いいえ、まったくありません。コンパイラが代わりにアセンブリコードを発行することは完全に可能です(多くの場合はさらに好都合です)。次に、アセンブラが実際のマシンコードを作成します。

ちなみに、VM以外の実装とVM実装の区別は役に立ちません。

  • はじめに、VMまたはマシンコードへのプリコンパイルを使用することは、言語を実装するための異なる方法です。ほとんどの場合、言語はどちらかの戦略を使用して実装できます。実際にはC++を使用する必要がありました- インタプリタ 1回。

  • また、JVMのような多くのVMには、通常のアーキテクチャと同様に、両方にバイナリマシンコードといくつかのアセンブラがあります。

LLVM(Clangコンパイラで使用される)は、ここで特別な言及に値します。これは、VMを定義し、バイトコード、テキストアセンブリ、またはそれを作成するデータ構造のいずれかとして表すことができます。コンパイラから非常に簡単に出力できるため、デバッグには役立ちますが(そして何をしているのかを理解するには)、アセンブリ言語について知る必要はなく、LLVM APIについてのみ知る必要があります。

LLVMの良い点は、VMは単なる抽象化であり、バイトコードは通常は解釈されず、透過的にJITされることです。そのため、効果的な言語を書くことは完全に可能です。 CPUの命令セットについて知る必要はありません。

15
amon

いいえ。質問の要点は、コンパイルは非常に広い用語であるということです。コンパイルは任意の言語から任意の言語で発生する可能性があります。そして、アセンブリ/マシンコードは、コンパイルターゲットの多くの言語の1つにすぎません。たとえば、JavaおよびC#、F#、VB.NETなどの.NET言語はすべて、マシン固有のコードではなく、ある種の中間コードにコンパイルされます。その後、VMで実行されるかどうかは問題ではありません。 、言語はまだコンパイルされています。Cなどの他の言語にコンパイルするオプションもあります。Cは実際に非常に人気のあるコンパイルターゲットであり、多くのツールがそれを実行します。最後に、ツールまたはライブラリを使用して、マシンコードを生成します。たとえば、 [〜#〜] llvm [〜#〜] があり、スタンドアロンコンパイラの作成に必要な労力を削減できます。

また、あなたの編集は意味がありません。それは「すべてのエンジニアはエンジンの仕組みを理解する必要があるのでしょうか?そして、私はエンジンに取り組んでいるエンジニアについて尋ねています」と尋ねるようなものです。マシンコードを生成するプログラムまたはライブラリで作業している場合は、それを理解する必要があります。重要なのは、コンパイラーを作成するときにそのようなことをする必要がないということです。多くの人があなたの前にそれをしたので、あなたは再びそれをするために深刻な理由が必要です。

9
Euphoric

古典的には、コンパイラーには字句解析、構文解析、コード生成の3つの部分があります。字句解析は、プログラムのテキストを言語のキーワード、名前、値に分解します。構文解析により、字句解析からのトークンが言語の構文的に正しいステートメントにどのように結合されるかがわかります。コード生成は、パーサーによって生成されたデータ構造を取得し、それらをマシンコードまたはその他の表現に変換します。今日では、字句解析と構文解析を1つのステップに組み合わせることができます。

明らかに、コードジェネレータを作成する人は、命令セット、プロセッサパイプライン、キャッシュの動作など、非常に深いレベルでターゲットマシンコードを理解する必要があります。そうしないと、コンパイラーによって生成されるプログラムは遅くなり、非効率になります。彼らは8進数または16進数で表されるマシンコードを読み書きできるかもしれませんが、通常、マシン命令のテーブルを内部的に参照して、マシンコードを生成する関数を記述します。理論的には、レクサーとパーサーを書いている人は、マシンコードの生成について何も知らないかもしれません。実際、一部の最新のコンパイラでは、レクサーやパーサーの作成者が聞いたことのない一部のCPUのマシンコードを生成する可能性のある独自のコード生成ルーチンをプラグインできます。

ただし、実際には、各ステップのコンパイラー作成者はさまざまなプロセッサー・アーキテクチャーについて多くを知っているため、コード生成ステップに必要なデータ構造を設計するのに役立ちます。

3

ずっと前に、2つの異なるシェルスクリプト間で変換するコンパイラを作成しました。マシンコードにはほど遠いものでした。

コンパイラの書き込みはそれらの出力を理解する必要がありますが、それは多くの場合マシンコードではありません。

ほとんどのプログラマはneverマシンコードまたはアセンブリコードを出力するコンパイラを記述しますが、カスタムコンパイラは他の出力を生成する多くのプロジェクトで非常に役立ちます。

YACCは、マシンコードを出力しないそのようなコンパイラーの1つです。

2
Ian

入力言語と出力言語のセマンティクスについての詳細な知識があればstartする必要はありませんが、両方について非常に詳細な知識があるとfinishより優れています。それ以外の場合はコンパイラバギーになります。したがって、入力がC++で、出力が特定の機械語である場合、最終的には両方のセマンティクスを知る必要があります。

C++をマシンコードにコンパイルする際の機微の一部を以下に示します(頭の真上から、忘れていたものが他にもあると思います)。

  1. intはどのようなサイズになりますか?ここでの「正しい」選択は、マシンの自然なポインターサイズ、さまざまなサイズの算術演算に対するALUのパフォーマンス、およびマシンの既存のコンパイラーによる選択の両方に基づく技術です。マシンには64ビット演算さえありますか?そうでない場合、32ビット整数の追加は命令に変換され、64ビット整数の追加は64ビット追加を実行する関数呼び出しに変換されます。マシンには8ビットと16ビットの加算演算がありますか、それとも32ビットの演算とマスキング(DEC Alpha 21064など)でそれらをシミュレートする必要がありますか?

  2. マシンの他のコンパイラ、ライブラリ、および言語で使用される呼び出し規約は何ですか?パラメータはスタックに右から左または左から右にプッシュされますか?一部のパラメーターはレジスターに入り、他のパラメーターはスタックに入りますか? intとfloatは異なるレジスタスペースにありますか?レジスター割り当てパラメーターは、varargs呼び出しで特別に処理する必要がありますか?発信者が保存するレジスタと着信者が保存するレジスタはどれですか。リーフコール最適化を実行できますか?

  3. マシンの各シフト命令は何をしますか? 64ビット整数を65ビットシフトするように依頼すると、結果はどうなりますか? (多くのマシンでは、結果は1ビットのシフトと同じですが、他のマシンでは "0"です。)

  4. マシンのメモリ整合性セマンティクスは何ですか? C++ 11には、非常に明確に定義されたメモリセマンティクスがあり、場合によっては一部の最適化に制限を課しますが、他の場合には最適化を許可します。 notを実行する言語をコンパイルしている場合は、メモリセマンティクスが明確に定義されています(C++ 11より前のC/C++のすべてのバージョン、および他の多くの命令型言語のように)、メモリセマンティクスを発明する必要があります。進むにつれて、通常はマシンのセマンティクスに最も一致するメモリセマンティクスを発明する必要があります。

0
Wandering Logic