make cpu
こういうタイトルで始めると、半田こてを握って、パソコン作りでも余生の楽しみとして、始めたのかと誤解を招くな。
でも、お前が隠れ東大生で、名物授業のCPU実験に参加してるなんてのは、想像を絶する誤解だからね。
systat again
前回はsystatに取りついてしまったものだから、一応manしたんだ。そしたら、初画面で表示される画面は、デフォの表示って知った。起動時にオプションを与えるか、起動中に矢印キーで、表示情報を次々に変えられる。たとえば、 vmstat -mと言う、コマンドの結果も、プチグラフィック的に表示出来る(これはkernel屋さんの御用達)。
Memory statistics by type
1 users Load 0.00 0.00 0.00 (1-29 of 39) ob.localdomain 06:54:41 TYPE INUSE MEMUSE HIGHUS LIMIT REQUESTS TYPE KERN BUCKETS ACPI 8798 1055K 1152K 78643K 41785 0 0 ||||||||........ ISOFS mount 1 32768 32768 78643K 1 0 0 ...........|.... MFS node 1 128 128 78643K 1 0 0 ...|............ MSDOSFS mount 1 16384 16384 78643K 1 0 0 ..........|..... NDP 4 128 128 78643K 4 0 0 .|..............
bucket size
1 users Load 0.00 0.00 0.00 ob.localdomain 06:55:16 BUCKET REQUESTS INUSE FREE HIWAT COULDFREE 16 1143 245 523 1280 0 32 3880 648 888 640 79 64 9350 1544 56 320 0 128 40991 8877 275 160 586 256 2714 107 5 80 0 512 304 127 1 40 0
これはもう、手放せないですよ。って、プロバイダーでも始めるんか。まあ、manは3文の得って事です。
curses互換?
cls :: IO () cls = putStr "\ESC[2J" type Pos = (Int,Int) goto :: Pos -> IO () goto (x,y) = putStr ("\ESC[" ++ show y ++ ";" ++ show x ++ "H")
これは、何かの仕様書ですか? いいえ、Haskellのコードの一部です。前回の最後で、cursesの言語バインディングとしてchicken(scheme)のそれを見つけた。例として挙げられていたのが、life。流行りのピンポンじゃない所が奥ゆかしい。
で、ライフだったら他にも有ったなと思って蔵書に当たったら、『プログラミングHaskell』に見つかった。
画面をクリアーする関数と、指定のポジションにカーソルを移動する関数が、エスケープシーケンスを使って実装されてる。これぐらいの事なら、使い方がプチ面倒な(環境を用意するのが)cursesを引っ張り出す事は無いだろう。
エスケープシーケンス に、ANSIで定めるシーケンスの解説が載ってた。
curses of gauche
とか、言いながら、gaucheでcursesってコースは無いかなと探してみたのは事実だ。いや、鶏用を見つける前に、こんなリンクを探し当てていたのさ。
これを試そうと思ったら
を、熟読の上、準備をしなければならない。gccの障壁もあるみたいだし。本当にcursesを使うはめにならない限りは、避けた方が無難と思われ。(君子危うきに近寄らずは、処世訓です)
それが分かってるgaucheの生みの親は、examplesに snake.scmなんてのを置いてくれている。これならgaucheを入れたその日から、安心して使えるぞ。結構使いでがありそうだ。
(define (get-dir con) ;returns W, S, N, E or #f (and (chready? con) (case (getch con) [(#\h KEY_LEFT) 'W] [(#\j KEY_DOWN) 'S] [(#\k KEY_UP) 'N] [(#\l KEY_RIGHT) 'E] [else #f])))
ちょいとコードを覗いてみたら、こんなのに出くわした。方向制御は、素人は矢印キーを使えばいいし、viな人は慣れたキーを使ってねって事だね。
詳細は、 12.50 text.console - テキスト端末制御を参照。普通に使うなら、これで十分。
それから、ちょっとこのサンプルで遊んだけど、蛇がすばしすぎた。ちょっと遅くしてやろうと、コードを見たら、
(case (snake-dir snake) [(N S) (sys-nanosleep #e18e7)]
こんなのが出て来た。ふーん、待ち時間を入れてるのね。時間待ちの単位はナノセコンドか。ええい雑に10倍ぐらい遅くしちゃえ。e18e7を1e18e7にしたよ。
debian:tmp$ gosh snake.scm *** READ-ERROR: Read error at "./snake.scm":line 99: invalid numeric prefix (#, =, r or R is expected) : #1e While loading "./snake.scm" at line 99
改変禁止ですって言われた。でもヒントがそこはかとなく隠されていたぞ。シャープサインは、リーダーマクロの始まりだね。調べてみたら、正確数の接頭語ですって。ふーん、雑に言うと整数って事。実際の整数は、18e7って言う16進表記。そう言われてもピンとこない。
gosh> (/ #e18e7 1e6) 180.0
180ミリセコンドって事だね。こういう技は、ゲーミング世界に身を置いた人でないと、発想出来ないなあ。まあ、#eの接頭語って言うgaucheへのロッキングの罠が仕掛けられている事を承知しておかねばならなけどね。
コンソール機能関連で、進行状態をグラフィック的に表示するのもあるね。良く使う物じゃないから、text.progressをuseしてから使う。
Example |oooooooooooooooooooooooooooooooooooooooo|256/256(100%) 00:13
マニュアルの例を実行すると、上のようになった。これで、curlっぽい真似とかが簡単に出来るぞ。
ext/termios
そして、更に使う事が限定されそうな場面。パスワードをシークレットに入力したい。 gauche.termiosをuseしておく。
gosh> (get-password) Password: *********
ext/termios/termios.scmに埋め込まれていたのを抽出したもの。ソース参観は3文の得だ。
#| ;; sample (define (get-password) (with-output-to-file (cond-expand [gauche.os.windows "CON"] [else "/dev/tty"]) (lambda () (display "Password: ") (flush))) (without-echoing #f read-line)) |#
このtermiosだけど、上で出て来たtext.consoleの内部からも使われている。縁の下の力持ち的な存在。OSに近い所の機能を使うんで、gaucheに組み込む時は大変だ。ちゃんとした例は、examples/noecho.scmにあるぞ。 コンパイル時のログから拾ってみる。
(cd termios; make default) make[2]: Entering directory '/home/sakae/src/Gauche-0.9.9/ext/termios' gcc -DHAVE_CONFIG_H -I. -I. -I../../src -I../../src -I../../gc/include -g -O2 -Wall -Wextra -Wno-unused-label -fPIC -fomit-frame-pointer -o termios.o -c termios.c "../../src/gosh" -ftest "../../src/precomp" -e -P -o gauche--termios ./termios.scm gcc -DHAVE_CONFIG_H -I. -I. -I../../src -I../../src -I../../gc/include -g -O2 -Wall -Wextra -Wno-unused-label -fPIC -fomit-frame-pointer -o gauche--termios.o -c gauche--termios.c gcc -g -O2 -Wall -Wextra -Wno-unused-label -fPIC -shared -o gauche--termios.so termios.o gauche--termios.o -L"../../src" -ldl -lcrypt -lutil -lrt -lm -lpthread -b "../.." -s . gauche--termios.so termios.sci link /home/sakae/src/Gauche-0.9.9/ext/termios/gauche--termios.so <- ../../src/gauche--termios.so link /home/sakae/src/Gauche-0.9.9/ext/termios/termios.sci <- ../../lib/gauche/termios.sci
termios.cと言うOSとのやり取りをするC語のソース(俗に低レイヤーとか言う)。termios.scmは高レイヤーなgauche側のスクリプト。これを、precompってスクリプトでC語に変換。コンパイル、そこからダイナミックロード出来るようにsoファイルを作成。複雑極まりない。
VM instraction
上で見てきたように、text.consoleだとかtermiosだとかは、標準入出力の一種の拡張になっている。テキスト版のグラフィック機構とでも言うようなやつ。
gaucheを仮想機械(VM)と捉えれば、手足になる入出力だな。じゃ頭脳部分はどうなる。それは、gauche語で書かれていて、コンパイル時にそれをC語に変換。CコンパイラーがリアルCPU(amd64とかね)用にマシン語まで落とす。
この仮想機械が理解出来るのはS式だ。マシン語レベルから、抽象度の高いS式に持ち上げられている。その仮想機械との接点は、そうreplになる。仮想機械をOSと捉えれば、replはそのOSとのやり取りをするshellと考えても良いな。
昔から、『能ある鷹は爪を隠す』って言うけど、能はlibgauche.soとかにある。そしてそれを呼び出して、replを実現してるのがmain.cになるな。この構成方法は、初期のgaucheから見られる。普段呼び出して使っているgoshは、mainが実現してる。
こうしておけば、ユーザーの求めに応じて(一般ユーザーに、括弧付きの入力を強いる事は、多分出来ないだろう)、表層のmain.cだけを入れ替え、爪の部分はlibgaucheを呼び出す事で、快適に開発出来る(多分)。
CPU実験でも取り上げられていたけど、仮想機械用のコンパイラーも重要だ。勿論、コンパイラーもgauche言語で書かれている。そして機械を作成する時に、コンパイラーもC語に変換してる。そうすれば、何倍か速くコンパイル出来るからね。
C語は、世界共通の言語、インテルの石ばかりかARMでもMIPSでもほぼどんな石でも動くだろう。 ゆえに、汎用性が確保出来るとな。
こうして見ると、 VMはそれを扱う同じ言語で書かれているってのが、意味深長だ。Lisp界の伝統の技だな。
VMを記述してるのは、src/vminsn.scmになる。
;; Some immediate constants (define-insn CONSTI 1 none #f ($result:i (SCM_VM_INSN_ARG code))) ; small int (define-insn CONSTN 0 none #f ($result SCM_NIL)) ; () (define-insn CONSTF 0 none #f ($result SCM_FALSE)) ; #f : ;; CLOSURE <code> ;; Create a closure capturing current environment. ;; CODE is the compiled code. Leaves created closure in val0. ;; (define-insn CLOSURE 0 code #f (let* ((body)) (FETCH-OPERAND body) INCR-PC ($result (Scm_MakeClosure body (get_env vm)))))
これが、実際の命令を定義してる部分の一部。間違いをしないように、定義はマクロで書くようになってる。
マクロの定義部分の詳細説明が、冒頭付近にコメントされている。
;;; (define-insn <name> <num-params> <operand-type> ;;; :optional (<combination> '()) ;;; (<body> #f) ;;; <flags> ...)
命令の名前、引数の個数、オペランドのタイプまでが必須で、それ以降は、オプション扱いだ(命令によって有ったり無かったり、意味もそれぞれだ)。
(base) sakae@debian:src$ grep define-insn vminsn.scm | wc -l 194
命令の個数を数えてみた。多いんだか少ないんだか?
disasm
どんな風に命令が使われるかは、その場で確認出来る。
gosh> (disasm (^() (cons #t 'b))) CLOSURE #<closure (#f)> === main_code (name=#f, cc=0x2f4770, codevec=0x125168, size=6, const=1 stack=1): signatureInfo: ((#f)) 0 CONST-PUSH #t 2 CONST b 4 CONS ; (cons #t (quote b)) 5 RET
consを実行する無名関数。consの引数は、定数なので、constを使って計算用のstackに展開。 定義は、関数単位で行われる為、その場関数にする必要が有る。
gosh> (define (fuga x y) (+ (* x y) x)) fuga gosh> (disasm fuga) CLOSURE #<closure (fuga x y)> === main_code (name=fuga, cc=0x7f2d760adc00, codevec=0x7f2d76279060, size=5, const=0 stack=1): signatureInfo: ((fuga x y)) 0 LREF1-PUSH ; x 1 LREF0 ; y 2 NUMMUL2 ; (* x y) 3 LREF-VAL0-NUMADD2(0,1) ; (+ (* x y) x) 4 RET
簡単な関数、引数は変数になるんで、その値をstackに積む為のlref命令が表れている。
gosh> (define (mu) (write (eval (read) (current-module)))) mu gosh> (disasm mu) CLOSURE #<closure (mu)> === main_code (name=mu, cc=0x2f44d0, codevec=0x1465b0, size=14, const=4 stack=18): signatureInfo: ((mu)) 0 PRE-CALL(2) 11 ; (eval (read) (current-module)) 2 PRE-CALL(0) 6 ; (read) 4 GREF-CALL(0) #<identifier user#read.32b580>; (read) 6 PUSH 7 CONST-PUSH #<module user> 9 GREF-CALL(2) #<identifier user#eval.32b5a0>; (eval (read) (current-module)) 11 PUSH-GREF-TAIL-CALL(1) #<identifier user#write.32b5c0>; (write (eval (read) (current-module))) 13 RET
readとかevalみたいなのは、VMの命令外なんだね。VMからしたら、よそで定義されてるのを呼び出すって扱いなのか。
etc
こういう本が出てたんだね。しかも料理本として。認知度が上がってきているんでしょうね。pythonの遅さにイラついた人がきっと飛びつくでしょう。
サンプルがtar玉の供給じゃなくて、gitってのが現代風。使う環境もnotebookからってのが、世の趨勢なんですかね。
オイラーもgitで落としてきたんで、取り合えず遊んでみるかな。1.2用みたいだけど、今Downloadで提供してるのは、1.3.1だった。動かなくて右往左往する人がいない事を望む。まあ、足が速い世界ですから、こればかりは宿命。そこを乗り越えてこそ一人前です。
「CentOS 8-1911」リリース、RHEL 8.1をベースとした互換環境
17日にオイラーの所にもやってきた。1Gの大容量がDLされて、無事に脱皮完了。 再起動後に、sudo dnf autoremove と言うガベコレを実施したら、貴重な150MのDisk-spaceが回収された。得した気分。
guile 3.0
Guile is a programming language
久しぶりにメジャーバージョンアップしたようだ。debianとかだとapt用が供給されてるようだけど、CentOS用のずばりは無い。Guixとか言うマネージャーを使うと簡単に入るみたいだけど。
CentOSもアップデートされた事だし、テストを兼ねて、tar玉から入れてみるか。それが正しいOSS道ってもんです。
(base) [sakae@c8 guile-3.0.0]$ emacs config.log (base) [sakae@c8 guile-3.0.0]$ dnf provides /usr/include/ltdl.h (base) [sakae@c8 guile-3.0.0]$ sudo dnf install libtool-ltdl-devel (base) [sakae@c8 guile-3.0.0]$ sudo dnf install libunistring-devel
例によっていじわるがあります。非生産的な仕組みが相変わらずのさばっています。今回引っかかったのは、configure時に、libltdlが無いよーーとlibunistrigが無いよーーの2箇所。
大体はライブラリィー名に-develを付けるとヘッダーファイルがインストールされるんだけど、libltdlの方は、そうは行かなかった。
上の実行例みたいに、config.logの最後の方(あるいはlibltdlを探してもよい)を見ると、ミニgccが実行されてる所が有る。そこでltdl.hが見つからんエラーになってる。次はそのヘッダーファイルが、どのパッケージに所属してるか、dnf providesにフルパスを与えて探し出す。そしてそのパッケージをインストール。馬鹿Linuxを欺く、典型的な手法である。
これで、configureが成功したんで、コンパイル時間を計測。
real 12m43.448s user 13m2.182s sys 0m54.403s
コンパイル時間の多少は、複雑度と相関があるんで、新しい奴をコンパイルする場合、土地勘を掴む為、必ずやる儀式だ。我らが愛用するgaucheよりは3倍複雑って出たぞ。
コンパイルが完了したら、make checkして、出来栄えを確認後インストールすれば良い。
(base) [sakae@c8 tmp]$ guile GNU Guile 3.0.0 Copyright (C) 1995-2020 Free Software Foundation, Inc. Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. This program is free software, and you are welcome to redistribute it under certain conditions; type `,show c' for details. Enter `,help' for help. scheme@(guile-user)> ,help compile Compile Commands [abbrev]: ,compile EXP [,c] - Generate compiled code. ,compile-file FILE [,cc] - Compile a file. ,expand EXP [,exp] - Expand any macros in a form. ,optimize EXP [,opt] - Run the optimizer on a piece of code and print the result. ,disassemble EXP [,x] - Disassemble a compiled procedure. ,disassemble-file FILE [,xx] - Disassemble a file.
これが、今回の目玉なんですかねぇ。
どんなライブラリィーが使えるか当たってみたら、3.0は出たばかりで、全く対応してなかった。普通に使うなら、枯れたやつの方が良い鴨。