コンパイル

Euslispコンパイラは、Lispプログラムの実行を高速化するために使用される。 実行時間の5〜30倍の高速化とマクロ展開によるガーベージコレクション 時間の大幅な減少が期待できる。

euscompは、計算処理とベクトル処理のための最適化を行う。 ときどきコンパイラが最適化を効率良く実行するために、固有の型宣言が必要となる。

compile-functionは、1つずつ関数をコンパイルする。 compile-fileは、すべてのソースファイルをコンパイルする。 compile-fileを実行している間、ファイル内のすべての書式が読み込まれ 評価される。 これは、現在のEuslispの環境を変化させる。 例えば、defparameterはsymbolに新しい値を設定するし、 defunはコンパイルされていない関数をコンパイルされた関数に 置き換える。 これらの予期しない影響を避けるためには、compile指定のないeval-whenを 使用したり、euscompを使用して別プロセスとしてコンパイラを実行したりする。

euscompはUNIXのコマンドで、普通eusにシンボリックリンクされている。 幾つかのオプションを持っている。 -OフラグはCコンパイラの最適化を指示し、 -O1,-O2, -O3 はそれぞれEuslispコンパイラの最適化のレベルを指示する。 これは、(optimize 1 or 2 or 3)と宣言するのと同等である。 -S0, -S1, -S2, -S3は、compiler:*safety*に0,1,2,3を設定する。 もし*safety*が2より小さければ、割り込みチェックのためのコードを発行しない。 もし、プログラムが無限ループに入ったとき、制御を失うことになる。 もし*safety*が0のときは、引き数の数をチェックしない。 -Vフラグは、コンパイルされている関数名を表示する。 -cフラグは、ccの実行やforkを防ぐ。 -Dは、*features*リストに続く引き数を置く。 これは、読み込みマクロ#-#+ を用いた条件付きコンパイルのために使用することができる。

コンパイラは"xxx.l"という名のEuslispソースプログラムを 中間Cプログラムファイル"xxx.c" とヘッダーファイル"xxx.h"に変換する。 それから、Cコンパイラが実行され、"xxx.o"が生成される。 中間ファイル"xxx.c"と"xxx.h"はクロスコンパイルの目的のために残される。 したがって、違うアーキテクチャーの機械の上で使用したいとき、UNIX命令のccで "xxx.c"ファイルをコンパイルするだけでよい。 コンパイルされたコードは、'(load "xxx")'によってEuslispにロードされる。

中間ファイルはそれぞれ、"eus.h"ヘッダーファイルを参照する。 このファイルは、*eusdir*/cディレクトリに置かれていると仮定している。 *eusdir*は、EUSDIR環境変数からコピーされる。 もし設定されてなければ、/usr/local/eus/がデフォルトディレクトリ として扱われる。

コンパイルされたとき、中間のCプログラムは普通元のソースコードよりも かなり大きくなる。例えば、1,161行のlispソースコード"l/common.l"は、 8,194行の"l/common.c"と544行の"l/common.h"に展開される。 1,000行のlispソースをコンパイルするのは、難しい作業ではないが、 10,000行近いCのプログラムを最適コンパイルすることは、長い時間(数分)かかる とともに、たくさんのディスク空間を消費する。 そのため、もし相対的に大きなプログラムのコンパイルをするならば、 /var/tmpに十分なディスクがあるかどうかを確認すること。 そうでなければ、CCは死ぬだろう。 TMPDIR環境変数をもっと大きなディスク部分に設定することが助かる道である。

リンクがロード時または実行時に実行されるので、 eusのカーネルがバージョンアップされても再コンパイルする必要はない。 もう一方で、実行時リンクは不便なことがある。 2つの関数AとBが"x.l"ファイルにありAがBを呼び出していると仮定する。 "x.l"をコンパイル後、"x.o"をロードし内部でBを呼び出しているAを呼び出そうとする。 それから、Bの中でbugを見つけると、たぶんBを再定義しようとするだろう。 ここで、コンパイルされたAとコンパイルされていないBとができる。 再びAを呼び出したとすると、Aはまだ古いコンパイルされているBを呼び出す。 これは、Aが最初にBを呼び出したとき固定的にリンクされるからである。 この問題を避けるためには、Aを再定義しなおすかあるいは"x.o"がロードされた直後で Aを呼び出す前にBを再定義しなければならない。

コンパイルされたコードがロードされるとき、一般的にdefunやdefmethodの 列である最上位コードが実行される。 この最上位コードはロードモジュールのエントリ関数として定義されている。 コンパイラがそのエントリ関数の名前を付け、 ローダがこの関数の名前を正確にわからなければならない。 状況を簡単にするために、コンパイラとローダの両方とも そのエントリ関数の名前としてオブジェクトファイルのbasenameと同一のもの と仮定する。 例えば、もし"fib.l"をコンパイルしたならば、 コンパイラは"fib.c"のエントリ関数として"fib(...)"を生成する。 そして、ローダはオブジェクトファイル"fib.o"の中から"fib"を探す。 最終的にオブジェクトファイルはUnixの"cc"や"ld"で生成されるので、 このエントリ関数名は、C関数の命名ルールを満足しなければならない。 したがって、ファイル名としてCの予約キーワード(例えば、 "int", "struct", "union", "register", "extern"など)や"c/eus.h"に 定義されているプライベート指示語(例えば、"pointer", "cons", "makeint"など)を避けなければならない。 もし、ソースファイルの名前としてこれらの予約語の内の1つを 使用しなければならないならば、コンパイラやローダの :entry引数を別に指定すること。

closureの使用には制限がある。 closureの中のreturn-from特殊書式とunwind-protectの中のclean-up書式は いつも正しくコンパイルされるわけではない。

disassembleは、実現されていない。 コンパイルされたコードを解析するためには中間Cプログラムを見るかあるいは adbを使用する。



euscomp {filename}* [UNIXコマンド]

Euslispコンパイラを呼びだす。



\begin{emtabbing}
{\bf compile-file}
\it srcfile \&key \= (:verbose nil) \hspac...
...m ; run c compiler} \\
\> (:entry (pathname-name file)) \\
\rm
\end{emtabbing}

ファイルをコンパイルする。 ".l"がsrcfileの拡張子として仮定される。 もし、:verboseがTならば、コンパイルされた関数やメソッド名が表示される。 これは、エラーが発生した箇所を簡単に探すのに役立つ。 :optimize, :c-optimize:safetyは、最適化のレベルを 指定する。 モジュールが作成中にEuslispのコアにハードリンクされていないかぎり、 :picは、Tに設定すべきである。


compile funcname [関数]

関数をコンパイルする。compileは、最初に関数定義をテンポラリファイルに 出力する。そのファイルは、compile-fileによってコンパイルされ、 それからloadによってロードされる。 テンポラリファイルは削除される。


compile-file-if-src-newer srcfile &key compiler-options [関数]

srcfileが対応するオブジェクトファイルよりも新しい(最近変更された) ならば、コンパイルする。そのオブジェクトファイルは、".o"拡張子を 持っていると仮定される。


*optimize* [変数]

コンパイラの最適化レベルを制御する。


*verbose* [変数]

non-NILが設定されたとき、コンパイルされている関数名やメソッド名そして コンパイルに要した時間を表示する。


*safety* [変数]

安全性レベルを制御する。


2016-04-05