Из Википедии, бесплатной энциклопедии
  (Перенаправлено из встроенной сборки )
Перейти к навигации Перейти к поиску

В компьютерном программировании , встроенный ассемблер является особенностью некоторых компиляторов , что позволяет низкоуровневого код , написанный на языке ассемблера , чтобы быть внедрены в рамках программы, в том числе кода , который в противном случае было составлено из языка высокого уровня , таких как C или Ada .

Мотивация и альтернативы [ править ]

Встраивание кода на ассемблере обычно выполняется по одной из трех причин: [1]

  • Оптимизация : программисты могут использовать код на языке ассемблера для реализации наиболее чувствительных к производительности частей алгоритмов своей программы , код, который может быть более эффективным, чем тот, который в противном случае мог бы быть сгенерирован компилятором.
  • Доступ к специфическим для процессора инструкциям : большинство процессоров предлагают специальные инструкции, такие как инструкции сравнения и замены и проверки и установки, которые могут использоваться для создания семафоров или других примитивов синхронизации и блокировки. Практически каждый современный процессор имеет эти или похожие инструкции, поскольку они необходимы для реализации многозадачности . Примеры специализированных инструкций можно найти в наборах инструкций SPARC VIS , Intel MMX и SSE , а также Motorola Altivec .
  • Доступ к специальным соглашениям о вызовах, еще не поддерживаемым компилятором.
  • Системные вызовы и прерывания: языки высокого уровня редко имеют прямую возможность выполнять произвольные системные вызовы, поэтому используется ассемблерный код. Прямые прерывания используются еще реже.
  • Для создания специальных директив для компоновщика или ассемблера, например, для изменения секционирования, макросов или создания псевдонимов символов.

С другой стороны, встроенный ассемблер представляет собой прямую проблему для самого компилятора, поскольку усложняет анализ того, что делается с каждой переменной, ключевой частью распределения регистров. [2] Это означает, что производительность может снизиться. Встроенный ассемблер также усложняет перенос и сопровождение программы в будущем. [1]

Альтернативные средства часто предоставляются как способ упростить работу как компилятору, так и программисту. Внутренние функции для специальных инструкций предоставляются большинством компиляторов, а оболочки C-функций для произвольных системных вызовов доступны на каждой платформе Unix .

Синтаксис [ править ]

В языковых стандартах [ править ]

Стандарт ISO C ++ и стандарты ISO C (приложение J) определяют условно поддерживаемый синтаксис для встроенного ассемблера:

Объявление asm имеет вид
  объявление
     asm : asm ( строковый литерал );
Объявление asm поддерживается условно; его значение определяется реализацией. [3]

Это определение, однако, редко используется в реальном C, поскольку оно одновременно слишком либерально (в интерпретации) и слишком ограничено (в использовании только одного строкового литерала).

В реальных компиляторах [ править ]

На практике встроенная сборка, работающая со значениями, редко бывает автономной в виде свободно плавающего кода. Поскольку программист не может предсказать, какому регистру назначена переменная, компиляторы обычно предоставляют способ заменить их в качестве расширения.

Как правило, компиляторы C / C ++ поддерживают два типа встроенной сборки:

  • asm (или __asm__ ) в GCC . GCC использует прямое расширение правил ISO: шаблон кода сборки записывается в виде строк, причем входы, выходы и закрытые регистры указываются после строк в двоеточиях. Переменные C используются напрямую, а имена регистров заключаются в строковые литералы. [4]
  • __asm в Microsoft Visual C ++ (MSVC), компиляторе Borland / Embarcadero C и потомках. Этот синтаксис вообще не основан на правилах ISO; программисты просто пишут ASM внутри блока без необходимости соответствовать синтаксису C. Переменные доступны, как если бы они были регистрами, и разрешены некоторые выражения C. [5] Компилятор ARM имел подобное средство. [6]

Два семейства расширений представляют собой разные представления о разделении труда при выполнении встроенной сборки. Форма GCC сохраняет общий синтаксис языка и разделяет то, что компилятору нужно знать: что необходимо и что нужно изменить. Он явно не требует, чтобы компилятор понимал имена инструкций, поскольку компилятор нужен только для замены своих назначений регистров, а также нескольких операций mov для обработки требований ввода. Однако пользователь склонен к неправильному указанию засоренных регистров. Форма MSVC встроенного предметно-ориентированного языка обеспечивает простоту написания, но требует, чтобы сам компилятор знал об именах кодов операций и их свойствах затирания, что требует дополнительного внимания при обслуживании и переносе. [7]Зная набор инструкций, все еще можно проверить сборку в стиле GCC на ошибки тупицы. [8]

GNAT (интерфейс GCC на языке Ada), а LLVM использует синтаксис GCC. [9] [10] В языке программирования D используется DSL, аналогичный официальному расширению MSVC для x86_64, [11] но LDC на основе LLVM также обеспечивает синтаксис в стиле GCC для каждой архитектуры. [12] MSVC поддерживает встроенный ассемблер только на 32-битной платформе x86. [5]

С тех пор язык Rust перешел на синтаксис, абстрагирующийся от встроенных параметров сборки, а не на версию LLVM (в стиле GCC). Он предоставляет достаточно информации, чтобы можно было преобразовать блок во внешнюю сборку, если серверная часть не может обрабатывать встроенную сборку. [7]

Примеры [ править ]

Системный вызов в GCC [ править ]

Как правило, прямой вызов операционной системы невозможен в системе, использующей защищенную память. ОС работает на более привилегированном уровне (режим ядра), чем пользовательский (пользовательский режим); (программное) прерывание используется для выполнения запросов к операционной системе. Это редко встречается в языках высокого уровня, поэтому функции-оболочки для системных вызовов пишутся с использованием встроенного ассемблера.

