Rust -> V with tcc
コロナ黙示録
なんて言う時流に乗った小説を読んだ。著者は、あの海堂尊先生と言う現役のお医者さん。 リアリティのある小説である。前半の50ページぐらいまでは、あの安保政権を揶揄してて、非常に痛快である。
まだ半分ぐらいまでしか読んでいないので、結論が分からない。けど、帯をみると、コロナを打ち破るなんて無理。共存もできない。ならば、人類は新型コロナに合わせた社会を作るしか無い、とか。
その通りだな。 だから、 国産ワクチン、なぜ出てこない? なんて言う疑問も生まれる訳で。。。
rustでアプリの続き
前々回にrustのアプリって事で、多少のコードを書いてみた。だけど、気に入らない所が有るんだ。一つは、csvを読み込むコードがmainの一角を占めている事。この部分をfnに追い出してしまいたい。もう一つはgnuplotへの引数渡しが出来ていない事。今回は、これに対処する。
fn main() { let rv = csv_read("current.csv"); let am = rv.0; let pm = rv.1; let sd = dump4gp("am.dat", am); let _ = dump4gp("pm.dat", pm); scr4gp("topdf.plt"); run4gp(sd); remove_file("am.dat").expect("Can't remove"); remove_file("pm.dat").expect("Can't remove"); remove_file("topdf.plt").expect("Can't remove"); }
その結果、mainはこうなった。 remove_file
が3行に渡って書かれていて無駄であるけど、一度に複数のファイルを消す手続きが無いので、そのまま放置してる。
問題は新たに書いた csv_read
である。当初、mainの中でam,pm変数を宣言しておいて、それを関数に渡し、答えを入れて帰って来るように企んだ。だが、貸し借りと型合わせが上手くいかず断念した。
fn csv_read(sname: &str) -> (Vec<Vec<String>>, Vec<Vec<String>>) { let mut am: Vec<Vec<String>> = Vec::new(); let mut pm: Vec<Vec<String>> = Vec::new(); let f = File::open(sname).expect("Not found"); for line in BufReader::new(f).lines() { let ap = line .unwrap() .split(',') .map(String::from) .collect::<Vec<_>>(); if &ap[0][6..7] == "0" { am.push(ap); } else { pm.push(ap); } } (am, pm) }
発想を変えて、関数の中で変数を宣言。結果をタプルで返すようにした。IOがらみの関数だと、返値がResult型ってのが常識みたい。なら、Okの中に入れて返せばよさそうに思うけど、Okで許されるのは()と言うユニット型だけみたいだ。
よって、関数の中では、エラー処理にexpectを使ってる。これが正しい方法かどうかは自信が無いな。大体、色々な例を引いても、mainの中でIO処理をやってるものばかりしか、見当たらなかったぞ。
同様な構成は、dump4gpでも採用してる。これもIO処理を伴っているので、当初Result型だった。今回は、データの一番最初の年月日が返値で欲しかったので、内部的にexpectを使ってしまった。
こんな調子でグタグタ書いていると終わらないので、後は、 bld4main2.rs を参照あれ。
これを書くのにWeb上のrust同士に、大いに助けてもらった。苦労は有るけど書いてて楽しい。オイラーに取ってrustってのは、haskellと思っている。まだ教本は読破していなけど、後ろの方では、なんとgcの話も出て来るようだ。すらすらと使えるようになったら、嬉しいな。
所で、 rustは2015年にやってますよって、読者の方からお便りが有った。そんな昔の事はすっかり失念してる、ボケ老人だのう。
余りrustに執着してても、あれ、なので、前回偶然に見つけてしまった、V語を暫く楽しんでみよう。ええ、我ながら、節操が無い事は自覚してますんで悪しからず!
speed
vのソースツリーを見てたら、~/src/v/cmd/v/helpなんて所に行き着いた。helpのテキストが一式置いてある。で、面白いオプションを発見。前回スピードチェックもどきをやってみたけど、それを精密に測定する方法が案内されてた。
sakae@pen:/tmp$ v -show-timings tetris.v 42.829 ms SCAN 89.715 ms PARSE 42.026 ms CHECK 86.298 ms C GEN 108.078 ms C tcc.exe
tcc速いな。正にlight-speed-c(光速C)だな。
sakae@pen:/tmp$ v -cc gcc -show-timings tetris.v 42.684 ms SCAN 85.754 ms PARSE 39.681 ms CHECK 86.044 ms C GEN 2313.334 ms C gcc
まて、速度は比べるものがあってこそ光輝くものだ。って事で、鈍重なgccでも走らせてみた。 tccに比べて20倍も遅いじゃん!!
気をよくしたオイラーは、emacsから操作出来るように、インチキ環境を作った。 for emacs borrow go-mode
;; V (setq auto-mode-alist (append '(("\\.v" . go-mode)) auto-mode-alist))
Makefile
PN = hoge comp: v $(PN).v r: v $(PN).v ./$(PN)
c compiler
cコンパイラーの使い分けって、どうなってるの? そんなの騒ぎを起こせば、内情が暴露されるに違い無い。ってんで、OpenBSDで、クロスコンパイラーを要求すると言う、無理難題な事をしてみる。
ob$ v -os windows tetris.v Cross compiling for Windows... : x86_64-w64-mingw32-gcc -std=gnu99 -fPIC ... builder error: sh: x86_64-w64-mingw32-gcc: not found
ふーん、クロスコンパイル用には、wingw32-gccが必要なのか。次はこれを頼りに、家探しだ。
ob$ find . -name '*.v' | xargs grep x86_64-w64-mingw32-gcc ./vlib/v/builder/cc.v: mingw_cc = 'x86_64-w64-mingw32-gcc' ./vlib/v/pref/default.v: p.ccompiler = 'x86_64-w64-mingw32-gcc'
vlibたら、vが提供する標準装備品じゃないですか。その一角にvの挙動を司っている物が含まれているって、面白いな。詳細は、 vlib の説明をどうぞって訳だな。
tcc
vのコンパイルスピードが速いのは、tccのおかげってのが暴かれた(v自身の頑張りも高く評価するのは、言うまでもありませんけど)。
そんな恩恵は、64Bitのリナ業界にしかないのか(32Bitのdebianには無い)? 速いCのコンパイラーなら、色々と需要が有るだろう。OpenBSDはportsが厳選されてるので、何でも有りのFreeBSDで調べてみる。
- It is fast! TCC generates optimized x86 code. No byte code overhead. Compile, assemble, and link about 7 times faster than 'gcc -O0'; - C script supported: just add '#!/bin/env tcc -run' at the first line of your C source, and execute it directly from the command line. WWW: https://github.com/TinyCC/tinycc
能書き帳にこんな事が書かれていたぞ(オイラーの箏線に触れるものだけを抜粋)。控え目に見積もってもgccよりは7倍高速。更に嬉しい特典として、C語でスクリプティングが出来るとな。
入れてみた。FreeBSDからのお願いとして、現在メンテナーがおりません。このままでは、朽ち果ててしまいますから、篤志家は名乗り出てくださいですって。OpenBSDでコンパイルに挑戦して、腕を磨いてから、名乗り出てみる?
昔、とある医者のエッセイを読んだ。ヨーロッパで開かれる学会出張の為飛行機に乗ってたら、 急病人発生。お医者様はいらっしゃいませんかのアナウンス。反射的の名乗り出た。
が、飛行機の中の医療設備は貧弱。出来る事は限られる。有る設備だけで、必死の治療(医者ってHackerなんだな)、機長からは、緊急なら近くの(ロシア)の空港に緊急着陸しますが、、って連絡有り。若し降りたとしても、地上の救急隊との引き継ぎで、へたをしたら、置いてきぼりを喰うかも知れない。非常に躊躇する判断を迫られた。
その時はなんとか容態が安定したんで、目的地まで飛べた。けど、生きた心地がしなかったそうな。そして、後で振り返ってみると、ヘタしたら治療が悪かったとか言われて、訴訟になる危険性もあるなと、心底肝が冷えたと、書いておられた。
まあ、portsのメンテナーも、同じ危険性を孕んでいるな。悪質なユーザーからいちゃもんを付けられて、炎上させられるとかね。
[sakae@fb /tmp]$ cat test.c #!/usr/local/bin/tcc -run main(){ printf("Hello TCC\n"); }
こんなtest.cを書いて、実行属性を付ける。stdio.hは、多分要らないんだろうな。
[sakae@fb /tmp]$ ./test.c Hello TCC
これも内部的には、compile ando run してるんだろうね。コンパイルが爆速だからこそ、ユーザーを騙せる訳だな。詳しい事は、ソース嫁。tcc -h すると、色々なオプションが出てきて、楽しそうであります。
ob$ ./tcc -v tcc version 0.9.27 - unknown hash (x86_64 OpenBSD)
楽しい事は、すぐやる課 であります。
ob$ ls COPYING arm64-asm.c i386-asm.c riscv64-link.c tcclib.h Changelog arm64-gen.c i386-asm.h stab.def tccmacho.c CodingStyle arm64-link.c i386-gen.c stab.h tccpe.c Makefile c67-gen.c i386-link.c tcc-doc.html tccpp.c README c67-link.c i386-tok.h tcc-doc.info tccrun.c RELICENSING coff.h il-gen.c tcc-doc.texi tcctok.h TODO config.h il-opcodes.h tcc.1 tcctools.c USES config.mak include/ tcc.c tests/ VERSION config.texi lib/ tcc.h texi2pod.pl* arm-asm.c configure* libtcc.c tccasm.c win32/ arm-gen.c conftest.c libtcc.h tcccoff.c x86_64-asm.h arm-link.c elf.h riscv64-asm.c tccelf.c x86_64-gen.c arm-tok.h examples/ riscv64-gen.c tccgen.c x86_64-link.c
arm/arm64, i386/X86-64, riscv64 とは、時代を反映してますなあ。
v with tcc for FreeBSD
で、この楽しいtccをどうやってvに認識させる。そんなの考える事無いわな。
[sakae@fb ~]$ cd src/v/thirdparty/tcc/ [sakae@fb ~/src/v/thirdparty/tcc]$ ls -l total 4 -rw-r--r-- 1 sakae sakae 142 Mar 28 13:07 README.md lrwxr-xr-x 1 sakae sakae 18 Apr 2 06:39 tcc.exe@ -> /usr/local/bin/tcc
リナと同じ配置にした。で、ハロワを試してみる。
[sakae@fb /tmp/hoge]$ v -cc tcc -show-timings . 27.832 ms SCAN 52.072 ms PARSE 25.108 ms CHECK 58.516 ms C GEN 447.314 ms C tcc.exe 526.127 ms C cc
あらら、2度コンパイルされてるよ。最後はccで締めているんで、tccは実質役にたっていない。
[sakae@fb /tmp/hoge]$ v -cc cc -show-timings . 29.041 ms SCAN 54.286 ms PARSE 26.525 ms CHECK 63.471 ms C GEN 525.789 ms C cc
vを自分自身でコンパイルしてみる。
[sakae@fb /tmp/hoge]$ v -cc tcc -show-timings self V self compiling (-cc tcc -show-timings -o v2)... 164.104 ms SCAN 283.710 ms PARSE 238.059 ms CHECK 535.732 ms C GEN 435.357 ms C tcc.exe 4411.439 ms C cc V built successfully!
本当にtcc君は、働いているのだろうか?
で、BSD系のOSではtccが動かないって前提になるロジックが組まれているな。ならば、リナ系で、追及してみる。
debian(32bit)の救済
そう、何もしなければ32Bit版のdebianでは、tccがサポートされないってのを解消しよう。
何も難しい事は無い。既にFreeBSDで実験してるからね。tccを普通にインストールして、サードパーティーの所からリンクは張るだけだ。
debian:hoge$ v -show-timings . 71.934 ms SCAN 127.569 ms PARSE 68.025 ms CHECK 132.296 ms C GEN 210.962 ms C tcc.exe
tetris.v しか置いていないdir内なら、ソースファイルを指示するのに、ドットだけで良いんだ。コマンド名のvと言い、ものぐささんと言うかhacker仕様のコマンド体系だな。
debian:hoge$ v -cc gcc -show-timings . 72.500 ms SCAN 127.067 ms PARSE 68.071 ms CHECK 137.927 ms C GEN 4648.303 ms C gcc
こちらは、わざと遅いgccを指定してみた。やっぱり20倍の差が付いているね。
debian:v$ make cd ./vc && git clean -xf && git pull --quiet cd ./thirdparty/tcc && git clean -xf && git pull --quiet Removing tcc.exe cc -g -std=gnu99 -w -o ./v ./vc/v.c -lm -lpthread ./v -o v2.exe cmd/v mv -f v2.exe v V has been successfully built V 0.2.2 5229428
こうして、32Bitの救急策を施しておいても、vを再創生すると、tcc.exeが削除されてしまう。これは64Bit族によるパワハラか。訴えてやる。
いや、普通は v up するだけでいいしょ。makeなんて、非日常的な事だから、笑って許したれ。かつかつするな、おおらかでいいんでないかい。
そうそう、v/cmd/toolsの下にサブコマンドのソースが有って、一度使うとコンパイルされたバイナリーもそこに置かれると書いた。いわゆるキャッシュね。ソース変更すると、サブコマンドを使った時点で、再コンパイルされるぞ。優しい気遣いだな。
try v on OpenBSD
上の実験で、おぼろげながら機構が見えてきたような気がするので、常用のOpenBSDで確認してみる。まずは、由緒あるMakefileが何をやってるか、確認。
ob$ gmake cd ./vc && git clean -xf && git pull --quiet gmake fresh_tcc gmake[1]: Entering directory '/home/sakae/src/v' rm -rf ./thirdparty/tcc Pre-built TCC not available for thirdparty-unknown-amd64 at https://github.com/v lang/tccbin, will use the system compiler: cc git clone --depth 1 --quiet --single-branch --branch thirdparty-unknown-unknown https://github.com/vlang/tccbin ./thirdparty/tcc gmake[1]: Leaving directory '/home/sakae/src/v' cd ./thirdparty/tcc && git clean -xf && git pull --quiet cc -g -std=gnu99 -w -o ./v ./vc/v.c -lm -lpthread ./v -o v2.exe cmd/v mv -f v2.exe v V has been successfully built V 0.2.2 1a32467
外からのtccが無ければccを使う(設定するんだな)。後は、C語で書かれたやつをコンパイルして、そいつを使ってcmd/vをコンパイルし、それを最終成果物にするとな。
tccを用意するのは簡単。configure; gmake; doas gmake install tccを準備しておいてから
ob$ cd src/v/thirdparty/tcc/ ob$ ls README.md ob$ ln -s /usr/local/bin/tcc tcc.exe ob$ cd ../.. ob$ pwd /home/sakae/src/v ob$ cc -g -std=gnu99 -w -o ./v ./vc/v.c -lm -lpthread ob$ ./v -o v2.exe cmd/v ob$ mv -f v2.exe v
素直にgmakeがやっている事をトレースしてみた。
ob$ v -show-timings . 67.782 ms SCAN 241.434 ms PARSE 74.378 ms CHECK 139.529 ms C GEN 74.640 ms C tcc.exe ob$ v -cc cc -show-timings . 68.774 ms SCAN 242.606 ms PARSE 74.139 ms CHECK 140.637 ms C GEN 1081.469 ms C cc
今度は、ちゃんとコンパイラーの使い分け(デフォでtcc)が出来ているね。
ob$ time v . 0m00.64s real 0m00.23s user 0m00.39s system ob$ time v -cc cc . 0m01.63s real 0m00.90s user 0m00.73s system
もっと高所から確認しても、同様な結果になった。
肝はcmd/v.vにありそうだな。何をやってるか、ちゃんと拝んでおけよ。
ob$ make -f BSDmakefile rm -rf vc/ git clone --depth 1 --quiet https://github.com/vlang/vc cc -std=gnu11 -w -o v vc/v.c -L/usr/local/lib -lm -lexecinfo V has been successfully built
BSDmakefileの方は、tccなんて無いと決めつけて、手抜きの作成をしてるんだな。