OpenLisp

明けましておめでとうございます。本年も宜しくお願い致します。

OpenLisp

ひょんなことから、 OpenLisp by Eligis なんてのを知った。 Downloads にあるように、色々なOSで動く。

OpenLispのOpenは、ソースがオープンになってるのではなくて、いろんなプラットフォームに対してのOpenを目指しての事。それにしても、多数のOS環境を用意するのは大変だろうに。

ソースが欲しければ、メールで作者様を説得して下さいとの事。決してクローズでは無いらしい。フリー版には、コンパイラーが付いていないけど、ちょいと使うには十分だろう。 まあ、Comparison of ISLISP implementations. なんてのを見ると、そのうち欲しくなるかも。

開発方針として

OpenLisp  is  KISS  (Keep  It  Small  and Simple) for full conforming
implementation  of  ISO/IEC  13816  ISLISP Language the International
Standard version of Lisp.

小さくてシンプルにだ。どこかのCL連中(とリナ軍団)に、捧げたい言葉だな。

ドキュメントが公開されてる。何を血迷ったか、doc式の奴しか無い。しょうがないので、libreofficeで開いて、htmlでセーブしたよ。PDFぐらいはサポートしてるかと思ったけど、ちと甘かった。

とか思っていたら、アイコンをぷちゅっとするだけで、PDFに輸出出来る事に気が付いた。普段は、メニューにあるアイコンなんて見ないからね。

2020/12/28  07:15         1,150,432 olus.html
2020/12/28  07:17           636,877 olus.pdf
2020/12/28  07:17           266,906 olus.txt

run OpenLisp

取り合えずリナ用を落として実行。

sakae@pen:/tmp/openlisp-11.0.0$ ./uxlisp
./uxlisp: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found 
(required by ./uxlisp)

Debianでは、どうやら根幹のバージョンが古いようで、起動せず。Changelogを見たら、Fedora系しかサポートしていない。営業戦略だな。しょうがないので、OpenBSD(64Bit)で試す。

2020/10/22: Qualified port on OpenBSD 6.8 (x86_64 with Clang 10.0.1).
2020/06/05: Qualified port on OpenBSD 6.7 (x86_64 with gcc 4.2.1/Clang 8.0.1).
2020/02/03: Qualified port on OpenBSD 6.6 (sparc64 with gcc 4.2.1).
2019/10/28: Qualified port on OpenBSD 6.6 (x86_64 with gcc 4.2.1/Clang 8.0.1).
2019/07/03: Qualified port on OpenBSD 6.5 (x86_64 with gcc 4.2.1/Clang 7.0.1).
2018/10/21: Qualified port on OpenBSD 6.4 (x86_64 with gcc 4.9.3/Clang 6.0.0).
2017/03/05: Qualified port on OpenBSD 6.0 (i386 & x86_64 with gcc 4.9.3).
2016/09/03: Qualified port on OpenBSD 6.0 (i386 & x86_64 with gcc 4.9.1).
2016/09/01: Qualified port on OpenBSD 6.0 (i386 with gcc 4.2.1).
2015/10/24: Qualified port on OpenBSD 5.8 (x86_64 + PGO) with egcc 4.9.3.
2015/10/04: Qualified port on OpenBSD 5.7 (x86_64 + PGO) with gcc 4.2.1.
2011/11/02: Qualified port on OpenBSD 5.0 (i386/x86_64) with gcc 4.2.1.
2011/01/05: Qualified port on OpenBSD 4.8 (i386/x86_64) with gcc 4.2.1.
2003/01/02: Qualified port on OpenBSD 3.1 (Intel).

これ、ChangelogからのOpenBSDサポート状況。どちらも実績が長いですなあ。

ob$ ./uxlisp
;; OpenLisp v11.0.0 (Build: 6650) by C. Jullien [Oct 29 2020 - 23:54:42]
;; Copyright (c) Eligis - 1988-2020.
;; System 'unix' (64bit, 4 CPU) on 'ob.localhost.jp', ASCII.
;; Reading startup ..
;; God thank you, OpenLisp is back again!

