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

パッケージ作り

invenia/PkgTemplates.jl

Julia でのパッケージの作り方

Juliaのパッケージディレクトリの変更

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

ほら、状況証拠が揃ったぞ。 でも、これだけじゃ、ああそうですかで終わってしまいそう。