Inside newLISP (3)

先月号のCQ誌は買ったけど、今月号はネットで内容を確認のみ。で、気になったのが“TRX-305”って やつ。初めて聞く名前だな。フルデジタルのトランシーバーですって。

ぐぐってみると、トラ技スペシャルらしい。 トラ技は常にモニターしてる訳ではないので、洩れてたんだな。

それにしても、トラ技が去年で創刊50年とは、素晴らしい記録ですな。輝かしい電子立国に 発展に大いに寄与したってんで、国から表彰されても良いと思うぞ。

会社勤めしてる頃は、会社で取ってくれていたので毎号読んでいたけど、実践的な内容が 多くて、専門書を補間するにはうってつけだった。そうそう、昔のインターフェースも 面白かったな。今は記事が細切れ過ぎるように思う。

なにはともあれ、こういうおもちゃがあると、いろいろな人が飛びついている。

フルディジタルトランシーバ TRX-305A (2014.11.6) スペアナ持ってる人は無銭家とは言えんな。ましてや、このキットが10万円以上もするんだから。

DSP空挺団も触手を動かされてます。

AOR TRX-305プラモデルを 組み立てる感覚だなあ。リグをパソコンからコントロールするってのは、もう当たり前な 機能なんだな。

電子工作オタクのメモ オイラーより1年先輩のページ。パワーが有りますなあ。仙台在住って事でパワーの源は、かき徳の 牡蠣なんでしょうか。そろそろ牡蠣が美味しくなるな。

SDR体験なにやら、簡単にSDRを体験出来そう。 世の中進歩が速いなあ。

なんとなくクリスタル

以前見つけておいたhttp://crystal-lang.org/である。 間違って田中康夫へ飛んでいかないように!

冗談はさておき、入れてみる。って、入れ方は、ドキュメントの頭の所に 書いてある。例によってBSD系とWindows系は無視されてるんであしからず。オイラーは、 なんとなくクリスタルモードなんで、ウブに突っ込んでみた。 そしたら、何と/optなんてのを堀やがって、そこに鎮座した。開発者は新人類かMAC屋さん?

emacs用のクリスタルモードが有るかと思ったら、普通の所にはなくて、 Emacs で Crystal を書くがQiitaに 上がっていた。メジャーになるのは何時だ。それまでは、生暖かく、、、、emacsに標準で入っている ruby-modeを使っておk。

サンプルで、赤黒木が有ったので、走らせてみる。

sakae@uB:~/z$ crystal red_black_tree.cr
/opt/crystal/bin/../embedded/lib/../lib/libunwind.a(Los-linux.o): In function `_ULx86_local_resume':
Los-linux.c:(.text+0x73b): warning: sigreturn is not implemented and will always fail
delete: 00:00:03.4539240, res: 0
add: 00:00:01.9207680, res: 100000
search: 00:00:01.3763080, res: [63178, 100000]
walk: 00:00:03.0119300, res: [100277, 100000]
reverse_walk: 00:00:03.0378360, res: [100277, 100000]
min: 00:00:09.1905040, res: 0
max: 00:00:09.6184000, res: 9999800000
summary time: 00:00:31.6782830

オイラーのウブでは、ちとご不満なようで。でも、走ってくれた。

次は、コンパイルしておいて実行

sakae@uB:~/z$ crystal build red_black_tree.cr
/opt/crystal/bin/../embedded/lib/../lib/libunwind.a(Los-linux.o): In function `_ULx86_local_resume':
Los-linux.c:(.text+0x73b): warning: sigreturn is not implemented and will always fail
sakae@uB:~/z$ ./red_black_tree
delete: 00:00:03.5843710, res: 0
add: 00:00:01.8124170, res: 100000
search: 00:00:01.2016790, res: [63508, 100000]
walk: 00:00:02.9265340, res: [99801, 100000]
reverse_walk: 00:00:02.8420790, res: [99801, 100000]
min: 00:00:08.3951490, res: 100000
max: 00:00:09.9405880, res: 9999700000
summary time: 00:00:30.7708040

コンパイル時間が無くなるので、多少早くなるね。

sakae@uB:~/z$ crystal types red_black_tree.cr
$inst : RedBlackTree::NilNode
t : Time
b : RedBlackTreeRunner

グローバル変数のチェック

sakae@uB:~/z$ crystal hierarchy red_black_tree.cr | col -bx

