Lispy Days

趣味で Lisp な日々 (index) (weblog) (view) (edit) (help)

CMUCL - CMU Common Lisp

CMU Common Lisp 紹介

Allegro の体験版はライセンス更新がめんどいな〜っと思ってたところに cmucl なんて Common Lisp のコンパイラを発見.すげー.使い始めたばっかなので基本的にマニュアルで調べた事をメモっときます.

個人的に嬉しい順 18d -> 18e での変更点 (release-18e.txt より)

CMU Common Lisp の未来(from CVS)

19a で取りこまれる予定の機能

どうやら 19a のリリースは 12 月半ばあたりになる予定.lisp-executable ブランチはマージされるのだろうか…. とか書いてたら CVS サーバーマシンが落ちて順調に遅れてます。

CMU Common Lisp CVS 版のビルド

2004-03-22 版 on FreeBSD 4.9

common-lisp.net から 入手できるようになったばかりの CVS 版。ビルドには 2003 年 12 月の スナップショットのバイナリを使用。

;; from CMUCL CVS 2003-12-03 (bootfiles 使わないでチャレンジ)
% cvs -d :pserver:anonymous@common-lisp.net:/project/cmucl/cvsroot login
;; password: anonymous
% cvs -d :pserver:anonymous@common-lisp.net:/project/cmucl/cvsroot co src
% ./src/tools/create-target.sh freebsd FreeBSD_gencgc FreeBSD
% vi freebsd/setenv.lisp
% ./src/tools/build-world.sh freebsd
% ./src/tools/rebuild-lisp.sh freebsd
% ./src/tools/build-world.sh freebsd
% ./src/tools/load-world.sh freebsd "pre19a `date \"+ %Y%m%d\"`"
% ./src/tools/build-util.sh freebsd
% ./src/tools/make-dist.sh -g freebsd pre19a x86 freebsd
;; これで
;; cmucl-pre19a-x86-freebsd.extra.tar.gz
;; cmucl-pre19a-x86-freebsd.tar.gz
;; ができる。-b オプションを使うと bzip を使ってアーカイブしてくれる。

MULTIPROCESSING パッケージ

パッケージ概要

Lisp 上でマルチスレッドを実現するためのパッケージ. この Multiprocessing (MP) パッケージは現在 x86 専用です. これは Lisp で実現されているスレッドであるため,x86 上の CMUCL なら環境を問 わず使える…ような気がしますが,念のため *features* を確認してください.

で,注意すべきなのは mp::idle-process-loop のドキュメント部分にあるコレです.

"idle loop to be run by the initial process. The select based event server
is called with a timeout calculated from the minimum of the
idle-loop-timeout and the time to the next process wait timeout. To avoid
this delay when there are runnable processes the idle-process should be
setup to the initial-process. If one of the processes quits by throwing to
%end-of-the-world then quitting-lisp will have been set to the exit value
which is noted by the idle loop which tries to exit gracefully destroying
all the processes and giving them a chance to unwind."

よーするに idle-process-loop は initial process によって実行されなきゃならん わけです.initial process を idle-process-loop で使っちゃったら操作できない じゃん!! というわけで,対話的に使いたい場合は top-level-loop を make-process して動かしておくわけですが,mp パッケージに入っている startup-idle-and-top-level-loops という関数を使えばこの辺が一発でできて便利 です.

使用例

* (mp::startup-idle-and-top-level-loops)

