コンパイラ・シリーズ
5823 ワード
GCC , もともとGNUオペレーティングシステム用に書かれたのは、いくつかの異なるプログラミング言語のフロントエンド、ライブラリ、バックエンドのセットです.これは古いソフトウェアですが、現在まで維持され、定期的に多く使用されます.コンパイラは、C/C++、Object C、Fortran、AdaとGoのためのフロントエンドを持っています.
コンパイラはもともとGNUオペレーティングシステム用のベンダコンパイラを使用するためのライセンスの支払いを避けるために構築されました.それ以来、それは人気とパフォーマンスの両方で過去の独自のコンパイラを上昇している.
ユーザーがコマンドを実行したときに開始されるプロセス
プリプロセッサは、コンパイル前にソースコードに変更を加えることに対処します.C/C++プリプロセッサを特に見ています.他のプリプロセッサまたはフロントエンドは、上述のように異なる言語で利用可能である.この段階で一定量のレクチンが発生する.実行するアクションは以下の通りです.
エスケープされた改行が使用されている場合、これはソースの行を傾ける.エスケープされた改行文字は、行を分割するために使用されるバックスラッシュです.それらはトークンの間に、またはコメントの中に現れなければなりません.
コード内のすべてのコメントは、単一のスペースに置き換えられます.
これらの変換が完了すると、プログラムはプリプロセッサトークンに分割される.これらのトークンはコンパイラに渡され、コンパイラトークンとして使用されます.現在のところ、プリプロセッサトークンは5つのカテゴリーに分けられることができます:識別子、前処理番号、ストリングリテラル、句読点、および他. 識別子-文字、数字、または数字で始まることがないアンダースコアの任意のシーケンス.キーワード- ifなどのトークンは、識別子として扱われます.これは、実際のC/C++言語の予約キーワードと同じ名前のマクロを定義できることを意味します 前処理番号-定義は、数字の大きな範囲を含みます.Hexや他の表現をサポートするために、文字、数字、期間、アンダースコアの任意の組み合わせを10進数後に許可されます.これにより、多くの不正なフォームがこのステップを介してスリップすることができます.しかし、それらはコンパイラによって拾われるでしょう. stringリテラル--これは、角括弧で包まれたinclude文の定数、文字定数、引数を含みます. punctuators - '@'や'$'と''以外のASCIIでの句読点は全て句読点と見なされます.句読点はコンパイラに意味を持っていますが、その意味はそれらが見つかる文脈に依存します.
これはほとんどの人がC/C++プリプロセッサと関連するステップです.トークンのストリームが前処理言語で何も含んでいないなら、単にコンパイラに渡されます.
前処理言語で使用される共通の特徴は以下の通りである.
ヘッダーファイルにはC/C++宣言と他のプリプロセッサ命令が含まれます.ヘッダーファイルは、システムAPIを使用するためにOSによって提供されたり、ユーザが定義することができます.これはファイル間の定義を共有することができます.
すべての中間のステップによって出力されるコードを見るならば、我々は何かを見ます
マクロはオブジェクトのようになります.オブジェクトのようなマクロはデータオブジェクトのように見えます.これらは最もよく使われ、シンボル定数Egに名前を付ける.
マクロはパフォーマンスを向上させるために使用されます.コードがちょうどプリプロセッサによってコピーされるので、アセンブリ出力で、機能スタックでオーバーヘッドが全くありません.このパラグラフは、マクロの表面をかろうじて傷をつけます、そして、彼らがどれくらい役に立つかは、しかし、それらはコンパイラの重要な部分ではありません.
これにより、プリプロセッサはコードの次のセクションをコンパイラに渡すかどうかを決定できます.これが有用ないくつかの状況があります.例えば、includeガードです.インクルードガードは、同じヘッダーファイルがコピーされるのを防ぎます.これはコンパイルをスピードアップします.
これは前処理されたコードを受け取り、それからアセンブリを生成する段階です.GCCコンパイラは、入力プログラムの表現を越えて複数のパスを作成し、マシンコードを出力する前に異なる中間表現に変換します.
以下の図は関係するステップを示します3
これはコンパイラのフロントエンドです.上記のCプリプロセッサについての詳細はこのステップにあてはまります.プリプロセッサがその仕事をしたあと、コンパイラはトークンストリームから抽象構文木(AST)を生成します.今のところ、これは入力プログラムを表す木データ構造と考えられます.ASTは非常に簡単に変更し、変換するので、入力の内部表現として有用です.
各フロントエンドは別の方法で動作しますが、通常ジェネリック(またはgimple)という形式の出力を終了します.この段階の歴史と内部の作業は少し複雑で混乱しているようですが、一般的に出力され、コンパイラに渡される限り、私たちは幸せです.
このパスは一般的な表現をgimple表現に変換することを含んでいます.しかし、これはASTよりも制限的な中間表現です.後で最適化パスに使用されます.最も基本的に、gimpleは一般的な式を表すタプルのコレクションです.構造上の顕著な点は以下の通りである. 式あたり3オペランドしか使えません.3つ以上の場合、式は小さい部分に分割されます. すべての制御フローは、条件とgotoステートメントで構成されます.
このステップでは、現在のコードの内部表現を複数パス渡します.SSAは単一の静的代入を表します.各変数が一度だけ割り当てられるように、gimple表現を変更します.変数が複数回割り当てられる必要がある場合、新しい変数が作成されます.
上で参照されるGCC内部マニュアルのセクション9.4でわかるように、このステップは47の異なる最適化を含みます.これらはループ、コンディショナルズを最適化し、unreachableコードや他の複雑に見える操作を除去する.適用される変更を伴う新しいGimple表現はこのステップから返されて、次へ通過します.
RTLはレジスタ転送言語を表し、目的のマシンコード出力に近づいている.Gimple表現はRTLに翻訳されます.そして、それは無限の数のレジスタで抽象的なプロセッサーを仮定します.多くのパスは、さらにこのコードを最適化し、この形式で行われます.最適化されたフォームはGCCバックエンドに渡されます.
フロントエンドのように、GCCはプラットフォーム特有のマシンコードの出力に対処する多くのバックエンドをサポートしています.これらはx 86 , i 386およびarmを含みます.最適化されたRTLを指定すると、バックエンドは指定されたプラットフォームのアセンブリを発行します.最も一般的に使われるバックエンドはGNUアセンブラである.これは1986年にリリースされたが、まだ頻繁に使用されている.
アセンブリの出力はオブジェクトファイルです.これをリンカに渡す.
コンパイルおよびアセンブリ処理は、個々のファイルに対して行われる.リンカは、ヘッダーと実装ファイルか外部ライブラリであるかどうかにかかわらず、プロジェクトのすべての部分をつなぐのに用いられます.関数が別のファイルでExexternまたは類似して定義されている場合、それは実際に定義されているかどうかをチェックするリンカの仕事です.数千行のコードを持つプロジェクトでは、ファイルの1つの小さな変更のためにすべてを再コンパイルすることは大きな痛みになるでしょう.代わりに、変更を含むファイルが再コンパイルされ、プロジェクトが再リンクされます.
一般にGNU/Linuxシステムでは、プログラムldが使用されます.これはGNUリンカです.gccプログラムはldに対する呼び出しをラップして開発者にとってより楽になるようにします.
これは、巨大な巨大なGCCプロジェクトでちょうど垣間見ることでした.技術的なドキュメントを選ぶことによって、コンパイラがどのように構築されるべきかについての概観を得ることができました.この分析の後、私は自分のコンパイラを計画し始めることができました.
"The C Preprocessor" 畝↩
"GCC Internals" 畝↩
"GCC Internals/GCC Architecture - Wikibooks." 畝↩
"c++ - How does the compilation/linking process work? - Stack Overflow." 畝↩
コンパイラはもともとGNUオペレーティングシステム用のベンダコンパイラを使用するためのライセンスの支払いを避けるために構築されました.それ以来、それは人気とパフォーマンスの両方で過去の独自のコンパイラを上昇している.
ユーザーがコマンドを実行したときに開始されるプロセス
gcc myfile.c
端末では、前処理、コンパイル、アセンブリ、リンクの4つの手順に分割できます.プリプロセッサ1
プリプロセッサは、コンパイル前にソースコードに変更を加えることに対処します.C/C++プリプロセッサを特に見ています.他のプリプロセッサまたはフロントエンドは、上述のように異なる言語で利用可能である.この段階で一定量のレクチンが発生する.実行するアクションは以下の通りです.
ラインスプライシング
エスケープされた改行が使用されている場合、これはソースの行を傾ける.エスケープされた改行文字は、行を分割するために使用されるバックスラッシュです.それらはトークンの間に、またはコメントの中に現れなければなりません.
コード内のすべてのコメントは、単一のスペースに置き換えられます.
調整
これらの変換が完了すると、プログラムはプリプロセッサトークンに分割される.これらのトークンはコンパイラに渡され、コンパイラトークンとして使用されます.現在のところ、プリプロセッサトークンは5つのカテゴリーに分けられることができます:識別子、前処理番号、ストリングリテラル、句読点、および他.
指令とマクロ処理
これはほとんどの人がC/C++プリプロセッサと関連するステップです.トークンのストリームが前処理言語で何も含んでいないなら、単にコンパイラに渡されます.
前処理言語で使用される共通の特徴は以下の通りである.
ヘッダーファイルを含む
ヘッダーファイルにはC/C++宣言と他のプリプロセッサ命令が含まれます.ヘッダーファイルは、システムAPIを使用するためにOSによって提供されたり、ユーザが定義することができます.これはファイル間の定義を共有することができます.
すべての中間のステップによって出力されるコードを見るならば、我々は何かを見ます
#include <xyz.h>
の内容に置き換えられるxyz.h
マクロ処理
マクロはオブジェクトのようになります.オブジェクトのようなマクロはデータオブジェクトのように見えます.これらは最もよく使われ、シンボル定数Egに名前を付ける.
#define MAX_SIZE = 1024
名前MAX_SIZE
我々のコードで使用され、プリプロセッサはこのテキストを1024
関数のようなマクロは関数呼び出しのようになります.定義されたコードが呼び出される場所にコピーされます.エ.#define SQUARE(x) x*x
どこSQUARE(X)
コードに表示され、x*x
, ここでxは正方形に渡される値です.マクロはパフォーマンスを向上させるために使用されます.コードがちょうどプリプロセッサによってコピーされるので、アセンブリ出力で、機能スタックでオーバーヘッドが全くありません.このパラグラフは、マクロの表面をかろうじて傷をつけます、そして、彼らがどれくらい役に立つかは、しかし、それらはコンパイラの重要な部分ではありません.
条件付編集
これにより、プリプロセッサはコードの次のセクションをコンパイラに渡すかどうかを決定できます.これが有用ないくつかの状況があります.例えば、includeガードです.インクルードガードは、同じヘッダーファイルがコピーされるのを防ぎます.これはコンパイルをスピードアップします.
// An example include guard
#ifndef __MY_HEADER
#define __MY_HEADER
...
#endif
コンピレーション2
これは前処理されたコードを受け取り、それからアセンブリを生成する段階です.GCCコンパイラは、入力プログラムの表現を越えて複数のパスを作成し、マシンコードを出力する前に異なる中間表現に変換します.
以下の図は関係するステップを示します3
解析パス
これはコンパイラのフロントエンドです.上記のCプリプロセッサについての詳細はこのステップにあてはまります.プリプロセッサがその仕事をしたあと、コンパイラはトークンストリームから抽象構文木(AST)を生成します.今のところ、これは入力プログラムを表す木データ構造と考えられます.ASTは非常に簡単に変更し、変換するので、入力の内部表現として有用です.
各フロントエンドは別の方法で動作しますが、通常ジェネリック(またはgimple)という形式の出力を終了します.この段階の歴史と内部の作業は少し複雑で混乱しているようですが、一般的に出力され、コンパイラに渡される限り、私たちは幸せです.
角化パス
このパスは一般的な表現をgimple表現に変換することを含んでいます.しかし、これはASTよりも制限的な中間表現です.後で最適化パスに使用されます.最も基本的に、gimpleは一般的な式を表すタプルのコレクションです.構造上の顕著な点は以下の通りである.
SSAツリー
このステップでは、現在のコードの内部表現を複数パス渡します.SSAは単一の静的代入を表します.各変数が一度だけ割り当てられるように、gimple表現を変更します.変数が複数回割り当てられる必要がある場合、新しい変数が作成されます.
上で参照されるGCC内部マニュアルのセクション9.4でわかるように、このステップは47の異なる最適化を含みます.これらはループ、コンディショナルズを最適化し、unreachableコードや他の複雑に見える操作を除去する.適用される変更を伴う新しいGimple表現はこのステップから返されて、次へ通過します.
RTL
RTLはレジスタ転送言語を表し、目的のマシンコード出力に近づいている.Gimple表現はRTLに翻訳されます.そして、それは無限の数のレジスタで抽象的なプロセッサーを仮定します.多くのパスは、さらにこのコードを最適化し、この形式で行われます.最適化されたフォームはGCCバックエンドに渡されます.
組立
フロントエンドのように、GCCはプラットフォーム特有のマシンコードの出力に対処する多くのバックエンドをサポートしています.これらはx 86 , i 386およびarmを含みます.最適化されたRTLを指定すると、バックエンドは指定されたプラットフォームのアセンブリを発行します.最も一般的に使われるバックエンドはGNUアセンブラである.これは1986年にリリースされたが、まだ頻繁に使用されている.
アセンブリの出力はオブジェクトファイルです.これをリンカに渡す.
リンク4
コンパイルおよびアセンブリ処理は、個々のファイルに対して行われる.リンカは、ヘッダーと実装ファイルか外部ライブラリであるかどうかにかかわらず、プロジェクトのすべての部分をつなぐのに用いられます.関数が別のファイルでExexternまたは類似して定義されている場合、それは実際に定義されているかどうかをチェックするリンカの仕事です.数千行のコードを持つプロジェクトでは、ファイルの1つの小さな変更のためにすべてを再コンパイルすることは大きな痛みになるでしょう.代わりに、変更を含むファイルが再コンパイルされ、プロジェクトが再リンクされます.
一般にGNU/Linuxシステムでは、プログラムldが使用されます.これはGNUリンカです.gccプログラムはldに対する呼び出しをラップして開発者にとってより楽になるようにします.
これは、巨大な巨大なGCCプロジェクトでちょうど垣間見ることでした.技術的なドキュメントを選ぶことによって、コンパイラがどのように構築されるべきかについての概観を得ることができました.この分析の後、私は自分のコンパイラを計画し始めることができました.
"The C Preprocessor" 畝↩
"GCC Internals" 畝↩
"GCC Internals/GCC Architecture - Wikibooks." 畝↩
"c++ - How does the compilation/linking process work? - Stack Overflow." 畝↩
Reference
この問題について(コンパイラ・シリーズ), 我々は、より多くの情報をここで見つけました https://dev.to/miiizen/compiler-series-part-2-gcc-2mm4テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol