Встроенный ассемблер

редактировать

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

Содержание
  • 1 Мотивация и альтернативы
  • 2 Синтаксис
    • 2.1 In l стандарты языка
    • 2.2 В реальных компиляторах
  • 3 Примеры
    • 3.1 Системный вызов в GCC
    • 3.2 Специфическая для процессора инструкция в D
  • 4 Ссылки
  • 5 Внешние ссылки
Мотивация и альтернативы

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

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

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

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

Синтаксис

В языковых стандартах

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

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

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

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

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

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

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

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

GNAT (интерфейс языка Ada из набора GCC), LLVM и язык программирования Rust используют синтаксис, аналогичный синтаксису GCC. Язык программирования D использует DSL, аналогичный расширению MSVC, официально для x86_64, но LDC на основе LLVM также предоставляет синтаксис в стиле GCC для каждой архитектуры.

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

Примеры

Системный вызов в GCC

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

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

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

extern int errno; int syscall3 (int num, int arg1, int arg2, int arg3) {int res; __asm__ volatile ("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 показывает код, который вычисляет тангенс x с помощью инструкций FPU (x87 ) x86.

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

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

Ссылки
Внешние ссылки
Последняя правка сделана 2021-05-24 03:06:05
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте