xv6には、以下のような記述がある。
github.com
static inline void
insl(int port, void *addr, int cnt)
{
asm volatile("cld; rep insl" :
"=D" (addr), "=c" (cnt) :
"d" (port), "0" (addr), "1" (cnt) :
"memory", "cc");
}
インラインアセンブラだが、構文がいまいち意味が分からない。どういう意味だろう?
asm文を使うことで、インラインアセンブラを利用できる。さらに、volatileを付加することで、最適化による命令削除を抑止することができるのだが、それ以外の文法はどういう意味だ?
調査してみると、これは拡張アセンブリ構文というものであるらしい。以下のサイトが参考になった。
GCC Inline Assembler
__asm__ ( アセンブリテンプレート
: 出力オペランド /* オプション */
: 入力オペランド /* オプション */
: 破壊されるレジスタのリスト /* オプション */
);
出力オペランド、入力オペランドを指定することで、インラインアセンブラとC言語の変数を交互にやり取りさせることができる。さらに、破壊レジスタを指定することで、インラインアセンブラ内で破壊されるレジスタを指定し、コンパイラにあらかじめレジスタの退避を指定することができる。
簡単な例
ここで、理解を深めるために簡単な例を作ってみた。
int inline_test (int a, int b)
{
int ret;
asm volatile ("addw %0, %1, %2"
: "=r"(ret)
: "r"(a), "r"(b)
:
);
return ret;
}
int inline_test2 (int a, int b)
{
return a + b;
}
inline_testでは、インラインアセンブリを利用している。オペランドはすべて拡張インラインアセンブラで指定されている。それぞれ、0番目、1番目、2番目のオペランドとして記述されているのだが、どうやら拡張構文におけるオペランドリストの順に番号が付くらしい。ここでは、
と対応がつけられた。コンパイルしてみよう。
$ riscv64-unknown-elf-gcc -O3 -c inline.c
$ riscv64-unknown-elf-objdump -D inline.o
inline.o: file format elf64-littleriscv
Disassembly of section .text:
0000000000000000 <inline_test>:
0: 00b5053b addw a0,a0,a1
4: 00008067 ret
0000000000000008 <inline_test2>:
8: 00b5053b addw a0,a0,a1
c: 00008067 ret
ちゃんと対応が取られているね。
では、改めてxv6のx86.hを見てみよう。以下のようなインラインアセンブリの記述がある。
static inline void
insl(int port, void *addr, int cnt)
{
asm volatile("cld; rep insl" :
"=D" (addr), "=c" (cnt) :
"d" (port), "0" (addr), "1" (cnt) :
"memory", "cc");
}
不思議なのは、ここでアセンブラとしてはオペランドの指定が一つもないことだ。
そもそも、rep inslという命令は、ECXで指定されたカウンタだけinslを繰り返すというものになる。破壊されるのは、メモリとccだ。あれ、ccって何だ?
とりあえず、ここでコンパイルされたコードを見てみよう。
static inline void
insl(int port, void *addr, int cnt)
{
80102548: 55 push %ebp
80102549: 89 e5 mov %esp,%ebp
8010254b: 57 push %edi
8010254c: 53 push %ebx
asm volatile("cld; rep insl" :
8010254d: 8b 55 08 mov 0x8(%ebp),%edx
80102550: 8b 4d 0c mov 0xc(%ebp),%ecx
80102553: 8b 45 10 mov 0x10(%ebp),%eax
80102556: 89 cb mov %ecx,%ebx
80102558: 89 df mov %ebx,%edi
8010255a: 89 c1 mov %eax,%ecx
8010255c: fc cld
8010255d: f3 6d rep insl (%dx),%es:(%edi)
8010255f: 89 c8 mov %ecx,%eax
80102561: 89 fb mov %edi,%ebx
80102563: 89 5d 0c mov %ebx,0xc(%ebp)
80102566: 89 45 10 mov %eax,0x10(%ebp)
"=D" (addr), "=c" (cnt) :
"d" (port), "0" (addr), "1" (cnt) :
"memory", "cc");
}
80102569: 5b pop %ebx
8010256a: 5f pop %edi
8010256b: 5d pop %ebp
8010256c: c3 ret
cldが実行される前に、レジスタの退避をしている。それぞれ、オペランド制約で指定されたレジスタの退避と、もとに戻す作業が入っている。