Встроенный ассемблер
В компьютерном программировании встроенный ассемблер — это функция некоторых компиляторов низкоуровневый код, написанный на языке ассемблера , которая позволяет встраивать в программу , среди кода, который в противном случае был скомпилирован из языка более высокого уровня, такого как C или Ada .
Мотивация и альтернативы
[ редактировать ]Встраивание кода языка ассемблера обычно выполняется по одной из следующих причин: [1]
- Оптимизация своей программы . Программисты могут использовать код языка ассемблера для реализации наиболее чувствительных к производительности частей алгоритмов , код, который может быть более эффективным, чем тот, который в противном случае мог бы быть сгенерирован компилятором.
- , специфичным для процессора Доступ к инструкциям . Большинство процессоров предлагают специальные инструкции, такие как инструкции «Сравнить и поменять местами» , «Тестировать и установить », которые можно использовать для создания семафоров или других примитивов синхронизации и блокировки. Почти каждый современный процессор имеет такие или подобные инструкции, поскольку они необходимы для реализации многозадачности . Примеры специализированных инструкций можно найти в SPARC VIS , Intel MMX и SSE , а также Motorola Altivec наборах инструкций .
- Доступ к специальным соглашениям о вызовах, которые еще не поддерживаются компилятором.
- Системные вызовы и прерывания. Языки высокого уровня редко имеют возможность напрямую выполнять произвольные системные вызовы, поэтому используется ассемблерный код. Прямые прерывания используются еще реже.
- Выдавать специальные директивы для компоновщика или ассемблера, например, для изменения секционирования, макросов или создания псевдонимов символов.
С другой стороны, встроенный ассемблер создает прямую проблему для самого компилятора, поскольку усложняет анализ того, что делается с каждой переменной, что является ключевой частью распределения регистров. [2] Это означает, что производительность может фактически снизиться. Встроенный ассемблер также усложняет будущее портирование и обслуживание программы. [1]
Альтернативные возможности часто предоставляются как способ упростить работу как компилятора, так и программиста. Встроенные функции для специальных инструкций предоставляются большинством компиляторов, а оболочки C-функций для произвольных системных вызовов доступны на каждой платформе Unix .
Синтаксис
[ редактировать ]В языковых стандартах
[ редактировать ]Стандарт ISO C++ и стандарты ISO C (приложение J) определяют условно поддерживаемый синтаксис для встроенного ассемблера:
Объявление asm имеет форму
ассемблерное объявление :
asm ( строковый литерал );
Объявление asm условно поддерживается; его значение определяется реализацией. [3]
Однако это определение редко используется в реальном языке C, поскольку оно одновременно слишком либерально (в интерпретации) и слишком ограничено (в использовании только одного строкового литерала).
В реальных компиляторах
[ редактировать ]На практике встроенный ассемблер, работающий со значениями, редко бывает автономным в виде свободно плавающего кода. Поскольку программист не может предсказать, какому регистру присвоена переменная, компиляторы обычно предоставляют возможность подставить их в качестве расширения.
Обычно существует два типа встроенной сборки, поддерживаемые компиляторами C/C++:
- асм (или __asm__ ) в GCC . GCC использует прямое расширение правил ISO: шаблон ассемблерного кода записывается в виде строк, при этом входы, выходы и затертые регистры указываются после строк через двоеточия. Переменные C используются напрямую, а имена регистров заключаются в кавычки как строковые литералы. [4]
- __asm в Microsoft Visual C++ (MSVC), компиляторе Borland/Embarcadero C и его потомках. Этот синтаксис вообще не основан на правилах ISO; программисты просто пишут ASM внутри блока без необходимости соблюдения синтаксиса C. Переменные доступны так же, как если бы они были регистрами, и разрешены некоторые выражения C. [5] Раньше в ARM Compiler была аналогичная возможность. [6]
Два семейства расширений представляют собой различное понимание разделения труда при обработке встроенной сборки. Форма GCC сохраняет общий синтаксис языка и разделяет то, что нужно знать компилятору: что необходимо, а что изменено. Он явно не требует, чтобы компилятор понимал имена инструкций, поскольку компилятору необходимо только заменить назначения своих регистров, а также несколько операции mov для обработки входных требований. Однако пользователь склонен неправильно указывать затертые регистры. Форма MSVC встроенного предметно-ориентированного языка обеспечивает простоту написания, но требует, чтобы сам компилятор знал об именах кодов операций и их свойствах затирания, что требует дополнительного внимания при обслуживании и портировании. [7] Зная набор команд, по-прежнему можно проверить сборку в стиле GCC на наличие ошибок. [8]
GNAT (интерфейс языка Ada пакета GCC) и 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 . Такие вызовы обычно записываются с помощью макросов; полный код включен для ясности. В этом конкретном случае оболочка выполняет системный вызов числа, заданного вызывающей стороной, с тремя операндами, возвращая результат. [13]
Напомним, что GCC поддерживает как базовую , так и расширенную сборку. Первый просто дословно передает текст ассемблеру, а второй выполняет некоторые замены регистров. [4]
extern int errno;
int syscall3(int num, int arg1, int arg2, int arg3)
{
int res;
__asm__ (
"int $0x80" /* make the request to the OS */
: "=a" (res), /* return result in eax ("a") */
"+b" (arg1), /* pass arg1 in ebx ("b") [as a "+" output because the syscall may change it] */
"+c" (arg2), /* pass arg2 in ecx ("c") [ditto] */
"+d" (arg3) /* pass arg3 in edx ("d") [ditto] */
: "a" (num) /* pass system call number in eax ("a") */
: "memory", "cc", /* announce to the compiler that the memory and condition codes have been modified */
"esi", "edi", "ebp"); /* these registers are clobbered [changed by the syscall] too */
/* 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;
}
Инструкция, специфичная для процессора, в D
[ редактировать ]В этом примере встроенного ассемблера языка программирования D показан код, который вычисляет тангенс x с помощью ( x86 инструкций FPU x87 ) .
// 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 ; // C0 = 1: 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 ; // if (!(fp_status & 0x20)) goto Lret
jnp Lret ; // C2 = 1: x is out of range, do argument reduction
fldpi ; // load pi
fxch ;
SC17: fprem1 ; // reminder (partial)
fstsw AX ;
sahf ;
jp SC17 ; // C2 = 1: partial reminder, need to loop
fstp ST(1) ; // remove pi from stack
jmp SC18 ;
}
trigerr:
return real.nan;
Lret: // No need to manually return anything as the value is already on FP stack
;
}
Для читателей, незнакомых с программированием x87, fstsw-sahf, за которым следует идиома условного перехода, используется для доступа к битам C0 и C2 слова состояния FPU x87. fstsw сохраняет статус в регистре общего назначения; sahf устанавливает в регистре FLAGS старшие 8 бит регистра; и переход используется для оценки любого бита флага, который соответствует биту состояния FPU. [14]
Ссылки
[ редактировать ]- ^ Перейти обратно: а б «НеИспользоватьИнлайнАсм» . GCC Wiki . Проверено 21 января 2020 г.
- ^ Стригель, Бен (13 января 2020 г.). " "Для компилятора кусок встроенного ассемблера - это как пощечина." " . Реддит . Проверено 15 января 2020 г. .
- ^ С++, [dcl.asm]
- ^ Перейти обратно: а б «Расширенный Asm — инструкции ассемблера с операндами-выражениями C» . Использование компилятора GNU C. Проверено 15 января 2020 г. .
- ^ Перейти обратно: а б «Встроенный ассемблер» . docs.microsoft.com .
- ^ «Руководство по миграции и совместимости: встроенная сборка с помощью Arm Compiler 6» .
- ^ Перейти обратно: а б д'Антрас, Аманье (13 декабря 2019 г.). «Rust RFC-2873: стабильный встроенный ассемблер» . Проверено 15 января 2020 г. .
Однако можно реализовать поддержку встроенной сборки без поддержки со стороны серверной части компилятора, используя вместо этого внешний ассемблер.
Запрос на включение для отслеживания статуса - ^ «⚙ D54891 [RFC] Проверка валидности встроенной сборки» . Reviews.llvm.org .
- ^ «Справочник по языку LLVM: встроенные ассемблерные выражения» . Документация ЛЛВМ . Проверено 15 января 2020 г. .
- ^ «Встроенная сборка» . Документация по Rust (1.0.0) . Проверено 15 января 2020 г. .
- ^ «Встроенный ассемблер» . Язык программирования D. Проверено 15 января 2020 г. .
- ^ «Встроенные ассемблерные выражения LDC» . Д Вики . Проверено 15 января 2020 г. .
- ^ Linux программиста Руководство – Системные вызовы –
- ^ «FSTSW/FNSTSW — сохранение слова состояния FPU x87» .
Форма инструкции FNSTSW AX используется в основном при условном ветвлении...