コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3...

25
コンパイラ 解説資料集 3 2 3 4 lex yacc による 6 8 による 10 プログラム メモリ 割り 12 コールスタック 13 コード 14 ブロック フローグラフ 15 16 大域 18 データフロー 20 コード 22 グラフ によるレジスタ割り 24 http://www.cs.info.mie-u.ac.jp/~toshi/lectures/compiler/ 2020 6 1

Transcript of コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3...

Page 1: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料集 3

中間表現の種類 2

抽象構文木と後置記法 3

構文主導翻訳 4

lex と yacc による構文主導翻訳 6

構文主導翻訳の例 8

構文主導翻訳による属性値の受け渡し 10

プログラム実行時のメモリの割り当て 12

手続き実行時のコールスタックの動作 13

コード最適化の有無の比較 14

基本ブロックと制御フローグラフ 15

局所最適化 16

大域最適化 18

データフロー解析 20

算術式の目的コード生成 22

グラフ彩色によるレジスタ割り当て 24

山田 俊行

http://www.cs.info.mie-u.ac.jp/~toshi/lectures/compiler/

2020年 6月

1

Page 2: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

中間表現の種類

コンパイラがフロントエンド (解析部)で得たソースコードの情報は,木構造や命令列の形の中間表現と

してバックエンド (合成部)に渡る.

●中間表現 (intermediate representation, IR)

ソースコードの情報を,目的コードの種類に依存しない形で表したもの

最適化やコード生成をしやすい形にしたプログラムの表現

●抽象構文木 (abstract syntax tree, AST)

演算子やキーワードを節とし,演算対象などの構成要素を子として表す木

y = (x + 10) * 2

=jjjjjj

SSSSSS

y *jjjjjj UUUUUU

+kkkkkk UUUUUU 2

x 10

ソースコードを複数回走査する場合に,2度目以降の走査対象として利用

※プログラムの構文木は,解説資料「字句解析と構文解析」の例を参照

●後置コード (postfix code)

(仮想的な)スタック計算機の機械語コード

y = (x + 10) * 2

load xloadc 10addload 2mulstore y

データがそろってから演算を実行し,中間結果はスタック上に保持

● 3番地コード (3-address code)

データを処理する命令が x = y op z の形に表せるコード (飛び越しなどの制御命令もある)

num = 62;ones = 0;while num > 0 do

if num % 2 == 1 thenones = ones + 1;

finum = num / 2;

done

num = 62ones = 0

L1: _t1 = num > 0ifnot _t1 goto L2_t2 = num % 2_t3 = _t2 == 1ifnot _t3 goto L3_t4 = ones + 1ones = _t4

L3: _t5 = num / 2num = _t5goto L1

L2:

中間結果を一時変数 (上記の例では _ti)に格納する形で実行

2

Page 3: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

抽象構文木と後置記法

抽象構文木や後置記法を使うと,コンパイラの意味解析や中間コード生成を見通しよく実現できる.

● (構文)解析木 (parse tree)

文法の規則に沿って入力字句列が生成される過程を表す木

※解析木の走査順が構文解析器の実行順に対応

例 (算術式 ((x + 10) * 2) の解析木)

exp

aaaaaaaaaaaaaaaaaaaaaaaaaadddddddddddddd

QQQQQXXXXXXXXX

exp

fffffffffmmmmm

QQQQQXXXXXXXXX exp

exp exp

( ( IDx

+ NUM10

) * NUM2

)

exp

ccccccccccccccccgggggggg

WWWWWWWW\\\\\\\\\\\\\\\\

( exp

dddddddddddkkkkkk

TTTTTTZZZZZZZZZZZ * exp )

( exp + exp ) NUM2

IDx

NUM10

●抽象構文木 (abstract syntax tree, AST)

演算子やキーワードを節とし,演算対象などの構成要素を部分木として表す木

解析木に字句の意味情報を追加,意味に無関係な部分(区切り記号など)を省略

例 (算術式 ((x + 10) * 2) の構文木)

nnnnnnPPPPPP

nnnnnnPPPPPP

●後置記法 (postfix notation)

演算子をオペランド (演算対象)の後に置く記法

算術式 eを後置記法で表した式 ⟨ e ⟩を,次の通り帰納的に定義(1) eが部分式 e1, e2と中置演算子 op からなる式 (e1 op e2)のとき,⟨ e ⟩は列 ⟨ e1 ⟩ ⟨ e2 ⟩ op(2) eが変数か定数のとき,⟨ e ⟩は e自身

例 (算術式 ((x + 10) * 2) の後置式への変換)

⟨ ((x + 10) * 2) ⟩

= ⟨ (x + 10) ⟩ ⟨ 2 ⟩ * ((1)より)

= ((1)より)

= ((2)より)

3

Page 4: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

構文主導翻訳

構文主導翻訳は,構文解析時の動作を,生成規則の右辺中に埋め込んで指定する.各文法記号に,意味

