移植(4)

移植の過程で思わぬBug(虫)に出会い、楽しんで咀嚼してきた。普通は虫は忌み嫌われて出来る 事なら出会いたくない、避けて通りたいと願っている事だろう。

リアル社会では、虫さんに積極的に関わって(愛でて)、食べてしまおうという奇特な方々が おられる。是非、こういう人たちをソフトウェア業界へ招聘したいぞ。とは言え、両者に通じる 方は、そうおられるものでは無いと思う。

ってな訳で、『昆虫食入門』(平凡社)の新書を手に取ってみた。人間生きる為には食べなければ ならない。食べるって事は、命をリレーして行くって事。スーパーに行けば、豚肉やら魚やらの 死体が綺麗に並べられているから、命のリレーって言われても、ピンとはこないだろう。

みずから食べ物を見つけて採取し、それを調理して食べる。命のリレーって事を実感出来るぞ。 蟻さんは、ちょいと酸っぱいらしい。ゴキブリのから揚げは海老の味がするそうだ。挑戦して みませんかね。人気番組で、世界の果てまでイッテQなんかを見てると、時々やってますよね。 芋虫の踊り食い、クリーミーで美味しいらしいですよ。

そんじゃお前はどうなんだと言われそう。幼少の頃は、ありきたりだけど定番を食べましたねぇ。 まずは、いなごの佃煮。おばあちゃんに連れられて、よく取りに行きましたよ。朝早いと活動が 鈍いので、ほいほいと面白いように取れる。時々カマキリも居るので取って、布の袋に入れ、 ばあちゃんに怒られたっけ。生きのいいやつを袋ごと熱湯に漬けて殺してから、ごみを 取り除き、後はひたすら、醤油と砂糖で煮詰めていくだけ。

この間、東京へ行った時巣鴨に寄ったら、いなごの佃煮を売ってたよ。店の親父に、どこ産の いなご?って聞いてみたら、福島から仕入れているとか。ちょっと試食させて貰って、昔を 思いだしはしたんだけど、放射能大丈夫かなと頭を過ぎった瞬間、買うのを諦めてしまったよ。 くだんの新書でも、放射能を心配して廃業しちゃった所があるとかで、伝統食の衰退に拍車が かかるんではと、心配してたよ。

後、食べたものは、蜂の子。うじ虫みたいなやつね。フライパンで炒って、仕上げに醤油を 数滴垂らすだけのシンプルな料理。学校の帰りによく取りに行ったよな。巣が見つかると 枯れ草を燃やして、親の蜂を煙幕攻め。親が気絶してる間に、子供だけを誘拐してきて食べちゃう という血も涙も無い事をやりましたね。だって、香ばしくてとっても美味なんだもん。

昆虫は、良質な蛋白質を大量に含んでいて次世代の主要な食物源になるのではと期待されて いるとか。狭い空間でも効率的に生産(生育)出来るので、宇宙食にも最適とか。 昔、自衛隊の機密マニュアルがネットに流出して問題になったけど、マニュアルの中にも サバイバルとして昆虫食が載っていたとか。勿論、昆虫だけではなく、かたつむりとか蛇とか 食べちゃうって項目も有ったそうです。そう言えば、蛙とかシマヘビとかも食べたなあ。

昆虫は足が早いので(逃げ足が速いんじゃなくて、腐るのが早い)、採取したらなるべく 早く食べてしまうのがコツとか。

さあさあ、虫食いねぇ とか むしくいへ 行って、Bugを腹に収めちゃおう。学術的にも注目 されてます。これそ、ソフトウェア業界人の集うサイトですよ!

assq

前回、assqでてこずった。FreeBSD付属のgccだと、オプティマイザーを効かせないと、間違った 答えを返しちゃうんだ。微妙な所だなあ。きっと、gccのバージョンが変わると動かなくなるよ。 あああ、FreeBSDでは今後、clangに置き換わるんだった。これでも、やはり動かなかったな。

教育用Schemeって事で、再帰を使ってassqが書かれていたけど、ループ版なら、スタックの巻き戻し とかの微妙な問題も無いだろう。で、書いてみた。

