В компьютерном программировании , встроенный ассемблер является особенностью некоторых компиляторов , что позволяет низкоуровневого код , написанный на языке ассемблера , чтобы быть внедрены в рамках программы, в том числе кода , который в противном случае было составлено из языка высокого уровня , таких как 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]
Ссылки [ править ]
- ^ a b "DontUseInlineAsm" . GCC Wiki . Проверено 21 января 2020 года .
- ^ Стригель, Бен. " " Для компилятора капля встроенной сборки - это как пощечина. " " . Reddit . Проверено 15 января 2020 года .
- ^ C ++, [dcl.asm]
- ^ a b «Расширенный ассемблер - инструкции ассемблера с операндами выражения C» . Использование GNU C Compiler . Проверено 15 января 2020 года .
- ^ a b «Встроенный ассемблер» . docs.microsoft.com .
- ^ https://www.keil.com/support/man/docs/armclang_mig/armclang_mig_ioz1485879652178.htm
- ↑ a b d'Antras, Amanieu (13 декабря 2019 г.). «Rust RFC-2873: стабильный встроенный asm» . Проверено 15 января 2020 года .
Однако можно реализовать поддержку встроенной сборки без поддержки со стороны компилятора, используя вместо этого внешний ассемблер.
Pull Request для отслеживания статуса - ^ «⚙ D54891 [RFC] Проверка корректности встроенной сборки» . reviews.llvm.org .
- ^ «Справочник по языку LLVM: встроенные выражения ассемблера» . Документация LLVM . Проверено 15 января 2020 года .
- ^ "Inline Assembly". Rust Documentation (1.0.0). Retrieved 15 January 2020.
- ^ "Inline Assembler". D programming language. Retrieved 15 January 2020.
- ^ "LDC inline assembly expressions". D Wiki. Retrieved 15 January 2020.
- ^ – Linux Programmer's Manual – System Calls
- ^ "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