comparison implementation.tex @ 7:8ef81ff8cb52

emended.
author kent <kent@cr.ie.u-ryukyu.ac.jp>
date Fri, 12 Feb 2010 13:10:57 +0900
parents dfb89e32eea1
children 4b2af58b0302
comparison
equal deleted inserted replaced
6:b59d31966d7d 7:8ef81ff8cb52
1 \chapter{GCCにおける実装・改善} 1 \chapter{GCCにおける実装・改善}
2 \label{chp:impl} 2 \label{chp:impl}
3 3
4 この章では、GCCにおけるCbCコンパイラの実装方法と、 \ref{chp:cbc}章で洗 4 この章では、GCCにおけるCbCコンパイラの実装方法の説明と、 \ref{chp:cbc}
5 い出したGCCでの問題点の改善を行う。 5 章で洗い出したGCCでの問題点の改善を行う。
6 6
7 実装にはGCCのフロントエンドであるcc1というプログラムを直接変更する。 7 実装にはGCCのフロントエンドであるcc1というプログラムを直接変更する。
8 このcc1はCからアセンブラへ変換を行う純粋なコンパイラとして実行されるプ 8 このcc1はCからアセンブラへ変換を行う純粋なコンパイラとして実行されるプ
9 ログラムである。このcc1をCbCの構文解析に対応させる。 9 ログラムである。このcc1をCbCの構文解析に対応させる。
10 10
51 適化は末尾呼び出し最適化(tailcall)と呼ばれている。 51 適化は末尾呼び出し最適化(tailcall)と呼ばれている。
52 \begin{figure}[htpb] 52 \begin{figure}[htpb]
53 \begin{center} 53 \begin{center}
54 \includegraphics[width=.6\textwidth]{figures/tailcall.eps} 54 \includegraphics[width=.6\textwidth]{figures/tailcall.eps}
55 \end{center} 55 \end{center}
56 \caption{末尾呼び出し最適化が可能な関数funcBの例} 56 \caption{末尾呼び出し最適化が可能な関数funcYの例}
57 \label{fig:tailcall} 57 \label{fig:tailcall}
58 \end{figure} 58 \end{figure}
59 59
60 Scheme処理系では仕様上この最適化が必須となっているが、Cはそうではない。 60 Scheme処理系では仕様上この最適化が必須となっているが、Cはそうではない。
61 しかしGCCはこの最適化をデフォルトで行っている。 61 しかしGCCはこの最適化をデフォルトで行っている。
62 62
63 \subsubsection{軽量継続への摘要} 63 \subsubsection{軽量継続への摘要}
64 tailcallをコードセグメントの呼び出しに適用することで軽量継続が実装でき 64 tailcallをコードセグメントの呼び出しに適用することで軽量継続が実装でき
65 る。具体的にはソースコード上にコード\ref{code:goto}のような式があった 65 る。具体的にはソースコード上にコード\ref{code:goto}のような式があった
66 場合に、これをコード\ref{code:ret-call}と同じように解釈すれば良い。 66 場合に、これをコード\ref{code:ret-call}と同じように解釈する。
67 つまり、``goto''が前置する関数呼び出しは、必ず後ろに\verb|return;|がつ
68 くと解釈するのである。これでtailcallの条件ほぼ満たされる。
69
67 この構文解析はGCCのgcc/c-parser.c内で行う。 70 この構文解析はGCCのgcc/c-parser.c内で行う。
68 71
69 \begin{minipage}[t]{.45\textwidth} 72 \begin{minipage}[t]{.45\textwidth}
70 \lstinputlisting[caption=goto文の例,label=code:goto] 73 \lstinputlisting[caption=goto文の例,label=code:goto]
71 {sources/goto-expression.cbc} 74 {sources/goto-expression.cbc}
74 \begin{minipage}[t]{.45\textwidth} 77 \begin{minipage}[t]{.45\textwidth}
75 \lstinputlisting[caption=構文木での解釈,label=code:ret-call] 78 \lstinputlisting[caption=構文木での解釈,label=code:ret-call]
76 {sources/ret-call.cbc} 79 {sources/ret-call.cbc}
77 \end{minipage} 80 \end{minipage}
78 81
79 しかし構文木の変更だけでは最適化が行われるとは限らない。特にスタックの 82 しかし構文木の変更だけではtailcallが行われるとは限らない。特にスタック
80 状態や変数の数、順番によっても最適化はカットされる場合がある。 83 の状態や変数の数、順番によっても最適化はカットされる場合がある。
81 そのため最適化を判断する条件式を修正、また構文木から中間コードを生 84 そのため最適化を判断する条件式を修正、また構文木から中間コードRTLを生
82 成する部分でも修正が必要になる。 85 成する部分でも修正が必要になる。
83 86
84 \paragraph{expand-call}関数は関数を表す構文木から中間コードを生成する 87 \paragraph{expand-call}関数は関数を表す構文木からRTLを生成する処理であ
85 処理である。この関数内では呼び出される関数のアドレスを取得するコードの 88 る。この関数内では呼び出される関数のアドレスを取得するコードの生成、ス
86 生成、スタックへの引数をプッシュするコードの生成、引数のプッシュの度に 89 タックへの引数をプッシュするコードの生成、引数のプッシュの度に
87 tailcallが可能かのチェックなどが行われている。 90 tailcallが可能かのチェックなどが行われている。
88 91
89 ここでは以下の処理を追加することでtailcallカットの条件判断をパスしている。 92 ここでは以下の処理を追加することでtailcallカットの条件判断をパスしている。
90 \begin{itemize} 93 \begin{itemize}
91 \item スタックのサイズをごまかす 94 \item スタックのサイズをごまかす
125 ここから\ref{sec:cbc-problem}節で紹介した問題点について、本研究での改 128 ここから\ref{sec:cbc-problem}節で紹介した問題点について、本研究での改
126 善点を説明する。 129 善点を説明する。
127 130
128 131
129 \subsection{並列代入}\label{sec:impl-parallel} 132 \subsection{並列代入}\label{sec:impl-parallel}
130 % 一時変数を取得する例を示す
131 % 最適化の期待
132 % おい、replace_arguments関数、あんまり意味ないぞ
133 133
134 \ref{sec:gcc-problems}節で説明した様に、コードセグメントの受け取 134 \ref{sec:gcc-problems}節で説明した様に、コードセグメントの受け取
135 った引数と継続の際に渡す引数の順序が変わる場合等に並列代入が必要になる。 135 った引数と継続の際に渡す引数の順序が変わる場合等に並列代入が必要になる。
136 過去の実装ではこの並列代入を、\verb|expand_call|という構文木から中間コ 136 過去の実装ではこの並列代入を、\verb|expand_call|という構文木から RTLを
137 ードを生成する処理の部分で行っていた。 137 生成する処理の部分で行っていた。
138 138
139 しかし実際にはGCCは元より並列代入を実装しているため、独自の実装は必要 139 しかし実際にはGCCは元より並列代入を実装しているため、独自の実装は必要
140 としない。また、この独自の実装にも問題があった。 140 としない。また、この独自の実装にも問題があった。
141 そのため独自の実装は廃止し、GCCの機能を利用することにする。 141 そのため独自の実装は廃止し、GCCの機能を利用することにする。
142 142
169 この手法を用いる。 169 この手法を用いる。
170 170
171 \subsubsection{問題点と最適化の期待} 171 \subsubsection{問題点と最適化の期待}
172 この手法でどのように引数を入れ替えても正しく代入可能になる。ただし、一 172 この手法でどのように引数を入れ替えても正しく代入可能になる。ただし、一
173 時変数の使用は処理速度に問題がある。特にレジスタの少ないアーキテクチャ 173 時変数の使用は処理速度に問題がある。特にレジスタの少ないアーキテクチャ
174 では一時変数の確保にはメモリ上のスタックを用いるため、余計なメモリアク 174 では一時変数の確保にメモリ上のスタックを用いるため、余計なメモリアクセ
175 セスや冗長な命令が増えてしまう。このため、この手法を実践したコードでは 175 スや冗長な命令が増えてしまう。このため、この手法を実践したコードではそ
176 そうでないコードに比べて若干の速度低下が見込まれる。 176 うでないコードに比べて若干の速度低下が見込まれる。
177 177
178 その代わり、この速度低下はGCCのもつ最適化機構で回避され得るものである。 178 その代わり、この速度低下はGCCのもつ最適化機構で回避され得るものである。
179 GCCでは中間コード生成後、必要のない一時変数へのコピーなどは最適化によ 179 GCCでは中間コード生成後、必要のない一時変数へのコピーなどは最適化によ
180 りカットされる。そのため、最適化を有効にした場合はこの処理速度の低下は 180 りカットされる。そのため、最適化を有効にした場合はこの処理速度の低下は
181 起きないはずである。この影響に関しては\ref{chp:eval}章にて評価を行う。 181 起きないと考えられる。この影響に関しては\ref{chp:eval}章にて検証する。
182 182
183 \subsubsection{一時変数への退避の実装} 183 \subsubsection{一時変数への退避の実装}
184 184
185 この手法の実装は、中間コード生成時ではなく構文木生成で可能である。 185 この手法の実装は、中間コード生成時ではなく構文木生成で可能である。
186 tailcallの関数呼び出しを表す構文木の生成時に以下の処理を追加する。 186 tailcallの関数呼び出しを表す構文木の生成時に以下の処理を追加する。
191 \item 同じ型の名前なし一時変数を作成 191 \item 同じ型の名前なし一時変数を作成
192 \item 引数の値を一時変数に代入 192 \item 引数の値を一時変数に代入
193 \item 関数に渡す引数を一時変数に変更 193 \item 関数に渡す引数を一時変数に変更
194 \end{enumerate} 194 \end{enumerate}
195 \item 呼び出す関数がポインタだった場合 195 \item 呼び出す関数がポインタだった場合
196 \item 関数と同じ型(関数ポインタ)の一時変数を作成 196 \begin{enumerate}
197 \item 関数アドレスを一時変数に代入 197 \item 関数と同じ型(関数ポインタ)の一時変数を作成
198 \item 呼び出す関数を一時変数に変更 198 \item 関数アドレスを一時変数に代入
199 \item 呼び出す関数を一時変数に変更
200 \end{enumerate}
199 \end{enumerate} 201 \end{enumerate}
200 202
201 ここでは関数ポインタも引数と同じように扱い、一時変数に退避する。 203 ここでは関数ポインタも引数と同じように扱い、一時変数に退避する。
202 実際のプログラムはコード\ref{code:replace-args}の様になる。 204 実際のプログラムはコード\ref{code:replace-args}の様になる。
203 この関数\verb|cbc_replace_arguments|は関数呼び出し構文木を引数として受 205 この関数\verb|cbc_replace_arguments|は関数呼び出し構文木を引数として受
204 け取り、上記の処理を行う。tree callがその構文木である。 206 け取り、上記の処理を行う。引数として渡される\verb|tree call|がその構文
205 \verb|build_decl|は名無し一時変数の宣言、 \verb|build_modify_expr|は一 207 木である。 \verb|build_decl|は名無し一時変数の宣言、
206 時変数への代入を行う構文木の生成をしている。 208 \verb|build_modify_expr|は一時変数への代入を行う構文木の生成をしている
209
207 210
208 \lstinputlisting 211 \lstinputlisting
209 [caption=上記の処理を行う関数,label=code:replace-args] 212 [caption=上記の処理を行う関数,label=code:replace-args]
210 {sources/replace-args.c} 213 {sources/replace-args.c}
211 214
212 これによりソースコードの構文解析時、軽量継続をパースしてその構文木を生 215 ソースコードの構文解析時、軽量継続をパースしてその構文木を生成した際に
213 成した際にこの関数\verb|cbc_replace_arguments|を実行することで、この軽 216 、この関数\verb|cbc_replace_arguments|を実行することで、この軽量継続は
214 量継続は並列代入に対応できるようになった。 217 並列代入に対応できるようになった。
215 218
216 219
217 \subsection{環境付き継続} 220 \subsection{環境付き継続}
218 221
219 環境付き継続は過去の研究では実装されていなかった。 222 環境付き継続は過去の研究では実装されていなかった。
232 \subsubsection{GCCにより追加されるコード} 235 \subsubsection{GCCにより追加されるコード}
233 環境付き継続で使う\verb|__return|変数は特殊なコードセグメントへのポイ 236 環境付き継続で使う\verb|__return|変数は特殊なコードセグメントへのポイ
234 ンタとなる必要がある。このコードセグメントはユーザでは定義せず、その変 237 ンタとなる必要がある。このコードセグメントはユーザでは定義せず、その変
235 数を参照した関数の返り値型を基にコンパイラが自動で生成する事が望ましい。 238 数を参照した関数の返り値型を基にコンパイラが自動で生成する事が望ましい。
236 239
237 具体的には、コード\ref{code:cbcreturn}の関数funcBをコンパイラは次のコ 240 具体的には、コード\ref{code:cbcreturn2}の関数funcB(
238 ード\ref{code:nestedcode}の様に解釈し、内部コードセグメントを自動生成 241 \pageref{code:cbcreturn}ページのコード\ref{code:cbcreturn}と同じ)をコ
239 する。 242 ンパイラは次のコード\ref{code:nestedcode}の様に解釈し、内部コードセグ
240 \lstinputlisting 243 メントを自動生成する。
241 [caption=コード\ref{code:cbcreturn}のfuncBに追加される処理, 244
242 label=code:nestedcode,numbers=left] 245 \begin{minipage}[t]{.33\textwidth}
243 {sources/nestedcode.cbc} 246 \lstinputlisting
247 [caption=\_\_returnの例,
248 label=code:cbcreturn2,
249 basicstyle=\footnotesize\ttfamily,
250 emph=\_\_return]
251 {sources/cbcreturn2.cbc}
252 \end{minipage}
253 \hfill
254 \begin{minipage}[t]{.55\textwidth}
255 \lstinputlisting
256 [caption=コード\ref{code:cbcreturn}のfuncBに追加される処理,
257 label=code:nestedcode,numbers=left]
258 {sources/nestedcode.cbc}
259 \end{minipage}
244 260
245 261
246 5--14行がGCCにより追加される処理である。内部コードセグメント 262 5--14行がGCCにより追加される処理である。内部コードセグメント
247 \verb|__unnamed_code|は受け取った引数を関数の返り値として保持し、ラベ 263 \verb|_segment|は受け取った引数を関数の返り値として保持し、ラベ
248 ル\verb|__unnamed_label|にjumpする。この時点で内部コードセグメントを抜 264 ル\verb|_label|にjumpする。この時点で内部コードセグメントを抜
249 けて元の関数funcBの環境に復帰する。 265 けて元の関数funcBの環境に復帰する。
250 266
251 さらにjump先もGCCにより自動で追加される。しかしこのjump先は 267 さらにjump先もGCCにより自動で追加される。しかしこのjump先は
252 \verb|__unnamd_code|以外からは実行してはならない。そのため条件式が真に 268 \verb|_segment|以外からは実行してはならない。そのため条件式が真に
253 ならないif文で囲み、実行を回避している。 269 ならないif文で囲み、実行を回避している。
254 jump先での処理は、\verb|__unnamed_code|内で代入された値を持ってリター 270 jump先での処理は、\verb|_segment|内で代入された値を持ってリター
255 ンするのみである。 271 ンするのみである。
256 272
257 273
258 \subsubsection{内部コードセグメント自動生成の実装方法} 274 \subsubsection{内部コードセグメント自動生成の実装方法}
259 275
260 GCCは変数や関数、また文字列や数値などのリテラルに関する処理を 276 GCCは変数や関数、また文字列や数値などのリテラルに関する処理を \\
261 \verb|c_parser_postfix_expression|で行っている。この関数では変数や数値 277 \verb|c_parser_postfix_expression|で行っている。この関数では変数や数
262 、文字列などの判定に500行にわたるswitch文を使っているが、ここに 278 値、文字列などの判定に500行にわたるswitch文を使っているが、ここに
263 \verb|__return|の判定も追加する。 279 \verb|__return|の判定も追加する。
264 280
265 必要な処理は以下の様になる。 281 必要な処理は以下の様になる。
266 \begin{itemize} 282 \begin{itemize}
267 \item ラベル\verb|_ _unnamed_label|の宣言 283 \item ラベル\verb|_label|の宣言
268 \item 返り値を保持しておく変数の宣言 284 \item 返り値を保持しておく変数の宣言
269 \item 内部関数の定義 285 \item 内部関数の定義
270 \item 条件分岐制御の構文木生成 286 \item 条件分岐制御の構文木生成
271 \item 条件分岐内でのラベルの定義 287 \item 条件分岐内でのラベルの定義
272 \item 条件分岐内での復帰構文の構文木生成 288 \item 条件分岐内での復帰構文の構文木生成
273 \end{itemize} 289 \end{itemize}
274 参考のため付録にコード\ref{code:nestedcode}を用意した。 290 参考のため付録\ref{apx:postfix-expression}にこの処理のコードを掲載す
291 る。
275 292
276 %コード\ref{code:nestedcode}にその処理を示す。 293 %コード\ref{code:nestedcode}にその処理を示す。
277 %\lstinputlisting 294 %\lstinputlisting
278 % [caption=c\_parser\_postfix\_expressionでの処理, 295 % [caption=c\_parser\_postfix\_expressionでの処理,
279 % label=code:nestedcode] 296 % label=code:nestedcode]
329 \lstinputlisting 346 \lstinputlisting
330 [caption=PowerPCにおける間接継続のRTL, 347 [caption=PowerPCにおける間接継続のRTL,
331 label=code:rtl-indirecttailcall, 348 label=code:rtl-indirecttailcall,
332 language=Lisp] 349 language=Lisp]
333 {sources/rtl-indirecttailcall.rtl} 350 {sources/rtl-indirecttailcall.rtl}
351
334 このRTL内の\verb|(mem:SI (reg/f:SI 129)|が関数のポインタを示すレジスタ 352 このRTL内の\verb|(mem:SI (reg/f:SI 129)|が関数のポインタを示すレジスタ
335 である。間接呼び出しでない場合はこれが 353 である。間接呼び出しでない場合はこれが
336 \verb|(mem:SI (symbol_ref:SI (``cs0'')|となり、コードセグメントの関数 354 \verb|(mem:SI (symbol_ref:SI (``cs0'')|となり、コードセグメントの関数
337 を直接表している。 355 を直接表している。
338 356
346 \lstinputlisting 364 \lstinputlisting
347 [caption=\ref{code:rtl-indirecttailcall}の変換規則, 365 [caption=\ref{code:rtl-indirecttailcall}の変換規則,
348 label=code:md-for-indirect, 366 label=code:md-for-indirect,
349 language=Lisp] 367 language=Lisp]
350 {sources/md-for-indirect.md} 368 {sources/md-for-indirect.md}
351 このコードの2番目の要素はコード\ref{code:rtl-indirecttailcall}とよく似 369 このコードの3番目の要素はコード\ref{code:rtl-indirecttailcall}とよく似
352 ていることがわかる。これは変換対象としてこの型に合うものに制限するため 370 ていることがわかる。これは変換対象としてこの型に合うものに制限するため
353 である。 371 である。
354 372
355 ここでは出力するアセンブラとして\verb|b%T0|が使われている。 373 ここでは出力するアセンブラとして\verb|b%T0|が使われている。
356 \verb|%T0|はレジスタ名に置き換えられる部分である。このアセンブラは最終 374 \verb|%T0|はレジスタ名に置き換えられる部分である。このアセンブラは最終
368 コードセグメントの間の軽量継続は、Cの関数呼び出しと同じように引数を渡 386 コードセグメントの間の軽量継続は、Cの関数呼び出しと同じように引数を渡
369 すことができる。関数呼び出しでのこの引数の渡し型はほとんどの場合アーキ 387 すことができる。関数呼び出しでのこの引数の渡し型はほとんどの場合アーキ
370 テクチャやオペレーティングシステム、また各プログラミング言語毎に違った 388 テクチャやオペレーティングシステム、また各プログラミング言語毎に違った
371 規約があり、これは一般に呼出規約(Calling convention)と呼ばれている。 389 規約があり、これは一般に呼出規約(Calling convention)と呼ばれている。
372 390
373 CbCでは同じアーキテクチャでもコンパイラによってこの呼出規約は違う。mc 391 CbCでは同じアーキテクチャでもコンパイラによってこの呼出規約は異なる。
374 の軽量継続では、なるべく多くの引数をレジスタに格納するようになっており 392 mc の軽量継続では、なるべく多くの引数をレジスタに格納するようになって
375 、 PowerPCでは最大11個のint型をレジスタに格納する。レジスタの少ない 393 おり、 PowerPCでは最大11個のint型をレジスタに格納する。レジスタの少な
376 x86でも2つだけだがやはりレジスタを使用している。 394 い x86でも2つだけだが、やはりレジスタを使用している。
377 395
378 GCCベースコンパイラでは継続制御の引数渡しに関数の呼出規約と同じ方法を 396 GCCベースコンパイラでは継続制御の引数渡しに関数の呼出規約と同じ方法を
379 使っている。そのため、x86では引数渡しに全てスタックを用いることになり 397 使っている。そのため、x86では引数渡しに全てスタックを用いることになり
380 、mcに比べて速度低下がみられた。 398 、mcに比べて速度低下がみられた。
381 399
393 411
394 通常、GCCの拡張機能を用いて関数をfastcallにするにはコード 412 通常、GCCの拡張機能を用いて関数をfastcallにするにはコード
395 \ref{code:fastcall-example}の様に ``attribute''キーワードを関数宣言の 413 \ref{code:fastcall-example}の様に ``attribute''キーワードを関数宣言の
396 後ろに記述する。 414 後ろに記述する。
397 415
398 \begin{minipage}[t]{.7\textwidth} 416 \lstinputlisting
399 \lstinputlisting 417 [caption=fastcallな関数fastfuncを宣言する例,label=code:fastcall-example]
400 [caption=fastcallな関数funcBを呼び出す例,label=code:fastcall-example]
401 {sources/fastcall-example.c} 418 {sources/fastcall-example.c}
402 \end{minipage}
403 419
404 しかし全てのコードセグメントに対してこの属性を宣言するのは現実的でなく 420 しかし全てのコードセグメントに対してこの属性を宣言するのは現実的でなく
405 、mcとのソースコードレベルの整合性もとれない。そこでGCCではコードセグ 421 、mcとのソースコードレベルの整合性もとれない。そこでGCCではコードセグ
406 メントの解析時に全てfastcall属性を付加することにする。 422 メントの解析時に全てfastcall属性を付加することにする。
407 423
441 全コードは付録\ref{apx:make-prototype}に掲載する。 457 全コードは付録\ref{apx:make-prototype}に掲載する。
442 458
443 このプトロタイプ自動生成により、これまでに作られたCbCのプログラムとの 459 このプトロタイプ自動生成により、これまでに作られたCbCのプログラムとの
444 互換性が確保できた。 460 互換性が確保できた。
445 461
446 % TODO: prototype declaration 462
447
448