クラスのリスト。エスケープ文字が含まれているので、それを避けています。けど、sedを 噛ませないとだめだな。噛ませると、落ち着いて見られるな。

  +- class Reference (4 bytes)
     |
     +- class RedBlackTreeRunner (20 bytes)
     |      @n    : Int32        (4 bytes)
     |      @a1   : Array(Int32) (4 bytes)
     |      @a2   : Array(Int32) (4 bytes)
     |      @tree : RedBlackTree (4 bytes)
     |
     +- class RedBlackTree (12 bytes)
     |      @root : RedBlackTree::Node+ (4 bytes)
     |      @size : Int32               (4 bytes)
     |
     +- class RedBlackTree::Node (24 bytes)
     |  .   @key    : Int32                       (4 bytes)
     |  .   @color  : Symbol                      (4 bytes)
     |  .   @parent : RedBlackTree::Node+         (4 bytes)
     |  .   @right  : (Nil | RedBlackTree::Node+) (4 bytes)
     |  .   @left   : (Nil | RedBlackTree::Node+) (4 bytes)
     |  |
     |  +- class RedBlackTree::NilNode (24 bytes)
     |
     +- class Random::MT19937 (2504 bytes)
     |      @mt  : StaticArray(UInt32, 624) (2496 bytes)
     |      @mti : Int32                    (   4 bytes)
     |
     +- class FileDescriptorIO (40 bytes)
     |      @edge_triggerable : Bool              (0 bytes)
     |      @flush_on_newline : Bool              (0 bytes)
     |      @sync             : Bool              (0 bytes)
     |      @closed           : Bool              (0 bytes)
     |      @fd               : Int32             (4 bytes)
     |      @in_buffer_rem    : Slice(UInt8)      (8 bytes)
     |      @out_count        : Int32             (4 bytes)
     |      @readers          : Array(Fiber)?     (4 bytes)
     |      @writers          : Array(Fiber)?     (4 bytes)
     |      @event            : LibEvent2::Event? (4 bytes)
     |      @out_buffer       : Pointer(UInt8)?   (4 bytes)

こんな物まで見せちゃって大丈夫?

rubyと競争してみる。お題は素数。例にあったやつ

max = 10_000_000

sieve = Array.new(max + 1, true)
sieve[0] = false
sieve[1] = false

2.step(Math.sqrt(max)) do |i|
  if sieve[i]
    (i * i).step(max, i) do |j|
      sieve[j] = false
    end
  end
end

sieve.each_with_index do |prime, number|
  if prime
    puts number
  end
end
sakae@uB:~/z$ time ruby sieve.cr > /dev/null

real    0m12.961s
user    0m12.764s
sys     0m0.184s
sakae@uB:~/z$ time crystal sieve.cr > /dev/null
/opt/crystal/bin/../embedded/lib/../lib/libunwind.a(Los-linux.o): In function `_ULx86_local_resume':
Los-linux.c:(.text+0x73b): warning: sigreturn is not implemented and will always fail

real    0m3.331s
user    0m2.212s
sys     0m0.900s
sakae@uB:~/z$ crystal build sieve.cr
/opt/crystal/bin/../embedded/lib/../lib/libunwind.a(Los-linux.o): In function `_ULx86_local_resume':
Los-linux.c:(.text+0x73b): warning: sigreturn is not implemented and will always fail
sakae@uB:~/z$ time ./sieve > /dev/null

real    0m2.251s
user    0m1.932s
sys     0m0.312s

コンパイルすると、5倍は速くなったな。後は、ライブラリィが既存のものをそのまま 使えるかだな。言い換えると、rakeとか、gemが使えるか。当面レールが動けば、喜ぶ人が 多数と思われ。みんなで支援してあげましょう。

で、Crystal で Ruby のコードってどんだけそのまま動くの? の苦労が無くなると嬉しいぞ、と。

で、aptで入れた奴はオイラーの所で文句垂れるんで、ソースから入れてみる。いきなりソースからってのは、 例によって例のごとく、鶏卵問題に遭遇するんでだめです。種のcrystalを用意しましょう。

順調にいくかと思ったら、またリナの嫌がらせが有った。コンパイルが済んで、リンカーの 段階で、leditが無いと言う。そして極めつけは、

sakae@uB:~/z$ crystal build sieve.cr
Using compiled compiler at .build/crystal
/usr/bin/ld: cannot find -levent
/usr/bin/ld: cannot find -lpcl
/usr/bin/ld: cannot find -lgc
collect2: error: ld returned 1 exit status
  :

全くもって、いろいろなものを要求され、疲れるわい。適当にらいぶらりを持ってきても、 マッチングが取れていないようで、エラーですよ。

