calc and Easy-ISLisp

巷では、平成最後の〇〇とか平成時代の△△とかってのが流行っている。流行に乗り遅れないように、オイラーもやってみるか。

平成最後の車検を受けてきたぞ。

そしたら、フロントガラスの車検ステッカーが更新されてた。次回は33年ですってさ。普通は年の下二桁と言えば、西暦年のはず。わーい、次回の車検は2033年ねと喜んだよ。 一応、正式な書類を確認しておくか。新しくなった車検証を点検すると、有効期限は、平成33年ってなってた。何このあやふやな表現。(それは、お前の勝手な解釈だろうに)

そう言えば、運転免許証の有効期限とか無線局の期限も、平成XX年って表示だなあ。未来を表す年に、和暦とはいかがなものかな。パスポートは西暦年になってるって事は、ダブルスタンダード、問題有るって認識してるからこそ、使い分けている。

運転免許証は、西暦表示になるんかな。何処か知らんけど、圧力がかかったんだろね。コンピューター系は、今後も和暦と併用していくのかな。MSに圧力をかけて、和暦を存続させたんだろうね。MSは不合理ですからって理由で拒否すれば良かったのに。そしたら、誉めて遣わすぞ。

新しい年号の発表は4月らしいけど、出し惜しみするものかね。来月になると、某マスコミあたりが、新年号を当てましょう。見事正解を出した人には、豪華な賞品を差し上げますとかやり出すだろか(漢はエゲレス人と違って真面目。競馬はどの国から輸入された? こう考えると、差は無いか)。それとも、何処かからのリークがあって、躍起なって否定したりして。新年号狂騒騒動が起きたりして。

所で、平成元年は、1989年。次の年は平成2年だった。この表記もコンピューターでは、扱いにくいな。H00、H02とか表現したんだろうか? 年が飛ぶ事は無いはずだから、H01,H02と進んでいくはずだな。

とか、考えていたら、 西暦と和暦を変換するには?が出てた。面倒なこっちゃ脳。脳味噌すり減るぞ。

calc programming

歴史の項を見ると、夏休みの自由研究から始まったそうな。まずは、多倍長精 度をサポートする関数ね。emacsには無いからね。

その恩恵を受けるには、defunとしてた所をdefmathにするだけらしい。

(defmath myfact (n)
       (if (> n 0)
           (* n (myfact (1- n)))
         1))

calcFunc-myfact

展開してみるか。