今度は、ちゃんと起動して素敵なバナーが出て来た。READMEにベンチマークしろって出てたんで、試してみた。

? (load "contrib/gabriel.lsp")
 01Fib     : ok, time =   0.001s. ( 0 GC)
 02Tak     : ok, time =   0.005s. ( 0 GC)
 03Stak    : ok, time =   0.009s. ( 0 GC)
 04Ctak    : ok, time =   0.080s. ( 0 GC)
 05Takl    : ok, time =   0.007s. ( 0 GC)
 06Takr    : ok, time =   0.007s. ( 0 GC)
 07Boyer   : ok, time =   0.064s. ( 0 GC)
 08Browse  : ok, time =   0.075s. ( 3 GC)
 09Destru  : ok, time =   0.011s. ( 0 GC)
 10Travini : ok, time =   0.073s. ( 0 GC)
 11Travrun : ok, time =   0.336s. ( 0 GC)
 12Deriv   : ok, time =   0.012s. ( 1 GC)
 13Dderiv  : ok, time =   0.014s. ( 1 GC)
 14Divit   : ok, time =   0.010s. ( 0 GC)
 15Divrec  : ok, time =   0.009s. ( 0 GC)
 16FFT     : ok, time =   0.058s. ( 0 GC)
 17Puzzle  : ok, time =   0.076s. ( 0 GC)
 18Triang  : ok, time =   1.029s. ( 0 GC)
 19Fprint  : ok, time =   0.001s. ( 0 GC)
 20Fread   : ok, time =   0.001s. ( 0 GC)
 21Tprint  : ok, time =   0.000s. ( 0 GC)
 22Frpoly  : ok, time =   0.178s. ( 0 GC)
                       2.056 s.

使ってるライブラリーは、極普通なものだけ。

ob$ ldd uxlisp | cut -b 35-
 Type  Open Ref GrpRef Name
 exe   2    0   0      uxlisp
 rlib  0    1   0      /usr/lib/libpthread.so.26.1
 rlib  0    1   0      /usr/lib/libm.so.10.1
 rlib  0    1   0      /usr/lib/libc.so.96.0
 ld.so 0    1   0      /usr/libexec/ld.so

setup openlisp

ここまでは、導入テスト。普段使いするなら、ちゃんとした場所に配置して、emaacsと併用する事になる。

openlisp-11.0.0って言う2.1M程のdirを、/usr/local/openlispって名前で配置した。 これに合わせて、.profileに設定を書く。

export OPENLISP=/usr/local/openlisp
PATH=$PATH:/usr/local/openlisp

openlisp.elは、.emacs.d/lisp/の下で集中管理する事にしてるんで、本体側からcpしておいた。

(setq inferior-lisp-program "/usr/local/openlisp/openlisp -emacs")
(load-file "~/.emacs.d/lisp/openlisp.el")

説明書では、本体側にリンクが必要無いとなってたが、uxlispにリンクしないと動かなかった。 後は、M-x run-lispで起動する。裸のopenlispより格段に使い易くなる。

? (dynamic *system-directory*)
;; elapsed time =  0.0000s, (0 gc).
= "/usr/local/openlisp"
? (dynamic *system-path*)
;; elapsed time =  0.0000s, (0 gc).
= ("." "/usr/local/openlisp/lib" "/usr/local/openlisp/net" 
"/usr/local/openlisp/contrib" "/usr/local/openlisp/bench" 
"/usr/local/openlisp/tst")

本体側の各dirが、検索PATHに追加されてる。

? (room)
System name: openlisp, pointer size 8, character size: 1
Type    Call      Init   MemInit      Used   MemUsed      Free   MemFree Free%
user       1         0         0         0         0         0         0  100%
cons       0    252928   4046848      5856     93696    247072   3953152   97%
symbol     0     15616    999424      1147     73408     14469    926016   92%
string     0    252928   4046848      1298     20768    251630   4026080   99%
vector     0    252928   4046848       392      6272    252536   4040576   99%
float      0         0         0         0         0         0         0  100%
integer    0         0         0         0         0         0         0  100%
heap       0  16777216  16777216     58520     58520  16718696  16718696   99%
Total      1         0  29917184         0    252664         0  29664520   99%
;; elapsed time =  0.0086s, (1 gc).
= t

