Smalltalk(5)

何時も常用してるArchLinuxのX Windowが言う事を聞かなくなった。頑張って表示しようとしてる みたいなんだけど、カラフルな窓が出てこないんだ。話によるとXのモジュールが悪さをしてる みたいなんだけど、VMwareなんてけちな環境で使ってる人の事は後回しみたい。

そこでだ、今注目のManjaroVirtualBoxへ入れてみた。この変な名前、キリ・マンジャロ から取ったらしい。そそっかしいおいらは、万次郎って読んじゃったぞ。 そのうちに、FujiとかGeisyaなんて言う日本発の亜流が出てこないか楽しみしてる。

で、このManjaroなんだけど、ちょいとてこづったぞ。おいらの知らないパッケージマネージャが 入っていたんだ。面倒なので、pacman -Syy しようとしたら、エラーで落ちる。素のpacmanは 混ぜるな危険って事で、ロックされてたんだな。(/var/lib/pacman/db.lck) そいつを消して、最低限必要な、mlocateを入れてあげたよ。

次なるトラブルは、sshでWindows 7側から接続出来ない事。sshdが動いていなかった。/etc/rc.conf をいじれば済むかと思ってたら、そんなの入っていなかった。思い出したよ。systemdなんて言う 邪悪なものにダエモン君の管理方法が変わったいたんだ。はて、どうすべ?

何か注意書きが引越し元に置いてあるだろうと思って、/etc/init.dを見たんだ。そしたら、思った 通りREADMEって貼紙してあったよ。斜め読みすると、

You are running a systemd-based OS where traditional init scripts have
been replaced by native systemd services files. Service files provide
very similar functionality to init scripts. To make use of service
files simply invoke "systemctl", which will output a list of all
currently running services (and other units). Use "systemctl
list-unit-files" to get a listing of all known unit files, including
stopped, disabled and masked ones. Use "systemctl start
foobar.service" and "systemctl stop foobar.service" to start or stop a
service, respectively. For further details, please refer to
systemctl(1).

昔Solarisを使ってた時、8から9だったかに移行した時、大混乱が起きたのを思い出したよ。Linux界隈 にもSolarisの亡霊が乗り移ったのね。最初に取付かれたのはfedoraらしい。debianは大反対してる らしい。面白いな。

[sakae@manjaro ~]$ systemctl | grep sshd
sshd.service                loaded active running   OpenSSH Daemon
sshdgenkeys.service         loaded active exited    SSH Key Generation

今は動いているけど、rebootしたら、元の木阿弥になっちゃうのかなあ。きっとsqliteとかで 保存してんのかなあ。嗚呼、佳きtext文化が廃っていくなあ。これって、形を変えたマイクロソフトの レジストリィー文化じゃないですか。毒されているぞ。

[sakae@manjaro ~]$ systemctl enable sshd.service
ln -s '/usr/lib/systemd/system/sshd.service' '/etc/systemd/system/multi-user.target.wants/sshd.service'

bootしたら木阿弥になっちゃったので、enableしといた。亡霊の隠れ家はこちらだったのね。何となく、 考え方が分かったよ。恐れるに足らずだな。

MethodFinder

pharoのトレーニングマニュアルを見てると、'abcd' を 'ABCD' に変換するにはどんなメソッドを 使えばいいか簡単に探せるメニューが用意されてた。残念ながら、squeakにはそんなメニューが無い。 squeakを使う人は、みんな知ってるから用意してないんだな。初心者お断りの雰囲気がプンプンするよ。

でも、pharoはsqueakから分家してるんで、きっと本家の方にも有るだろう。旨く探しあてられたら、 プチ嬉しいぞ。で、探してみたよ。

ブラウザーの一番左側で、Alt-fして、クラスからMethodFinderを探したら、ぴったりのクラスが 出てきた。?を押すと、説明が出てきた。

Find a method in the system from a set of examples.  Done by brute force, trying 
every possible selector.  Errors are skipped over using 
( [3 + 'xyz'] ifError: [^ false] ).
Submit an array of the form ((data1 data2) answer  (data1 data2) answer).

	MethodFinder methodFor: #( (4 3) 7  (0 5) 5  (5 5) 10).

