F# to ocaml(4)
トラ技を買ってみた。自分の金で買うなんて何年ぶりだろう? 会社勤めをしてる時は、 会社に置いてあったから買う事も無かったしなあ。自由の身になった時は、興味がソフトに 移ってしまっていて、全く手を出さなかったからなあ。
それが突然興味が出たって事は、特集目当てですよ。デジタル時代のMyラジオ製作 ですって。 RFワールドにも同じようなのが有ったけど、あの本はこちらでは手に入らなかったから、 その反動もあるのかな。
雑誌が薄くなりましたね。広告が減ったから。もう電波少年はいなくなった、それとも 中少企業の技術者も減った? 広告はWebでって事になったのかな。昔、出入りの業者さんに トラ技に広告出すのに幾ら必要なんて興味本位で聞いた事がある。そしたら、最小のヒトコマで 5万円との事だった。自前でサーバー立てて広告を打った方が得だろうね。今のご時勢。
興味深く広告を見たら、TIがチップでも部品を売り出しているのね。最低単位が10個って 事だけど、面白そうな取り組みだね。簡単に試験したダイと、真面目に試験したゴールデン チップと2種類のうちから懐具合で選べるのが面白い。日の丸ICは品質過剰で世界商戦に 敗れた感があるんで、TI流がグローバルスタンダードなんだな。
で、SDRですよ。ハードが安く手に入るようだ。ワンセグ用のドングルがヨーロッパで大量に 消費され、そのおこぼれが東洋にまでやってきたためらしい。
DS-DT305とか ワンセグRX DVB-T+DAB+FM R820T高性能受信機 を使って、ごにょごにょやるみたい。直接パソコンに接続 してもいいし、ラズベリーパイと組み合わせるのがトレンドらしい。 ICOMなんて無線機メーカーのWebにも、「超かんたんSDR(ソフトウエアラジオ)入門」 なんて記事が載るぐらいだから、いつの間にか世間から取り残されているな。
知らないと言えば解説記事の中に有ったんだけど、デシメーションって考え方が人生初見 でしたよ。A/Dの出力を間引き。ただ間引きするんじゃ、何の誤利益も無いんで、積分して 差分を取るらしい。こうする事により、データ量が減って後段のフィルター設計が楽になる。 積分効果により、実質的なA/D分解能が上がるそうだ。
CQ出版からワンセグUSBドングルで作るオールバンド・ソフトウェア・ラジオ なんていうムックが出てたのね。更に、ちょっと高いけど、実験キットが出てて、 その解説本も発売されてるようだ。 また、関連で、 お手軽ARMコンピュータ ラズベリー・パイでI/O なんてのも出てるし、ブームになってるな。rubyも動くようだし、ちょっと遊ぶには 面白いかもしれないな。
電卓ぐらいになるかな?
この間からずっとやってるretroのocaml移植、やっとpromptが出る所まで漕ぎつけた。これで 当初の目的は達した訳だけど、ちょっと欲が出てきた。電卓ぐらいにはなるかな? おっと、その前にvmの各命令のテストカバー率でも調べておくかな。promptが出るまでに 使った命令を比率化しておけば良いだろう。ちょっと長いパイプを施設してみました。 数値(だけのcall命令)は、一応dcallって名前に置き換えておきました。
[sakae@pcbsd ~/src/ml]$ cut -f 2 Hist.log | sed -e 's/[0-9].*/dcall/' | sort | uniq -c | sort -nr 310 rtn 302 dcall 273 lit 106 swap 87 push 74 inc 71 dup 70 fetch 58 pop 58 dec 53 z_rtn 52 out 44 jmp 40 drop 29 eq_jmp 26 ne_jmp 23 wait 13 store 6 in 2 1 xor 1 sub
命令の種類が30あるうちの20種しか使ってません。(空白の命令は、retroからの改行を 誤カウントしてるためです) 後の10種はまだ未確認。手動でコードを 設定して、単独でテストするって方法も頭を過ぎりましたが、もぐら叩きが楽しそうなので いきなり、vmを働かせてみます。
[sakae@pcbsd ~/src/ml]$ ocaml z.ml Retro 11.6 ok 1 1 ok 2 2 ok + + ok putn putn Exception: Failure "Underflow". [sakae@pcbsd ~/src/ml]$ ocaml z.ml Retro 11.6 ok 1 1 ok putn putn A ok bye bye
あれま? putn(TOSを数値と思って表示する)が挙動不審ですよ。化けるのより、落ちるの 方が、直すのが簡単だと思うので、追いかけてみる。まずは、ICEだかロジアナにかけて、 検体を採取だな。
[sakae@pcbsd ~/src/ml]$ ruby retro.rb > Ref.log 1 2 + putn ^Cretro.rb:69:in `getbyte': Interrupt from retro.rb:69:in `get_char' from retro.rb:111:in `handle_devices' from retro.rb:377:in `process' from retro.rb:429:in `<main>' [sakae@pcbsd ~/src/ml]$ ocaml z.ml > Bad.log 1 2 + putn Exception: Failure "Underflow". [sakae@pcbsd ~/src/ml]$ wc *.log 114973 344904 11003317 Bad.log 115454 461792 11682881 Ref.log 230427 806696 22686198 total
たかが足し算するだけで、こんなに命令を実行してるなんて、ちょっと驚き、ってか、効率が 悪すぎるような気がしますね。まあ、それはさておき、これだけの行数が有ると、相違点を 探すのも至難の業だなあ。こういう汚れ仕事はコンピュータにやらせましょ。相違が 発生した所かそれ以前に問題があるはずだから。。
[sakae@pcbsd ~/src/ml]$ cut -f 1 Ref.log > /tmp/ref [sakae@pcbsd ~/src/ml]$ cut -f 1 Bad.log > /tmp/bad [sakae@pcbsd ~/src/ml]$ cmp /tmp/ref /tmp/bad /tmp/ref /tmp/bad differ: char 11517, line 1889
promptが出て来るまで、約1700ステップを費やしていたから、コマンドを打ち込んで直ぐに 差異が表れたって事か。なんてこったい。
[sakae@pcbsd ~/src/ml]$ cat -n Ref.log | head -n 1890 | tail -n 30 > /tmp/ref [sakae@pcbsd ~/src/ml]$ cat -n Bad.log | head -n 1890 | tail -n 30 > /tmp/bad [sakae@pcbsd ~/src/ml]$ cd /tmp [sakae@pcbsd /tmp]$ cat ref bad | sort : 1888 1755: [17846;0;49;49;999744] [5901;5817;17852;13032;17856;17899;19025;19058;19087;4144;4292;4368;8390] 1888 1755: rtn [17846,0,49,49,999744,] [5901,5817,17852,13032,17856,17899,19025,19058,19087,4144,4292,4368,8390,] 1889 5902: [17846;0;49;49;999744] [5817;17852;13032;17856;17899;19025;19058;19087;4144;4292;4368;8390] 1889 5904: 1385 [17846,0,49,49,999744,] [5817,17852,13032,17856,17899,19025,19058,19087,4144,4292,4368,8390,] 1890 1387: push [17846,0,49,49,999744,] [5904,5817,17852,13032,17856,17899,19025,19058,19087,4144,4292,4368,8390,] 1890 5903: [17846;0;49;49;999744] [5817;17852;13032;17856;17899;19025;19058;19087;4144;4292;4368;8390]
おいらの80桁端末では、行長すぎて、折り返し表示されてしまい、おちおち見てられん。 Xを上げて、横に広い端末を作ってもいいんだけど、おいらは昔の人。80桁端末で快適に 表示する方法は無いかな?
ふと、スタックの全データを表示するなんて無駄じゃん。大体、演算の対象がスタック上の 2つのデータしかないんだから、せいぜい3個ぐらいのデータ表示で十分と気付く。
let mon3 rx = let si i = string_of_int i and sis i = string_of_int i ^ "; " in match !rx with [] -> "[]" | [a] -> "[" ^ si a ^ "]" | [a; b] -> "[" ^ sis a ^ si b ^ "]" | [a; b; c] -> "[" ^ sis a ^ sis b ^ si c ^ "]" | a::b::c::_ -> "[" ^ sis a ^ sis b ^ sis c ^ "..]" let rec exec () = if not (!halt) then begin Printf.printf "%d:\t\t%s %s\n" !ip (mon3 data) (mon3 address) ; execOne(); ip := !ip + 1 ; exec () end ;;
letで宣言してる内部関数、無くてもいいんだけど、入力文字数短縮の為。ocamlの関数名を 決めた人、センス悪すぎ。まあ、フランス人に英語で考えろって言ったら見紛えちゃうだろう から無理は無いか。ocamlにもprintfなんてのが有った。不定個の引数を関数型言語はどうやって 処理してるのだろう? 後でPrintfモジュールを覗いておこう。
def ds3(x) def is(x, n) x.nth(n).to_s end def iss(x, n) x.nth(n).to_s + ", " end case x.depth when 0 then "[]" when 1 then "[" + is(x,0) + "]" when 2 then "[" + iss(x,0) + is(x,1) + "]" when 3 then "[" + iss(x,0) + iss(x,1) + is(x,2) + "]" else "[" + iss(x,0) + iss(x,1) + iss(x,2) + "..]" end end
rubyでmatchもどきをやろうとしたら、こうなったぞ。 Rubyによる関数型プログラミング なんてのと対比すると、面白いかも知れない。
とまあ、道具を作って、今出てる虫を眺めてみたんだけど、虫取り方法がピンとこないので 別アプローチしてみる。そもそも、おいらはプロンプトが出て来るまで移植が済んだら、 ベンチをやってみたかったんだ。目的へ一直線でgoするか。
ocamlは早いのか?
ってな訳で、retroのベンチで作っておいたloop用のROM(retroImage)を持ってきた。 走らせてみると
[sakae@pcbsd ~/src/ml]$ ocaml z.ml Exception: Failure "hd".
あらま、虫が沸いてきた、と言うより新たなretroImageに隠れて虫が侵入きたんだな。 そして繁殖したとな。クリーンなCPUのそれと比べてみよう。
304 5819: [23047] [14237; 1000; 5619; ..] 304 5819: rtn [23047] [14237, 1000, 5619, ..] 305 14238: [23047] [1000; 5619; 14248; ..] 305 14238: pop [23047] [1000, 5619, 14248, ..] 306 14239: [1000; 23047] [5619; 14248; 23048; ..] 306 14239: loop [1000, 23047] [5619, 14248, 23048, ..] 307 14235: [23047] [5619; 14248; 23048; ..] 307 14235: push [999, 23047] [5619, 14248, 23048, ..] 308 14236: [] [23047; 5619; 14248; ..] 308 14236: dup [23047] [999, 5619, 14248, ..]
空のスタック(トップ)を複製しようとして、落ちたんだな。loop命令の後ocamlのvmの方は スタックが一段浅くなってる。call内に虫が潜んでいるな。
loop命令は外にくくり出したloopマクロに丸投げしてた。じっと眼をこらしていたら虫が 見えたぞ。括弧忘れで虫が逃げ出したんだな。毎度、カッコ悪い。
[sakae@pcbsd ~/src/ml]$ time ocaml z.ml real 0m41.562s user 0m40.706s sys 0m0.134s
以前mono上でF#でやった時には、46秒だったから、ocamlの方が速いな。
[sakae@pcbsd ~/src/ml]$ ocamlopt z.ml [sakae@pcbsd ~/src/ml]$ time ./a.out real 0m7.349s user 0m7.220s sys 0m0.064s
やっとコンパイルして実行だ。mono上では13秒、C言語だと4秒だったな。
出来上がったa.outは、極々普通なもの
[sakae@pcbsd ~/src/ml]$ file a.out a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), dynamically linked (uses shared libs), for FreeBSD 9.1, not stripped [sakae@pcbsd ~/src/ml]$ ldd a.out a.out: libm.so.5 => /lib/libm.so.5 (0x2807f000) libc.so.7 => /lib/libc.so.7 (0x28099000)
ocamloptでコンパイルする時に、-Sを付けるとアセンブラ出力が得られる。 アセンブリ具合も見ておくか
.text .align 16 .globl camlZ__incIp_1083 camlZ__incIp_1083: .L207: movl camlZ + 12, %eax addl $2, (%eax) movl $1, %eax ret .type camlZ__incIp_1083,@function .size camlZ__incIp_1083,.-camlZ__incIp_1083
メインが何処に有るか探したけど見つからず。しょうがないので、-gを付けてコンパイル したやつをgdbに喰わせてみた。 適当な所で止めて、呼び出し具合を確認。
[sakae@pcbsd ~/src/ml]$ gdb a.out GNU gdb 6.1.1 [FreeBSD] : (gdb) b camlZ__loop_1089 Breakpoint 1 at 0x804a280 (gdb) run Starting program: /usr/home/sakae/src/ml/a.out (no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)... Breakpoint 1, 0x0804a280 in camlZ__loop_1089 () (gdb) l No symbol table is loaded. Use the "file" command. (gdb) bt #0 0x0804a280 in camlZ__loop_1089 () #1 0x0804a7fa in camlZ__exec_1100 () #2 0x0804a9d2 in camlZ__entry () #3 0x0804963d in caml_startup__code_begin () #4 0x0805bb76 in caml_start_program () #5 0x00000000 in ?? () #6 0xbfbfe7a8 in ?? () #7 0xbfbfe80c in ?? () #8 0x00000001 in ?? () #9 0x08049620 in frame_dummy () Previous frame inner to this frame (corrupt stack?)
camlZ__entryがおいらの書いたコードへの入り口、いわゆるtoplevelの環境を実現してる。 このルーチンの最後はretになってた。ocamlが用意したcrt.o相当へ帰って行くんだな。
BUG入りソース
いいかげん飽きてきたんで、BUG入りだけど移植したソースを掲げて置きます。昆虫食が 好きな方はどうぞ味わってみて下さい。
(* import retro.fsx *) let mem_size = 1_000_000 let image_file = "retroImage" let ports_size = 12 let ip = ref 0 let ports = Array.make ports_size 0 let memory = Array.make mem_size 0xdeadbee let halt = ref false let data = ref [] let address = ref [] let pushVal d x = d := x :: !d let popVal d () = match !d with h :: t -> d := t; h | _ -> failwith "Underflow" let push = pushVal data let pushr = pushVal address let pop = popVal data let popr = popVal address let tos () = List.hd (!data) let (|>) (x: int) f = f x ;; (* for debug, monitor data or address stack *) let mon3 rx = let si i = string_of_int i and sis i = string_of_int i ^ "; " in match !rx with [] -> "[]" | [a] -> "[" ^ si a ^ "]" | [a; b] -> "[" ^ sis a ^ si b ^ "]" | [a; b; c] -> "[" ^ sis a ^ sis b ^ si c ^ "]" | a::b::c::_ -> "[" ^ sis a ^ sis b ^ sis c ^ "..]" let load () = let b4 = Array.make 4 0 in let binReader = open_in_bin image_file in for i = 0 to (in_channel_length binReader / 4 - 1) do b4.(0) <- input_byte binReader; (* LSB *) b4.(1) <- input_byte binReader; b4.(2) <- input_byte binReader; b4.(3) <- input_byte binReader; memory.(i) <- b4.(3) lsl 24 + b4.(2) lsl 16 + b4.(1) lsl 8 + b4.(0) done; close_in binReader ;; let key () = input_byte stdin ;; let devices () = if ports.(0) = 0 then ports.(0) <- 1 ; if ports.(1) = 1 then ports.(1) <- key () ; if ports.(2) = 1 then begin ports.(2) <- 0 ; let x = pop () in if x < 0 then print_char '?' else (print_char (char_of_int x) ; flush stdout; ) end ; ports.(5) <- match ports.(5) with | -1 -> mem_size | -5 -> List.length (!data) | -6 -> List.length (!address) | -9 -> halt := true; 0 | _ -> 0 ;; let dyadic fn = let x = pop () in fn (pop ()) x |> push ;; let dyadic2 fn = let x, y = fn (pop ()) (pop ()) in push x; push y ;; let incIp () = ip := (!ip) + 1 ;; let condJump fn = let x = pop () in if fn (pop ()) x then ip := memory.(!ip + 1) - 1 else incIp () ;; let jump () = ip := memory.(!ip) - 1 ; if memory.(!ip + 1) = 0 then incIp () ; if memory.(!ip + 1) = 0 then incIp () ;; let drop () = pop () |> ignore ;; let loop () = pop () - 1 |> push ; if tos () > 0 then ip := memory.(!ip + 1) - 1 else (incIp (); drop ()) ;; let execOne () = match memory.(!ip) with 0 -> () (* nop *) | 1 -> ( incIp (); memory.(!ip) |> push ) (* lit *) | 2 -> tos () |> push (* dup *) | 3 -> drop () (* drop *) | 4 -> dyadic2 (fun x y -> y, x) (* swap *) | 5 -> pop () |> pushr (* push *) | 6 -> popr () |> push (* pop *) | 7 -> loop () (* loop *) | 8 -> ( incIp (); jump () ) (* jmp *) | 9 -> ip := popr () (* rtn *) | 10 -> condJump (>) (* gt_jmp *) | 11 -> condJump (<) (* lt_jmp *) | 12 -> condJump (<>) (* ne_jmp *) | 13 -> condJump (=) (* eq_jmp *) | 14 -> memory.(pop ()) |> push (* fetch *) | 15 -> let p = pop () in let v = pop () in memory.(p) <- v (* store *) | 16 -> dyadic (+) (* add *) | 17 -> dyadic (-) (* sub *) | 18 -> push (pop () * pop ()) (* mul *) | 19 -> dyadic2 (fun x y -> y / x, y mod x) (* divmod *) | 20 -> dyadic (land) (* and *) | 21 -> dyadic (lor) (* or *) | 22 -> dyadic (lxor) (* xor *) | 23 -> dyadic (lsl) (* lsl *) | 24 -> dyadic (asr) (* asr *) | 25 -> if tos () = 0 then (drop (); ip := popr ()) (* z_ret *) | 26 -> pop () + 1 |> push (* inc *) | 27 -> pop () - 1 |> push (* dec *) | 28 -> let x = pop () in ports.(x) |> push; ports.(x) <- 0 (* in *) | 29 -> let p = pop () in let v = pop () in ports.(p) <- v (* out *) | 30 -> devices () (* wait *) | _ -> (pushr !ip; jump ()) ;; (* call by op-data *) let rec exec () = if not (!halt) then begin Printf.printf "%d:\t\t%s %s\n" !ip (mon3 data) (mon3 address) ; execOne(); ip := !ip + 1 ; exec () end ;; load () ;; exec () ;;