関数呼び出しとアセンブリコード

次のような簡単な関数について見ていく。return_twotwice 関数を呼び出している。

こいつはどのようなアセンブリコードになるのだろうか。とりあえず clang -S -mllvm --x86-asm-syntax=intel call.c をしてみる。

_return_two はいつも通り、rbp レジスタの内容を退避させる。その後、twice 関数を呼び出しをおこなう。twice 関数は引数をひとつ取る関数で、どうやら edi レジスタに格納することにより、引数の引き渡しを実現しているようだ(mov edi, 1)。次に call 命令により _twice ラベルへと制御を引き渡している。

_twice でもいつも通り、rbp レジスタの内容を退避させる。それから、edi レジスタから引数を受け取り、ローカル変数と同じような感じで rbp-4へ格納する。その後の edi 領域を同じ値で上書きしている処理が見えるが、この意図はよく分からない。最適化をかけるとこういった処理はなくなるので一旦無視。次に edi レジスタの値を 1bit左シフトした値を、eax レジスタに格納して、この手続きは終了する。

最適化をかけた状態のアセンブリコードも見てみよう。clang -O2 -S -mllvm --x86-asm-syntax=intel call.c という感じでやってみると以下のような具合。

_return_two のほうに関しては、関数の戻り値が常に 2 になるために、もはや _twicecall すら走らない形に最適化されている。

_twice についてみてみると lea という命令が目につく。lea <src>, <dest> 命令は、scr のアドレスを計算し、dest にロードするというものです。lea はアドレス計算に使われるものではあるが、足し算を実現するのにも使われるようだ。64bit汎用レジスタ rdi の値を足し合わせて eax レジスタに格納している。なお、lea vs add についてはこの記事が詳しそう。

One significant difference between LEA and ADD on x86 CPUs is the execution unit which actually performs the instruction. Modern x86 CPUs are superscalar and have multiple execution units that operate in parallel, with the pipeline feeding them somewhat like round-robin (bar stalls). Thing is, LEA is processed by (one of) the unit(s) dealing with addressing (which happens at an early stage in the pipeline), while ADD goes to the ALU(s) (arithmetic / logical unit), and late in the pipeline. That means a superscalar x86 CPU can concurrently execute a LEA and an arithmetic/logical instruction.

複数の引数を取る関数

複数の引数を取る関数についても見ていこう。

このCコードはどのようになるだろうか。-O2 で最適化をかけたアセンブリコードを見てみる。

_add2 は引数が edi, esi に格納されて渡されるようだ。_add3edi, esi, edx から引数を受け取っている。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です