int assq(int obj, int lis)
{
    while (!(nullp(lis))){
        if (eqp(obj, caar(lis)))
            return (car(lis));
        lis = cdr(lis);
    }
    return (BOOLF);
}

問題なく動く。clangを使ってコンパイルしても、問題なく動く。やっぱり微妙な問題だったのね。 ああ、assq系にはassvとか、同じ作りのやつが一杯あるから、それらにも手を入れておかないと だめか。それとも、C言語で高階関数化するか。後悔しないかな?

clock()改め

gbcのルーチンを見てた時、実行時間を計るのにclockが使われている事を知った。いままで、そんな 関数が有る事を知らなかったよ。

解説
     clock() 関数は、呼び出し元プロセスの、起動時からのプロセッサ使用時間を計
     算します。これは 1 秒の CLOCKS_PER_SEC 分の 1 を単位として計測されます。

時間分解能って、どのくらい? time.h を見ると

#define CLOCKS_PER_SEC  128

8msと随分荒いことに気づいた。折角Unixを使ってるんだから、もっと細かく計測したいよねと 思った。それには、常套手段のgettimeofdayだな。これだと、嘘か眞か、マイクロ秒の分解能が 得られる。

#include <sys/time.h>

double u_clock()
{
    struct timeval tv;

    gettimeofday(&tv, NULL);
    return tv.tv_sec + tv.tv_usec * 1e-6;
}

返り値は、高精度なEPOCだ。これを使うと、

Simp> (time (tarai 10 5 0))
10
total 1.081397 second
gc    0.262933 second, 350 times

一応、もっともらしい値を返してくれるので、自己満足にはうってつけです。Windowsの場合、 自己満足したかったら、QueryPerformanceFrequencyとQueryPerformanceCounterを組み合わせて 使うらしい。

shellの代わりに

ならないかと突然思った。simpの起動時に、スクリプトを与えてそれを実行したら終了って ぐあいになれば、一歩gaucheに近づくんではなかろうか。

スクリプトを読み込む手続きはすでに用意されてるから、後はそれを何処に潜り込ませるか だけだな。誰が考えたって、そりゃ、マクロを取り込んで、replへ移る間しかないよな。

    initsubr();
    f_load(list1(makesym("simpmacs.scm")));
    if (argc == 2)
        f_load(list1(makesym(argv[1])));
    ret = setjmp(toplevel);

  repl:

main.cの冒頭付近に置いた。f_load()を見ると、実行した結果の表示は行っていないので、実行 するスクリプト側で、積極的に表示してあげる事にする。

(define tarai
    (lambda (x y z)
      (if (<= x y)
        y
        (tarai
                (tarai (- x 1) y z)
                (tarai (- y 1) z x)
                (tarai (- z 1) x y)))))

(print (time (tarai 10 5 0)))
(exit)

実行してみると

[sakae@secd ~/Simple]$ ./simp tarai.scm
educational Scheme compiler Simple Ver0.17.4-1 (written by sasagawa888)
10
total 1.042141 second
gc    0.259372 second, 9 times
セグメンテーション違反: 11 (コアダンプ)

ははは、またお会いしましたね。coreさん。さて、臨場するぞ。