ELISP> (symbol-function 'calcFunc-myfact)
(lambda
  (n)
  (if
      (math-posp n)
      (math-mul n
                (calcFunc-myfact
                 (math-add n -1)))
    1))

マクロの定義なんで、関数の頭に勝手にcalcFunc- が付与される。使うには、 特製手続きのcalc-evalを経由する。

(calc-eval (calcFunc-myfact 50))

"30414093201713378043612608166064768844377641568960512000000000000"

それらしい答えが返ってきた。答え合わせは、schemeでね。

気をよくしたオイラーは、fact 400を求めようとしたんだ。そしたら debuggerが駆けつけてきたよ。

Debugger entered--Lisp error: (error "Lisp nesting exceeds ‘max-lisp-eval-depth’")
  (math-mul n (calcFunc-myfact (math-add n -1)))
  (if (math-posp n) (math-mul n (calcFunc-myfact (math-add n -1))) 1)
  calcFunc-myfact(70)
  (math-mul n (calcFunc-myfact (math-add n -1)))
  (if (math-posp n) (math-mul n (calcFunc-myfact (math-add n -1))) 1)
  calcFunc-myfact(71)
     :

深い呼び出しは不快だそうですよ。calcでやってみると、

Computation got stuck or ran too long.  Type ‘M’ to increase the limit

やはり文句を言われた。けど、ヒントが出てきたなあ。

max-lisp-eval-depth is now 2000

Mを押した時の初期値。更にMを押すと倍々に増加していく。16000に設定した ら、2000 ! が計算できたよ。余りに巨大な整数なんで、実感が解ない。 1.0をかけ算してみると、3.31627509245e5735 すごい桁数になってる事が分か る。

そんな面倒な事をしなくても、 c f で、E形式になるよ。但し、status-lineに表示されてる、有効桁数(デフォが12)を越えた場合ね。E形式から整数にするには、c F。この場合は桁落ちに注意な。

このまま、calcを追いかけてもいいんだけど、他に面白いものを見付けたので、ちょいと浮気する。

Easy-ISLisp

昔、SICPの読書会で一度だけお会いした事がある笹川さんが、素晴しいLispを公開されている。

Easy-ISLispのコンパイラ

O-Prologの仕様、覚書

https://github.com/sasagawa888

どんな物か試してみるか。ウブで開発されているとの事なので、素直にそれに従う。 gccの拡張を使っているそうなので、FreeBSDだとgccをいれないとだめだろうね。

$ ls -d gcc*
gcc/            gcc49/          gcc6-aux/       gcc8/
gcc-ecj45/      gcc5/           gcc7/           gcc8-devel/
gcc48/          gcc6/           gcc7-devel/     gcc9-devel/

portsの中に、こんなに沢山用意されてた。種類が多すぎて、何も買わないで戻ってくるパターンだな。

sakae@usvr:~/src/eisl$ ./eisl -r
Easy-ISLisp Ver0.91
> (load "compiler.lsp")
T
> (load "tarai.lsp")
T
> (time (tarai 10 5 0))
Elapsed Time(second)=0.421907
<undef>
> (compile-file "tarai.lsp")
type inference
initialize
pass1
pass2
compiling PACK
compiling TARAI
compiling FIB
compiling FIB*
compiling ACK
compiling GFIB
compiling TAK
compiling LISTN
compiling TAKL
compiling CTAK
compiling CTAK-AUX
finalize
invoke GCC
T
> (load "tarai.o")
Segmentation fault (core dumped)

うーん、マンダム! 昭和のテレビあるあるによると、主演のチャールズ・ブロンソンさん、撮影が延びて契約時間を過ぎた時、自分の腕時計を1時間遅らせて、大丈夫まだ1時間あるよと言ったとか。カッケイイ!

でも現実は厳しいのよ。ふと、ウブに備わっているgccを確認。

sakae@usvr:~/src/eisl$ gcc -v
 :
gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04)

河岸を代えて、CentOSでやってみると。

cent:eisl$ gcc -v
 :
gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC)

保守的なgccがはいっていました。

> (load "tarai.lsp")
T
> (time (tarai 12 6 0))
enter GBC free=42
exit  GBC free=9997307
 :
Elapsed Time(second)=16.906877
<undef>
> (compile-file "tarai.lsp")
> (load "tarai.o")
T
> (time (tarai 12 6 0))
Elapsed Time(second)=0.028837
<undef>

taraiって、メモリーを消費するような奴だっけな? lispのインプリメント方法によっては、GCが走る場合もあるって事かな。

sakae@usvr:~/src/eisl$ cat test.lsp
(c-include "<stdio.h>")

(defun 1+ (n)
  (c-lang "N+1"))

C語も混在できるそうなので、、、

> (compile-file "test.lsp")
type inference
initialize
pass1
pass2
compiling 1+
finalize
invoke GCC
test.c: In function ‘oneplus’:
test.c:14:1: error: expected ‘;’ before ‘if’
 if(CELLRANGE(N)) Fshelterpop();
 ^~
T

自動生成されるtest.cは醜いので、indentで整形。それをコンパイル。

sakae@usvr:~/src/eisl$ gcc test.c
 :
test.c: In function ‘oneplus’:
test.c:21:9: error: expected ‘;’ before ‘if’
   N + 1 if (CELLRANGE (N))

エラー行が、21行目に移ったので、そのあたりを確認。

sakae@usvr:~/src/eisl$ cat -n test.c
     :
    14  oneplus (int N)
    15  {
    16    int res;
    17    if (CELLRANGE (N))
    18      Fshelterpush (N);
    19    if (Ffreecell () < 900)
    20      Fgbc ();
    21    N + 1 if (CELLRANGE (N))
    22      Fshelterpop ();
    23    return (res);
    24  }

