Ассемблерная вставка
В программировании, ассемблерной вставкой называют возможность компилятора встраивать низкоуровневый код, написанный на ассемблере, в программу, написанную на языке высокого уровня, например, Си или Ada. Использование ассемблерных вставок может преследовать следующие цели:
- Оптимизация: С этой целью, вручную пишется ассемблерный код, реализующий наиболее критичные в отношении производительности части алгоритма. Это позволяет программисту использовать в полной мере свою изобретательность, не ограничиваться конструкциями компилятора.
- Доступ к специфичным инструкциям процессора: Некоторые процессоры поддерживают специальные инструкции, такие как сравнение с обменом и test-and-set — инструкции, которые могут быть использованы для реализации семафоров или других примитивов синхронизации и блокировок. Практически все современные процессоры имеют такие или сходные инструкции, так как они необходимы для реализации многозадачности. Специальные инструкции можно найти в системах команд следующих процессоров: SPARC VIS, Intel MMX и SSE, Motorola AltiVec.
- Системные вызовы: Языки программирования высокого уровня редко предоставляют прямую возможность делать системные вызовы, для этих целей используется ассемблерный код[1].
Пример оптимизации и использования специальных инструкций процессора
Этот пример ассемблерной вставки на языке программирования D, реализующий вычисление тангенса x, использует инструкции FPU архитектуры x86. Этот код исполняется быстрее, чем код, который мог бы быть сгенерирован компилятором. Также, здесь использована инструкция fldpi
, которая загружает наиболее близкую аппроксимацию числа для архитектуры x86.
// Compute the tangent of x
real tan(real x)
{
asm
{
fld x[EBP] ; // load x
fxam ; // test for oddball values
fstsw AX ;
sahf ;
jc trigerr ; // x is NAN, infinity, or empty
// 387's can handle denormals
SC18: fptan ;
fstp ST(0) ; // dump X, which is always 1
fstsw AX ;
sahf ;
jnp Lret ; // C2 = 1 (x is out of range)
// Do argument reduction to bring x into range
fldpi ;
fxch ;
SC17: fprem1 ;
fstsw AX ;
sahf ;
jp SC17 ;
fstp ST(1) ; // remove pi from stack
jmp SC18 ;
}
trigerr:
return real.nan;
Lret:
;
}
Пример системного вызова
Обращение к операционной системе напрямую, как правило, невозможно при наличии защищенной памяти. ОС работает на более привилегированном уровне (режим ядра), чем пользователь (пользовательский режим). Для того чтобы делать запросы в ОС, используются программные прерывания. Редко языки высокого уровня поддерживают такую возможность, поэтому интерфейсы системных вызовов пишутся с использованием ассемблерных вставок[1].
Следующий пример на языке СИ содержит интерфейс системного вызова, написанный с использованием AT&T синтаксиса GNU Assembler. Для начала рассмотрим формат ассемблерной вставки на простом примере:
asm("movl %ecx, %eax"); /* moves the contents of ecx to eax */
Идентификаторы asm
и __asm__
эквивалентны. Другой пример простой вставки:
__asm__("movb %bh, (%eax)"); /* moves the byte from bh to the memory pointed by eax */
Пример реализации интерфейса системного вызова:
extern int errno;
int funcname(int arg1, int *arg2, int arg3)
{
int res;
__asm__ volatile(
"int $0x80" /* make the request to the OS */
: "=a" (res), /* return result in eax ("a") */
"+b" (arg1), /* pass arg1 in ebx ("b") */
"+c" (arg2), /* pass arg2 in ecx ("c") */
"+d" (arg3) /* pass arg3 in edx ("d") */
: "a" (128) /* pass system call number in eax ("a") */
: "memory", "cc"); /* announce to the compiler that the memory and condition codes have been modified */
/* The operating system will return a negative value on error;
* wrappers return -1 on error and set the errno global variable */
if (-125 <= res && res < 0) {
errno = -res;
res = -1;
}
return res;
}