Все эти инструкции asm должны быть в одинаковом asm
выражении, если вы хотите быть уверены, что они непрерывны (без сгенерированного компилятором кода между ними), и вам нужно объявить ввод / output / clobber операнды или вы наступите на регистры компилятора.
Вы не можете использовать lea
или mov
в / из имени переменной C (за исключением глобальных / статических символов, которые фактически определены в выводе asm компилятора, но даже тогда вы обычно не должны).
Вместо использования mov
инструкций для настройки входных данных, попросите компилятор сделать это для вас, используя ограничения входных операндов. Если первая или последняя инструкция встроенного asm-оператора GNU C, обычно это означает, что вы делаете это неправильно и пишете неэффективный код.
И, кстати, GNU C ++ допускает массивы переменной длины в стиле C99, поэтому howmany
может быть не const
и даже установлен таким образом, что не оптимизируется до константы. Любой компилятор, который может компилировать встроенный asm в стиле GNU, также будет поддерживать массивы переменной длины.
Как правильно написать цикл
Если это выглядит слишком сложно, то https://gcc.gnu.org/wiki/DontUseInlineAsm. Напишите отдельную функцию в asm, чтобы вы могли просто изучить asm вместо того, чтобы изучать gcc и его сложный, но мощный интерфейс inline-asm. В основном вам нужно знать asm и понимать компиляторы, чтобы правильно его использовать (с правильными ограничениями для предотвращения поломок при включенной оптимизации).
Обратите внимание на использование именованных операндов, таких как %[ptr]
вместо %2
или %%ebx
. Позволить компилятору выбирать, какие регистры использовать, как правило, хорошо, но для x86 есть буквы, отличные от "r"
, которые вы можете использовать, например, "=a"
для rax / eax / ax / al. См. https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html,, а также другие ссылки в вики-теге inline-Assembly .
Я также использовал buf_loop%=:
для добавления уникального номера к метке, поэтому, если оптимизатор клонирует функцию или вставляет ее в несколько мест, файл все равно будет собран.
Исходный код + выходные данные компилятора в проводнике компилятора Godbolt .
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;
}
Я использовал %%al
в качестве примера того, как явно писать имена регистров: Extended Asm (с операндами) требуется двойной %
, чтобы получить литерал %
в выводе asm. Вы также можете использовать %[res]
или %0
и позволить компилятору подставлять %al
в свой вывод asm. (И тогда у вас не будет причин использовать ограничение конкретного регистра, если вы не захотите воспользоваться cbw
или lodsb
или чем-то подобным.) result
- это unsigned char
, поэтому компилятор выберет регистр байтов для этого. Если вам нужен младший байт более широкого операнда, вы можете использовать, например, %b[count]
.
При этом используется "memory"
clobber, который неэффективен . Вам не нужно, чтобы компилятор пролил все в память, только для того, чтобы убедиться, что содержимое buffer[]
в памяти соответствует состоянию абстрактной машины C. (Это не гарантируется передачей указателя в регистр).
gcc7.2 -O3
вывод:
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
Без расширения памяти или ограничения ввода, leave
появляется перед встроенным блоком asm, освобождая эту память стека до того, как встроенный asm использует устаревший указатель. Обработчик сигнала, запущенный в неподходящее время, может засорить его.
Более эффективный способ - использовать фиктивный операнд памяти, который сообщает компилятору, что весь массив является входом памяти только для чтения для оператора asm
. См. , чтобы получить длину строки в встроенный GNU Assembler , чтобы больше узнать об этом трюке с гибким элементом массива, чтобы сообщить компилятору о том, что вы прочитали весь массив, без явного указания длины.
В C вы можете определить новый тип внутри приведения, но вы не можете в C ++, поэтому using
вместо действительно сложного входного операнда.
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
мог быть непосредственно входным, а выходной операнд в том же регистре означает, что мы можем изменить этот регистр. Мы получили тот же эффект в foo()
, используя const char *bufptr = buffer;
и затем используя ограничение чтения-записи, чтобы сообщить компилятору, что новое значение этой переменной C - это то, что мы оставляем в регистре. В любом случае мы оставляем значение в мертвой переменной C, которая выходит из области видимости без чтения, но способ ограничения соответствия может быть полезен для макросов, где вы не хотите изменять значение ввода (и не нуждаетесь в тип ввода: int dummy
тоже будет работать нормально.)
Назначения buffer[100] = 100;
и buffer[100] = 101;
предназначены для того, чтобы показать, что они оба появляются в asm, а не объединяются в inline-asm (что происходит, если вы пропустите входной операнд "m"
). IDK, почему buffer[100] = 101;
не оптимизирован; он мертв, так и должно быть. Также обратите внимание, что asm volatile
не не блокирует это переупорядочение, поэтому это не альтернатива "memory"
clobber или использование правильных ограничений.