Все эти asm-инструкции должны находиться в одной и той же инструкции asm
, если вы хотите быть уверенными, что они непрерывны (без сгенерированного компилятором кода между ними), и вам нужно объявить ввод/вывод/затирание операнды или вы наступите на регистры компилятора.
Вы не можете использовать lea
или mov
в/из имени переменной C (за исключением глобальных/статических символов, которые фактически определены в выводе компилятора asm, но даже в этом случае обычно не следует).
Вместо использования инструкций mov
для настройки входных данных попросите компилятор сделать это за вас, используя ограничения входных операндов. Если это первая или последняя инструкция встроенного ассемблерного оператора GNU C, обычно это означает, что вы делаете это неправильно и пишете неэффективный код.
И кстати, GNU C++ допускает массивы переменной длины в стиле C99, поэтому howmany
может быть не-const
и даже устанавливаться таким образом, чтобы не оптимизировать его до константы. Любой компилятор, который может скомпилировать встроенный ассемблер в стиле GNU, также будет поддерживать массивы переменной длины.
Совет при возникновении проблемы: (1) Каково ожидаемое поведение? (2) Каково наблюдаемое поведение?
Если это выглядит слишком сложным, тогда https://gcc.gnu.org/wiki/DontUseInlineAsm. Напишите автономную функцию на ассемблере, чтобы вы могли просто изучить ассемблер вместо того, чтобы изучать gcc и его сложный, но мощный встроенный ассемблерный интерфейс. Вы в основном должны знать asm и понимать компиляторы, чтобы использовать его правильно (с правильными ограничениями, чтобы предотвратить поломку, когда включена оптимизация).
Обратите внимание на использование именованных операндов, таких как %[ptr]
вместо %2
или %%ebx
. Позволить компилятору выбирать, какие регистры использовать, обычно хорошо, но для x86 есть буквы, отличные от "r"
, которые вы можете использовать, например, "=a"
для rax/eax/ax/al, в частности. См. https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html, а также другие ссылки в вики тега inline-assembly.
Я также использовал buf_loop%=:
для добавления уникального номера к метке, поэтому, если оптимизатор клонирует функцию или встраивает ее в несколько мест, файл все равно будет собираться.
Я использовал %%al
в качестве примера явного написания имен регистров: расширенный ассемблер (с операндами) нуждается в двойном %
, чтобы получить литерал %
в выводе ассемблера. Вы также можете использовать %[res]
или %0
и позволить компилятору заменить %al
в выводе asm. (И тогда у вас не было бы причин использовать ограничение конкретного регистра, если только вы не хотите воспользоваться преимуществом cbw
или lodsb
или чего-то подобного.) result
равно unsigned char
, поэтому компилятор выберет для него байтовый регистр. Если вам нужен младший байт более широкого операнда, вы можете использовать, например, %b[count]
.
void ext(char *);
int foo(void)
{
int howmany = 5046; // could be a function arg
char buffer[howmany];
//ext(buffer);
const char *bufptr = buffer; // copy the pointer to a C var we can use as a read-write operand
unsigned char result;
asm("buf_loop%=: \n\t" // do {
" movb (%[ptr]), %%al \n\t" // Copy buffer[x] to al
" inc %[ptr] \n\t"
" dec %[count] \n\t"
" jnz buf_loop \n\t" // } while(ebx>0)
: [res]"=a"(result) // al = write-only output
, [count] "+r" (howmany) // input/output operand, any register
, [ptr] "+r" (bufptr)
: // no input-only operands
: "memory" // we read memory that isn't an input operand, only pointed to by inputs
);
return result;
}
При этом используется "memory"
стиратель, что неэффективно. Вам не нужно, чтобы компилятор перебрасывал все в память, только чтобы убедиться, что содержимое buffer[]
в памяти соответствует состоянию абстрактной машины C. (Это не гарантируется передачей указателя на него в регистре).
gcc7.2 -O3
вывод:
Без затирания памяти или ограничения ввода leave
появляется перед встроенным ассемблерным блоком, освобождая память стека до того, как встроенный ассемблер использует устаревший указатель. Обработчик сигнала, запущенный в неподходящее время, забьет его.
pushq %rbp
movl $5046, %edx
movq %rsp, %rbp
subq $5056, %rsp
movq %rsp, %rcx # compiler-emitted to satisfy our "+r" constraint for bufptr
# start of the inline-asm block
buf_loop18:
movb (%rcx), %al
inc %rcx
dec %edx
jnz buf_loop
# end of the inline-asm block
movzbl %al, %eax
leave
ret
Более эффективным способом является использование фиктивного операнда памяти, который сообщает компилятору, что весь массив является памятью, доступной только для чтения, для ввода оператора asm
. См. получить длину строки во встроенном ассемблере GNU, чтобы узнать больше об этом трюке с гибкими элементами массива для сообщения компилятору вы читаете весь массив без явного указания длины.
В C вы можете определить новый тип внутри приведения, но вы не можете в C++, поэтому using
вместо действительно сложного входного операнда.
Я также использовал ограничение соответствия, поэтому buffer
мог быть входом напрямую, а выходной операнд в том же регистре означает, что мы можем изменить этот регистр. Мы получили тот же эффект в foo()
, используя const char *bufptr = buffer;
, а затем используя ограничение чтения-записи, чтобы сообщить компилятору, что новое значение этой переменной C — это то, что мы оставляем в регистре. В любом случае мы оставляем значение в мертвой переменной C, которая выходит за пределы области видимости без чтения, но способ ограничения сопоставления может быть полезен для макросов, где вы не хотите изменять значение вашего ввода (и не нуждаетесь в тип вашего ввода: int dummy
тоже подойдет.)
int bar(unsigned howmany)
{
//int howmany = 5046;
char buffer[howmany];
//ext(buffer);
buffer[0] = 1;
buffer[100] = 100; // test whether we got the input constraints right
//using input_t = const struct {char a[howmany];}; // requires a constant size
using flexarray_t = const struct {char a; char x[];};
const char *dummy;
unsigned char result;
asm("buf_loop%=: \n\t" // do {
" movb (%[ptr]), %%al \n\t" // Copy buffer[x] to al
" inc %[ptr] \n\t"
" dec %[count] \n\t"
" jnz buf_loop \n\t" // } while(ebx>0)
: [res]"=a"(result) // al = write-only output
, [count] "+r" (howmany) // input/output operand, any register
, "=r" (dummy) // output operand in the same register as buffer input, so we can modify the register
: [ptr] "2" (buffer) // matching constraint for the dummy output
, "m" (*(flexarray_t *) buffer) // whole buffer as an input operand
//, "m" (*buffer) // just the first element: doesn't stop the buffer[100]=100 store from sinking past the inline asm, even if you used asm volatile
: // no clobbers
);
buffer[100] = 101;
return result;
}
Присваивания buffer[100] = 100;
и buffer[100] = 101;
нужны для того, чтобы показать, что они оба появляются в ассемблерном коде, а не сливаются во встроенном ассемблере (что происходит, если вы пропустите входной операнд "m"
). IDK, почему buffer[100] = 101;
не оптимизирован; он мертв, так и должно быть. Также обратите внимание, что asm volatile
не блокирует такое переупорядочение, так что это не альтернатива "memory"
клоберу или использованию правильных ограничений.
Как правильно написать цикл
person
Peter Cordes
schedule
17.11.2017