Core was generated by `simp'.
Program terminated with signal 11, Segmentation fault.
#0  0x280f7827 in longjmp () from /lib/libc.so.7

これって、longjmpのおかげで、フレームが亡きものになっちゃってるのかな。でも、何故 セグフォ? こういう時は、

(gdb) disassemble longjmp
Dump of assembler code for function longjmp:
   0x280f77e4 <+0>:     mov    0x4(%esp),%edx
   0x280f77e8 <+4>:     push   %ebx
   0x280f77e9 <+5>:     call   0x280f77ee <longjmp+10>
   0x280f77ee <+10>:    pop    %ebx
   0x280f77ef <+11>:    add    $0xcade2,%ebx
   0x280f77f5 <+17>:    push   $0x0
   0x280f77f7 <+19>:    lea    0x1c(%edx),%eax
   0x280f77fa <+22>:    push   %eax
   0x280f77fb <+23>:    push   $0x3
   0x280f77fd <+25>:    call   0x280da25c <_sigprocmask@plt>
   0x280f7802 <+30>:    add    $0xc,%esp
   0x280f7805 <+33>:    pop    %ebx
   0x280f7806 <+34>:    mov    0x4(%esp),%edx
   0x280f780a <+38>:    mov    0x8(%esp),%eax
   0x280f780e <+42>:    mov    (%edx),%ecx
   0x280f7810 <+44>:    mov    0x4(%edx),%ebx
   0x280f7813 <+47>:    mov    0x8(%edx),%esp
   0x280f7816 <+50>:    mov    0xc(%edx),%ebp
   0x280f7819 <+53>:    mov    0x10(%edx),%esi
   0x280f781c <+56>:    mov    0x14(%edx),%edi
   0x280f781f <+59>:    fldcw  0x18(%edx)
   0x280f7822 <+62>:    test   %eax,%eax
   0x280f7824 <+64>:    jne    0x280f7827 <longjmp+67>
   0x280f7826 <+66>:    inc    %eax
=> 0x280f7827 <+67>:    mov    %ecx,(%esp)
   0x280f782a <+70>:    ret
End of assembler dump.
(gdb) p/x $esp
$1 = 0x0

ふーん、マシンスタックが0番地になってて、そこに書き込もうとして、セグフォ発令なのね。 ちなみに、setjmpで保存されてる継続データは、

(gdb) p/x load
$3 =   {{_jb =       {0x805512e,
      0xbfbfe828,
      0xbfbfe78c,
      0xbfbfe7a8,
      0x2,
      0xbfbfe834,
      0x127f,
      0x0,
      0x0,
      0x0,
      0x0,
      0x0}}}

どのデータが、どのレジスタに対応してるんでしょうね? こういう時は、/usr/src/lib/libc/i386/gen/setjmp.S を見ろってお告げが聞こえてきたぞ。 で、見ると、_setjmp なんていう簡易版もあるのね。両者の違いは、signalの状態も保存復帰するかの 差だったよ。それはさておき、3番目のデータがespっぽい。

しょうがないので、main.cを見直すと、コンパイルの中にあるvtimeの最後でlongjmpして、 replの中に戻ってるよ。でも、replに戻る為の準備はまだ出来ていない。従って、不正な データで復帰してセグフォなんだな。以上、解析終わり。解析中に気づいたんだけど、コンパイル 中でも、仮想マシンを動かしているのね。知らなかったよ。 で、結局、現状では、定義の読み込みにしか使えないって事が判明しましたな。

rubyの場合

とまあ、ずっとSimpleを追ってきた訳であるが、ここらでちょいとお口直しに、matzLispにも 触れてみる。どんな具合にコンパイルされるかは、irbから簡単に確かめられるよ。

irb(main):001:0> puts RubyVM::InstructionSequence.compile("1+2").disasm
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
0000 trace            1                                               (   1)
0002 putobject        1
0004 putobject        2
0006 opt_plus         <ic:1>
0008 leave
=> nil
irb(main):002:0> puts RubyVM::InstructionSequence.compile("a='Ruby'").disasm
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] a
0000 trace            1                                               (   1)
0002 putstring        "Ruby"
0004 dup
0005 setlocal         a
0007 leave
=> nil

RubyVMはスタックマシンなのね。 開発者自らが、解説されてますよ。

LLVM Clang

LLVMって、Low Level Virtual Machine の略なのね。みんな糞CPUのアセンブラに嫌気がして、 低いレベルでも仮想機械ですよ。

だったら、その仮想機械の命令表が有るはず。探したら、 LLVM 言語マニュアルが公開されて ました。

C言語を一度、LLVMにコンパイルして、それを最終的にはそれぞれの金物用(x86とかsparcとか)に 変換するって訳。アプルがパトロンになって、オブジェクトーC用のコンパイラ開発を支援 してるとか。GNUにもオブジェクトーC用のコンパイラーは有るけど、使うのはアプル信者だけ みたいで、開発が遅々して進まない為らしい。

どんな仮想命令が生成されるかは、

[sakae@secd ~/Simple/z]$ clang -S hoge.c -emit-llvm

元のソース

[sakae@secd ~/Simple/z]$ cat hoge.c
int fact(int n){
   return (n == 1 ? 1 : n * fact(n - 1));
}

int main(){
   fact(3);
}

LLVM語の出力

[sakae@secd ~/Simple/z]$ cat hoge.s
; ModuleID = 'hoge.c'
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128"
target triple = "i386-unknown-freebsd9.0"

define i32 @fact(i32 %n) nounwind {
entry:
  %n.addr = alloca i32, align 4
  store i32 %n, i32* %n.addr, align 4
  %0 = load i32* %n.addr, align 4
  %cmp = icmp eq i32 %0, 1
  br i1 %cmp, label %cond.true, label %cond.false

cond.true:                                        ; preds = %entry
  br label %cond.end

cond.false:                                       ; preds = %entry
  %1 = load i32* %n.addr, align 4
  %2 = load i32* %n.addr, align 4
  %sub = sub nsw i32 %2, 1
  %call = call i32 @fact(i32 %sub)
  %mul = mul nsw i32 %1, %call
  br label %cond.end

cond.end:                                         ; preds = %cond.false, %cond.true
  %cond = phi i32 [ 1, %cond.true ], [ %mul, %cond.false ]
  ret i32 %cond
}

define i32 @main() nounwind {
entry:
  %retval = alloca i32, align 4
  store i32 0, i32* %retval
  %call = call i32 @fact(i32 3)
  %0 = load i32* %retval
  ret i32 %0
}

噂によると、最終ターゲットへの変換を容易にし強力な最適化を施すために、LLVMレベルでは、 変数への再代入(変数の使いまわし)は、無いそうです。これって、Haskellチックだ脳!

暇な人は、コンパイルする時、-O3 とかのフラグを付けてみると、最適化の効き具合が確認 出来て、面白いよ。

おまけ

Cコンパイラーが吐いたコードをgdbの中で逆アセンブルして眺めてみたけど、別の方法もあるよ。 コンパイルする時に、-g を付けておけば、後から、C言語に対するアセンブラ出力が同時に 確認出来るんだ。

[sakae@secd ~/Simple]$ objdump -d -S ./simp >CASM.txt

混ぜこぜになったやつを、一度テキストに落としておいて、後でじっくりと観察すれば よい。これ、アセンブラの勉強にもなるな。

/*大域変数のアクセス*/
int get_gvar(int sym)
{
 804fd10:       55                      push   %ebp
 804fd11:       89 e5                   mov    %esp,%ebp
 804fd13:       83 ec 28                sub    $0x28,%esp
    int val;

    val = assq(sym, G);
 804fd16:       a1 74 3c 68 0a          mov    0xa683c74,%eax
 804fd1b:       89 44 24 04             mov    %eax,0x4(%esp)
 804fd1f:       8b 45 08                mov    0x8(%ebp),%eax
 804fd22:       89 04 24                mov    %eax,(%esp)
 804fd25:       e8 86 03 00 00          call   80500b0 <assq>
 804fd2a:       89 45 fc                mov    %eax,-0x4(%ebp)
    if (val != BOOLF)
 804fd2d:       83 7d fc 05             cmpl   $0x5,-0x4(%ebp)
 804fd31:       74 10                   je     804fd43 <get_gvar+0x33>
        return (cdr(val));
 804fd33:       8b 45 fc                mov    -0x4(%ebp),%eax
 804fd36:       89 04 24                mov    %eax,(%esp)
 804fd39:       e8 22 02 00 00          call   804ff60 <cdr>
 804fd3e:       89 45 ec                mov    %eax,-0x14(%ebp)
 804fd41:       eb 07                   jmp    804fd4a <get_gvar+0x3a>
    else
        return (BOOLF);
 804fd43:       c7 45 ec 05 00 00 00    movl   $0x5,-0x14(%ebp)
 804fd4a:       8b 45 ec                mov    -0x14(%ebp),%eax
}
 804fd4d:       c9                      leave
 804fd4e:       c3                      ret
 804fd4f:       90                      nop