C言語コンパイラ依存した話の続編「clang はどうコンパイルするのか」
こちらの記事の延長戦です。
というわけで(どういうわけ?)、 clang コンパイラが吐き出したアセンブラを解析してみました。
結論から言うと、clang コンパイラの場合は次のようになります。
引数 i 、 j は三項演算する前に待避しておく。
「j--」(後置デクリメント)は、 j-- する前の値を別のレジスタに待避する。
まず、clang が出力したアセンブラを見てみます。スマホ版なのでアセンブラは ARM です。ARM では「@」から行末までがコメントであるらしいのですが、 clang の出力したアセンブラに「// #3」とあったこと、また見やすいことから「//」を使用しました。
では早速、こちらがアセンブラです。
556734 <+0> : sub sp, sp, #0x30 // SP = SP + 0x30
556738 <+4> : stp x29, x30, [sp, #32] // SP[32] = x29、SP[36] = x30
55673c <+8> : add x29, sp, #0x20 // x29 = sp + 0x20
556740 <+12> : stur wzr, [x29, #-4] // [x29, #-4] = 0
// int i = 3;
// [x29, #-8] = i = 3
556744 <+16> : mov w8, #0x3 // w8 = #3
556748 <+20> : stur w8, [x29, #-8] // [x29, #-8] = w8
// int j = 5;
// [x29, #-12] = j = 5
55674c <+24> : mov w8, #0x5 // w8 = #5
556750 <+28> : stur w8, [x29, #-12] // [x29, #-12] = w8
// printf("MAX処理前 i = %d, j = %d\n", i, j);
556754 <+32> : ldur w1, [x29, #-8] // w1 = i
556758 <+36> : ldur w2, [x29, #-12] // w2 = j
55675c <+40> : nop
556760 <+44> : adr x0, 0x555555556d
556764 <+48> : bl 0x5555556860 <printf@plt>
// printf("MAX処理中 i = %d, j = %d, max = %d\n", i, j, MAX(++i,j--));
// i を待避
// [sp, #12] = i
556768 <+52> : ldur w8, [x29, #-8] // w8 = i
55676c <+56> : str w8, [sp, #12] // [sp, #12] = i
// j を待避
// [sp, #16] = j
556770 <+60> : ldur w8, [x29, #-12] // w8 = j
556774 <+64> : str w8, [sp, #16] // [sp, #16] = j
// ++i
556778 <+68> : ldur w8, [x29, #-8] // w8 = i
55677c <+72> : add w8, w8, #0x1 // w8 = w8 + 1
556780 <+76> : stur w8, [x29, #-8] // i = w8
// j--
556784 <+80> : ldur w9, [x29, #-12] // w9 = j
556788 <+84> : subs w10, w9, #0x1 // w10 = w9 - 1
55678c <+88> : stur w10, [x29, #-12] // j = w10
// MAX(++i,j--)
// ++i ≦ j ならば 0x55555567b4 へ
// cset:条件が真ならば Rd に 1 を返し、偽ならば Rd に 0 を返す。
// 条件はサフィックスで指定する。
// サフィックス LE:≦ 小さいか等しい (符号付)
556790 <+92> : subs w8, w8, w9 // w8 = w8 - w9
556794 <+96> : cset w8, le
556798 <+100>: tbnz w8, #0, 0x55555567b4 <main+128>
55679c <+104>: b 0x55555567a0 <main+108>
// [sp, #8] = 三項演算の結果
// ++i ≦ j-- でないとき
// ++i (2回目)
5567a0 <+108>: ldur w8, [x29, #-8] // w8 = i
5567a4 <+112>: add w8, w8, #0x1 // w8 = w8 + 1
5567a8 <+116>: stur w8, [x29, #-8] // i = w8
5567ac <+120>: str w8, [sp, #8] // [sp, #8] = ++i (2回目)
5567b0 <+124>: b 0x55555567c8 <main+148>
// ++i ≦ j-- のとき
// j-- (2回目)
5567b4 <+128>: ldur w8, [x29, #-12] // w8 = j
5567b8 <+132>: subs w9, w8, #0x1 // w9 = w8 - 1
5567bc <+136>: stur w9, [x29, #-12] // j = w9
5567c0 <+140>: str w8, [sp, #8] // [sp, #8] = j-- (1回目)
5567c4 <+144>: b 0x55555567c8 <main+148>
// w8 は -- する前の j
// w9 は -- した後の j
// 三項演算の結果 [sp, #8] は
// w8 「-- する前の j」
// となる。
// printf の呼出し
5567c8 <+148>: ldr w2, [sp, #16] // w2 = i
5567cc <+152>: ldr w1, [sp, #12] // w1 = j
5567d0 <+156>: ldr w3, [sp, #8] // w3 = j-- (2回目)
5567d4 <+160>: adrp x0, 0x5555555000
5567d8 <+164>: add x0, x0, #0x58a
5567dc <+168>: bl 0x5555556860 <printf@plt>
// printf("MAX処理後 i = %d, j = %d\n", i, j);
5567e0 <+172>: ldur w1, [x29, #-8] // w1 = ++i (2回目)
5567e4 <+176>: ldur w2, [x29, #-12] // w2 = j-- (2回目)
5567e8 <+180>: adrp x0, 0x5555555000
5567ec <+184>: add x0, x0, #0x550
5567f0 <+188>: bl 0x5555556860 <printf@plt>
5567f4 <+192>: ldur w0, [x29, #-4]
5567f8 <+196>: ldp x29, x30, [sp, #32]
5567fc <+200>: add sp, sp, #0x30
556800 <+204>: ret
さて。
次の2つのコードを見比べてみます。
// ++i
556778 <+68> : ldur w8, [x29, #-8] // w8 = i
55677c <+72> : add w8, w8, #0x1 // w8 = w8 + 1
556780 <+76> : stur w8, [x29, #-8] // i = w8
// j--
556784 <+80> : ldur w9, [x29, #-12] // w9 = j
556788 <+84> : subs w10, w9, #0x1 // w10 = w9 - 1
55678c <+88> : stur w10, [x29, #-12] // j = w10
ここで、ちょっとびっくりしなければなりません。
「++i」と「j--」で違うと思いませんか?
だって「++i」は全部「w8」で賄ってるでしょう?
なのに「j--」は「w9」と「w10」を使っている。
何故全てを「w9」で賄えないのでしょうか。
何故「w10」が必要なのでしょうか。
それはとりもなおさず、「--」が「jの後ろ」に付いているからです。
式中に「--」があった場合、式を評価してから「--」しなければならない。ですから、式を評価するために「--」する前の値を取っておくわけです。
今回のケースですと、「(++i) > (j--) ?」の条件は、
i は ++ した後に、
j は -- する前に
判定しなければなりません。
そのために、「-- する前の j 」をとっておくのです。
w9 は -- する前の j
w10 は -- した後の j
ということになります。
そして、次の比較処理では、しっかり「w9」、すなわち「-- する前の j」で比較しています。
// ++i ≦ j ならば 0x55555567b4 へ
// cset:条件が真ならば Rd に 1 を返し、偽ならば Rd に 0 を返します。 条件はサフィックスで指定します。
// サフィックス LE:≦ 小さいか等しい (符号付)
556790 <+92> : subs w8, w8, w9 // w8 = w8 - w9
556794 <+96> : cset w8, le
556798 <+100>: tbnz w8, #0, 0x55555567b4 <main+128>
55679c <+104>: b 0x55555567a0 <main+108>
素晴らしい!
今風に言えばブラボー!(笑)