処理系:実行ファイルの作り方

処理系:実行ファイルの作り方

Tags: 処理系, CCL, CLISP, SBCL

実行ファイルの作り方の、仕組みと処理系ごとの詳しい方法について説明します。



実行ファイルの作り方の仕組み

ランタイム

ランタイムとは、Lispのプログラムを動かすために必要なもののことです。S式を読んだり、評価したり、といった中核の部分(カーネルと呼ばれたりします)と、その中核の部分を利用して動かす、基本的なライブラリなどが含まれる部分を合わせて、ランタイムと呼びます。

ANSI Common Lispで決められている、処理に必要な振る舞いのうち、どこまでを中核の部分に含めるか、どこまでをどの言語で書くかは、処理系によって異なります。本当に最低限の部分だけを中核としてCで書き、コンパイラまでLispで書いてしまう処理系もあれば、ライブラリの部分までCで書いてしまう処理系もあります。また、C++やJavaで書かれている処理系もあります。

実際のLispのプログラムは、このランタイムを利用して動きます。

イメージ

イメージとは、コンパイルされた関数の定義や、変数の定義、型の定義、クラスの定義、処理系内部の状態など、その時点での処理系の状態を再現するために必要な、様々な情報のことです。コアイメージ、ヒープイメージなどと呼ばれたりもします。

狭い意味でのイメージは、こういった情報それ自体、あるいは、ひとつのファイルにまとめて保存したもののことを指しますが、CへのトランスレータがCのコンパイラを経由して作るオブジェクトファイルなども、目的は同じため、一種のイメージと言えます。

ランタイムとイメージの組み合わせ

Lispの処理系が実行ファイルを作る方法は単純です。Cなどと本質的に同じで、結局のところは、ランタイムとイメージを組み合わせているだけです。詳細に差はありますが、どの処理系でもこれは変わりません。

トップレベルの処理

ここでのトップレベルと、HyperSpecで定義されているトップレベルフォームとは関係ありません。ランタイムによるスタートアップコードが起動する、最初に行われる処理のことです。エントリーポイントとも呼ばれます。

ランタイムのトップレベルの処理は多くの場合REPL(式の読み込み→式の評価→結果の表示の繰り返し)ですが、ほとんどのアプリケーションでは、起動直後にREPLに入られても困ります。REPLが不要な場合、適切なトップレベルの処理を指定したり、REPLに入る前にプログラムを終了させたりする必要があります。

Tree-shaking

商用処理系などでは、ランタイムとイメージを組み合わせるとき、アプリケーションを配布するのに不要な部分、例えばコンパイラやデバッガなどを省いて、出来上がるファイルのサイズを抑える機能があります。これをTree-shakingと呼びます。

Common Lispは標準ライブラリが大きいので、できあがる実行ファイルも大きくなりがちですが、この機能がある処理系では多少大きさを抑えることができます。

Purifying

ヒープ領域にあるオブジェクトを静的領域に移動することで、GCの効率を上げることができる機能です。イメージを保存するときにこの処理をすることで、効率良く動作するイメージを作ることができる場合があります。


処理系ごとの詳細

Steel Bank Common Lisp

イメージをファイルに保存する関数のsb-ext:save-lisp-and-dieキーワード引数executableにnil以外の値を渡します。

トップレベルの処理はキーワード引数toplevelで指定した関数で、標準ではREPLです。

$ cat sbcl.lisp
(defun hello ()
  (format t "hello, world~%"))

(sb-ext:save-lisp-and-die "hello-sbcl"
                          :toplevel #'hello     ; トップレベルをhelloに
                          :executable t)
$ sbcl --noinform --no-sysinit --no-userinit --load sbcl.lisp
[undoing binding stack and other enclosing state... done]
[saving current Lisp image into hello-sbcl:
writing 6352 bytes from the read-only space at 0x20000000
writing 4064 bytes from the static space at 0x20100000
writing 59428864 bytes from the dynamic space at 0x1000000000
done]
$ ./hello-sbcl
hello, world

CLISP

イメージをファイルに保存する関数のext:saveinitmemでキーワード引数executableにnil以外の値を渡します。

トップレベルの処理はREPLから変更できませんが、REPLを起動する前にキーワード引数init-functionに指定した関数を呼ぶことができるため、その関数の中でext:exitを呼んでプログラムを終了すれば、REPLには入りません。

$ cat clisp.lisp
(defun hello ()
  (print "hello, world")
  (ext:exit))

(ext:saveinitmem "hello-clisp"
                 :quiet t               ; バナーを表示しない
                 :norc t                ; 初期化ファイルをロードしない
                 :init-function #'hello ; REPLの前にhelloを呼ぶ
                 :executable t)
$ clisp -norc clisp.lisp

Bytes permanently allocated:             92,512
Bytes currently in use:               2,189,152
Bytes available until next GC:          546,473
$ ./hello-clisp

"hello, world"

Clozure CL

イメージをファイルに保存する関数のccl:save-applicationでキーワード引数prepend-kernelにnil以外の値を渡します。

トップレベルの処理はキーワード引数toplevel-functionで指定した関数で、標準ではREPLです。

$ cat ccl.lisp
(defun hello ()
  (print "hello, world"))

(ccl:save-application "hello-ccl"
                      :toplevel-function #'hello        ; トップレベルをhelloに
                      :prepend-kernel t)
$ ccl --no-init --load ccl.lisp
$ ./hello-ccl

"hello, world"

参考文献


Last modified : 2012/06/11 22:37:36 JST
CC0 1.0
Powerd by WiLiKi 0.6.1 on Gauche 0.9