コンパイル技術 - その他の理論

1.理論

  • フロントエンド: 字句解析、文法解析、記号表の作成、意味解析、中間コード生成、エラー処理を含む解析部分

  • バックエンド: マシン依存コードの最適化、オブジェクト コードの生成、シンボル テーブルの操作、エラー処理を含む包括的な部分

  • コンパイルには、字句解析、構文解析、意味解析と中間コード生成、コード最適化、ターゲット プログラム生成の5 つの段階があります。シンボル テーブル管理とエラー処理の2 つが実行されますまた、7 つの論理部分を構成します。

  • アルファベットと文字列

    • 記号: a 、 b 、 c 、 ϕ a 、 b 、 c 、 \phiなど、1 文字ずつ理解できる_b c φ
    • アルファベット: 記号の集合です。たとえば、∑ = { a , b , ϕ } \sum = \{a,b,\phi\}={ _b } _
    • シンボル文字列: 一連のシンボルです: abababa abababaなど父親
    • 空の記号列: 記号を含まない記号列、つまりϵ \epsilonϵ
    • シンボル文字列のセット: シンボル文字列のコレクション
    • 言語: 記号文字列の特別なコレクション
  • 文法は、言語の構造を規定する文法です。

  • 左再帰文法はトップダウン分析法を使えないだけで、役に立たないわけではない

  • ランタイム ストレージの組織と管理者が回答すべき質問は次のとおりです。

    • メモリ内でのデータ構造の表現方法

    • 関数がメモリ内でどのように表現されるか

      • 通話の終了後に戻ることができるように、通話前の状態を記憶する必要があります。
      • 呼び出し元の関数と呼び出された関数の間でパラメーターを渡し、値を返す方法を提供する必要がある
      • スコープの要件を満たし、すべてのデータ構造を見つけることができる
    • それらをメモリに配置して管理する方法 (ガベージ コレクション メカニズムに関する特別なトピックがあります)

    • オペレーティング システムがメモリを配列にカプセル化し、コンパイラがこの配列の使用方法を決定します。

  • ストレージ管理には、主に次の 2 つの側面があります。

    • 静的ストレージ管理は実験で.data
    • 動的ストレージ管理、この部分はコンパイル実験で 2 つの部分に分割され、1 つの部分は llvm の変換中に完成します。たとえば、スタック シンボル テーブルのようなものがここに表示され、1 つの部分は mips の変換中に完成します。関数実行スタック。その結果、統一的なシステムを確立することは不可能と思われ、表面的には理論との齟齬があるとしか言いようがありません。
  • 分割されていないプログラムの構造言語: 個別にコンパイルできる各プログラム単位は、サブモジュールを含まない単一のモジュールです。FORTRAN言語など。また、よく言われるModuleという概念もこの言語から来ているのかもしれません。ここに画像の説明を挿入

  • IR (中間表現): さまざまなレベルの IR が、階層化された抽象化を通じてさまざまなレベルの抽象化 (型、操作) を表し、最大限の再利用を実現します。

  • 誤分類:

    • 文法上の誤り: 語彙および文法規則に従わない誤り。
    • セマンティック エラー:
      • プログラムはセマンティック ルールに準拠していません。たとえば、定義されていない場合は、それが使用されます。
      • スタック オーバーフロー、型オーバーフローなど、特定のコンピューター システムの制限を超える。

2. 圧縮文法

圧縮する必要がある文法には、次の 2 種類があります。

  • 有害规则,就是 U : : = U U::=U ::=この種のUは非常に明白なので、これについては説明しません。
  • 文法規則の左側部分 (左側部分は記号) の冗長な規則は、次のように分割されます。
    • 到達不能記号:識別記号から推測できない左の部分なので、到達不能です。これは非常に簡単に識別でき、1 つのシンボルの BFS を実行するだけで済みます。
    • 非アクティブなシンボル、つまり $A ::= Aa $ の形式のルールは、無限に一定のループを引き起こします。

いわゆる圧縮文法は、これらすべてのルールを削除した文法です。駆除の際の注意点は以下の2点です。

  • 非アクティブなシンボルの決定は、特定のルールの最適化に関連するだけでなく、左部分がこのシンボルであるすべてのルールにも関連しています。たとえば、A : : = A a A A::=Aa のみの場合です::=A a は間違っていますが、もしA : : = A a ∣ c A::=Aa | c::=A a | cは正しいです。端的に言えば、「無活動」はルールではなく「シンボル」を決定します。
  • シンボルを削除すると、シンボルの性質が変化する場合があるため、ルールを削除した後は、有害なルールや冗長なルールがないか再検討する必要があります。

