Ассемблерная вставка

Перейти к навигацииПерейти к поиску

Ассемблерная вставка — в программировании возможность компилятора встраивать низкоуровневый код, написанный на ассемблере, в программу, написанную на языке высокого уровня, например, Си или 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;
}

Критика

С начала XXI века применение ассемблерных вставок всё чаще осуждается по множеству причин[2][3]:

  • Современные оптимизирующие компиляторы способны генерировать более оптимальный ассемблерный код, чем может написать программист средней квалификации. Сами по себе ассемблерные вставки могут препятствовать оптимизации других участков кода. Некоторые приёмы, позволявшие оптимизировать выполнение кода на процессорах 1980-90-х годов на более поздних процессорах могут приводить к существенному замедлению исполнения в связи с другой организацией вычислений. Как и любую оптимизацию, ассемблерные вставки необходимо подвергать тестированию, чтобы проверить гипотезу об их эффективности. В связи с ростом производительности вычислительных систем многие оптимизации могут быть неактуальными, а на первый план выходит читаемость кода, простота поддержки и предотвращение ошибок.
  • Ассемблерный код более трудоёмок в написании. В ассемблерной вставке легко допустить ошибку, которую сложно заметить. К примеру, язык ассемблера не поддерживает контроль типов. Уже созданный ассемблерный код сложнее поддерживать.
  • Код на ассемблере не является переносимым. Ассемблерные вставки оправданы для доступа к специфическим механизмам платформы. При использовании ассемблерных вставок в кроссплатформенных программах необходимо дублировать ассемблерные вставки для разных платформ, а также по возможности сохранять альтернативную реализацию на языке высокого уровня — но подобная практика создаёт проблемы при сопровождении программы из-за необходимости параллельно вносить изменения в несколько участков кода, написанных на разных языках и для разных платформ.

См. также

Примечания

  1. 1 2 "Программирование в Linux" Глава 5. Как pаботают системные вызовы. OpenNET. Дата обращения: 29 сентября 2013. Архивировано 2 октября 2013 года.
  2. Анализ использования ассемблерных вставок в коде открытых проектов. OpenNet. Дата обращения: 3 мая 2022. Архивировано 3 мая 2022 года.
  3. Reasons you should NOT use inline asm. Дата обращения: 3 мая 2022. Архивировано 28 апреля 2022 года.

Ссылки