Ключом к пониманию встроенного asm является понимание того, что каждое выражение asm состоит из двух частей:
Текст фактического ассемблера, в котором компилятор будет выполнять текстовые замены, но не понимает .
Это шаблон ассемблера в документации (все до первого :
в __asm__()
).
Описание того, что делает ассемблер, в терминах, которые компилятор понимает .
Это : OutputOperands : InputOperands : Clobbers
в документация .
Это должно сказать компилятору, как ассемблер вписывается во весь код, который генерирует компилятор вокруг него. Генерация кода занята тем, что выделяет регистры для хранения значений, решает, в каком порядке их выполнять, перемещает объекты из циклов, удаляет неиспользуемые фрагменты кода, отбрасывает значения, которые больше не нужны, и так далее.
Фактический ассемблер - это черный ящик, который принимает описанные здесь входы, производит описанные выходы и в качестве побочного эффекта может «заглушить» некоторые регистры и / или память. Это должно быть полным описанием того, что делает ассемблер ... в противном случае сгенерированный компилятором ассемблер вокруг вашего шаблона возьмет с собой sh и будет полагаться на ложные предположения.
Вооружен эту информацию компилятор может решить, какие регистры может использовать ассемблер, и вы должны позволить ему сделать это.
Итак, ваш фрагмент:
asm volatile(
"movq %0 , %%rax\n"
"rol %%rax\n"
"rol %%rax\n"
:"=r"(X)
:"r"(X)
);
имеет несколько «проблем»:
- вы, возможно, выбрали
%rax
для результата на основании того, что asm () похожа на функцию, и можно ожидать, что она вернет результат в %rax
- но это не так. - вы пошли дальше и использовали
%rax
, который компилятор мог (ну) уже выделил чему-то другому ... так что вы, по сути, 'clobbering' %rax
, но вы не сообщили об этом компилятору! - вы указали
=r(X)
( OutputOperand ), который указывает компилятору ожидать вывода в некотором регистре и что вывод будет новым значением переменной X
. %0
в шаблоне Assembler будет заменен регистром, выбранным для вывода. К сожалению, ваша сборка воспринимает %0
как входные данные :-( А выходные данные, на самом деле, находятся в %rax
- о чем, как и выше, компилятору ничего не известно. вы также указан r(X)
( InputOperand ), который указывает компилятору упорядочить текущее значение переменной X
для помещения в некоторый регистр для использования ассемблером. Это будет %1
в AssemblerTemplate . К сожалению, ваша сборка не использует этот ввод.
Несмотря на то, что оба операнда вывода и ввода относятся к X
, компилятор может не сделать %0
таким же регистром, как %1
. (Это позволяет использовать блок asm в качестве неразрушающей операции, при которой исходное значение ввода остается неизменным. Если это не так, как работает ваш шаблон, не пишите его таким образом.
- , как правило, вам не нужен
volatile
, когда все входы и выходы правильно описаны ограничениями. Одна из прекрасных вещей, которые сделает компилятор, - отбросить asm()
, если (все) выходные данные ( с) не являются used ... volatile
говорит компилятору не делать этого (и сообщает ему ряд других вещей ... см. руководство).
Кроме того, все было замечательно. Следующее является безопасным и избегает инструкции mov
:
asm("rol %0\n"
"rol %0\n" : "+r"(X));
, где "+r"(X)
говорит, что требуется один объединенный регистр ввода и вывода, принимая старое значение X
и возвращая новое один.
Теперь, если вы не хотите заменять X
, то, предполагая, что результатом будет Y
, вы можете:
asm("mov %1, %0\n"
"rol %0\n"
"rol %0\n" : "=r"(Y) : "r"(X));
Но лучше уйти это до компилятора, чтобы решить, нужно ли ему mov
или он может просто позволить разрушить ввод.
Есть пара правил о InputOperands , которые Стоит упомянуть:
Ассемблер не должен перезаписывать какие-либо из InputOperands - компилятор отслеживает, какие значения он имеет в каких регистрах, и ожидает InputOperands должен быть сохранен.
Компилятор ожидает, что все InputOperands будут прочитаны до any OutputOperand is написано. Это важно, когда компилятор знает, что данный InputOperand больше не используется после asm()
, и поэтому он может выделить регистр InputOperand для OutputOperand . Есть вещь под названием earlyclobber (=&r(foo)
), чтобы справиться с этой маленькой морщинкой.
В приведенном выше примере, если вы фактически не используете X
снова, компилятор может выделить %0
и %1
для одного и того же регистра! Но (избыточный) mov
все равно будет собран - помня, что компилятор действительно не понимает AssemblerTemplate . Таким образом, в целом вам лучше переставлять значения в C, а не asm()
. См. https://gcc.gnu.org/wiki/DontUseInlineAsm и Лучшие практики для операций кругового сдвига (поворота) в C ++
Итак, здесь есть четыре варианта темы и сгенерированный код (g cc -O2):
// (1) uses both X and Y in the printf() -- does mov %1, %0 in asm()
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov $0x492782,%edi # address of format string
unsigned long X, Y ; xor %eax,%eax
mov $0x63,%esi # X = 99
X = 99 ; rol %rsi # 1st asm
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) mov %rsi,%rdx # 2nd asm, compiler using it as a copy-and-rotate
) ; rol %rdx
rol %rdx
__asm__("\t mov %1, %0\n" jmpq 0x4010a0 <printf@plt> # tailcall printf
"\t rol %0\n"
"\t rol %0\n" : "=r"(Y) : "r"(X)
) ;
printf("%lx %lx\n", X, Y) ;
}
// (2) uses both X and Y in the printf() -- does Y = X in 'C'
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov $0x492782,%edi
unsigned long X, Y ; xor %eax,%eax
mov $0x63,%esi
X = 99 ; rol %rsi # 1st asm
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) mov %rsi,%rdx # compiler-generated mov
) ; rol %rdx # 2nd asm
rol %rdx
Y = X ; jmpq 0x4010a0 <printf@plt>
__asm__("\t rol %0\n"
"\t rol %0\n" : "+r"(Y)
) ;
printf("%lx %lx\n", X, Y) ;
}
// (3) uses only Y in the printf() -- does mov %1, %0 in asm()
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov $0x492782,%edi
unsigned long X, Y ; xor %eax,%eax
mov $0x63,%esi
X = 99 ; rol %rsi
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) mov %rsi,%rsi # redundant instruction because of mov in the asm template
) ; rol %rsi
rol %rsi
__asm__("\t mov %1, %0\n" jmpq 0x4010a0 <printf@plt>
"\t rol %0\n"
"\t rol %0\n" : "=r"(Y) : "r"(X)
) ;
printf("%lx\n", Y) ;
}
// (4) uses only Y in the printf() -- does Y = X in 'C'
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov $0x492782,%edi
unsigned long X, Y ; xor %eax,%eax
mov $0x63,%esi
X = 99 ; rol %rsi
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) rol %rsi # no wasted mov, compiler picked %0=%1=%rsi
) ; rol %rsi
jmpq 0x4010a0 <printf@plt>
Y = X ;
__asm__("\t rol %0\n"
"\t rol %0\n" : "+r"(Y)
) ;
printf("%lx\n", Y) ;
}
, который, как мы надеемся, демонстрирует компилятор, активно распределяющий значения в регистрах, отслеживая, какие значения он должен хранить, сводя к минимуму перемещения регистров / регистров и обычно умный.
Итак, хитрость в том, чтобы работать с компилятором, понимая, что :
OutputOperands :
InputOperands :
Clobbers где вы описываете, что делает ассемблер.