コンパイラーは言われた事を素直にやってるっぽい。源が悪いな。

(c-include "<stdio.h>")
(defun 1+ (n)
  (c-lang "res=N+1;"))

S式の引数は、小文字で与えても、C語内では、大文字になるっぽい。文字列はそのままCのソースになるんで、その積りで書く。

cent:eisl$ ./eisl -c compiler.lsp
Easy-ISLisp Ver0.91
> (compile-file "test.lsp")
type inference
initialize
pass1
pass2
compiling 1+
finalize
invoke GCC
T
> (load "test.o")
T
> (1+ 5)
6

面白いのは、math.lsp。 Oh- モーレツ(小川ローザ風)って、感嘆詞が飛び出しますよ。 素数、簡約、微分と盛り沢山です。それに巨大整数もC語で扱っているしね。

check core

どんな所で落ちているか、coreを作って確認してもいいんだけど、もう少し詳細に追ってみる。gdbにかけられる様に、makefileの該当部分を -ggdb3 -O0 に変更して再コンパイル。

376         init_deftfunc((int)defsubr);
(gdb)
377         init_tfunctions();
(gdb)

Program received signal SIGSEGV, Segmentation fault.
0x000000005555a1c8 in ?? ()
(gdb) bt
#0  0x000000005555a1c8 in ?? ()
#1  0x000055555555c5b0 in dynamic_link (x=1825) at function.c:377
#2  0x0000555555560cab in f_load (arglist=1828) at function.c:1951
#3  0x0000555555559383 in apply (func=1248, args=1828) at main.c:1448
#4  0x000055555555913b in eval (addr=1827) at main.c:1417
#5  0x0000555555555b0b in main (argc=1, argv=0x7fffffffe488) at main.c:309

これか。後はソースを閲覧だな。

このぐらいにしといたる。gccの非互換性ぽいエラーと思われるので、別の環境でも確認。 32Bit機のdebian様。ウブが分家で、本家はこちら。

debian:eisl$ gcc -v
 :
gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1)

この環境だと、すんなりと動いたぞ。

myfunc

今度は、もう少し建設的に、自前の関数を定義。 たいした関数じゃなくて、こんなのね。俗に言う、積和演算。

(defun ma (a x b)
  (c-lang "res=A*X+B;"))
> (ma 3 2 1)
-2147483641

3 * 2 + 1 の計算をお願いしたはずなんだけど、サチッるな。お前は、言う事をきいてくれない、FPGAかと、思わずツィートしたくなるよ。

gdbで悪い所を暴き出したい。なんたって、gdbはソフトウェア業界のオシロスコープですからね。前準備で、自前で、gdbできる様にするか。コンパイラーは、どうやってるかな?

