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


10

ちょっと変わった質問かもしれません。

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

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



1
いいえ。あなたはそれを知る必要すらありません。目的のISA仕様を盲目的に、無意識にコピーできます。dl.acm.org
=

1
Coffescript はjavascriptにコンパイルされます。
Kartik 14

@Kartik JavascriptにコンパイルされるCoffeeScriptコンパイラーには、JavascriptがコンパイルされるものにコンパイルされるJavascriptコンパイラーも含まれますか?それともJavascriptのソースコードにコンパイルするだけですか?
Aviv Cohn

coffeescriptコンパイラは単にcofeescriptをjavascriptに変換します。JavaScriptはコンパイルされていません。ブラウザによって処理されます。ある言語を別の言語にコンパイルするコンパイラーを作成できます。そのための機械語を知っている必要はありません。もう1つの例は、シェークスピアの演劇をC ++にコンパイルする「SPL」コンパイラーです。shakespearelang.sourceforge.net
Kartik 14

回答:


15

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

ちなみに、非VM実装とVM実装を区別することは役に立ちません。

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

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

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

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


また、LLVMのもう1つの優れた特性は、効率的なバックエンドを実装するためにターゲットISAを深く理解する必要がないことです。これはかなり宣言的であるため、ISA仕様を.tdファイルにほとんどコピーアンドペーストして、それを理解しようとすることさえありません。
SK-logic

回答ありがとうございます。質問:私はあなたの回答と他の回答から、コンパイラー作成者がマシンコードを理解する必要がないことを理解しています。彼は実際のマシンコードへの変換を行う別のツールを使用できます。しかし、そのツールを書いた人は機械語を理解なければなりませんでしたよね?ある言語から機械語への実際の変換を行うソフトウェアを書いた人は、実際に機械語を理解しなければなりませんか?
アビブコーン

5
@Progはい。抽象化のレイヤーを作成する場合は、自分の下のレイヤーを理解するだけで済みます。すべてのレイヤーの基本を理解しておくと便利ですが、これは必ずしも必要ではありません。トランジスタを使用するために量子物理を理解する必要はありません。CPUを使用するためにチップ設計を理解する必要はありません。アセンブリを書くためにマシンコードを知る必要はありません。VMを使用する場合など、プラットフォームの命令セットを知っている必要はありません。しかし、誰かがあなたの抽象化レベルより下の抽象化レベルを構築する必要がありました。
アモン

1
@ SK-logic私が聞いたことから、(少なくとも良いコードが必要な場合は)trueではありません。llvmにAarch64バックエンドを実装した人々は、かなりの数の課題を抱えていました(1つのロードストアパターンへの再配置、恐ろしいことなど)。そして、それは無視していますよ興味中のISAのメモリモデルと言語のメモリモデルあなたは、コンパイラに取り組むことができますが、アーキテクチャを理解せずにバックエンドで動作することはできません..:最大の部屋に象を。
ブー14

1
概念的には、アセンブリコードとマシンコードの間に実質的な違いはありません。特定の命令の使用方法を知ったら、その命令のオペコードが何であるかは、実際にはあまりメリットがありません。MOVの使用方法を知っている場合、それが命令27であることを知らなくても、問題はありません。これは、@ SK-logicが記述しているものに似ていると思います。
whatsisname 2014

9

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

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


そして、実際に機械語に変換するツールやライブラリを作成する人は、機械語を完全に理解する必要がありますか?
アビブコーン

3
@Progプログラミング言語を完全に理解する必要がありますか?いいえ。ただし、次善のコードを作成する可能性があり、他の人が実行できる可能性がある特定のことを実行できません。それを変換するコンパイラーを作成する場合、機械語を完全に理解する必要がありますか?いいえ、ただしコンパイラは最適ではなく、特定の処理を実行できません。
Sumurai8 14

@ Sumurai8:全体を理解するマシンコードエミッターを作成するために、ある程度機械語を区分的に「理解」できます。たとえば、優れたフレームワークを作成する場合、各オペコードの定義をそのコストとパイプラインの考慮事項とともに構成することができ、その特定のマシンを最適化する専門知識がなくても、フレームワークは最適化されたマシンコードを書き込むことができます。自分でそのマシンコードを適切にプログラミングできることは、おそらく害にはなりません。
スティーブジェソップ2014

@SteveJessop各オペコードを理解して、そのオペコードを他のオペコードと連鎖させてより高いレベルの概念を表現する方法を機械に学習できる場合は、機械語を完全に理解しています。あなたはそれから、そこにあるすべての問題に対する最適な解決策を見つけるのが
面倒です

@ Sumurai8:うーん、少なくとも原則として、構成するのにかかる5分間、各オペコードを短時間「理解」し、次にオペコードを「理解」するまでにそれを忘れてしまう可能性があります。それはおそらく、質問者が「生の機械語を読み書きできる」という意味ではありません。もちろん、ここではかなり良いフレームワークを想定しています。これは、命令セットの各オペコードに関するすべての有用な情報を定義して使用するのに十分な構成が可能です。LLVMは多少これを狙っていますが、 "Voo"(以下のコメント)によると、これはヒットしていません。
スティーブジェソップ2014

3

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

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

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


2

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

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

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

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


0

入力言語と出力言語のセマンティクスの詳細な知識で開始する必要はありませんが、両方の非常に詳細な知識で終了することをお勧めします。そうしないと、コンパイラにバグが発生します。したがって、入力が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には、非常に明確に定義されたメモリセマンティクスがあり、場合によっては一部の最適化に制限を課しますが、他の場合には最適化を許可します。十分に定義されたメモリセマンティクスを持たない言語(C ++ 11より前のすべてのバージョンのC / C ++や他の多くの命令型言語など)をコンパイルしている場合、通常はメモリセマンティクスを発明する必要があります。マシンのセマンティクスに最も一致するメモリセマンティクスを発明する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.