情報を表す属性を与えて,その値を 定義・参照 しながら,指定位置で動作を実行する.この方法で,

意味解析や中間コード生成を簡潔に書ける.

●上昇型の構文主導翻訳の例

構文主導翻訳

次の構文主導翻訳は,算術式の列の文法に,値の計算と表示の動作を付加したものである.

prog → prog com| ε

com → exp ; { /*(1)*/ print(exp.val); }exp → ( exp + exp ) { /*(2)*/ exp.val = exp1.val + exp2.val; }

| ( exp * exp ) { /*(3)*/ exp.val = exp1.val * exp2.val; }| NUM { /*(4)*/ exp.val = NUM.val; }

この文法は,上昇型の構文解析向けのものであり,規則右辺の末尾まで解析が進んだとき,

つまり,構文解析器の還元動作のときに,{ } 内の動作を実行する.

各文法記号の属性は 文法記号.属性名 で表す.上の例では,非終端記号 exp と字句 NUM

の値を表す属性 exp.val と NUM.val を使う.

左辺が com の規則の動作は,右辺の式の値 exp.val の表示である.左辺が exp の規則の

動作は,左辺の属性値 exp.val を右辺の部分式の属性値から求める.和や積の規則のよ

うに,規則に同じ文法記号が複数現れる場合,exp1, exp2 のように,右辺の各出現を数字

で区別する.例えば,exp.val = exp1.val + exp2.val は,( e1 + e2 ) の形の式の

値 exp.val を部分式 e1 と e2 の値 exp1.val と exp2.val の和で求める.なお,字句の

属性 NUM.val は,字句解析器から得られるものと想定する.

属性付き解析木 prog

eeeeeeeeeeeeeeeeeeeee

ZZZZZZZZZZZZZZZZZZZZZZZ

prog com

rrrrrrrr

QQQQQQQQQQ

exp.val =

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaddddddddddddddddd

SSSSSSSYYYYYYYYYYYY

exp.val =

eeeeeeeeeeeejjjjjjj

TTTTTTTYYYYYYYYYYYY

exp.val =

exp.val =

exp.val =

ε ( ( NUM.val =

+ NUM.val =

) * NUM.val =

) ;

( ( 10 + 30 ) * 50 ) ;

出力

属性付き解析木

属性付き字句列

入力文字列

属性付き解析木は,解析木の各節の属性値が,{ } 内の属性値の定義からどう決まるかを表す.上記の exp 規則の動作のように,規則左辺の属性値が規則右辺の属性値から定まる

場合,属性付き解析木では,子の属性値から親の属性値が決まる.

4

Page 5: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

●下降型の構文主導翻訳の例

文脈自由文法による構文主導翻訳

算術式列の文法の左再帰を除去して共通部分を括り出せば,再帰下降型の構文解析に適し

た文法が得られる.

prog → com prog| ε

com → exp ; { print(exp.val); }exp → ( exp op exp ) { exp.val = eval(exp1.val, op.type, exp2.val); }

| NUM { exp.val = NUM.val; }op → + { op.type = ’+’; }

| * { op.type = ’*’; }

下降型の構文解析向けの文法の場合も,上昇型と同様に,生成規則の右辺中の適切な位置

の { } 内に動作を埋め込む.ただし,文法の変形に伴って動作にも手を加える必要がある.

動作記述中の eval(v1, ty, v2) は,二つの部分式の値 v1, v2 から演算子の種類 ty に

応じて式全体の値を求める関数である.

再帰下降型の構文主導翻訳で,右辺の最後に左辺の属性値を決める場合には,構文解析手

続きの戻り値としてその値を返すと,翻訳の動作を見通しよく実現できる.

拡張文脈自由文法による構文主導翻訳

算術式列の文法は,右辺に選択や反復を許すと次の形で簡潔に書ける.

prog → 【 exp ; 】⋆

exp → ( exp【 + | * 】exp )| NUM

拡張文脈自由文法に対する構文主導翻訳には,確立した表示法がない.作動を付加するに

は,動作番号 { i } を規則内に書き,動作を別に指定すると読み易い.

prog → 【 exp {1} ; 】⋆

exp → ( exp【 + {2} | * {3} 】exp ) {4}| NUM {5}

{ /*1*/ print(exp.val); }{ /*2*/ exp.op = ’+’; }{ /*3*/ exp.op = ’*’; }{ /*4*/ exp.val = eval(exp1.val, exp.op, exp2.val); }{ /*5*/ exp.val = NUM.val; }

●参考文献

構文主導翻訳 (やそれを理論的に整理した属性文法)の詳細は,以下の文献で解説されている.

• 『コンパイラ』,第 2版,A. V. エイホ,M. S. ラム,R. セシィ,J. D. ウルマン,サイエンス社,

2009.5章 構文主導翻訳.

• 『プログラミング言語処理系』,佐々政孝,岩波書店,1980.5.2節 属性文法.

5

Page 6: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

lex と yacc による構文主導翻訳

lex と yacc を併用して構文主導翻訳を実現するプログラムの例を示す.