(defun compile-file1 (x)
  (let ((option (cond ((eq (self-introduction) 'windows)
                       "gcc -O3 -shared -o ")
                      ((eq (self-introduction) 'linux)
                       "gcc -O3 -w -shared -fPIC -o ")))

真似て自前でコンパイル。

cent:eisl$ gcc -g -O0 -w -shared -fPIC -c test.c -o test.o
cent:eisl$ ./eisl
Easy-ISLisp Ver0.91
> (load "test.o")
Illegal argument at load "test.o"
debug mode ?(help)
>>:b
NIL
 :
NIL
(DEFGENERIC* CREATE (x :REST y) (:METHOD (x :REST y) (LET ((obj (CREATE* x NIL))) (INITIALIZE-OBJECT obj y) obj)))
(DEFGENERIC* INITIALIZE-OBJECT (x y) (:METHOD (x y) (INITIALIZE-OBJECT* x y)))
(DEFGENERIC* REPORT-CONDITION (x y) NIL)
(LOAD "test.o")

どうも、お気に召さないようだな。しょうがないので、compile.lsp内を変更しちゃえ。

cent:eisl$ gdb -q eisl
Reading symbols from /tmp/eisl/eisl...done.
(gdb) b MA
Function "MA" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y

Breakpoint 1 (MA) pending.
(gdb) r
Starting program: /tmp/eisl/eisl
Easy-ISLisp Ver0.91
> (load "test.o")
T
> (ma 3 2 1)

Breakpoint 1, MA (A=1073741827, X=1073741826, B=1073741825) at test.c:26
26      if(CELLRANGE(A)) Fshelterpush(A);
(gdb) bt
#0  MA (A=1073741827, X=1073741826, B=1073741825) at test.c:26
#1  0x00007ffff7304ae0 in f_MA (arglist=1844) at test.c:15
#2  0x0000000000404e10 in apply (func=1834, args=1844) at main.c:1448
#3  0x0000000000404bec in eval (addr=1841) at main.c:1417
#4  0x00000000004019b8 in main (argc=1, argv=0x7fffffffe428) at main.c:309

とんでもない値で、計算しようとしてるな。少し進めてみる。

29      if(Ffreecell() < 900) Fgbc();
(gdb)
30      res=A*X+B;
(gdb)
31      if(CELLRANGE(B)) Fshelterpop();
(gdb) p A
$1 = 1073741827
(gdb) p res
$2 = -2147483641
(gdb) p A*X+B
$3 = -2147483641

途中で引数が整数になってるかと思ったらそうでもないな。gdb計算機と同じ値になったぞ。

ついでなんで、本体側もgdbできる様にして、呼び出し側も観測してみる。

Breakpoint 2, fast_convert (x=1848) at fast.h:205
205         if(x < 0 || x >= INT_FLAG)
(gdb) n
207             else if(Fintegerp(x)){
(gdb)
208             n = Fgetint(x);
(gdb)
209             if(n >= 0)
(gdb)
210                 res = n | INT_FLAG;
(gdb) p n
$4 = 1
(gdb) n
217         return(res);
(gdb) p res
$5 = 1073741825

あれれ? 折角、整数を取り出したのに、INT_FLAGを付けてる。C語の世界には、そのまま渡して欲しいな。

それはそうと、fast.hを見るはめになって、おぼろげながら制御構造が見えてきた感じがする。 あちこち耕してみるのが、良いおこない。

イソップ物語にあったな。怠け者の息子に、親父が遺言を残す。畑の何処かにお宝が埋まっていると。欲に目がくらんだ息子は、一生懸命に畑を掘るが、何も見つからず。が、その年の収穫は、めっぽう豊作だったと。息子は気がついた。親父の言ってた宝って、この事だったんだと。

所で、8個の引数を持つ関数って、何かな? 興味津々で調べてみるか。4個以上の引数を持つ関数は、それぞれ、10種登録できるようになってるぞ。

Illegal argument at load

ちょいと生成されたCソースを自前でコンパイルしたら、上のようなエラーになってた。 悔しいので、なんとかしたい。

gccが呼びだされる瞬間を捉えればいいだろう。

cent:eisl$ gdb -q eisl
Reading symbols from /tmp/eisl/eisl...done.
(gdb) b system
Breakpoint 1 at 0x4013b0
(gdb) r -c compiler.lsp
Starting program: /tmp/eisl/eisl -c compiler.lsp
Easy-ISLisp Ver0.91
> (compile-file "test.lsp")
type inference
initialize
pass1
pass2
compiling 1+
compiling MA
finalize
invoke GCC

Breakpoint 1, __libc_system (
    line=0x1d4f2a80 "gcc -g3 -O0 -w -shared -fPIC -o test.o test.c ")
    at ../sysdeps/posix/system.c:178
178     {

ふむ、オイラーは、-c をつけていたな。どんな差があるのだろう?

cent:eisl$ indent test.c
cent:eisl$ gcc -g3 -O0 -w -shared -fPIC -o noc.o test.c
cent:eisl$ gcc -g3 -O0 -w -shared -fPIC -o yesc.o -c test.c
cent:eisl$ ls -l noc.o yesc.o
-rwxrwxr-x. 1 sakae sakae 45992 Feb 15 14:45 noc.o*
-rw-rw-r--. 1 sakae sakae 67416 Feb 15 14:45 yesc.o

ちょっと見、サイズが大分違うな。もう少し詳細に分析って事で、 readelf -a したログを見比べてみる。

--- noc.log     2019-02-15 14:47:46.428396663 +0900
+++ yesc.log    2019-02-15 14:48:05.195636256 +0900
@@ -5,387 +5,1528 @@
   Version:                           1 (current)
   OS/ABI:                            UNIX - System V
   ABI Version:                       0
-  Type:                              DYN (Shared object file)
+  Type:                              REL (Relocatable file)
   Machine:                           Advanced Micro Devices X86-64
   Version:                           0x1
-  Entry point address:               0xe60
-  Start of program headers:          64 (bytes into file)
-  Start of section headers:          43752 (bytes into file)
+  Entry point address:               0x0
+  Start of program headers:          0 (bytes into file)
+  Start of section headers:          61144 (bytes into file)
    :

これだけ違えば、怒られるわけだ。

このDYNにしておいて、後で取り込む方式、最近のruby 2.6 だかで、jitって言って宣伝してるな。

最適化は、gccに任せちゃえって方針は、gcl(Kyoto Common Lisp)のアイデアなのかな。 隠れLispであるrubyも、やっと採用に踏み切ったって訳だな。

function.c/dynamic_link

    hmod = dlopen(str, RTLD_LAZY);
    if(hmod == NULL)
        error(ILLEGAL_ARGS, "load", x);
    init_f0 = dlsym(hmod, "init0");
     :

dlopenで、それ用のやつか(遅延リンク可)判断してるんだな。このあたりの機構を理解できれば、rubyソースも問題なく読める(かな?)

at BSD

ふと、FreeBSDには、デフォでgccは入っていないけど、OpenBSDは内蔵してる事を思いだした。 駄目もとで、やってみるか。

ob64$ gcc -v
gcc version 4.2.1 20070719

こんな環境。

make -k
gcc -O4  -Wno-pointer-to-int-cast -Wall -c function.c -lm -ldl
function.c:277: error: expected declaration specifiers or '...' before numeric constant
function.c:277: error: expected declaration specifiers or '...' before '(' token
function.c:277: warning: data definition has no type or storage class
function.c:277: warning: type defaults to 'int' in declaration of 'init_f0'
function.c:278: error: expected declaration specifiers or '...' before numeric constant
function.c:278: error: expected declaration specifiers or '...' before '(' token
 :

リナちゃん用とは違う仕様のgccなのかな?

ならば、clangでは、どうよ?

ob64$ cc -v
OpenBSD clang version 6.0.0 (tags/RELEASE_600/final) (based on LLVM 6.0.0)
ob64$ gmake
cc -O4  -Wno-pointer-to-int-cast -Wall -c main.c -lm
cc: warning: -lm: 'linker' input unused [-Wunused-command-line-argument]
cc: warning: -O4 is equivalent to -O3 [-Wdeprecated]
main.c:88:18: warning: implicit conversion from enumeration type 'toktype' to
      different enumeration type 'backtrack' [-Wenum-conversion]
token stok = {GO,OTHER};
             ~   ^~~~~
1 warning generated.
cc -O4  -Wno-pointer-to-int-cast -Wall -c function.c -lm -ldl
cc: warning: -lm: 'linker' input unused [-Wunused-command-line-argument]
cc: warning: -ldl: 'linker' input unused [-Wunused-command-line-argument]
cc: warning: -O4 is equivalent to -O3 [-Wdeprecated]
function.c:277:13: error: expected parameter declarator
    init_f0(0,(int)checkgbc);
            ^
function.c:277:13: error: expected ')'
function.c:277:12: note: to match this '('
    init_f0(0,(int)checkgbc);
           ^
function.c:277:5: warning: type specifier missing, defaults to 'int'
      [-Wimplicit-int]
    init_f0(0,(int)checkgbc);
 :

やはり、盛大なエラーに見舞われてしまった。それはそうと、オプチマイズ・レベルの -O4って、gccにも有ったかなあ?

NetBSDでは、古いgccが現役で使われていた。やはり同様なエラーになったぞ。互換性を確保するのは大変そうだ。ありきたりの結論スマソ。

etags *.[ch] compiler.lsp

とかして、ソースをじっくりと読んでみよう。そうすれば、近代的なrubyのコードも読めるに違いない。