xv6には、以下のような記述がある。
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"); }
インラインアセンブラだが、構文がいまいち意味が分からない。どういう意味だろう?
GCCのインラインアセンブラについて
asm文を使うことで、インラインアセンブラを利用できる。さらに、volatileを付加することで、最適化による命令削除を抑止することができるのだが、それ以外の文法はどういう意味だ? 調査してみると、これは拡張アセンブリ構文というものであるらしい。以下のサイトが参考になった。
__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番目のオペランドとして記述されているのだが、どうやら拡張構文におけるオペランドリストの順に番号が付くらしい。ここでは、
- %0 : ret
- %1 : a
- %2 : b
と対応がつけられた。コンパイルしてみよう。
$ 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における拡張インラインアセンブラの利用
では、改めて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が実行される前に、レジスタの退避をしている。それぞれ、オペランド制約で指定されたレジスタの退避と、もとに戻す作業が入っている。