Compiler-design-intermediate-code-generations

提供:Dev Guides
移動先:案内検索

コンパイラ-中間コード生成

ソースコードをターゲットマシンコードに直接変換できますが、ソースコードを中間コードに変換してからターゲットコードに変換する必要があるのはなぜですか? 中間コードが必要な理由を見てみましょう。

中間コード

  • コンパイラが、中間コードを生成するオプションを持たずにソース言語をターゲットマシン言語に翻訳する場合、新しいマシンごとに完全なネイティブコンパイラが必要です。
  • 中間コードは、すべてのコンパイラーで分析部分を同じに保つことにより、すべてのユニークなマシンの新しい完全なコンパイラーの必要性を排除します。
  • コンパイラの2番目の部分である合成は、ターゲットマシンに応じて変更されます。
  • 中間コードにコード最適化手法を適用することにより、ソースコードの変更を適用してコードのパフォーマンスを向上させることが容易になります。

中間表現

中間コードはさまざまな方法で表すことができ、独自の利点があります。

  • *高レベルIR *-高レベルの中間コード表現は、ソース言語自体に非常に近いです。 ソースコードから簡単に生成でき、コードの変更を簡単に適用してパフォーマンスを向上させることができます。 ただし、ターゲットマシンの最適化にはあまり好ましくありません。
  • *低レベルIR *-これはターゲットマシンに近いため、レジスタおよびメモリの割り当て、命令セットの選択などに適しています。 マシン依存の最適化に適しています。

中間コードは、言語固有(Javaのバイトコードなど)または言語に依存しない(3アドレスコード)のいずれかです。

3アドレスコード

中間コードジェネレーターは、注釈付き構文ツリーの形式で、先行フェーズであるセマンティックアナライザーから入力を受け取ります。 次に、その構文ツリーは、後置記法などの線形表現に変換できます。 中間コードは、マシンに依存しないコードになる傾向があります。 したがって、コードジェネレーターは、コードを生成するためのメモリストレージ(レジスタ)の数に制限がないと想定しています。

例えば:

a = b + c *d;

中間コードジェネレーターは、この式をサブ式に分割し、対応するコードを生成しようとします。

r1 = c* d;
r2 = b + r1;
a = r2

ターゲットプログラムでレジスタとして使用されているr。

3アドレスコードには、式を計算するための最大3つのアドレス位置があります。 3つのアドレスコードは、4つと3つの2つの形式で表すことができます。

四倍

4倍表現の各命令は、operator、arg1、arg2、およびresultの4つのフィールドに分かれています。 上記の例は、以下の4倍形式で表されます。

Op arg1 arg2 result
* c d r1
+ b r1 r2
+ r2 r1 r3
= r3 a

トリプル

トリプル表示の各命令には、op、arg1、およびarg2の3つのフィールドがあります。それぞれの部分式の結果は、式の位置によって示されます。 トリプルは、DAGおよび構文ツリーとの類似性を表します。 式を表現する場合、DAGと同等です。

Op arg1 arg2
* c d
+ b (0)
+ (1) (0)
= (2)

トリプルは最適化中にコードが動かないという問題に直面します。結果は位置であり、式の順序または位置を変更すると問題が発生する可能性があるためです。

間接トリプル

この表現は、トリプル表現を拡張したものです。 位置の代わりにポインターを使用して結果を保存します。 これにより、オプティマイザは部分式を自由に再配置して、最適化されたコードを生成できます。

宣言

変数またはプロシージャは、使用する前に宣言する必要があります。 宣言には、メモリ内のスペースの割り当てと、シンボルテーブル内のタイプと名前のエントリが含まれます。 プログラムは、ターゲットマシンの構造を念頭に置いてコーディングおよび設計できますが、ソースコードをターゲット言語に正確に変換できるとは限りません。

プログラム全体をプロシージャとサブプロシージャのコレクションとして使用すると、プロシージャにローカルなすべての名前を宣言することが可能になります。 メモリの割り当ては連続して行われ、名前はプログラムで宣言された順序でメモリに割り当てられます。 オフセット変数を使用し、ベースアドレスを示すゼロ\ {offset = 0}に設定します。

ソースプログラミング言語とターゲットマシンのアーキテクチャは、名前の保存方法が異なる場合があるため、相対アドレス指定が使用されます。 最初の名前にはメモリ位置0 \ {offset = 0}から始まるメモリが割り当てられますが、後で宣言される次の名前には、最初の名前の隣にメモリが割り当てられます。

例:

整数変数に2バイトのメモリが割り当てられ、フロート変数に4バイトのメモリが割り当てられるCプログラミング言語の例を取り上げます。

int a;
float b;

Allocation process:
{offset = 0}

   int a;
   id.type = int
   id.width = 2

offset = offset + id.width
{offset = 2}

   float b;
   id.type = float
   id.width = 4

offset = offset + id.width
{offset = 6}

この詳細をシンボルテーブルに入力するには、プロシージャ_enter_を使用できます。 このメソッドの構造は次のとおりです。

enter(name, type, offset)

このプロシージャは、変数_name_のシンボルテーブルにエントリを作成する必要があります。そのタイプはデータエリアでtypeおよび相対アドレス_offset_に設定されます。