S : : = ABC ∣ CDA : : = A a ∣ a B : : = B b C : : = C c ∣ c D : : = D d ∣ d E : : = e S ::= ABC | CD \\ A ::= Aa | a \\ B ::= Bb \\ C ::= Cc | c \\ D ::= Dd | d \\ E ::= eS::=A BC C D::=_B::=B bC::=C c cD::=Dd∣d _ _ _::=e

明らかにEEEは到達不能シンボルで、BBBは非アクティブなシンボルです。A : : = A a A::=Aaもありますが::=Aそのようなルールですが、 A : : = a A::=aがあるためです。::=そのようなルールが存在するので、A , C , DA,C,D_C Dはどちらでもないので、まずB 、 EB,EB E
S : : = ABC ∣ CDA : : = A a ∣ a C : : = C c ∣ c D : : = D d ∣ d S ::= ABC | CD \\ A ::= Aa | \\ C ::= Cc | c \\ D ::= Dd | dS::=A BC C D::=_C::=C c cD::=D d d
これが終わったらまずいBBBはなくなりましたが、まだS : : = ABCS::=ABCがありますS::=A BCなのでこのルールを削除
S : : = CDA : : = A a ∣ a C : : = C c ∣ c D : : = D d ∣ d S ::= CD \\ A ::= Aa | a \\ C ::= Cc | c \\ D ::= Dd | dS::=C D::=_C::=C c cD::=D d d
しかし、AAA は到達不能なシンボルになるので、引き続き削除
S : : = CDC : : = C c ∣ c D : : = D d ∣ d S ::= CD \\ C ::= Cc | c \\ D ::=日|日S::=C DC::=C c cD::=Dd∣d _ _ _

3.逆ポーランド

後置式は逆ポーランド式とも呼ばれ、ポーランド式とも呼ばれます。