* (defun foo () (sleep 10) (print 'foo) (foo))

foo
* (defun bar () (sleep 20) (print 'bar) (foo))

bar
* (mp:make-process #'foo :name "foo-1")

#<Process foo-1 {48908825}>
* (mp:make-process #'bar :name "bar-1")

#<Process bar-1 {48912FED}>
*

foo
foo
bar
foo
foo

MP パッケージの(公開されてる)関数

で,cmucl-user マニュアルを見たら…あら?記述がない?というわけで,どうやら ドキュメントは documentation で読める分とソースコードしかない模様. src/multi-proc.lisp から public となってるやるを↓にまとめてみました.

(* 関数 *)
process-whostate (process)           - process の状態を返す
process-active-p (process)           - process が Active かどうかを調べる
process-alive-p (process)            - process が生きている(:active 又は :inactive)
                                       かどうかを調べる
current-process ()                   - カレントプロセスを返す
all-processes ()                     - 全てのプロセスのリストを返す

make-process (function &key (name "Anonymous")
                            (run-reasons (list :enable))
                            (arrest-reasons nil)
                            (initial-bindings nil))
                                     - function を実行する process を作成する
process-interrupt (process function) - process に割り込んで function を評価する
destroy-process (process)            - process を削除する
disable-process (process)            - process を :inactive にする
enable-process (process)             - process を :active にする
(process-wait (whostate predicate &rest args)
                                     - process は predicate が真を返すまで wait する.
process-wait-with-timeout (whostate timeout predicate &rest args)
                                     - process は predicate が真を返すか,timeout で
                                       指定した秒数が過ぎるまで wait する.
process-yield ()                     - 他のプロセスに実行を譲る
process-run-time (process)           - プロセスの実行時間を返す
process-idle-time (process)          - idle 時間を返す
process-wait-until-fd-usable (fd direction &optional timeout)
                                     - FD が direction に対して利用可能になるまで wait
                                       して真を返す.timeout 秒過ぎた場合は nil を返す.
sleep (n)                            - N 秒間サスペンドする.N は非負,非複素数の数.
                                      (つまり小数でも OK)
show-processes (&optional verbose)   - 全てのプロセスとその状態を表示する.
                                       verbose が t ならさらに run, real, idle 時間を表示する.

(* マクロ *)
without-scheduling (&body body)      - body を実行中はスケジューリングをしない
with-timeout ((timeout &body timeout-forms) &body body)
                                     - body を実行し,body の最後のフォームの値を返す.
                                       しかし,もし実行に timeout よりも長い時間がかかったら,
                                       実行を中断して timeout-forms を評価し,その値を返す.
with-lock-held ((lock &optional (whostate "Lock Wait") &key (wait t) timeout) &body body)
                                     - lock を保持したまま body を実行.もし,lock が他のプロセス
                                       によって保持されていたなら lock が解放されるまで wait する.
                                       timeout は process-wait-with-timeout と同じ秒を受けとり,
                                       timeout に達っしたら nil が返る.
                                       wait が nil ならば,ロックが他のプロセスに保持されていた
                                       場合は body を処理せずに,すぐに nil が返る.

さらに,CMUCL のランタイムがインタラプトセーフじゃないので危険ですが,

start-sigalrm-yield (&optional (sec 0) (usec 500000))
                                    - process-yield をコールする SIGALRM 割り込みを開始する

なんてのもあるみたいです.ただし,MP パッケージからエクスポートされていない事からもわかるように,お勧めはできません.

REMOTE と WIRE パッケージ

パッケージ概要

REMOTE パッケージと WIRE パッケージは TCP/IP を使って実装されたプロセス間通信(IPC)を提供します.

使用例

;; A.lisp
(defconstant +A+ 5000)
(defconstant +B+ 5001)

(defparameter s (wire:create-request-server +A+))

(defun function-A (str)
  (with-output-to-string (s)
    (write-line "<function-A>" s)
    (write-line str s)    
    (write-line "</function-A>" s)))
;; B.lisp
(defconstant +B+ 5001)

(defparameter s (wire:create-request-server +B+))

(defun function-B1 (str)
  (with-output-to-string (s)
    (write-line "<function-B1>" s)
    (write-line str s)
    (write-line "</function-B1>" s)))

(defun function-B2 (str)
  (with-output-to-string (s)
    (write-line "<function-B2>" s)
    (write-line str s)
    (write-line "</function-B2>" s)))
;;; main.lisp
(defconstant +A+ 5000)
(defconstant +B+ 5001)

(defun process-A (str)
  (let ((s (wire:connect-to-remote-server "localhost" +A+)))
    (wire:remote-value s (function-A str))))

(defun process-B (str)
  (let* ((s (wire:connect-to-remote-server "localhost" +B+))
     (str (wire:remote-value s (function-B1 str))))
    (wire:remote-value s (function-B2 str))))

(defun main ()
  (let* ((str "original")
     (A (process-A str))
     (B (process-B A)))
    (print B)))

というファイルを準備し,CMUCL プロセスを別々に起動してそれぞれ A.lisp,B.lisp, main.lisp をロードします. そして main.lisp をロードしたプロセスで main 関数を呼べば

lambda% lisp
* (load "main.lisp")

t
* (main)

"<function-B2>
<function-B1>
<function-A>
original
</function-A>

</function-B1>

</function-B2>
"

という結果が得られるハズです.

CLOCC のインストール

CLOCC 2004/03/20 日の CVS 版のインストールメモ。http://clocc.sourceforge.net/

clocc の入手

$ cvs -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/clocc login
$ cvs -z3 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/clocc checkout clocc

clocc-top と defsystem コンパイル

ホームディレクトリ以下の ~/commonlisp ディレクトリに clocc を展開 した場合の設定。ここは環境に応じて設定する。

$ cd clocc
$ export LISPTYPE=cmucl
$ vi clocc.lisp
$ cvs diff
Index: clocc.lisp
===================================================================
RCS file: /cvsroot/clocc/clocc/clocc.lisp,v
retrieving revision 1.22
diff -r1.22 clocc.lisp
112c112,113
<   #-(or win32 winnt mswindows cygwin) "/usr/local/src/clocc/"
---
>   ;;#-(or win32 winnt mswindows cygwin) "/usr/local/src/clocc/"
>   #-(or win32 winnt mswindows cygwin) "home:/commonlisp/clocc/"
$ gmake clocc-top

PORT

互換レイヤの PORT パッケージ。CLLIB 等のビルドに必要。

$ cd ../port
$ gmake system

CLLIB

様々なライブラリ。一部 CMUCL で動作しない物があるので cllib.system を編集している。

$ cd ../cllib
$ vi cllib.system
$ cvs diff
cvs server: Diffing .
Index: cllib.system
===================================================================
RCS file: /cvsroot/clocc/clocc/src/cllib/cllib.system,v
retrieving revision 1.30
diff -r1.30 cllib.system
64c64
<      (:file "prompt" :depends-on ("base"))
---
>      #+clisp (:file "prompt" :depends-on ("base"))
67c67
<      (:file "server" :depends-on ("base" "log" "prompt"))
---
>      #+clisp (:file "server" :depends-on ("base" "log" "prompt"))
$ gmake system

SCREAMER

非決定的プログラミングをサポートする SCREAMER パッケージ。

$ cd ../screamer
$ gmake system

ONLISP

Paul Graham 氏 の著作 OnLisp に掲載されていたコード。

$ vi package.lisp
$ cvs diff
cvs server: Diffing .
Index: package.lisp
===================================================================
RCS file: /cvsroot/clocc/clocc/src/onlisp/package.lisp,v
retrieving revision 1.1.1.1
diff -r1.1.1.1 package.lisp
3c3
< (in-package :user)
---
> (in-package :cl-user)
$ lisp
* (compile-file "package")
* (load "package")
* (compile-file "onlisp-util")
* (compile-file "onlisp-app")