В следующем примере кода C показана оболочка системного вызова x86 в синтаксисе ассемблера AT&T с использованием GNU Assembler . Такие вызовы обычно пишутся с помощью макросов; полный код включен для ясности. В этом конкретном случае оболочка выполняет системный вызов номера, предоставленного вызывающей стороной, с тремя операндами, возвращая результат. [13]

Напомним, что GCC поддерживает как базовую, так и расширенную сборку. Первый просто дословно передает текст ассемблеру, а второй выполняет некоторые замены для местоположений регистров. [4]

extern  int  errno ;int  syscall3 ( int  num ,  int  arg1 ,  int  arg2 ,  int  arg3 ) {  int  res ;  __asm__  (  "int $ 0x80"  / * сделать запрос к ОС * /  :  "= a"  ( res ),  / * вернуть результат в eax ("a") * /  "+ b"  ( arg1 ),  / * передать arg1 в ebx ("b") [как вывод "+", потому что системный вызов может его изменить] * /  "+ c"  ( arg2 ),  / * передать arg2 в ecx ("c") [то же самое] * /  "+ d " ( arg3 ) / * передать arg3 в edx ("d") [ditto] * /  :  "a"  ( num )  / * передать номер системного вызова в eax ("a") * /  :  "memory" ,  "cc" ,  / * объявить компилятору, что память и коды условий были изменены * /  "esi" ,  "edi" ,  "ebp" );  / * они тоже затерты * / / * Операционная система вернет отрицательное значение при ошибке;  * обертки возвращают -1 в случае ошибки и устанавливают глобальную переменную errno * /  if  ( -125  <=  res  &&  res  <  0 )  {  errno  =  - res ;  res  =  -1 ;  }  return  res ; }

Специфическая для процессора инструкция в D [ править ]

Этот пример инлайн сборки из D языка программирования шоу кода , который вычисляет тангенс х с использованием x86 «с FPU ( x87 ) инструкции.

// Вычислить тангенс x вещественный  tan ( вещественный  x ) {  asm  {  fld  x [ EBP ]  ;  // загружаем x  fxam  ;  // проверка  нечетных  значений fstsw AX  ;  сахф  ;  jc  trigerr  ;  // C0 = 1: x - NAN, бесконечность или пусто  // 387-е могут обрабатывать денормальные значения SC18 :  fptan  ;  fstp  ST ( 0 )  ;  // дамп X, который всегда равен 1  fstsw  AX  ;  сахф  ; // если (! (fp_status & 0x20)) goto Lret  jnp  Lret  ;  // C2 = 1: x вне допустимого диапазона, уменьшить аргумент  fldpi  ;  // загружаем pi  fxch  ; SC17 :  fprem1  ;  // напоминание (частичное)  fstsw  AX  ;  сахф  ;  jp  SC17  ;  // C2 = 1: частичное напоминание, необходимо выполнить цикл  fstp  ST ( 1 )  ;  // удалить пи из стека  jmp  SC18  ;  } trigerr :  вернуть  реальный . нан ; Лрет : ; }

Для читателей, незнакомых с программированием x87, для доступа к битам C0 и C2 слова состояния FPU x87 используется идиома fstsw-sahf, за которой следует условный переход. fstsw сохраняет статус в универсальном регистре; sahf устанавливает регистр FLAGS на старшие 8 бит регистра; и переход используется для определения того, какой бит флага соответствует биту состояния FPU. [14]

Ссылки [ править ]

  1. ^ a b "DontUseInlineAsm" . GCC Wiki . Проверено 21 января 2020 года .
  2. ^ Стригель, Бен. " " Для компилятора капля встроенной сборки - это как пощечина. " " . Reddit . Проверено 15 января 2020 года .
  3. ^ C ++, [dcl.asm]
  4. ^ a b «Расширенный ассемблер - инструкции ассемблера с операндами выражения C» . Использование GNU C Compiler . Проверено 15 января 2020 года .
  5. ^ a b «Встроенный ассемблер» . docs.microsoft.com .
  6. ^ https://www.keil.com/support/man/docs/armclang_mig/armclang_mig_ioz1485879652178.htm
  7. ↑ a b d'Antras, Amanieu (13 декабря 2019 г.). «Rust RFC-2873: стабильный встроенный asm» . Проверено 15 января 2020 года . Однако можно реализовать поддержку встроенной сборки без поддержки со стороны компилятора, используя вместо этого внешний ассемблер. Pull Request для отслеживания статуса
  8. ^ «⚙ D54891 [RFC] Проверка корректности встроенной сборки» . reviews.llvm.org .
  9. ^ «Справочник по языку LLVM: встроенные выражения ассемблера» . Документация LLVM . Проверено 15 января 2020 года .
  10. ^ "Inline Assembly". Rust Documentation (1.0.0). Retrieved 15 January 2020.
  11. ^ "Inline Assembler". D programming language. Retrieved 15 January 2020.
  12. ^ "LDC inline assembly expressions". D Wiki. Retrieved 15 January 2020.
  13. ^ syscall(2) – Linux Programmer's Manual – System Calls
  14. ^ "FSTSW/FNSTSW — Store x87 FPU Status Word". The FNSTSW AX form of the instruction is used primarily in conditional branching...

External links[edit]

  • GCC-Inline-Assembly-HOWTO
  • Clang Inline assembly
  • GNAT Inline Assembler
  • GCC Inline Assembler Reference
  • Compiler Explorer