answer:  'data1 + data2'

説明はずっと続くけど、冒頭部分だけを引っ張ってきた。4と3を何かして7になるのは何だ? 以下、0と5とかもだ。

ふーん、おいらもやってみよう。

MethodFinder methodFor: #( (5) 25 (8) 64). '(data1 squared) (#(-40 13) polynomialEval: data1) '

MethodFinder methodFor: #( ('abc') 'Abc'). '(data1 capitalized) '

ふーん。一応、動いているね。でも、分家の方がインターフェースが使い易いぞ。まあ、あるだけ ましか。これ、どうやって実現してるのかな。

想像するに、引数の型から調べるクラスを絞り込み、更にアリティーから、メソッドを絞って、後は 総当り。結果が期待値と一致してたら、候補だよーって事だろうな。参考にmethodForのコードを 見ておくか。

methodFor: dataAndAnswers
	"Return a Squeak expression that computes these answers.  (This method is called by the comment in the bottom pane of a MethodFinder.  Do not delete this method.)"

	| resultOC resultString |
	resultOC := (self new) load: dataAndAnswers; findMessage.
	resultString := String streamContents: [:strm |
		resultOC do: [:exp | strm nextPut: $(; nextPutAll: exp; nextPut: $); space]].
	^ resultString

肝は、resultOCを求めている式に有るっぽい。loadとかfindMessageは見つかったけど、dataAbdAbswersは 何処にあるの? 迷子になっちゃったな。まだ、ツールを使いこなしていない証拠を露呈しちゃったぞ。 だれか、教えてくださいな。後、ついでに、ワールドメニューにメソッドサーチを出す方法もね。

Little smalltalkの動き

折角なので、動きを追ってみる。その前にちょいと仕掛けを施しておく。

# if 1
        if (debugging) {
        fprintf(stderr,"method %s %d ",charPtr(basicAt(method, messageInMethod)), byteOffset);
        fprintf(stderr,"stack 0x%x %d ",pst, *pst);
        fprintf(stderr,"executing %d %d\n", high, low);
        }
# endif

上記は、interp.cの中のexecute関数の一齣です。こうしておいて、gdbからstを起動。 何もしないと、普通に動く。題材は前回やったbmiです。

>       66.0 bmi: 1.8
20.3704

まあ、普通に動いているな。そんじゃ、ちょっとおまじない。

>       ^C
Program received signal SIGINT, Interrupt.
0x0d6358c9 in read () from /usr/lib/libc.so.65.0
(gdb) set debugging=1
(gdb) c
Continuing.

CTL-cでgdbに入り、上記のifを通るように指定。

66.0 bmi: 1.8
method getString 9 stack 0x8beb485e 6110 executing 15 2
method getPrompt: 12 stack 0x8beb4850 6110 executing 15 2
method initialize 10 stack 0x8beb4840 6110 executing 7 0
method initialize 11 stack 0x8beb4840 6110 executing 15 5
method initialize 12 stack 0x8beb483e 0 executing 3 0
  :
method compile 11 stack 0x8beb4f14 6176 executing 9 3
method bmi: 2 stack 0x8beb4f20 0 executing 2 0
method bmi: 3 stack 0x8beb4f22 6194 executing 2 1
method bmi: 4 stack 0x8beb4f24 6176 executing 8 2
method bmi: 5 stack 0x8beb4f24 6176 executing 9 0
method / 2 stack 0x8beb4f30 0 executing 2 1
method / 3 stack 0x8beb4f32 6176 executing 8 1
method / 4 stack 0x8beb4f32 6176 executing 9 0
method isFloat 2 stack 0x8beb4f3e 0 executing 5 6
  :
method print: 6 stack 0x8beb4efc 0 executing 1 1
method print: 7 stack 0x8beb4efe 5 executing 2 1
method print: 8 stack 0x8beb4f00 6194 executing 13 2
20.3704
  :
