読者です 読者をやめる 読者になる 読者になる

FASLファイルの中身を見てみた

Practical Common Lisp (邦訳『実践Common Lisp』) を改めて読み直し中。第2章でLISPファイルのコンパイルについて書かれていたところがあったので、いくつかの処理系で試してみることに。

準備

まず以下のような簡単なプログラムを作り、hello.lispとしてファイルに保存します。

(defun hello-world ()
  (format t "Hello, world!"))

これを実行するときには

CL-USER> (load "hello.lisp")
T
CL-USER> (hello-world)
Hello, world!
NIL

のように、REPLで読み込んでから実行します。また、処理速度*1が必要な場合には、

CL-USER> (load (compile-file "hello.lisp"))

というように、あらかじめコンパイルしてから読み込んで実行することもできます。このとき、hello.lispと同じディレクトリにはコンパイル済みのファイル(Fast Loadファイル)ができますが、このファイルの中身は処理系に依存したバイトコードらしく、僕が試した限りでは、SBCLはhello.fasl、ABCLはhello.abcl、ClozureCLはhello.dx64fslと、拡張子も異なりました。

SBCL

それぞれのファイルの中をバイナリダンプで見てみることにします。SBCLは冒頭部分にshebangが書いてあります。

$ hexdump -C hello.fasl | head -n 4 
00000000  23 21 2f 75 73 72 2f 6c  6f 63 61 6c 2f 62 69 6e  |#!/usr/local/bin|
00000010  2f 73 62 63 6c 20 2d 2d  73 63 72 69 70 74 0a 23  |/sbcl --script.#|
00000020  20 46 41 53 4c 0a 20 20  63 6f 6d 70 69 6c 65 64  | FASL.  compiled|
00000030  20 66 72 6f 6d 20 22 2f  55 73 65 72 73 2f 6d 61  | from "/Users/ma|

shebangがあるので、パーミッションを実行形式に変えてやればコマンドラインから実行することができます。ただし、上記のhello.lispのままだと関数定義しかしていませんので何も表示されません。以下のように関数を呼びだす一文を追加します。

(defun hello-world ()
  (format t "Hello, world!"))
(hello-world)

それをSBCLで(compile-file "hello.lisp")して、次にコマンドラインでchmodすると、ちゃんと実行できました。

$ chmod 755 hello.fasl 
$ hello.fasl
Hello, world!

ABCL

さて、ABCLが書き出したhello.abclはどうなっているかと言うと、

$ hexdump -C hello.abcl | head -n 4
00000000  50 4b 03 04 14 00 08 00  08 00 12 53 4c 42 00 00  |PK.........SLB..|
00000010  00 00 00 00 00 00 00 00  00 00 07 00 00 00 68 65  |..............he|
00000020  6c 6c 6f 2e 5f 6d 8f 41  4f 84 30 10 85 ef fc 8a  |llo._m.AO.0.....|
00000030  49 7b 58 4b 52 ba e2 49  36 7b 20 6c 51 62 a1 4a  |I{XKR..I6{ lQb.J|

冒頭にPKの2文字があるため、zipアーカイブ形式なのだろうと見当が付きます。実際、

$ unzip hello.abcl
Archive:  hello.abcl
  inflating: hello._                 
  inflating: hello_1.cls             
  inflating: hello_2.cls             

のように解凍することができました。.clsファイルの冒頭を確認すると「ca fe ba be」とあるので、Javaのクラスファイルなのでしょう。

$ hexdump -C hello_1.cls | head -n 4
00000000  ca fe ba be 00 00 00 31  00 3b 01 00 0b 48 45 4c  |.......1.;...HEL|
00000010  4c 4f 2d 57 4f 52 4c 44  08 00 01 01 00 10 43 4f  |LO-WORLD......CO|
00000020  4d 4d 4f 4e 2d 4c 49 53  50 2d 55 53 45 52 08 00  |MMON-LISP-USER..|
00000030  03 01 00 17 6f 72 67 2f  61 72 6d 65 64 62 65 61  |....org/armedbea|

ただ、実行可能なJARファイルではなさそうです。(JARもZIP圧縮されているファイルなので、jar xvf hello.abclとすれば解凍はできます)

$ java -jar hello.abcl 
Invalid or corrupt jarfile hello.abcl

ClozureCL

ClozureCLのhello.dx64fslも見てみましたが、こちらは意味が分かりませんでした。処理系独自のバイトコードなんでしょうかね。

$ hexdump -C hello.dx64fsl | head -n 4
00000000  ff 00 00 01 00 00 00 0c  00 00 01 eb ff 5f 00 00  |............._..|
00000010  00 00 18 87 17 0a 00 53  2f c5 8a 68 65 6c 6c 6f  |.......S/..hello|
00000020  2e 6c 69 73 70 46 a2 36  84 8f 1c 2c 82 42 c1 83  |.lispF.6...,.B..|
00000030  43 43 4c 8f 46 49 4e 44  2d 43 4c 41 53 53 2d 43  |CCL.FIND-CLASS-C|

おわりに

以上、SBCL、ABCL、ClozureCLと、どれも全く異なるFASLファイルを書き出しましたが、コマンドライン・プログラムを作るのであればSBCLが楽チンそうなのが分かりました。もちろん、他の処理系でも実行形式ファイルを作ることは可能です。例えばABCLならTowards ABCL Standalone Executablesなどが参考になります。

*1:どちらかというと元々の意図はプログラムの実行速度ではなく、プログラム実行前にメモリに読み込む際の時間短縮が目的のようです。処理系の中にはコンパイルすることで実行速度も上がるものもありますが。