見出し画像

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>

素晴らしい!
今風に言えばブラボー!(笑)


この記事が気に入ったらサポートをしてみませんか?