method printNoReturn: 8 stack 0x8beb4862 5912 executing 13 2
>       method printNoReturn: 10 stack 0x8beb4860 0 executing 15 6
method printNoReturn: 18 stack 0x8beb4860 0 executing 15 5
  :
method getString 4 stack 0x8beb485e 5984 executing 15 8
method getString 6 stack 0x8beb485c 0 executing 1 1
method getString 7 stack 0x8beb485e 3 executing 13 1

おまじないを解いておく。

^C
Program received signal SIGINT, Interrupt.
0x0d6358c9 in read () from /usr/lib/libc.so.65.0
(gdb) set debugging=0
(gdb) c
Continuing.
70.3 bmi: 1.8
21.6975

これ、VMの実行状態のトレースだな。bmiを実行すると、新たなプロセスが走ってコンパイルされ、 それが実行される。bmiの中では、割り算とか使ってるけど、それらも更に細かいVMコードに 分解されて実行されていく。

前回bmiのバイトコードをダンプした。

32 2 0
33 2 1
130 8 2
144 9 0
33 2 1
130 8 2
145 9 1
242 15 2
245 15 5
241 15 1

バイトコードは文字通り8ビットで表されるが、内部的には、それを4ビットずつに分けている。 上位の4ビットが命令で下位の4ビットが引数になる。上記では分かり易いように、分けたものも 表示してくれている。

そんじゃ、命令はどうなってるかと探してみると、interp.hに定義されてた。

# define Extended 0
# define PushInstance 1
# define PushArgument 2
# define PushTemporary 3
# define PushLiteral 4
# define PushConstant 5
# define AssignInstance 6
# define AssignTemporary 7
# define MarkArguments 8
# define SendMessage 9
# define SendUnary 10
# define SendBinary 11
# define DoPrimitive 13
# define DoSpecial 15

全部で15個の命令が定義されている。そのうちの最後の命令は、下位の4ビットで動作を指定 している。どんな動作かと言うと

# define SelfReturn 1
# define StackReturn 2
# define Duplicate 4
# define PopTop 5
# define Branch 6
# define BranchIfTrue 7
# define BranchIfFalse 8
# define AndBranch 9
# define OrBranch 10
# define SendToSuper 11

Pushと言いPopと言い、命令の対象はスタックだ。このレベルになるとRubyでもPerlでもさして 違いは無い。あの人が頑張って拡張してるVMも、これを拡張して命令融合とかやってんだろうな。

これらのVM命令を実行してるexecuteって、昔に見たRubyの巨大なeval関数と似てるな。面白い。

面白い

と言えば、ソースのあちこちに散りばめられている、コメントも面白いぞ。

/* exit and return - belt and suspenders, but it keeps lint happy */
exit(0); return 0;

これ、st.cの中に有ったやつ。exitとreturnってベルトとサスペンダーだよね。五月蝿いlintを 黙らせようってさ。

/*
        there is a sort of chicken and egg problem with regards to making
        the initial image
*/

こちらは、initial.cに有ったやつ。鶏と玉子の問題って、良く聞く話だな。表面的には、すいすい 行ってるようだけど、舞台裏は大変なのよって露呈されてて、同情しちゃうぞ。

        reference counts are not stored as part of an object image, but
        are instead recreated when the object is read back in.
        This is accomplished using a mark-sweep algorithm, similar
        to those used in garbage collection.

これは、memory.cに有ったまともなコメント。コメントってこういう風に書きたいものだ。

        Some systems (e.g. the Macintosh) have static limits (e.g. 32K)
        which prevent the object table from being declared.
        In this case the object table must first be allocated via
        calloc during the initialization of the memory manager.

これも、memory.cに有ったもの。Macへの恨み節が、そこはかと無く漂っています。

        primitives are how actions are ultimately executed in the Smalltalk
        system.
        unlike ST-80, Little Smalltalk primitives cannot fail (although
        they can return nil, and methods can take this as an indication
        of failure).  In this respect primitives in Little Smalltalk are
        much more like traditional system calls.

primitive.cに有った、自己主張。堂々としてるなあ。