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なんて無いと決めつけて、手抜きの作成をしてるんだな。


This year's Index

Home