LoginSignup
18
18

More than 5 years have passed since last update.

#JuliaLang ていすいじゅん♡の話(LLVM IRとNative code 触りだけ)

Posted at

(#JuliaLang) Julia Advent Calendar 2014

12月5日担当 kimrin(twitter:kimrin)

やっぴー♡ きむりんだよ♡ 小六女子のおっさん(40)だよ♡
(ここで読者おおいに混乱w)

今日はJulia言語に興味のある方だけでなく、組み込みとか低レベルのAPI/ABIに興味のある方にも見ていただきたい内容となっております。れっつちぇきらっちょーw

さて、結論から言います。

実はJulia言語、REPLで定義した関数の

LLVM IR(LLVMの中間表現)と

Native Code(マシン語)が

リアルタイムで観れますー!!!

すごいでしょ! (俺はすごくないけどw)

さて、興味湧きまくりんぐの視聴者のみなさんはここでJuliaの処理系をダウンロードするのです! そしてREPL(Julia本体)を起動するのです!!\(^o^)/


じゃ、準備はいいかなー? 起動してみましょう。

REPL
kimuraken-no-MacBook-Prox:~ kimrin$ julia
               _
   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: http://docs.julialang.org
   _ _   _| |_  __ _   |  Type "help()" for help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 0.3.0 (2014-08-20 20:43 UTC)
 _/ |\__'_|_|_|\__'_|  |  Official http://julialang.org/ release
|__/                   |  x86_64-apple-darwin13.3.0

julia> 

julia> ってプロンプトが出ればめでたしめでたし! ちなみにjuliaは言語そのもの、Juliaが具体的にいま使ってる処理系のことだからね!(これ豆なw)

じゃ、関数を定義してみましょー!! まずはInt64の二乗を返す、簡単な関数からいってみましょー


julia> function jijo(x::Int64)
           x*x
       end
jijo (generic function with 1 method)

関数名がダサいのはワザとさ、ふふっw(ぉぃ ちなみに文法のおさらいしとくと、function キーワードが関数定義の開始で、endまでが関数本体。そしてLISPチックなのだが、最後のexpressionが返り値になりまする。ここでは引数はInt64にしましたー。

もひとつ豆だが、Julia, 32bitバージョンではIntがInt32、64bitバージョンではIntがInt64だから、覚えておくといいよ♡ まぁほとんどの人がMacかLinuxで64bitつこてるみたいだけどね。

さてお立ち会い。ここでおもむろにcode_llvm関数にjijoを突っ込んであげると。。。


julia> code_llvm(jijo,(Int64,))

define i64 @"julia_jijo;19948"(i64) {
top:
  %1 = mul i64 %0, %0, !dbg !842
  ret i64 %1, !dbg !842
}

julia> 

やっべー、謎言語登場www ではないですが、なんだか不思議な表現が出てきました。
これ、Julia言語の処理系が使っているLLVMの中間表現(LLVM IR)なんです!
コンパイルしなくても中間表現が覗き見できちゃうんです!!! (というか、JITコンパイルされたLLVM IRを保存しているから見られる、が正解です)

読み解いてみると、引数i64, 返り値i64のjulia_jijo:...関数が出来ていて、
%1という変数(正確にはSSA変数)に引数*引数が入ってるみたいですね、でこれがretで返されるみたいな
(LLVMに詳しい方向けですが、関数定義しただけなのでmainはなく、あの謎文字列のポインタの大きさとか描いた文字列もそのままでは出ません、ですー)

う〜ん、まーべらすw

ノリノリの紀子さん(ぉぃ)になってきたところで、もういっちょやってみましょー。


julia> function うんこw(x::Int64)
           x = 1
           y = x
           z = x
           w = x + y
           w
       end
うんこw (generic function with 1 method)

julia> 

なんという才能の無駄遣いでしょう! なんとうんこwという関数が定義できるじゃ、あ〜りませんかー!! www ほんとJuliaたんごめんね、ごめんねw
まぁあの、こんな感じで変数とか関数とか、Cでいうところのidentifier的なところにUnicode文字が使えます。これで御社のJulia難読化が捗るね♡ (違うw)

さて、LLVM IRみてみましょー


julia> code_llvm(うんこw,(Int64,))

define i64 @"julia_?\81\86?\82\93?\81\93w;19980"(i64) {
top:
  ret i64 2, !dbg !941
}

julia> 

えっ、えっ、これ、2 を返してるだけだよ!! 最適化、されちゃった♡
あれ、SSA変数の説明するところだったんだけど、おかしいな〜w

ごめん、あと1分私に時間をください(CV:鈴木健二w)


julia> function うんこww(x::Int64)
           x = x*x
           y = x
           z = x
           w = x + y
           w
       end
うんこww (generic function with 1 method)

julia> code_llvm(うんこww,(Int64,))

define i64 @"julia_?\81\86?\82\93?\81\93ww;20093"(i64) {
top:
  %1 = shl i64 %0, 1, !dbg !1281
  %2 = mul i64 %1, %0, !dbg !1282
  ret i64 %2, !dbg !1283
}

julia> 

えーっと、返り値がw = x + y = 2*x*x になってるので shift left1bitだけ左にシフトして2xに掛けて2xにして、そいつを次の%2x(%0)掛けて2*x*xを返してるのですな。う〜ん、マンダムw

さて、流石に変数名が汚いwので、もうちょっと綺麗な例を出したいと思います♡


julia> function きれいなジャイアン(x::Int64)
           if x % 2 == 0
               y = 1
           else
               y = 2
           end
           y*x
       end
きれいなジャイアン (generic function with 1 method)

文句なしに綺麗です(ぉぃ) さて、こいつのLLVM IR表現みてみましょー


julia> code_llvm(きれいなジャイアン,(Int64,))

define i64 @"julia_?\81\8D?\82\8C?\81\84?\81??\82??\83??\82??\82??\83?;20133"(i64) {
top:
  %1 = and i64 %0, 1, !dbg !1403
  %2 = icmp eq i64 %1, 0, !dbg !1403
  br i1 %2, label %L1, label %L, !dbg !1403

L:                                                ; preds = %top
  br label %L1, !dbg !1404

L1:                                               ; preds = %top, %L
  %y.0 = phi i64 [ 2, %L ], [ 1, %top ]
  %3 = mul i64 %y.0, %0, !dbg !1405
  ret i64 %3, !dbg !1405
}

julia> 

おおっ、なんかマシン語っぽいのでてきたw じゃぁ上から見ていきましょー\(^o^)/

%0に引数のxが入ってまーす。で、1との論理積とります。これ、x % 2っすね。
で次に、icmp命令で%10かどうか確認します。bool値的なものが結果の%2に入ってますです。

で、次、br命令(ブランチ命令)です。あ、言い忘れましたが、!以降はコメントと思ってください。
i1(整数1bit)の%2が非ゼロなら、すなわちx % 2 == 0のthen partだったら、label %L1 に飛んでください、じゃなければ label %Lに飛んでください、というジャンプ文みたいな表現でーす。

じゃぁ次のL:x % 2 != 0)の時をみてみましょー。あれ、%L1に飛んで、とだけ書かれてるw このとき横に ; preds = %topって書かれているの、わかるでしょーかー。これはBasic Block (ここではラベルの次に続く命令列の塊だと思ってください)のpredecessor(流入元)が、%top(関数の先頭のBasic Block)からだよ♡ って;でコメントされているのです!! 確かに、L:には%topからしか流入していませんね。で、L:の中身は%L1に飛んで、ですね、わかります。

おーっとここでー!! phi 関数の登場でーす!!

まぁあの、みなさんもうすうす気がついているかと思われまするが、分岐して、分岐の先でthen partとelse part内で違う値を同じ変数に突っ込むと、ごっちんがゴッチンこw ではないですが(ぉぃ)「俺(%y.0)には何入れたらいいか、この時点で分からんじゃないかー!!!(怒)」となるわけです。そこで調停役(違うw)のphi関数が登場します。

phi関数は二つの流入する変数の定義(ここではwriteされた変数だと思ってください)をまとめる作用があります。で、[ 2, %L ], [ 1, %top ]みると分かるように、Lから来たジャイアンだった場合は定数2を(Juliaでのelse partですね)、topから来たジャイアンには定数1を(then part)代入して、i64phi 関数の結果としてこれを %y.0 に代入してね、てへっ、みたいな感じです。あとは一直線で、yにxを掛けて、最終的に結果の%3retしてあげます。いっちょあがり。

そういえばL1のところに、; preds = %top, %Lって書いてあって、これはもしかしたら値がごっちんこ♡ するかもよ〜? というわけです。いえ、全部の場合で例外なくphi関数が入るわけではなく、then part と else part で違う値を代入した時にだけ、phi関数の出番となります。あ、phiφとのことです。

実は(言い忘れていたのですが)、このLLVM IRの表現形式のこと、SSA形式といいます。左辺に注目してください。どの変数も一回だけしか出てきません。つまり代入(write)が一回だけなのです! そして先ほどのφ関数もSSA形式の特徴です。
一回だけしか代入しない、その場限りの恋、なんてロマンチックなんでしょー(www)

ここで素朴な疑問です。

じゃぁφ関数はどうやってマシン語に直すの?

きたー!! 君を待って居たんだ せにょりーたw
code_nativeってのがありまして、code_llvm同様にこれに「きれいなジャイアン」を突っ込むと、


julia> code_native(きれいなジャイアン,(Int64,))
    .section    __TEXT,__text,regular,pure_instructions
Filename: none
Source line: 2
    push    RBP
    mov RBP, RSP
    mov EAX, 1
Source line: 2
    test    DIL, 1
    je  5
    mov EAX, 2
Source line: 7
    imul    RAX, RDI
    pop RBP
    ret

julia> 

なんと、

関数のアッセンブリが表示されます!!やたー\(^o^)/

詳しくは追いませんが、testで判定をしていて、je 5が(おそらく)imulに飛んでる(y=1のまま、then part)で、else partの時だけEAX2を代入して、最後imuly*xを計算してRAXに結果を残したままretですー(多分)

そして注目すべきはphi関数が消えてるという点です。
詳しい話すると、LLVM IRがコンパイラのフロントエンド(LLVM IRを出力)が出力したIRをmachine independent に最適化した状態で、それを受けたコンパイラのバックエンド(機械語命令生成するところ)がかりっかりに最適化して、x86のマシン語を出力しているのでーす。で、これをJIT compilationで実行時に一気にマシン語に直しちゃうとです。すげー。

で、SSA形式は最適化がし易い形式なんです。で、これを入力として様々な最適化をコンパイラが施して、phi関数を消して、命令列を作り出すのです。ドラマッチックw

さて、LLVM IRとNative Codeの両方が手に取るようにわかりますね。
浮動小数点、いってみよー\(^o^)/



julia> function 限りなくfloatに近いfloat64(x::Float64)
           x * x * x
       end
限りなくfloatに近いfloat64 (generic function with 1 method)

julia> code_llvm(限りなくfloatに近いfloat64, (Float64,))

define double @"julia_?\99\90?\82\8A?\81??\81\8Ffloat?\81??\91?\81\84float64;20168"(double) {
top:
  %1 = fmul double %0, %0, !dbg !1502
  %2 = fmul double %1, %0, !dbg !1502
  ret double %2, !dbg !1502
}

julia> code_native(限りなくfloatに近いfloat64, (Float64,))
    .section    __TEXT,__text,regular,pure_instructions
Filename: none
Source line: 2
    push    RBP
    mov RBP, RSP
Source line: 2
    movaps  XMM1, XMM0
    mulsd   XMM1, XMM1
    mulsd   XMM1, XMM0
    movaps  XMM0, XMM1
    pop RBP
    ret

julia> 

自分おっさんなので、関数名も古めです(どやっw)
LLVM IRみるとfmul命令で2回掛け算してますね。おっすおっす。

native code みてみましょー。
XMM?使って掛け算しているの、わかるでしょうかー。なんと、Julia言語は浮動小数点の計算にSSE命令を使うのですー(>_<) きゃーかっこいー抱いてーwww(俺じゃなくてJuliaたんにw)

まぁあの、読者の方も気がついていると思いますが、この記事全体がパロディな訳でして、もうね、コンパイラ持ってけ泥棒状態ですw

あと、インストラクションはマシンによって異なりまーす。あれがあって、あれがなくて、とかあるでしょw

最後に、code_typedをご紹介。


julia> code_typed(限りなくfloatに近いfloat64, (Float64,))
1-element Array{Any,1}:
 :($(Expr(:lambda, {:x}, {{},{{:x,Float64,0}},{}}, :(begin  # none, line 2:
        return (top(box))(Float64,(top(mul_float))((top(box))(Float64,(top(mul_float))(x::Float64,x::Float64))::Float64,x::Float64))::Float64
    end::Float64))))

julia> 

僕も詳しくは分かりませんが(すまん)型付きのλ式みたいなのに、なっているのかなぁ、、、あ、みちゃいけないもの、みちゃった♡ って感じですw


さていかがだったでしょうか。みなさんも嫁(Juliaたん)片手に、バイナリハック、してみませんかーw

以上文責kimrinでした。ではねちゃおーw

18
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
18