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を公開されている。
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のコードも読めるに違いない。