これがメモリーエリアの使用状況。エリアが細かく分けられている。エリアの特性に合わせて、最適な管理を行う為だろう。

fib

(defun fib (n)
  (cond ((= n 1) 1)
        ((= n 2) 1)
        (t (+ (fib (- n 1)) (fib (- n 2))))))

fibを肴に、ちょっとお遊び

? (fib 40)
;; elapsed time = 17.9737s, (0 gc).
= 102334155

OpenBSDは、スピード控え目です。

compile-file

バイナリー版にもコンパイラーが付いていて、VM語に変換される

? (compile-file "fib.lsp")
;; elapsed time =  0.0227s, (0 gc).
= t
? (load "fib.lap")
;; elapsed time =  0.0001s, (0 gc).
= t
? (fib 40)
;; elapsed time =  8.7163s, (0 gc).
= 102334155

裸のコードに比べてスピードが2倍になった。

(set-dynamic 'load *situation*)

(openlisp:lap-require "loader")

(lap-loader '(
 ;;
 ;; fib
 ;;
  ((fentry fib 1 0 0)
   (param 0)
   (jeq _l004 '1)
   (jneq _l003 '2)
   (move a1 '1)
   (return)
  _l003
   (gsub1 a1)
   (recurse 1)
   (move a2 a1)
   (param 0)
   (gsub a1 '2)
   (recurse 1)
   (gadd a2 a1)
  _l004
   (return)
   (end))
))

これの解説は、説明書に出てた。有料版だとこれをC語に変換してC++でコンパイルするらしい。 そうすれば、更に10倍ぐらい速くなるそうな。

trace

traceはlisp系の必需品。

? (load "fib.lsp")
;; elapsed time =  0.0001s, (0 gc).
= t
? (trace 'fib)
;; elapsed time =  0.0000s, (0 gc).
= fib
? (fib 3)
   fib -> (n = 3)
      fib -> (n = 2)
      fib <- 1
      fib -> (n = 1)
      fib <- 1
   fib <- 2
;; elapsed time =  0.0001s, (0 gc).
= 2

debug

書いたコードがBUGを含んでいると、下記のようにエラーになる。そんな時はdebuggerの出番だ。debuggerをイネーブルにしてあげてから、再実行。

? (fib 4)
 ** <domain-error> : function '+' requires a <number> received "bad".
? (debug t)
;; elapsed time =  0.0000s, (0 gc).
= t
? (fib 4)
;; OpenLisp symbolic debugger.
;; Enter '?' for help, ':q' to quit.
 ** <domain-error> : function '+' requires a <number> received "bad".
 [001] (fib 3)
 [002] (fib 4)

履歴出てる。どうやら、(fib 2)の所で数値を期待するも文字列だったので、演算出来なかった模様、なんて事が分かる。debugを止めるには、nilにする。

speed check

(fib 40)が遅すぎると思ったんで、他と比べてみる。

たまたま入っていたeclです。オリジナルは随分昔の物だけど、今現在もちゃんとメンテナンスされていました。

ob$ ecl
ECL (Embeddable Common-Lisp) 20.4.24 (git:UNKNOWN)
Copyright (C) 1984 Taiichi Yuasa and Masami Hagiya
  :
Copyright (C) 2020 Daniel Kochmanski and Marius Gerbershagen

;;; Loading "/tmp/fib.lis"
#P"/tmp/fib.lis"
> (time (fib 32))

real time : 3.928 secs
run time  : 3.700 secs
gc count  : 67 times
consed    : 139411808 bytes
2178309

;;; Loading "/tmp/fib.fas"
#P"/tmp/fib.fas"
> (time (fib 32))

real time : 1.136 secs
run time  : 1.130 secs
gc count  : 1 times
consed    : 64 bytes
2178309

> (time (fib 40))

real time : 53.259 secs
run time  : 53.270 secs
gc count  : 1 times
consed    : 64 bytes
102334155

コンパイルすると、4倍近く増速になる。組み込み用途を目指しているので、ふっとプリントが軽井沢。openlispさすがに速いな。

debian(32Bit)と言うハンディが有る環境から、scheme界のエースが出場します。

debian:tmp$ scheme fib.ss
Chez Scheme Version 9.5.3
Copyright 1984-2019 Cisco Systems, Inc.

> (time (fib 40))
(time (fib 40))
    no collections
    2.348954777s elapsed cpu time
    2.384591957s elapsed real time
    0 bytes allocated
102334155

contrib/gnuplot.lsp

(defun trigonometrie ()
  (with-open-output-pipe (gp "gnuplot -persistent")
     (format gp "set samples 2000~%")
     (format gp "plot abs(sin(x))~%")
     (format gp "rep abs(cos(x))~%")))

(mandelbrot 200) ;; better with 1000 or more. なんて書いてあったので、Openlispで計算したデータをgnuplotに送っているかと思ったら違った。丸投げしてgnuplotに計算させてる。

そんな事より、with-open-output-pipeが、どう実現されてるか興味がある。多分マクロだろう。そしてその成分はpipeをオープンするやつと、unwind-protectと想像した。

lib/startup.lsp

(defmacro with-open-output-pipe (stream &rest body)
   `(let ((,(car stream) (open-output-pipe ,@(cdr stream))))
         (unwind-protect (progn ,@body)
                         (close ,(car stream)))))

ビンゴ!! このパターンは結構使い道があるぞ。ユーザーマニュアルに載ってた例。

(defmacro with-client-socket (socket &rest body)
  `(let ((,(car socket) (socket)))
        (when ,(car socket)
              (unwind-protect (when ,`(connect ,@socket)
                                    ,@body)
                              (close ,(car socket))))))

(defun daytime ()
   (with-client-socket (st "127.0.0.1" "daytime" "tcp")
        (read-line st () ‘eof)))

ローカルホストのdaytimeポートにtcpを使って接続しろ、かな?

debian:doc$ grep daytime /etc/services
daytime         13/tcp
daytime         13/udp

udpポートもサポートされてるのか。macroを使って、手軽に構文を作れるってとっても素敵。

debug on eisl

> (load "tests/verify.lsp")
enter COPY-GC free=6999994
exit  COPY-GC free=7000000
All tests are done!
T
> (debug)
Unbound function at eval DEBUG
debug mode ?(help)
>>?
?  help
:a abort
:b backtrace
:d dynamic environment
:e environment
:i identify examining symbol
:q quit
:r room
:s stepper ON/OFF
other S exps eval

明示的にdebug手続きを呼び出せる。

>>:r
EP = 0 (environment pointer)
DP = 10747982 (dynamic pointer)
HP = 6696963 (heap pointer)
SP = 0 (stack pointer)
FC = 5998368 (free counter)
AP = 0 (arglist pointer)
LP = 0 (shelter pointer)
GC = 1 (GC switch 0=m&sGC 1=copyGC)
WP = 10863751 (work area pointer)
SW = 1 (current work area 1or2)

そして、gdbを使わなければ見られないような重要なレジスターを一覧出来る。Pが付くレジスターは、HEX表示の方が便利だろうか? 実験した限りでは、heap配列のインデクス番号みたいなんで、整数の方が分かり易いか。 こんな感じね。

> (heapdump 6696880)
addr    F   TAG    CAR     CDR     AUX     NAME
6696880 FRE EMP
6696881 FRE LIS    6696930 6696989 0000021
6696882 FRE EMP
6696883 FRE EMP
6696884 FRE LONGN  274
6696885 FRE BIGX   274
6696886 FRE EMP
6696887 FRE BIGX   0
6696888 FRE EMP
6696889 FRE EMP
6696890 FRE EMP
T

cancel tel