●上昇型の構文主導翻訳

字句解析

/* calc.l -- 算術式の字句解析器の仕様 */

%{#include "calc.tab.h" // 構文解析器と共用の宣言void yyerror(char *); // 動作記述で使う関数を宣言%}

%% // 字句の属性値 yylval を決めて,// 字句の種類を表すコードを返す

[+*();] { return *yytext; }[0-9]+ { yylval = atoi(yytext); return NUM; }

[ \t\n]+ // 空白類を無視

. { yyerror("illegal character"); }

%%

// 誤り処理の関数を定義void yyerror(char *msg) { printf("%s at ’%c’\n", msg, *yytext); exit(1); }

構文解析と構文主導翻訳

/* calc.y -- 上昇型の構文主導翻訳による,算術式の値の計算と表示 */

%{// 動作記述に必要な宣言extern int yylex(void); // 字句解析関数 (字句解析器で定義)extern void yyerror(char *); // 誤り処理関数 (字句解析器で定義)#define YYSVAL int // 属性値の型#include <stdio.h> // 動作記述で使う printf() 向け%}

// 文法記述に必要な指定%token NUM

%%

// 文法(文脈自由文法) // 動作(プログラム片)

prog : prog com| /* 空 */;

com : exp ’;’ { printf("%d\n", $1); }; // $n は右辺の第 n 記号の属性

exp : ’(’ exp ’+’ exp ’)’ { $$ = $2 + $4; }| ’(’ exp ’*’ exp ’)’ { $$ = $2 * $4; }| NUM { $$ = $1; }; // $$ は左辺の記号の属性

%%

int main(void) { return yyparse(); }

実行例$ flex calc.l; bison -d calc.y; gcc lex.yy.c calc.tab.c -lfl # -d で flex と共用のヘッダを生成$ echo ’0; ((10 + 30) * 50);’ | a.out02000

6

Page 7: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

●下降型の構文主導翻訳

字句解析

(上昇型と同じ calc.lを利用)

構文解析

/* calc.h -- 字句解析器と構文解析器で共用する宣言 */

enum { END = 0, NUM = 128 };int yylval;

/* calc.c -- 下降型の構文主導翻訳による,算術式の値の計算と表示 */

#include "calc.tab.h" // 字句解析器と共用の宣言extern int yylex(void); // 字句解析関数 (字句解析器で定義)extern void yyerror(char *); // 誤り処理関数 (字句解析器で定義)#include <stdio.h> // 動作記述で使う printf() 向けint token; // 字句の種類int lexval; // 字句の値

// 構文解析の補助関数void error(void) { yyerror("syntax error"); }void read_token(void) { token = yylex(); lexval = yylval; }void match_token(int tok) { if (token == tok) read_token(); else error(); }

int parse_exp(void); // 解析関数は規則左辺の属性値を返す

// 構文解析 // 意味動作

void parse_prog(void) {/* prog -> 【 exp ; 】☆ */while (token != END) { int val = // 右辺の exp の属性値parse_exp(); printf("%d\n", val);match_token(’;’);

}}

int parse_exp(void) { int val; // 左辺 exp の属性値if (token == ’(’) {/* exp -> ( exp 【 + │ * 】 exp ) */match_token(’(’); int val1 = // 右辺の一つ目の exp の属性値parse_exp(); int op = token;if (token != ’+’ && token != ’*’)

error();read_token(); int val2 = // 右辺の二つ目の exp の属性値parse_exp();match_token(’)’); val = (op == ’+’) ? val1+val2 : val1*val2;

} else {/* exp -> NUM */ val = lexval; // 字句 NUM の属性値match_token(NUM);

} return val;}

int main(void) {read_token();parse_prog();return 0;

}

実行例$ cp calc.h calc.tab.h; flex calc.l; gcc lex.yy.c calc.c -lfl$ echo ’0; ((10 + 30) * 50);’ | a.out02000

7

Page 8: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

構文主導翻訳の例

構文主導翻訳を使えば,木構造の走査に伴う動作として,意味解析や中間コード生成を簡潔に書ける.

●算術式の後置形式への変換

exp → ( exp op exp ) { /*(1)*/ gen(op.s); }| NUM { /*(2)*/ gen(NUM.s); }

op → + { /*(3)*/ op.s = "+"; }| * { /*(4)*/ op.s = "*"; }

op.s, NUM.s · · · 演算子や数の文字列gen() · · · 翻訳結果の生成手続き (表示,列への追加,など)

exp

ddddddddddddddddddddddddddddddddddddd

jjjjjjjjjjjjjjjjjjjj

>>>>

>>>>

>

OOOOOOOOOOOOOO

出力

exp

oooooooooooooo

����

����

>>>>

>>>>

>

OOOOOOOOOOOOOO op.s =

exp 属性付き解析木

exp op.s =

exp

( ( NUM.s =

+ NUM.s =

) * NUM.s =

) 属性付き字句列

( ( 10 + 30 ) * 50 ) 入力文字列

●算術式の構文木への変換

exp → ( exp op exp ) { exp.tr = make_op(op.ty, exp1.tr, exp2.tr); }| NUM { exp.tr = make_num(NUM.val); }

op → + { op.ty = ’+’; }| * { op.ty = ’*’; }

exp.tr · · · 部分木の根へのポインタop.ty · · · 演算子の種類NUM.val · · · 定数の値 (字句の属性値)

make_op(), make_exp() · · · 節を生成して部分木の根へのポインタを返す関数

8

Page 9: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

●式や代入文に対する 3番地コードの生成

stm → ID { stm.var = use_var(ID.val); }= exp ; { gen_asmt(stm.var, exp.res); }

exp → exp + exp { exp.res = gen_opr(’+’, exp1.res, exp2.res); }| exp * exp { exp.res = gen_opr(’*’, exp1.res, exp2.res); }| ( exp ) { exp.res = exp1.res; }| ID { exp.res = use_var(ID.val); }| NUM { exp.res = use_con(NUM.val); }

※コード片は,右辺の途中にも書ける (例: 上の stm 規則)

※ *を +より優先し,*と +は左結合であると考えて,曖昧さを解消

※文法が左再帰なので,下降型より上昇型の構文解析に適する (構文木の走査にも使える)

stm.var · · · 代入文の左辺の変数exp.res · · · 部分式の結果の変数や定数ID.val, NUM.val · · · 識別子や数の属性値

gen_asmt(), gen_opr() · · · 一時変数を作り,代入命令や演算命令を生成use_var(), use_con() · · · 変数や定数の利用準備

●制御文に対する 3番地コードの生成

条件文 if 式 then 文列 1 else 文列 2 fi

式のコード (結果は tmp)

ifnot tmp goto L1

文列 1のコードgoto L2

L1 : 文列 2のコードL2 :

反復文 while 式 do 文列 done

L1 : 式のコード (結果は tmp)

ifnot tmp goto L2

文列のコードgoto L1

L2 :

seq → seq stm

| ε

stm → { stm.lab1 = new_lab(); stm.lab2 = new_lab(); }if exp { gen_ifnot_goto(exp.res, stm.lab1); }then seq { gen_goto(stm.lab2);

gen_lab(stm.lab1); }else seq fi { gen_lab(stm.lab2); }

| { stm.lab1 = new_lab(); stm.lab2 = new_lab();

gen_label(stm.lab1); }while exp { gen_ifnot_goto(exp.res, stm.lab2); }do seq done { gen_goto(stm.lab1);

gen_lab(stm.lab2); }

stm.lab1, stm.lab2 · · · 飛び越し命令に使うラベルexp.res · · · 式の評価結果の属性値

new_lab() · · · 新しいラベルを生成gen_ifnot_goto(), gen_goto() · · · 飛び越し命令を生成gen_label() · · · ラベルを生成

9

Page 10: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

構文主導翻訳による属性値の受け渡し

構文主導翻訳で,属性値を解析木の子から親へと上向きに計算できない場合,親や弟から受け渡した

属性値を使って計算できる.

●左結合の演算子を使う上昇型の構文主導翻訳

定数と減算からなる算術式の値を求めるための,構文主導翻訳を示す.

1 : exp → exp - NUM { exp.val = exp1.val - NUM.val; }2 : | NUM { exp.val = NUM.val; }

入力文字列が 9 - 3 - 2 のときの属性付き解析木は次の通りである.

exp.val = 4

eeeeeeeeeeeeeeeeeeTTTTTTTTTT

exp.val = 6

jjjjjjjjjjTTTTTTTTTT

exp.val = 9

NUM.val = 9

- NUM.val = 3

- NUM.val = 2

上記の文法は左再帰的なので,上昇型の構文解析に適しており,左結合での演算結果は,

値を葉から根へと上向きに計算すれば求まる.

●左結合の演算子を使う下降型の構文主導翻訳

上記の文法の左再帰を除去して,下降型の解析向けの文法を得る.

1 : exp → NUM exp′

2 : exp′ → - NUM exp′

3 : | ε

等価変換後の文法は,演算子を右結合で捉える構文木を生成する.

exp

iiiiiiiiiiVVVVVVVVV

exp′

hhhhhhhhh[[[[[[[[[[[[[[[[[[

exp′

hhhhhhhhhWWWWWWWWW

exp′

NUM.val = 9

- NUM.val = 3

- NUM.val = 2

ε

子から親へと上向きに値を計算するだけでは,左結合での演算結果が得られない.

10

Page 11: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

●左結合の演算子を使う下降型の構文主導翻訳 (続き)

左再帰を除去した算術式の文法を再掲する.

1 : exp → NUM exp′

2 : exp′ → - NUM exp′

3 : | ε

左結合で値を求めるには,定めた値を渡す属性以外に加えて,受け取った値を渡す属性を

使う.ここでは,2属性 i (input) と o (output) を使って属性付き解析木を作る.

exp .o =

kkkkkkkkkkkTTTTTTTTTTT

.i = exp′ .o =

jjjjjjjjjjjZZZZZZZZZZZZZZZZZZZZ

.i = exp′ .o =

jjjjjjjjjjjUUUUUUUUUU

.i = exp′ .o =

NUM.o = 9

- NUM.o = 3

- NUM.o = 2

ε

解析木の子から親へと属性値を上向きに定めるだけでなく,属性値を定めるために親や弟

から受け継いだ属性値を使う場所もあることに注意する.属性値を定義する動作を変形後

の文法に付加して,下降型の構文主導翻訳を得る.

1 : exp → NUM {1a} exp′ {1b}2 : exp′ → - NUM {2a} exp′ {2b}3 : | ε {3}

{ /*1a*/ exp’.i = NUM.o; } { /*1b*/ exp.o = exp’.o; }{ /*2a*/ exp’1.i = exp’.i - NUM.o; } { /*2b*/ exp’.o = exp’1.o; }

{ /*3*/ exp’.o = exp’.i; }

再帰下降型の構文主導翻訳で,受け渡す属性値を右辺の最後に決める場合,値を受け取る

属性を構文解析手続きの引き数に指定し,値を渡す属性を手続きの戻り値として指定する

と,翻訳の動作を見通しよく実現できる.

なお,文法の右辺に反復や選択を許せば,より簡潔な構文主導翻訳が得られる.

exp → NUM {1}【 - NUM {2} 】⋆

{ /*1*/ exp.val = NUM.val; }{ /*2*/ exp.val = exp.val - NUM.val; }

この例では,値の受け渡しの両方に一つの属性 val を使う.属性値を一度だけ定めて参照

する使い方と違い,属性値を再定義していることに注意する.

11

Page 12: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

プログラム実行時のメモリの割り当て

プログラム実行時には,データの利用目的に応じて,メモリの適切な場所が使われる.

●実行時のメモリの分割

一つのプログラムの実行用に割り当てられたメモリ全体は,複数の領域に分けて使われる.

コード領域 機械語コード

静的領域 大域データ (コンパイル時に割り当て)

ヒープ領域 動的データ (実行時に 確保・開放)

.↑

スタック領域 局所データ (手続きの実行で 確保・開放)

●コールスタックの構成

コールスタックには,手続き呼び出しごとにスタックフレームが下図のように作られる.

スタック成長↑

作業領域← SP

局所変数

制御領域← BP

退避した BP〃 PC (戻り番地)

呼び出し先

引き数

呼び出し元

●参考文献

実行時環境については,以下の文献が参考になる.

• 『コンパイラ』,第 2版,A. V. エイホ,M. S. ラム,R. セシィ,J. D. ウルマン,サイエンス社,

2009.7章 実行時環境.

• 『プログラミング言語処理系』,佐々政孝,岩波書店,1980.6章 実行時環境.

12

Page 13: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

手続き実行時のコールスタックの動作

手続きの呼び出しや復帰の際には,コールスタックを使って,実行に必要なデータを 退避・復元 する.

●呼び出し木 と コールスタック

手続きの実行がどのように実現されるか,簡単なプログラム例で解説する.非負整数の 2

進表示を求める次の Cプログラムの動作を考える.2進表示を得るには,与えられた整数

を繰り返し 2で割って,余りを逆順に並べればよい.

void binary(int n) {int b = n%2;if (n >= 2) binary(n/2);putchar(’0’ + b);

}int main(void) {binary(6);return 0;

}

2進表示の再帰手続き binary()は,自身を呼び出すたびに整数を 2で割り,呼び出しから

戻るたびに最下位ビットを putchar()で表示する.手続き main(), binary(), putchar()

の呼び出しと復帰の様子は,呼び出し木 (call tree) で表せる.

main()

binary(6)

mmmmmmQQQQQQ

binary(3)

mmmmmmQQQQQQ

putchar(’0’)

binary(1)

QQQQQQputchar(’1’)

putchar(’1’)

実行中の手続きの情報は,コールスタックという記憶域に格納される.手続きの呼び出し

ごとに,1回の呼び出しに必要なデータを格納する記憶域であるスタックフレームがコー

ルスタック上に積まれ,復帰時に取り除かれる.

//

oo

//

oo

//

oo

OO

��

OO

��

OO

��

13

Page 14: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

コード最適化の有無の比較

コンパイラのコード最適化では,中間コードや目的コードを効率の良いものに変える.

●最適化の有無の比較

コード最適化で目的コードがどう変わるか,gcc を使って比べる.

$ gcc -S ones.c -o ones.s # -S アセンブリコードを出力$ gcc -S -O3 ones.c -o ones-opt.s # -O3 最高度の最適化を実行$ wc -l ones.s; wc -l ones-opt.s # -l 行数を表示

62 ones.s38 ones-opt.s

$ cat ones.c ones.s ones-opt.s # ソースコードと各アセンブリコード (x86-64)を表示

int num_ones(int num) { num_ones: num_ones:int ones = 0; pushq %rbp xorl %eax, %eaxwhile (num > 0) { movq %rsp, %rbp testl %edi, %edi

if (num % 2 == 1) movl %edi, -20(%rbp) jle .L3ones++; movl $0, -4(%rbp) .L8:

num /= 2; jmp .L2 leal 1(%rax), %edx} .L4: testb $1, %dilreturn ones; movl -20(%rbp), %eax cmovne %edx, %eax

} movl %eax, %edx sarl %edisarl $31, %edx jne .L8

int main(void) { shrl $31, %edx .L3:num_ones(62); addl %edx, %eax repreturn 0; andl $1, %eax ret

} subl %edx, %eax main:cmpl $1, %eax xorl %eax, %eaxjne .L3 retaddl $1, -4(%rbp)

.L3:movl -20(%rbp), %eaxmovl %eax, %edxshrl $31, %edxleal (%rdx,%rax), %eaxsarl %eaxmovl %eax, -20(%rbp)

.L2:cmpl $0, -20(%rbp)jg .L4movl -4(%rbp), %eaxleaveret

main:pushq %rbpmovq %rsp, %rbpmovl $62, %edicall num_onesmovl $0, %eaxleaveret

gcc には,最適化オプションが多数あり,実行する最適化の種類を細かく指定できる.詳細は,オンラインマニュアル man gcc などで調べられる.

14

Page 15: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

基本ブロックと制御フローグラフ

制御フローグラフは,命令間の制御の流れを表す有向グラフで,コード最適化に使う中間表現である.

●ソースコード

num = 62;

ones = 0;

while num > 0 do

if num % 2 == 1 then

ones = ones + 1;

fi

num = num / 2;

done

●中間コード

num = 62

ones = 0

L1: _t1 = num > 0

ifnot _t1 goto L2

_t2 = num % 2

_t3 = _t2 == 1

ifnot _t3 goto L3

_t4 = ones + 1

ones = _t4

L3: _t5 = num / 2

num = _t5

goto L1

L2:

●基本ブロックと制御フローグラフ

基本ブロック順次実行される命令列下の 2条件を満たす命令列のうち極大なもの

制御フローグラフ基本ブロック間の制御の流れを表す有向グラフ

(条件 1) 他のブロックから入れるのはブロック先頭だけ

(条件 2) ジャンプ命令が許されるのはブロック末尾だけ

num = 62

ones = 0

L1: _t1 = num > 0

ifnot _t1 goto L2

_t2 = num % 2

_t3 = _t2 == 1

ifnot _t3 goto L3

_t4 = ones + 1

ones = _t4

L3: _t5 = num / 2

num = _t5

goto L1

L2:

15

Page 16: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

局所最適化

局所最適化とは,基本ブロック内の命令列に対して使う最適化である.目的コードの種類に依存しない(機械独立の)最適化は,通常,中間コードに適用するが,この解説では分かりやすさを重視して,最適化の例を主にソースコードの変換として示す.

●局所最適化の代表例

定数伝播値の変わらない変数をその定数値で置換low = 0;

high = 10;

mid = (low + high) / 2;

low = 0;

high = 10;

mid = ( + ) / 2;

定数畳み込み定数式の値をコンパイル時に計算

if (size > 10 * 5) ... if (size > ) ...

恒等式の利用演算子の性質を使い式を簡単化

(1 + n) - 1 n * 8 x & x *&p

共通部分式の除去同じ値の部分式を一度だけ計算

s1 = size * n;.........

s2 = size * n; s2 =

コピー伝播変数への値のコピー dst = src の後,dst も src も変わらないとき,dst を src に置換

old = n;.........

new = old + 1; new =

不要コードの除去実行結果に影響しないコードを削除

上の例で,old を他で使わないなら,old = n; を削除

16

Page 17: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

●DAG を使った局所最適化

DAG (directed acyclic graph)

閉路のない有向グラフ.基本ブロック中の命令列の中間表現に使える.

中間コードの命令列

1: j = i + 1

2: i = k

3: x = i + 1

4: y = k + 1

命令列を表す DAG

DAG の構成法(1) 各変数 v の初期値 v0 を表す節点と,各定数を表す節点を作る.(2) 命令列の先頭から順に,各命令 i (による変数の定義)を,以下の手順で節点 nに対応

させる.その際,節点 nには変数の最新の定義を表す列を添える.• 命令 iが v = a op bの形なら,nのラベルは演算子 opで,nの左右の子は a, b の値(の最新の定義)に対応する節点である.そのような節点 nがなければ追加する.• 命令 iが v = aの形なら,nは aの値 (の最新の定義)に対応する節点である.

DAG に基づく最適化

1: j = i + 1

2: i = k

3: x = i + 1

4: y = k + 1

j = i + 1

i = k

x =

y =

●参考文献

機械独立の最適化については,以下の文献が参考になる.

• 『コンパイラ』,第 2版,A. V. エイホ,M. S. ラム,R. セシィ,J. D. ウルマン,サイエンス社,2009.8.5節 基本ブロックの最適化,9章 機械独立の最適化.• 『プログラミング言語処理系』,佐々政孝,岩波書店,1980.9章 コード最適化.

17

Page 18: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

大域最適化

大域最適化とは,基本ブロックをまたぐ最適化である.通常,この最適化は中間コードに適用するが,この解説では分かりやすさを重視して,最適化の例をソースコードの変換として示す.

●ループ最適化

ループ不変コードの移動繰り返し中に値の変わらない式の計算を,ループ外に移動

do {

c = a + b;......

} while ( ... )

do {

} while ( ... )

ループ内の演算の強さの軽減繰り返される演算を,結果が同じで計算コストの小さいものに置換

x = 0

for (i = 1; ... ; i++) {

x = 5*i;

}

x = 0

for (i = 1; ... ; i++) {

x

}

帰納変数の除去ループの反復ごとに一定値だけ変化する変数が不要なら削除

for ( ; ... ; ) {

}

ループ展開ループの繰り返し部分を必要な回数だけ展開し,ループ制御の手間を削減

for (i = 3; i > 0; i--)

val *= 5;

val *= 5;

for ( ... ; i++) {

}

for ( ... ; i += 2) {

}

18

Page 19: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

●呼び出し最適化

インライン展開手続き呼び出しを手続き本体で置換し,呼び出しと復帰の手間を削減

int power(int m, int n) {

int i, val = 1;

for (i = n; i > 0; i--)

val *= m;

return val;

}...

y = power(x, 3);...

int i, val = 1;

for (i = n; i > 0; i--)

val *= m;

末尾再帰の除去手続き終了直前の再帰呼び出しを goto で置き換え,呼び出しと復帰の手間を削減

int pwr(int m, int n, int val) {

if (n > 0)

return pwr(m, n-1, val*m);

else

return val;

}

int pwr(int m, int n, int val) {

if (n > 0) {

} else {

return val;

}

}

●参考文献

機械独立の最適化については,以下の文献が参考になる.

• 『コンパイラ』,第 2版,A. V. エイホ,M. S. ラム,R. セシィ,J. D. ウルマン,サイエンス社,2009.8.5節 基本ブロックの最適化,9章 機械独立の最適化.• 『プログラミング言語処理系』,佐々政孝,岩波書店,1980.9章 コード最適化.

19

Page 20: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

データフロー解析

データフロー解析は,変数や式についての,定義と使用の依存関係を系統的に調べる方法である.この解説では,到達定義解析を題材に,データフロー解析の基本的な考え方を学ぶ.

●プログラム

データフロー解析は,制御フローグラフで表されたプログラムに適用する.具体例として,3 の n乗を y に代入するプログラムを扱う.

m = 3

val = 1

i = n

L1: ifnot i > 0 goto L2

val = val * m

i = i - 1

goto L1

L2: y = val

��

��

@A

GF //

ED

BCoo

●データフロー方程式

ブロック i の入り口と出口に到達する定義の集合 Ini, Outi は,集合に関する等式からなる次の連立方程式を満たす.

上記のプログラムに関するデータフロー方程式は,以下の 8個の等式からなる.

i Gen(i) Kill(i)

��

��

@A

GF //

ED

BCoo

In1 =Out1 = ∪ (In1\ )

In2 =Out2 = ∪ (In2\ )

In3 =Out3 = ∪ (In3\ )

In4 =Out4 = ∪ (In4\ )

20

Page 21: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

●データフロー方程式の反復解法

データフロー方程式の解は,反復して近似解を改良する方法で求められる.

In1 = ∅Out1 = {1m, 1v, 1i} ∪ (In1 \ {3v, 3i})In2 = Out1 ∪Out3Out2 = In2

In3 = Out2Out3 = {3v, 3i} ∪ (In3 \ {1v, 1i})In4 = Out2Out4 = {4y} ∪ In4

方程式を右辺から左辺への代入とみなす.全てが空集合であるという近似解から始めて,上から順に代入することを,結果が変わらなくなるまで繰り返せば,(最小)解が得られる.

+1m

+1v −3v+1i −3i

+3v −1v+3i −1i

+4y

��

��

@A

GF //

ED

BCoo

反復 0 1 2 3

In1 ∅Out1 ∅

In2 ∅Out2 ∅

In3 ∅Out3 ∅

In4 ∅Out4 ∅

解析結果から 1v, 3v ∈ In4 なので,第 1ブロックと第 3ブロックの valの定義が,第 4ブロック (の valの使用)に到達するとわかる.→ 定数伝播が不可能

また,In3 に属す mの定義は 1mだけなので,第 1ブロックの mの定義だけが第 3ブロック (の mの使用)に到達するとわかる.→ 定数伝播が可能

●参考文献

データフロー解析については,以下の文献が参考になる.

• 『コンパイラ』,第 2版,A. V. エイホ,M. S. ラム,R. セシィ,J. D. ウルマン,サイエンス社,2009.9.2節 データフロー解析の概要,9.3節 データフローの基礎.• 『プログラミング言語処理系』,佐々政孝,岩波書店,1980.9.5節 データフロー解析と各種最適化変換.

21

Page 22: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

算術式の目的コード生成

使用レジスタをなるべく節約して算術式の値を計算する目的コード,の生成方法を学ぶ.

●目的コードの命令

目的コードの生成に,次の 2種類の命令が使えることを想定する.

演算命令

op reg loc op は四則 +, -, *, / のどれかreg はレジスタ,loc は変数か定数かレジスタ命令の意味は reg ← reg op loc

転送命令

LD reg loc reg はレジスタ,loc は変数か定数かレジスタ命令の意味は reg ← loc

●必要レジスタ数の計算

算術式の各部分式 e の値の計算に必要なレジスタ数 numreg(e) を,次の場合分けで再帰的に求める.

e が変数か定数のとき

numreg(e) = 0

e が e1 op e2 の形で,numreg(e1) = n1, numreg(e2) = n2 のとき

numreg(e)は,次の表で定まる.

\ n2 = 0 > 0

n1 \ op :可換 op :非可換

= 0 1 n2 max(2, n2)

> 0 n2 + 1 max(n1, n2)

n1 // n2 = n1 ̸= n1

22

Page 23: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

●目的コード生成

具体例な算術式に対して numregを求めて,目的コードを生成する例を以下に示す.

\ n2 = 0 > 0n1\ op :可換 op :非可換

= 0 1 n2 max(2, n2)

> 0 n2 + 1 max(n1, n2)

n1// n2 = n1 ̸= n1

例 1

*

sssss LLL

LL

x +

sssss LLL

LL

w *

sssss MMMM

v 5

例 2

/

oooo NNNN

y -

sssss NNNN

x /

qqqq NNNN

w *

sssss MMMM

v 5

LD R1 v

* R1 5

+ R1 w

* R1 x

LD R1 v

* R1 5

LD R2 w

/ R2 R1

LD R1 x

- R1 R2

LD R2 y

/ R2 R1

例 3

+

iiiiiiiiWWWWWWWW

+

sssss MMMM /

qqqq OOOO

v 5 w 3

例 4

+

iiiiiiiiWWWWWWWW

+

sssss MMMM /

qqqq NNNN

x y w *

sssss MMMM

v 5

LD R1 v

* R1 5

LD R2 w

/ R2 3

+ R1 R2

LD R1 v

* R1 5

LD R2 w

/ R2 R1

LD R1 x

* R1 y

+ R1 R2

23

Page 24: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

コンパイラ 解説資料 (山田)

グラフ彩色によるレジスタ割り当て

中間コードに現れる各変数のレジスタへの割り当ては,変数の干渉を表すグラフで決定できる.

●中間コード

diff = m - n

sq = n * n

val = m * sq

変数の定義 □ と最後の使用 ○

●変数の生存期間と干渉

変数の生存期間 · · · 変数が定義されてから (再定義されすに)最後に使われるまで変数の干渉 · · · 生存期間の重なり

diff = m - n

sq = n * n

val = m * sq

m n d s v

●レジスタ干渉グラフ

2変数の生存期間が重なり,同一レジスタを使えない

⇔ 頂点間に辺(2頂点が隣接)

n

m

v

s d

●グラフ彩色によるレジスタ割り当て

レジスタを k個使えば,全変数を割り当てられる

⇔干渉グラフの頂点を k色使って塗り,隣接頂点が必ず違う色にできる(干渉グラフが k彩色可能)

出る辺の数が k未満の頂点 (と付随する辺)を選んで消すことを繰り返し,全頂点を消せれば,グラフは k彩色可能

頂点と辺の削除を逆にたどって,頂点と辺を加えるごとに,隣接頂点で未使用の色番号の最小値を割り当てる

24

Page 25: コンパイラ解説資料集 3 - Mie Universityコンパイラ解説資料集3 中間表現の種類 2 抽象構文木と後置記法 3 構文主導翻訳 4 lex とyacc による構文主導翻訳

●グラフ彩色によるレジスタ割り当ての例

例題の中間コードの変数への,レジスタ 3個の割り当て

干渉グラフに対して,辺数が 3未満の頂点 (と付随する辺)の削除を反復

n

m

v

s d

m

v

s d

v

s d

v

s

v (空)−n

//

oo2

−m//

oo1

−d//

oo3

−s//

oo2

−v//

oo1

逆順で,頂点と辺の追加ごとに,隣接頂点で未使用の (最小)番号を割り当て

●参考文献

目的コード生成については,以下の文献が参考になる.

• 『プログラミング言語処理系』,佐々政孝,岩波書店,1980.8章 コード生成.• 『コンパイラ』,第 2版,A. V. エイホ,M. S. ラム,R. セシィ,J. D. ウルマン,サイエンス社,2009.8章 コード生成.

25