llvm of julia
また、Debian話。javaが11になって、何か使い道がないかと模索。思いついたのは、clojure。 パッケージになってたので、入れた。ついでに、emacsからも操れるように。
M-x cider-jack-in すると、
Are you sure you want to run `cider-jack-in' without a Clojure project? (y or n) y [nREPL] Starting server via /usr/local/bin/clojure -Sdeps '{:deps {nrepl {:mvn/\ version "0.6.0"} cider/cider-nrepl {:mvn/version "0.22.0-beta8"}}}' -m nrepl.cm\ dline --middleware '["cider.nrepl/cider-middleware"]'
開発はプロジェクトのdirを切って、その中でやれってのが今の風潮。これもGUI系の影響なんでしょうかね。思いつたように、
user> (str 123 456) "123456"
あれ、この逆はどうするんだっけ? 思い出せないな。困ったものだ。更に昔の記憶が風化しないように、sbcl系も
;; slime (setq inferior-lisp-program "sbcl") (require 'slime) (slime-setup '(slime-repl slime-fancy slime-banner))
emacsの設定は、こうだったな。で、指使いは? どんどん忘れてる。毎日使って、体に染み込むように仕向けなければ。呑気にlisp系なんかいじってる場合じゃありませんで。
今や、複合系が好まれるのよ。それはね、juliaさ。
share
前回は、juliaにGnuplotを組み込んだものだから、別のアカウントで使えるだろうと思って試してみる。何たって組み込んだ核は、各アカウントが共通にアクセス出来る所に置いていますから。
[ham@cent ~]$ pj -q [ Info: Gnuplot version: 5.2.0 julia> using Gnuplot ERROR: IOError: stat: permission denied (EACCES) Stacktrace: [1] stat(::String) at ./stat.jl:68 [2] isdir at ./stat.jl:303 [inlined] [3] load_path_expand(::String) at ./initdefs.jl:175 [4] active_project(::Bool) at ./initdefs.jl:190 [5] load_path_expand(::String) at ./initdefs.jl:154 [6] load_path() at ./initdefs.jl:211 [7] identify_package(::String) at ./loading.jl:211 [8] identify_package(::Base.PkgId, ::String) at ./loading.jl:201 [9] identify_package at ./loading.jl:196 [inlined] [10] require(::Module, ::Symbol) at ./loading.jl:818
怒られた。立ち入り禁止だそうだ。ならば、
[ham@cent ~]$ julia -q julia> using Gnuplot ERROR: ArgumentError: Package Gnuplot not found in current path: - Run `import Pkg; Pkg.add("Gnuplot")` to install the Gnuplot package. Stacktrace: [1] require(::Module, ::Symbol) at ./loading.jl:823
別アカウントにはGnuplotを入れていないので、入れろとな。
julia> using Hoge ERROR: ArgumentError: Package Hoge not found in current path: - Run `import Pkg; Pkg.add("Hoge")` to install the Hoge package. Stacktrace: [1] require(::Module, ::Symbol) at ./loading.jl:823
全くインチキなパッケージ名を指定しても同じエラー。
多分GnuplotのPkgを組み込んだ核(sysGP.so)には、個人名が記載されてて、それを根拠に アクセスを拒否しているのだろう。
これって、Linuxの個人思想を継承してるんだな。個人思想ってのは
[ham@cent ~]$ ls -l /home 合計 8 drwx------. 18 ham ham 4096 8月 6 06:25 ham drwx------. 24 sakae sakae 4096 8月 5 06:27 sakae
自分の家をしっかりとガードしてるって事。BSD系はどうよ?
まずは、OpenBSD
ob$ ls -l /home total 4 drwxr-xr-x 9 sakae sakae 1024 Jul 16 05:45 sakae/
そして、FreeBSD
$ ls -l /home lrwxr-xr-x 1 root wheel 8 Feb 7 15:46 /home@ -> usr/home
多分、NetBSDも同じだろうね。お隣さんは、どんな生活してるの? なんて、興味を持ったら、覗きに行ける。勿論、お隣さんだってプライバシーが有るから、そういう物は、金庫に入れて自衛する。それ以外は、助け合いの精神発揮。
おおらかなよき時代が継続されている。それに引き換えLinuxは個人主義、お隣さんとの交流禁止っているギスギスした世にしちまった。それが蔓延するから、痛ましい事件が頻発するんだな。責任者、出てこい。
冗談は兎も角、パッケージをみんなでシェアする公式な方法って無いものですかね? 無い無い、そういう発想すら開発陣には無いでしょうから。
嘆いていても進歩が無いので、少し調査。そう、新たに作ったsysGP.soに、きっと出身地が埋め込まれているはず。アカウントsakaeの元で作ったんで、探ってみる。
[ham@cent julia]$ strings sysGP.so | grep sakae /home/sakae/.julia/packages/Gnuplot/GkEY3/src/Gnuplot.jl /home/sakae/.julia/packages/PackageCompiler/CJQcs/src /home/sakae/.julia/packages/PackageCompiler/CJQcs/src/pkg.jl /home/sakae/.julia/packages/PackageCompiler/CJQcs/src/system_image.jl /home/sakae/.julia/packages/PackageCompiler/CJQcs/src/PackageCompiler.jl : /home/sakae/.julia/registries/General/Registry.toml /home/sakae/.julia/packages/Gnuplot/GkEY3/src /home/sakae/.julia/packages/Gnuplot/GkEY3/src /home/sakae/.julia/packages/Gnuplot/GkEY3/src :
ビンゴでしたね。ならば、hamなアカウントからsakaeの中を覗けるようにBSD風の共有を設定しちゃえ。(共有違うだろう。そういうのを出歯亀って言うんよ。なんてったって夏ですから、デバ亀出現の季節です。ああ、若い人は知らないか。昔出っ歯の人が、風呂場を覗くとかの犯罪を多数犯した。それで、その種の犯罪者をデバ亀って言うんよ)
[ham@cent ~]$ ls -l /home 合計 8 drwx------. 18 ham ham 4096 8月 7 06:29 ham drwxr-xr-x. 24 sakae sakae 4096 8月 5 06:27 sakae
これで、覗きの準備完了。いざ事に及んでみる。
[ham@cent ~]$ pj -q [ Info: Gnuplot version: 5.2.0 julia> using Gnuplot julia> @gp "plot sin(x)" :- [ Info: Creating session default... [ Info: Gnuplot version: 5.2.0 GNUPLOT (default) -> wxt GNUPLOT (default) -> 0 enhanced julia> @gp :- "plot cos(x)"
見事成功。他人の褌で相撲を取れた。こういう大らかな世界大歓迎です。見せたって、別に減るもんじゃ無いしょ。こんな事ばかり書いていると、しょっぴかれるぞ。学術的に少し追加。:-が一行目の最後と2行目の冒頭付近についてるけど、これ結合記号ね。これで、1枚のグラフに複数の線を書けるようになります。
デバ亀ついでに、パイプ内を流れる信号のモニター法を伝授。
julia> setverbose(true) true julia> @gp "p sin(x)" GNUPLOT (default) reset session GNUPLOT (default) plot \ sin(x) GNUPLOT (default) julia> @gp 1:5 GNUPLOT (default) reset session GNUPLOT (default) $data0 << EOD GNUPLOT (default) 1 GNUPLOT (default) 2 GNUPLOT (default) 3 GNUPLOT (default) ... GNUPLOT (default) EOD GNUPLOT (default) plot \ $data0 GNUPLOT (default)
わざわざdefaultって断っているのは、複数のセッションを開けるようになっている為です。
julia> @gp :MINE "p sin(x)/x" [ Info: Creating session MINE... [ Info: Gnuplot version: 5.2.0 GNUPLOT (MINE) print GPVAL_TERM GNUPLOT (MINE) -> wxt GNUPLOT (MINE) print GPVAL_TERMOPTIONS GNUPLOT (MINE) -> 0 enhanced GNUPLOT (MINE) set term wxt 0 enhanced title 'Gnuplot.jl: MINE' GNUPLOT (MINE) reset session GNUPLOT (MINE) plot \ sin(x)/x GNUPLOT (MINE)
これで、MINE用の新たなgnuplotが起動し、defalut用のものと合わせて、gnuplotのプロセスが2個になりました。これが嬉しい場面って有るかなあ。まあ、選り取り見取りって事にしておきましょう。折角実装してくださった作者様に敬意を表して。
make pkg
ifelse
llvmの勉強を兼ねて、良く使いそうなopをコンパイルしてみる。コンパイルの単位は、関数になるんで、関数言語風にターゲットを定義。そして、llvmのコードをピーピング。
julia> aa(n) = if (n>12) n+34 else n-56 end aa (generic function with 1 method) julia> code_llvm(aa,(Int,)) ; @ REPL[6]:1 within `aa' define i64 @julia_aa_13488(i64) { top: ; ┌ @ operators.jl:286 within `>' ; │┌ @ int.jl:49 within `<' %1 = icmp slt i64 %0, 13 ; └└ br i1 %1, label %L5, label %L3 L3: ; preds = %top ; ┌ @ int.jl:53 within `+' %2 = add i64 %0, 34 ; └ ret i64 %2 L5: ; preds = %top ; ┌ @ int.jl:52 within `-' %3 = add i64 %0, -56 ; └ ret i64 %3 }
まずは、if式のコンパイル。テスト句を実行してみて、どちらに飛ぶか決定。それから、分岐先で返値を計算して抜けていく。常識的な流れだな。
julia> bb(n) = n>12 ? n+34 : n-56 bb (generic function with 1 method) julia> code_llvm(bb,(Int,)) ; @ REPL[10]:1 within `bb' define i64 @julia_bb_13498(i64) { top: ; ┌ @ operators.jl:286 within `>' ; │┌ @ int.jl:49 within `<' %1 = icmp slt i64 %0, 13 ; └└ br i1 %1, label %L5, label %L3 L3: ; preds = %top ; ┌ @ int.jl:53 within `+' %2 = add i64 %0, 34 ; └ ret i64 %2 L5: ; preds = %top ; ┌ @ int.jl:52 within `-' %3 = add i64 %0, -56 ; └ ret i64 %3 }
今度は3項演算子のコンパイル。ifと同じ流れになった。
julia> cc(n) = ifelse(n>12, n+34, n-56) cc (generic function with 1 method) julia> code_llvm(cc,(Int,)) ; @ REPL[12]:1 within `cc' define i64 @julia_cc_13504(i64) { top: ; ┌ @ operators.jl:286 within `>' ; │┌ @ int.jl:49 within `<' %1 = icmp slt i64 %0, 13 ; └└ %.v = select i1 %1, i64 -56, i64 34 %2 = add i64 %.v, %0 ret i64 %2 }
今度は、julia流の3項演算子を関数にしたもの。関数名がifelseって所が、なんとなくかわゆいい。
面白いのは、飛んで行った先で計算するって方式じゃなくて、計算対象をどちらか選べってやってから、計算してるって事。
なんとなく、コンパクトなマシン語になりそうなので、比べてみるか。
julia> code_native(bb,(Int,)) .text ; ┌ @ REPL[10]:1 within `bb' ; │┌ @ operators.jl:286 within `>' ; ││┌ @ REPL[10]:1 within `<' cmpq $13, %rdi ; │└└ jge L14 ; │┌ @ int.jl:52 within `-' addq $-56, %rdi ; │└ movq %rdi, %rax retq ; │┌ @ int.jl:53 within `+' L14: addq $34, %rdi ; │└ movq %rdi, %rax retq nopw %cs:(%rax,%rax) ; └
まずは、3項演算の方。出口が2箇所あって、ごちゃごちゃな雰囲気。
julia> code_native(cc,(Int,)) .text ; ┌ @ REPL[12]:1 within `cc' ; │┌ @ operators.jl:286 within `>' ; ││┌ @ REPL[12]:1 within `<' cmpq $13, %rdi ; │└└ movl $34, %ecx movq $-56, %rax cmovgeq %rcx, %rax addq %rdi, %rax retq nopl (%rax,%rax) ; └
どういう原理か解読。鍵はcmovgeq %rcs, %rax 辞書で調べてみると、コンデションによって(この場合はgeq)%rcxの内容を%raxに転送するか否かを決めてる。
コンデションはcmpqで決まる。13から関数の引数(%rdi)を引いてフラグを建てる。以上だったら成立で、%ecx(34)が%raxにセット。未満だったら、前もって設定した-56のままとな。 そのraxと入力値を加算したものが答え。
これだと、投機実行でどちらに飛んで行くって、ややこしい事をしなくて済むので、素直な流れが実現されるのだな。
すっきりしてて、速そうなコードだ。事実、loopの最深部とかで使うと(場合によっては)効果絶大らしい。
例によって、早すぎる最適化は害悪を招くので、最後の隠し玉として取っておきましょう。
llvm
それぞれの関数が何処で定義されてるか? 簡単な見分け方。
julia> ifelse ifelse (built-in function) julia> code_native code_native (generic function with 5 methods)
built-inって出てくるのは、Cフラフラ語とかで定義されてる奴。プリミティブ関数とLisp屋さんは称してる。built-in以外は、julia語で書かれたやつだ。
julia> Core.i ;; <- TABで補完 ifelse int128" io_pointer isa include invoke is_top_bit_set isdefined
こんな風に、補完を効かせて、システムに問い合わせてもよい。
そんな事で、ifelseの源流を、ソースで当たってみる。juliaのソース玉は、関連モジュール類(llvm,blasa等々)を含めたやつも提供されてるけど、スクラッチからコンパイルをしない限り、小さい玉だけで十分。
(base) cent:src$ grep -l ifelse * builtin_proto.h builtins.c codegen.cpp common_symbols1.inc grep: flisp: Is a directory init.c intrinsics.cpp staticdata.c grep: support: Is a directory
後はじっくり鑑賞するだけ。builtins.cは、関数を登録してる部分。実際の定義は、intrinsics.cppにある。
static jl_cgval_t emit_ifelse(jl_codectx_t &ctx, jl_cgval_t c, jl_cgval_t x, jl\ _cgval_t y, jl_value_t *rt_hint) { Value *isfalse = emit_condition(ctx, c, "ifelse"); jl_value_t *t1 = x.typ; jl_value_t *t2 = y.typ; : if (t1 != t2) { // type inference may know something we don't, in which case it may // be illegal for us to convert to rt_hint. Check first if either // of the types have empty intersection with the result type, // in which case, we may use the other one.
普通は、引数のタイプが一致してるはずって、厳しいチェックが入るんだけど、juliaはj柔軟に対処してくれる。Haskellみたいに石頭じゃないよ。
julia> aa(n) = ifelse(n>0, 1, "fu") aa (generic function with 1 method) julia> aa(3) 1 julia> aa(-4) "fu"
型が違っても、許してくれる。
その後、codeは、進み、大事なselect部分のコンパイルに達する。llvmの中間コードを生成 しているのでしょう。
Value *ifelse_result; bool isboxed; Type *llt1 = julia_type_to_llvm(t1, &isboxed); if (t1 != t2) isboxed = true; if (!isboxed) { if (type_is_ghost(llt1)) return x; ifelse_result = ctx.builder.CreateSelect(isfalse, emit_unbox(ctx, llt1, y, t1), emit_unbox(ctx, llt1, x, t1)); }
大雑把にまとめると、julia語のソースは、flispによって(多分)パースされて、Cフラフラ語が上で見たような具合にllvm語のソースを作成。それを内蔵のlibLLVMで最終的なamd64の機械語に変換するって流れなんだろうね。
核となるlibLLVMには、きっと逆アセンブラも仕込まているに違いない。
(base) cent:julia$ strings libLLVM-6.0.so | grep cmovgeq cmovgeq cmovgeq cmovgeq
ほら、状況証拠が揃ったぞ。 でも、これだけじゃ、ああそうですかで終わってしまいそう。