中置式を接尾辞式に変換するには、スタック構造と優先度で操作を行う必要がありますが、これでは面倒なので、ツリーを描画することを検討してから、ツリーに対してポストオーダー トラバーサルを実行し、得られたトラバーサル シーケンスを接尾辞式です。たとえば、
A − ( C + B ) × ( D − C ) A - (C + B) \times (D - C)( C+×( DC )
木があります

ここに画像の説明を挿入

ポストオーダー トラバーサル シーケンスを取得するのは簡単です:
ACB + DC − ∗ − ACB+DC-*-CB _+D C

4. 動的ストレージ割り当て

プログラムモジュール(モジュール)に入るときは、実行中のスタックにスタックをプッシュする必要があります。率直に言えば、実行中のスタックはプログラムの実行中のスタックであり、特定のモジュールの実行中のスタックではありません。

スタックにプッシュされるのはモジュールに属し、アクティベーション レコード (Activation Record、AR)と呼ばれるモジュール専用のデータ領域です。

ここに画像の説明を挿入

データ領域については、その機能は非常に明白です。

ここに画像の説明を挿入

パラメータ領域の場合、そのパーティションは

ここに画像の説明を挿入

主に prevabp を理解するため:前のアクティブ レコード ベース ポインタ 前のアクティブ レコードのベース アドレス端的に言えば、mips では、関数を呼び出すたびに、 を使用してsub sp実装されるadd sp。この操作はスタック ポインターに対して行われますが、スタック ポインターに prevabp の値を直接入力して、ポップアップ操作を完了します。

表示領域については、

ここに画像の説明を挿入

表示のアルゴリズムについて説明する前に、最初に表示の機能について説明しましょう。これは、内側のモジュールが外側のモジュールに便利にアクセスできるようにすることです。では、なぜ理解するのが難しいのでしょうか。C言語には表示領域がなく、llvmは「内部モジュールが外部モジュールに簡単にアクセスできるようにする」という要件をさらに弱めているためです。

C言語の場合、関数内に関数を定義する操作はありませんので、関数は仮引数の影響のみを受け、戻り値を通じて外界に影響を与えるだけのモジュールです.表示は必要ありません.ブロック、確かに内部変数と外部変数の区別がありますが、llvm を翻訳すると、内部変数と外部変数はスタック シンボル テーブルを介して 2 つの変数にリネームされ、内部変数と外部変数の区別がなくなりましたので、表示mips に変換する場合は不要です。

表示領域の生成アルゴリズムに関しては、全部で2つのルールがあり、どちらもスタック構造と同様に非常に直感的で理解しやすいものです。

ここに画像の説明を挿入

ここに画像の説明を挿入

また、表示領域は主にパスカルプログラムで使用されるため、いくつかの小さな構文をマスターする必要があります。例は次のとおりです。

{程序名}
program calFactorialAndFubnacii;   
 {声明属于 program 的变量}
 var m: integer;
 {阶乘计算}
 function factorial(n: integer): integer;
 begin
     {这是一整条 if-else 语句,所以只有结尾有分号}
     if n < 2 then
         {函数名就是返回值}
         factorial := n
     else
         factorial := n * factorial(n - 1);
 end;
begin
 write('please input the value you want to calculate: ');
 { read 用于读入}
 read(m);
 {* 
 'string' 表示字符串
 write 可以连续输出
 writeln 用于换行,没法支持 \n 
 *}
 writeln('factorial of ', m, ' is ', factorial(m));
 {begin end. 是主程序}
end.

例として質問を取ります:

program
    const ten = 10;
  	var i, j, k: integer;
        matrix: array[1..ten] of integer; 
    procedure add(m1, m2: integer; var rlt: integer);
        var tmp: integer;
		function mul(x, y: integer): integer;
        begin 
        	// 程序运行到了这里
        	mul := x * y
        end;
	begin 
  		tmp := mul(m1, m2);
		rlt := m1 + m2 + tmp;
    end;
begin 
	i := 1;
	j := 2;
	add(i, j, k);
	write(k);
end.

実行スタックを描画します。

コンテンツを指し示すポインターを使用するため、図をめちゃくちゃにするのは簡単ですが、クイズの答えは、手でポインターを描画する代わりに、テキストを使用して説明するというより簡単な方法を提供します。

最初に実行するコードは次のとおりです。

program
    const ten = 10;
  	var i, j, k: integer;
        matrix: array[1..ten] of integer; 
begin 
	i := 1;
	j := 2;
	// 即将运行
	add(i, j, k);
	write(k);
end.

i, j, k, matrixこれらのローカル変数が宣言されていることがわかりますten(これらは定数であり、動的領域には配置されませんが、コンパイル時に直接置き換えられる定数テーブルがあるため、シンボル テーブルには表示されません)。整数の場合は、それを直接宣言するだけです.配列の場合は、(当然のことながら)配列テンプレートを記録してから、それを記録する必要があります。

メイン機能のため、パラメータエリアや表示エリアはありません。

したがって、最終的なスタック構造は次のようになります

ここに画像の説明を挿入

次に実行しますadd(i, j, k)

procedure add(m1, m2: integer; var rlt: integer);
	var tmp: integer;
	function mul(x, y: integer): integer;
	begin 
		mul := x * y
	end;
	begin 
		// 即将运行这里
		tmp := mul(m1, m2);
		rlt := m1 + m2 + tmp;
end;

このモジュールが 3 つの仮パラメーターとローカル変数を宣言しているaddことがわかりますしたがって、スタックに反映する必要があります。m1, m2, rlttmp

また手続きaddなので戻り値はありません。最終的なスタック構造は次のとおりです (図ではretadd(1) 指向这里間違っています。戻り値はスタック上に配置できず、コード セグメントを指しています)。

ここに画像の説明を挿入

次に、これを実行します

function mul(x, y: integer): integer;
begin 
	// 程序运行到了这里
	mul := x * y
end;

ここで宣言されているx, y2 つの。

表示領域の世代を最初にコピーする必要があります。

ここに画像の説明を挿入

5、積み上げ記号表

このことは非常に明白であり、基本的にはllvmの生成プロセスで使用されます。これは明らかなプロセスです。注意すべき唯一のことは、ヘッダーの問題です。

型はデータの型であり、型にはパラメーターか変数かなどの特定のセマンティック情報が含まれます。

上記のpascalコードシンボル テーブルを作成します。

シリアルナンバー 名前 タイプ タイプ レベル
1 定数 整数 1
2 だった 整数 1
3 j だった 整数 1
4 k だった 整数 1
5 マトリックス だった 配列 1
6 追加 プロセス 1
7 m1 ために 整数 2
8 m2 ために 整数 2
9 株式会社 ために 整数 2
10 tmp だった 整数 2
11 ムル 機能 整数 2
12 バツ ために 整数 3
13 y ために 整数 3

おすすめ

転載: blog.csdn.net/living_frontier/article/details/129981116