SBCLのソースを解析する

SBCLで使用可能なexternal-formatの話の続き。

結局 SB-IMPL::*external-formats* を見ればわかるということで解決はしたのだが、SBCLはそれ自体がLispで書かれているので、関数readを使ってソースファイルからLispフォームを読み出し、内容を解析することでexternal-formatの定義を抽出することができるのではないか、と思いついた。

結論から言うと、一応は可能だった。しかし想像していたよりもはるかに面倒くさかった。以下はその苦労の記録である(完全なコードは最後に載せてある)。

基本的な戦略

まずfindとgrepによる下調べで次のようなことが判明した。

  • external-formatは3種類のマクロで定義されている
    • define-external-format
    • define-external-format/variable-width
    • define-multibyte-encoding
  • その定義が含まれているのは以下のファイル群である
    • src/code/fd-stream.lisp
    • src/code/external-formats/*.lisp

よってこれらのファイルを read で読んで、該当するマクロフォームからexternal-formatの名前部分を取り出していけば良いはずである。

(defun external-format-definition-p (form)
  (when (listp form)
    (case (first form)
      ((define-external-format define-external-format/variable-width) (second form))
      (define-multibyte-encoding (third form))
      (otherwise (apply #'append (mapcar #'external-format-definition-p form))))))

障害1.リーダーマクロ

対象となるソースファイルの中には #! で始まる独自のリーダーマクロを使ったコードが含まれており、そのままではreadで読み込めなかった。このマクロの正体が分からず随分苦労したのだが、どうやら既存の処理系でSBCL自体をコンパイルするときに、#!+ および #!- という形式で、それぞれ #+ と #- に類似する機能を提供するためのものらしい(参考)。そういう特殊な役割を持ったマクロであるために、実行時に使うことはできないようだ(実装は src/cold/shebang.lisp の中にある sb-cold::shebang-reader だが、実行時には sb-cold パッケージは存在しない)。

単純に #! を無視するだけのリーダーマクロでは現在の環境で動かないコードまで読まれてエラーになったので、#+ および #- の実装に処理を転送することにして、どうにか動くようにした。

(defun shebang-reader (stream sub-character infix-parameter)
  (let ((next-char (read-char stream)))
    (funcall (if (char= next-char #\+) #'sb-impl::sharp-plus #'sb-impl::sharp-minus)
	     stream sub-character infix-parameter))
  (values))

障害2.パッケージ参照

read は、読み出したLispフォームに存在しないパッケージへの参照が含まれているとエラーを起こす。

ところがSBCLのソース中には、ソース中の表記(SBCLのコンパイル中に使われる名前?)と実行時の名前が異なるというパッケージが存在することが分かった。例えばSB!IMPL(動作中はSB-IMPL)、SB!THREAD(動作中はSB-THREAD)などである。

ソースを注意深く読み、試行錯誤を繰り返した結果、sb-impl::bootstrap-package-not-found エラーに対し sb-impl::debootstrap-package 再起動を行ってやれば、これらのパッケージをソース中に表記された名前で参照できるということが分かった。

(defmacro with-debootstrap-package (&body body)
  `(handler-bind ((sb-impl::bootstrap-package-not-found
		   #'sb-impl::debootstrap-package))
     ,@body))

例:

(with-debootstrap-package (find-package "SB!IMPL"))
; => #<PACKAGE "SB-IMPL">

……

以上の障害(本当はもっといろいろあるが)を乗り越え、ようやく意図通りの動作をするようになった。以下に全コードを載せておく。しかしexportされていないシンボルを使いまくっているので、バージョンが違うと動かない可能性がある(sbcl-1.0.23-x86-darwin で動作確認)。もちろん、SBCLでしか動かない。

『実践Common Lisp』15章のライブラリを使っている。

Leave a Reply