flisp, Stack Machine (2)

弱LisperがMITでSICP(シクピー)を受講した結果 なんてのを読んでいる。この教科書のコースは終了したと聞いていたけど

無理矢理復活している(コースのメールアドレスが6.001-zombies!)のを発見。

ゾンビが存在してて、短時間でLAMBDA教へ入信出来るようだ。そして、無事にコースを 終了すると、LAMBDA団のバッチを貰えるらしい。以後、死ぬまで、ラムダ、ラムダ、ラムダと 唱える人生が待っているとか。

こちらに、その教義が出ている。 6.037 - Structure and Interpretation of Computer Programs

SICPとは章だてが違うようで、SICPではやたら長かった4章があっさりしたものに変わって いるっぽい。その代わり、call/ccが入っていたり、やたら、自分schemeを書いてみようでって のが重視されてるようだ。

そうだよな、LAMDA団なら、evalとapplyを行ったりきたりして、上り詰めていく体験を 是非深く味わって欲しいとの親心だろう。

今、SICPを確認したら、レジスター計算機って説明してた。そうか、スタックマシンに 対して、あちらはレジスターマシンか。そんな事なら、もう一度該当章を読み直しても いいな。あるいは、手っ取り早く、codeを眺めるのもいいか。

まてまて、LAMDA教としては、写経せいと言われそうだな。 なんてのを無視して、Diskを漁ってみたら、楽しいのが出てきた。ch5-compiler.scm。 SICPが佳き解説書になるな。

;;;;Then you can compile Scheme programs as shown in section 5.5.5

;;**implementation-dependent loading of syntax procedures
(load "ch5-syntax.scm")                 ;section 4.1.2 syntax procedures


;;;SECTION 5.5.1

(define (compile exp target linkage)
  (cond ((self-evaluating? exp)
         (compile-self-evaluating exp target linkage))
        ((quoted? exp) (compile-quoted exp target linkage))
        ((variable? exp)
         (compile-variable exp target linkage))
        ((assignment? exp)
         (compile-assignment exp target linkage))
        ((definition? exp)
         (compile-definition exp target linkage))
        ((if? exp) (compile-if exp target linkage))
        ((lambda? exp) (compile-lambda exp target linkage))
        ((begin? exp)
         (compile-sequence (begin-actions exp)
                           target
                           linkage))
        ((cond? exp) (compile (cond->if exp) target linkage))
        ((application? exp)
         (compile-application exp target linkage))
        (else
         (error "Unknown expression type -- COMPILE" exp))))

ほら、flispに付属のcompiler.scmとそっくりじゃん。flispの作者さんもやっぱり、LAMBDA団の 一員だな。

flispをNetBSDへ移植

gitからのflisp-dir内で、git log してたら、

commit 56b46ba923c2aa4d150bbb28de29fa57c3adbef6
Author: James Turner <james@calminferno.net>
Date:   Mon Jun 3 21:40:51 2013 -0400

    Allow the defining of an init file at build time

    Since OpenBSD is unable to determine the pathname of a running process,
    this allows us to specify the full path to flisp.boot. This will also
    come in handy for system wide installs where you want flisp to live in
    bin and flisp.boot to live in share or a similar location.

commit 19a835847c826572f6cff45852d496276b772056
Author: James Turner <james@calminferno.net>
Date:   Mon Jun 3 21:40:14 2013 -0400

    Add support for OpenBSD

こんなのに出くわした。OpenBSDもサポートしたよってさ。でもBSD界の本家である、NetBSD は、どうやら蚊帳の外らしい。で、見るに見かねて、NetBSDでも動くようにしたよ。