sakae@uB:~/z$ crystal build sieve.cr
Using compiled compiler at .build/crystal
/home/sakae/z/.crystal/home/sakae/z/sieve.cr/main.o: In function `__crystal_main':
main_module:(.text+0x5850): undefined reference to `GC_get_push_other_roots'
main_module:(.text+0x58c0): undefined reference to `GC_set_push_other_roots'
collect2: error: ld returned 1 exit status

しょうがないので、/opt/crystal/embedded/lib/libgc.a を、/usr/libに叩き込んであげたら、 コンパイル成功。

sakae@uB:~/z$ crystal build sieve.cr
Using compiled compiler at .build/crystal
sakae@uB:~/z$ file sieve
sieve: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=c4bb2b6c2cc1a642cee6c03c1b014d1ca8272f1d, not stripped
sakae@uB:~/z$ ldd sieve
        linux-gate.so.1 =>  (0xb76fc000)
        libevent-2.0.so.5 => /usr/lib/i386-linux-gnu/libevent-2.0.so.5 (0xb768d000)
        libpcre.so.3 => /lib/i386-linux-gnu/libpcre.so.3 (0xb761b000)
        libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb75fd000)
        libunwind.so.8 => /usr/lib/i386-linux-gnu/libunwind.so.8 (0xb75e6000)
        libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb75c9000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb740e000)
        /lib/ld-linux.so.2 (0xb76fd000)
        liblzma.so.5 => /lib/i386-linux-gnu/liblzma.so.5 (0xb73e8000)
        libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb73e2000)

出来上がったバイナリーはstripしてなかったので、gdbにかけてみたら、

(gdb) bt
#0  0xb7fdbbe0 in __kernel_vsyscall ()
#1  0xb7eeb5d3 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:81
#2  0x0805adf9 in *FileDescriptorIO#unbuffered_write<FileDescriptorIO, Slice(UInt8), Int32>:Int32 ()
#3  0x0805acb6 in *FileDescriptorIO@BufferedIOMixin#flush<FileDescriptorIO>:Int32 ()
#4  0x0805b630 in *FileDescriptorIO@BufferedIOMixin#write_byte<FileDescriptorIO, UInt8>:(Nil | Int32) ()
#5  0x0805b514 in *FileDescriptorIO@IO#puts<FileDescriptorIO>:(Nil | Int32) ()
#6  0x0805b781 in *FileDescriptorIO@IO#puts<FileDescriptorIO, Int32>:(Nil | Int32) ()
#7  0x08051593 in *puts<Int32>:(Nil | Int32) ()
#8  0x08050116 in __crystal_main ()
#9  0x080512ff in main ()
(gdb) f 8
#8  0x08050116 in __crystal_main ()
(gdb) l
1       ../sysdeps/i386/dl-procinfo.c: No such file or directory.
(gdb) f 9
#9  0x080512ff in main ()
(gdb) l
1       in ../sysdeps/i386/dl-procinfo.c

何やら、秘密めいたLinuxべったり感が漂ってますなあ。

evaluateExpression

回を重ねるInside newLISPもいよいよ核心に迫ってきた。今回は表題の部分。これは、evaluateStream から呼び出される。

主要部分は、大雑把にcellタイプによって場合分けしてる。

1472    switch (cell->type) {
1473    case CELL_QUOTE:
 :
1476    case CELL_EXPRESSION:
1477        args = (CELL *) cell->contents;
 :
1586    case CELL_DYN_SYMBOL:
 :
1590    default:
1591        result = nilCell;
1592    }

そして主要な部分は、1476行目から始まる部分だ。この中で、1477行でコンテンツを取り出し、 更に場合分けしていく。

普通の関数(例えば、(dump and)みたいなやつ)の場合、シンボルになるんで、その内容を取り出す。 そしてシンボルの内容を引き

1516        /* pCell is evaluated op element */
1517        if (pCell->type == CELL_PRIMITIVE) {
1518            evalFunc = (CELL * (*) (CELL *)) pCell->contents;
1519=>          result = evalFunc(args->next);
1520            evalFunc = NULL;
1521            break;

このあたりに落ちてくる。ここで、evalFuncの値は? と言うと

(gdb) p evalFunc
$1 = (CELL *(*)(CELL *)) 0x1bcf49a6 <p_dump>

見事にプリミティブな関数、p_dump が抽出された。後は、argsのcdrを引数にして、p_dumpへと 飛んで行く事になる。

飛んで行った(p_dump)では、

7011=>  if (params != nilCell) {
7012        cell = evaluateExpression(params);
7013        return (stuffIntegerList
7014              (5, cell, cell->type, cell->next, cell->aux, cell->contents));
7015    }

cellがnilじゃ無い事を確かめて、引数を評価し(相互再帰してんな)、返ってきた値を、 この場合は、数値のリストに変換して返している。

先ほどは、S式の第1項がプリミティブの場合だったけど、それ以外の場合もやはり、pCellの タグを見て場合分けしてる。こちらは、switchでの場合分けじゃなくてifのブロックに なってる。

1517        if (pCell->type == CELL_PRIMITIVE) {
 :
1523        if (pCell->type == CELL_LAMBDA) {
 :
1529=>      if (pCell->type == CELL_FEXPR) {
 :
1537        if (pCell->type & IMPORT_MASK) {
 :
1542        /*
1543         * implicit indexing or resting for list, array or string
1544         */
1545        if (args->next != nilCell) {
 :

特徴的なのが、1543行目にコメントで示されているが、リストや文字列やアレーのインデックシングに よる値の取り出し。これを実現したいばかりに、わざわざifのブロックで場合分けしてるのね。

それじゃ、代表的な例として、 (2 "ABCDEFGH") を追ってみる。

1574            /* implicit resting for lists and strings */
1575            else if (isNumber(pCell->type))
1576=>              result = implicitNrestSlice(pCell, args->next);

ここから、implicitNrestSliceが呼ばれる。

implicitNrestSlice (num=0x8314da90, params=0x8314da00) at newlisp.c:5242
5242    {
(gdb) p *num
$4 = {type = 898, next = 0x8314da00, aux = 2, contents = 0}
(gdb) p *params
$5 = {type = 260, next = 0x8314c000, aux = 9, contents = 2106858112}
(gdb) x/s 2106858112
0x7d941a80:     "ABCDEFGH"

paramesに渡した文字列の、2文字目以降を返せルーチンなんだな。 追って行くと、

substring (string=0x7d941a80 "ABCDEFGH", slen=8, offset=2, len=2147483647) at nl-string.c:61

文字列をスライスして、律儀にそれをセルに詰め込んで、結果としてるね。

evaluateLambda

今度は、ユーザー定義の関数がどう実行されるか追ってみる。お題は

> (define (hoge n) (* n n))
(lambda (n) (* n n))
> (hoge 8)

こんな簡単なやつ。C側の関数で目星を付けた、evaluateLambdaにBPを置いたら、ヒットしたぞ。 呼び出しは、

1523        if (pCell->type == CELL_LAMBDA) {
1524            pushLambda(args);
1525=>          result = evaluateLambda((CELL *) pCell->contents, args->next, newContext);
1526            --lambdaStackIdx;
1527            break;

newContextって何かと思えば

(gdb) p *newContext
$4 = {
  flags = 48,
  color = 0,
  name = 0x37d77edc "MAIN",
  contents = 2050551840,
  context = 0x86ab7240,
  parent = 0x80261720,
  left = 0x7d8e35e0,
  right = 0x80261880
}

MAINに定義したから、MAINで評価されるんだろうね。

1745                cell = result = copyCell(evaluateExpression(arg));
(gdb) p *arg
$5 = {
  type = 898,
  next = 0x7a38f000,
  aux = 8,
  contents = 0
}

引数を評価しておいて、1755行あたりで、現在のコンテキストを新しいコンテキストに切り替え、 ローカルシンボルに引数を割り当てて1817行で、評価。結果は

(gdb) p *result
$11 = {
  type = 898,
  next = 0x7a38f000,
  aux = 64,
  contents = 0
}

こんな風に実行されるのね。で、先のnewContextの疑問、今度は、FUGAってcontextに切り替えてみた。 そしたら、

Breakpoint 1, evaluateLambda (localLst=0x7a390c00, arg=0x7a390be0, newContext=0x84322000) at newlisp.c:1726
1726        CELL           *result = nilCell;
(gdb) p *newContext
$13 = {
  flags = 48,
  color = 1,
  name = 0x81881800 "FUGA",
  contents = 2050558464,
  context = 0x86ab7240,
  parent = 0x7d8e35c0,
  left = 0x37d7d2c0 <sentinel>,
  right = 0x37d7d2c0 <sentinel>
}

こんな具合に、新しいコンテキストと言うかモジュール環境に切り替わったね。予想通り! 追うのは、これぐらいで良いかな。