Обычно вы должны позволить компилятору выбирать регистры для вас, используя фиктивный вывод раннего клоббера с любыми необходимыми ограничениями 1 . Это дает возможность гибко распределять регистры для функции.
1 например вы можете использовать =&Q
для получения одного из регистров RAX / RBX / RCX / RDX: с AH / BH / CH / DH. Если вы хотите распаковать 8-битные поля с movzbl %h[input], %[high_byte]
; movzbl %b[input], %[low_byte]
; shr $16, %[input]
, вам нужен регистр, в котором его второй 8-битный блок связан с регистром старшего уровня.
Из любопытства, когда мы рассматриваем соглашение о вызовах amd64, некоторые регистры могут свободно использоваться внутри функций; и мы могли бы реализовать некоторые функции, используя только эти регистры внутри оператора asm. Почему разрешение компилятору выбирать регистры для использования лучше, чем упомянутый?
Поскольку функции могут быть встроенными, возможно, в цикл, который вызывает другие функции, таким образом, компилятор хотел бы выдавать свои входные данные в регистры с сохранением вызова. Если вы писали автономную функцию, то компилятор всегда нужно вызывать, все, что вы получаете из встроенного asm вместо автономного, это компилятор, обрабатывающий различия в соглашениях о вызовах и манипулирование именами в C ++.
Или, возможно, окружающий код использует некоторые инструкции, для которых требуются фиксированные регистры, например cl
для счетчиков смен или RDX: RAX для div
.
когда мне следует использовать список clobber? ...
например, инструкция syscall требует, чтобы ее параметр находился в регистре rdi rsi rdx r10 r8 r9 ??
Обычно вместо этого вы используете входные ограничения, так что только внутренняя инструкция syscall
находится внутри встроенного asm. Но syscall
(сама инструкция) блокирует RCX и R11, поэтому системные вызовы, сделанные с ее использованием, неизбежно разрушают пользовательское пространство RCX и R11. Нет смысла использовать фиктивные выходы для них, если только у вас нет использования для обратного адреса (RCX) или RFLAGS (R11). Так что да, здесь полезны клобберы.
// the compiler will emit all the necessary MOV instructions
#include <stddef.h>
#include <asm/unistd.h>
// the compiler will emit all the necessary MOV instructions
//static inline
size_t sys_write(int fd, const char *buf, size_t len) {
size_t retval;
asm volatile("syscall"
: "=a"(retval) // EDI RSI RDX
: "a"(__NR_write), "D"(fd), "S"(buf), "d"(len)
, "m"(*(char (*)[len]) buf) // dummy memory input: the asm statement reads this memory
: "rcx", "r11" // clobbered by syscall
// , "memory" // would be needed if we didn't use a dummy memory input
);
return retval;
}
Не встроенная версия этого компилируется следующим образом (с gcc -O3
в проводнике компилятора Godbolt ), потому что соглашение о вызовах функций почти соответствует соглашению о системных вызовах:
sys_write(int, char const*, unsigned long):
movl $1, %eax
syscall
ret
Было бы очень глупо использовать clobbers на любом из входных регистров и помещать mov
внутри asm:
size_t dumb_sys_write(int fd, const char *buf, size_t len) {
size_t retval;
asm volatile(
"mov %[fd], %%edi\n\t"
"mov %[buf], %%rsi\n\t"
"mov %[len], %%rdx\n\t"
"syscall"
: "=a"(retval) // EDI RSI RDX
: "a"(__NR_write), [fd]"r"(fd), [buf]"r"(buf), [len]"r"(len)
, "m"(*(char (*)[len]) buf) // dummy memory input: the asm statement reads this memory
: "rdi", "rsi", "rdx", "rcx", "r11"
// , "memory" // would be needed if we didn't use a dummy memory input
);
// if(retval > -4096ULL) errno = -retval;
return retval;
}
dumb_sys_write(int, char const*, unsigned long):
movl %edi, %r9d
movq %rsi, %r8
movq %rdx, %r10
movl $1, %eax # compiler generated before this
# from inline asm
mov %r9d, %edi
mov %r8, %rsi
mov %r10, %rdx
syscall
# end of inline asm
ret
И, кроме того, вы не позволяете компилятору использовать тот факт, что syscall
не засоряет ни один из его входных регистров. Компилятор вполне может по-прежнему хотеть len
в регистре, и использование чистого входного ограничения позволяет ему знать, что значение все еще будет там впоследствии.
Вы также можете использовать clobbers, если вы используете какие-либо инструкции, которые неявно используют определенные регистры, но ни ввод, ни вывод этих инструкций не являются прямым вводом или выводом оператора asm. Это было бы редко, если только вы не пишете весь цикл или большой блок кода во встроенном asm.
Или, может быть, если вы упаковываете call
инструкцию. (Это трудно сделать безопасно, особенно из-за красной зоны, но люди пытаются это сделать). Вы не можете выбирать, какие регистры будут иметь место в коде, поэтому просто сообщите об этом компилятору.