nb7$ egrep '(NetBSD|NETBSD)' -n *.[ch] llt/*.[ch]
flisp.c:2312:#elif defined(NETBSD)
llt/dirpath.c:92:#elif defined(OPENBSD) || defined(NETBSD)
llt/dtypes.h:28:#elif defined(__NetBSD__)
llt/dtypes.h:29:#  define NETBSD
llt/dtypes.h:36:#if defined(OPENBSD) || defined(FREEBSD) || defined(NETBSD)
llt/dtypes.h:77:#elif defined(MACOSX) || defined(OPENBSD) || defined(FREEBSD) || defined(NETBSD)
llt/timefuncs.c:109:#if defined(LINUX) || defined(NETBSD) || defined(OPENBSD) || defined(FREEBSD)
llt/utf8.c:28:#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__)

gitで述べられていた、問題部分は、OpenBSDに習っておいた。だって、/usr/src/sysの 所で、

nb7$ find . -type f | xargs grep KERN_PROC_PATHNAME

したけど、応答が無かったからね。参考までに、FreeBSDで探してみると、

$ cd /usr/src/sys
$  find . -type f | xargs grep KERN_PROC_PATHNAME
./sys/sysctl.h:#define  KERN_PROC_PATHNAME      12      /* path to executable */
./kern/kern_proc.c:static SYSCTL_NODE(_kern_proc, KERN_PROC_PATHNAME, pathname, CTLFLAG_RD |

うん、確かにあるね。FreeBSDは多方面に使われているから、色々な要求を取り入れているんだな。 正に、BSD界のLinuxか。とか言うと、FreeBSDの重鎮からLinuxと一緒にスナとまさかりが 飛んできそうですよ。

#elif defined(OPENBSD) || defined(NETBSD)
char *get_exename(char *buf, size_t size)
{
  /* OpenBSD currently has no way of determining a processes pathname */
  return NULL;
}
#elif defined(FREEBSD)
#include <sys/types.h>
#include <sys/sysctl.h>

char *get_exename(char *buf, size_t size)
{
  int mib[4];
  mib[0] = CTL_KERN;
  mib[1] = KERN_PROC;
  mib[2] = KERN_PROC_PATHNAME;
  mib[3] = -1;
  sysctl(mib, 4, buf, &size, NULL, 0);

  return buf;
}

そのせいで、テスト部分で失敗する。

gmake test
gmake[1]: Entering directory '/home/sakae/femtolisp'
cd tests && ../flisp unittest.lsp
fatal error:
(io-error "file: could not open \"flisp.boot\"")
Makefile:23: recipe for target 'test' failed
gmake[1]: *** [test] Error 1

しょうがないので、テスト環境へVMとROMを持ち込み、そこで現地検査を実施。

nb7$ cp flisp flisp.boot tests/
nb7$ cd tests/
nb7$ ./flisp unittest.lsp
all tests pass

git log に救われたな。以上、移植完了。

flispのhash, gensym

前回ちらっと書いたんだけど、hashの件。perlあたりからこの業界に入った人は(古さ満載、年代を暴露してる) ははん、キーに数値以外のものを取れる連想配列ねって思い当るだろう。最近のpython野郎 (若いって言う比喩で使ってます)にはピンとこないかも知れません。それって辞書じゃ ないですかって、聞き返されたりして。

flispにもhashがある。hashと言うよりhash関数です、って言うのが正解。誤解を受けない かな? ちょっと心配。ものは試し、試してみる。

> (hash 1)
-399682699
> (hash cons)
-443745403
> (hash 'cons)
392354999

与えられた引数に対して一意の数値を返す。この手のやつでよく使われるのは、gitで有名に 成ったsha1とかだけど。flispのやつは、独特な実装をしてるっぽい。ソース嫁。

uptrint_t hash_lispvalue(value_t a)
{
    int oob=0;
    uptrint_t n = bounded_hash(a, BOUNDED_HASH_BOUND, &oob);
    return n;
}

value_t fl_hash(value_t *args, u_int32_t nargs)
{
    argcount("hash", nargs, 1);
    return fixnum(hash_lispvalue(args[0]));
}

実行例から分かるように、控えめな大きさの数値を返しています。fixnumの範囲。 そしてbounded_hashが鍵になるんだけど、見てくと

// *oob: output argument, means we hit the limit specified by 'bound'
static uptrint_t bounded_hash(value_t a, int bound, int *oob)
{
   :
    int oob2, tg = tag(a);
    switch(tg) {
    case TAG_NUM :
    case TAG_NUM1:
        u.d = (double)numval(a);
        return doublehash(u.i64);
    case TAG_FUNCTION:
     :
    case TAG_SYM:
     :
    case TAG_VECTOR:
     :

みたいに、引数の属性をtagを根拠にして、適当にばらけるように計算してました。 まあ、控えめなhashですから、これぐらい緩いものでもOKなんでしょう。

hash似たやつに、gensymってのがあります。アクセスする度にユニークな値を返す。 マクロ内で、変数名の衝突を避けるため、システムに登録されない名前が欲しい時に 必須になります。かのマクロ指南書 On Lispも、これ無しでは書けなかったでしょう。

これとhashを組み合わせると、出目が予測されるという、とても楽しい事が起きます。

> (define a (gensym))
#:g1
> (define b (gensym))
#:g2
> (hash a)
1
> (hash b)
2
> (gensym? a)
#t
> (typeof a)
symbol

作者さんは、意図してこういうのを残してくれているのでしょうか? 目下、この機能を 悪用出来ないか、検討中であります。

table

辞書です。作り方と引き方とか追加とか

> (define mine (table 'ichi 1 'ni 2 'san 3 'si 4))
#table(si 4  san 3  ni 2  ichi 1)

> (get mine 'san)
3

> (put! mine 'go 5)
#table(go 5  si 4  san 3  ni 2  ichi 1)

ちょいとソースを眺めてみた。

equalhash_put(h, (void*)args[1], (void*)args[2]);

こんなのが使われていて、いとも簡単にtableに登録してると思ったら、とんだ喰わせ者で、 マクロを通じて、llt/の裏社会に繋がっていた。 裏は、とっても複雑でオイラーには理解不能。ケツまくって逃げ出しますよ。 やっぱりhashは大変だって事です。

binary patch

前回積み残していた、パッチ当てをやってみる。untraceの不具合修正だね。

> (disassemble untrace)
maxstack 8
00000:  argc    1
00002:  loadv
        maxstack 9
        00000:  argc    1
        00002:  loadg   traced?
        00004:  loada0
        00005:  call    1
        00007:  brf     @00018
        0000a:  loadv   #fn(set-top-level-value!)
        0000c:  loadc00
        0000d:  loadv   #fn(function:vals)
        0000f:  loada0
        00010:  call    1
        00012:  loadi8  3
        00014:  aref
        00015:  tcall   2
        00017:  ret
        00018:  loadt
        00019:  ret
00004:  closure
00005:  loadv   #fn(top-level-value)
00007:  loada0
00008:  call    1
0000a:  tcall   1
0000c:  ret
#t

アドレス12にあるloadi8のオペランドが今は正しい3になってる。元の2に戻して比べて みると、正解だった。それはいいんだけど、flisp.boot上でどんな違いが有るかが分からないと patchする場所が特定出来ないな。

こんな事もあろうかと、正しいflisp.bootをgoodって名前で保存しておいて、悪いflisp.boot を作ったのさ。後は、場所の特定。

$ cmp -x good flisp.boot
0000994c 33 32

次はbviとかhexeditで開いてみる事になる。世間ではhexeditが良く使われているようなので、 hexeditの練習かたがた、pkgを使って入れた。

00009930   23 66 6E 28  22 39 30 30  30 72 31 65  30 7C 33 31  #fn("9000r1e0|31
00009940   36 40 30 63  31 7E 63 32  7C 33 31 62  32 5B 34 32  6@0c1~c2|31b2[42
00009950   3B 5D 3B 22  20 5B 74 72  61 63 65 64  3F 0A 20 20  ;];" [traced?.
00009960   23 66 6E 28  73 65 74 2D  74 6F 70 2D  6C 65 76 65  #fn(set-top-leve

これと、function:valsを比べてみる。

> (function:vals untrace)
[#fn("9000r1e0|316@0c1~c2|31b2[42;];" [traced? #fn(set-top-level-value!)
                                       #fn(function:vals)])
 #fn(top-level-value)]

見事に一致が取れている。後は、hexeditの分かり憎いマニュアルと格闘して、該当箇所を 書き換え、Ctl-Xして、yを押して更新。

調子こいて、juliaに実装されているuntraceも変更しておこうかと思ったら、ソースレベルで コメントされてた。調べてみると、方々で殺しが目立っていたよ。juliaからflispが 起動出来ると言っても、牙を削がれたlispに成り下がっていてつまらないぞ。

そうそう、もしjuliaのflisp相当にパッチを当てる必要が出たら、flisp.boot相当が 収納されてる、libjulia.soあたりに入っているので、そこを攻めるべき。julia本体には ないからね。

00DFB880   20 20 20 20  20 23 66 6E  28 22 35 30  30 30 72 31       #fn("5000r1
00DFB890   7C 43 3B 22  20 5B 5D 29  0A 09 09 20  20 20 20 20  |C;" [])...
00DFB8A0   20 23 66 6E  28 22 35 30  30 30 72 31  7C 44 3B 22   #fn("5000r1|D;"
---  libjulia-debug.so       --0xDFB8A1/0x13B228C------------------------------

とまあ、ソフトにパッチを当てるなんて何年ぶりだろう? Macを使ってた頃は、patch 道具のResEditがオイラーの御用達だったからね。

勿論、生業にしてたハード屋のパッチ道具と言えば、良く切れるナイフ。これは、プリント板の パターンカットに必要。それから、先の細いニッパ。基板に半田付けされたICの足を ニッパで切って、(俗に言う)足上げ実施。そこから空中配線。このための先の細い ニッパは、スウェーデン製のやつが最高だった。

ICを追加する時は、基板にパッチ用ICの追加穴があれば、それを使うんだけど、無い 場合は、ICの上にICを載せる亀の子作戦発動ですよ。電源とグランドピンだけを、親IC に半田付け。これで、追加ICが物理的に固定される。子のICは、ICの各ピンを曲げて、 空中を泳ぐようにする。そしてその足から、各所に細い線で配線。 昔は、こんな事を当たり前にやってた。

今は、ハードの配線もFPGAの中に隠れてしまったようで、ソフトにパッチを当てるな んて事もしないんでしょうな。つまらない世界になっちゃったな。これも時代か。

ふと、juliaから起動したlispから、system.lspをロードしたら、いいんでないかと 思って実行してみると

> (load "/home/sakae/femtolisp/system.lsp")
eval: variable string.encode has no value
in file /home/sakae/femtolisp/system.lsp
#0 (lambda)
#1 (load/lambda/lambda (define (string.trim s at-start at-end)
      :
#2 (load "/home/sakae/femtolisp/system.lsp")

こんなエラーを吐いて、拒否されたぞ。juliaのlispでは、string.encodeって言う のが、疎外されてた。utf8がらみなんで、julia側で面倒は嫌ったんだろうね。 そこを旨くすり抜ければ、まともにflispを使えるだろうけど、そこまでするなら、 最初からflisp単体で使えよ、ってのが、最終結論だな。

etc

ガベージコレクション

プログラマのための数学勉強会(6)