6.47.2 Расширенный Asm — Инструкции ассемблера с операндами выражений C
С расширенным asm можно читать и записывать переменные C из ассемблера и выполнять переходы из ассемблерного кода к меткам C. Синтаксис расширенного asm использует двоеточия (‘:’) для разграничения параметров операндов после шаблона ассемблера:
asm asm-qualifiers ( AssemblerTemplate
: OutputOperands
[ : InputOperands
[ : Clobbers ] ])
asm asm-qualifiers ( AssemblerTemplate
: OutputOperands
: InputOperands
: Clobbers
: GotoLabels)
где в последней форме asm-qualifiers содержит goto (а в первой форме — нет).
Ключевое слово asm является расширением GNU. При написании кода, который может быть скомпилирован с -ansi и различными опциями -std, используйте __asm__ вместо asm (см. Альтернативные ключевые слова).
Квалификаторы
volatile-
Типичное использование расширенных
asmинструкций — манипулирование входными значениями для получения выходных значений. Однако вашиasmинструкции также могут иметь побочные эффекты. В таком случае, вам может потребоваться использовать квалификаторvolatileдля отключения определённых оптимизаций. См. Volatile. inline-
Если вы используете квалификатор
inline, то для целей встраивания размерasmинструкции принимается как наименьший возможный (см. Размер asm). gotoЭтот квалификатор сообщает компилятору, что
asmинструкция может выполнить переход к одной из меток, перечисленных в GotoLabels. См. GotoLabels.
Параметры
- AssemblerTemplate
-
Это строка-шаблон для кода ассемблера. Она состоит из фиксированного текста и маркеров, которые ссылаются на входные, выходные и параметры переходов. См. AssemblerTemplate.
- OutputOperands
-
Список C-переменных, изменяемых инструкциями в AssemblerTemplate, разделённый запятыми. Разрешён пустой список. См. OutputOperands.
- InputOperands
-
Список C-выражений, считываемых инструкциями в AssemblerTemplate, разделённый запятыми. Разрешён пустой список. См. InputOperands.
- Clobbers
-
Список регистров или других значений, изменяемых AssemblerTemplate, помимо перечисленных в качестве выходов. Разрешён пустой список. См. Clobbers и регистры-стеки.
- GotoLabels
-
Когда вы используете форму
gotoasm, этот раздел содержит список всех C-меток, на которые код в AssemblerTemplate может перейти. См. GotoLabels.Инструкции
asmне могут выполнять переходы в другиеasmинструкции, только к перечисленным GotoLabels. Оптимизаторы GCC не знают о других переходах, поэтому не могут учитывать их при принятии решений об оптимизации.
Общее количество входных + выходных + меток перехода операндов ограничено 30.
Примечания
Инструкция asm позволяет включать инструкции ассемблера непосредственно в код C. Это может помочь вам максимизировать производительность в коде, чувствительном к времени, или получить доступ к инструкциям ассемблера, которые не доступны для программ C.
Обратите внимание, что расширенные asm инструкции должны быть внутри функции. Только базовые asm инструкции могут быть вне функций (см. Basic Asm). Функции, объявленные с атрибутом naked, также требуют базовые asm инструкции (см. Атрибуты функций).
Хотя применения asm многообразны, можно представить asm инструкцию как последовательность низкоуровневых инструкций, которые преобразуют входные параметры в выходные параметры. Так, простой (хотя и не особенно полезный) пример для i386, использующий asm, может выглядеть так:
int src = 1;
int dst;
asm ("mov %1, %0\n\t"
"add $1, %0"
: "=r" (dst)
: "r" (src));
printf("%d\n", dst);
Этот код копирует src в dst и добавляет 1 к dst.
6.47.2.1 Volatile
Оптимизаторы GCC иногда удаляют asm инструкции, если определяют, что выходные переменные не нужны. Кроме того, оптимизаторы могут перемещать код из циклов, если считают, что код всегда возвращает одинаковый результат (т. е. ни одно из входных значений не изменяется между вызовами). Использование квалификатора volatile отключает эти оптимизации. asm инструкции без выходных операндов и asm goto инструкции неявно являются volatile.
Этот код i386 демонстрирует случай, который не использует (или не требует) квалификатора volatile. Если он выполняет проверку утверждений, этот код использует asm для выполнения проверки. В противном случае dwRes не используется ни одним кодом. В результате оптимизаторы могут удалить asm инструкцию, что, в свою очередь, устраняет необходимость в всей DoCheck процедуре. Опуская квалификатор volatile при необходимости, вы позволяете оптимизаторам создавать максимально эффективный код.
void DoCheck(uint32_t dwSomeValue)
{
uint32_t dwRes;
// Assumes dwSomeValue is not zero.
asm ("bsfl %1,%0"
: "=r" (dwRes)
: "r" (dwSomeValue)
: "cc");
assert(dwRes > 3);
}
Следующий пример демонстрирует случай, когда оптимизаторы могут распознать, что входной параметр (dwSomeValue) никогда не изменяется во время выполнения функции, и поэтому могут перенести asm за пределы цикла, чтобы создать более эффективный код. Опять же, использование квалификатора volatile отключает этот тип оптимизации.
void do_print(uint32_t dwSomeValue)
{
uint32_t dwRes;
for (uint32_t x=0; x < 5; x++)
{
// Assumes dwSomeValue is not zero.
asm ("bsfl %1,%0"
: "=r" (dwRes)
: "r" (dwSomeValue)
: "cc");
printf("%u: %u %u\n", x, dwSomeValue, dwRes);
}
}
Следующий пример демонстрирует случай, когда необходимо использовать квалификатор volatile. Он использует x86-инструкцию rdtsc, которая считывает счётчик временных меток компьютера. Без квалификатора volatile, оптимизаторы могут предположить, что блок asm всегда возвращает одинаковое значение, и поэтому оптимизировать второй вызов.
uint64_t msr;
asm volatile ( "rdtsc\n\t" // Returns the time in EDX:EAX.
"shl $32, %%rdx\n\t" // Shift the upper bits left.
"or %%rdx, %0" // 'Or' in the lower bits.
: "=a" (msr)
:
: "rdx");
printf("msr: %llx\n", msr);
// Do other work...
// Reprint the timestamp
asm volatile ( "rdtsc\n\t" // Returns the time in EDX:EAX.
"shl $32, %%rdx\n\t" // Shift the upper bits left.
"or %%rdx, %0" // 'Or' in the lower bits.
: "=a" (msr)
:
: "rdx");
printf("msr: %llx\n", msr);
Оптимизаторы GCC не рассматривают этот код как не-volatile код в предыдущих примерах. Они не перемещают его из циклов и не пропускают его с предположением, что результат предыдущего вызова по-прежнему действителен.
Обратите внимание, что компилятор может перемещать даже volatile asm инструкции относительно другого кода, включая переходы. Например, на многих целевых платформах есть системный регистр, который управляет режимом округления операций с плавающей запятой. Установка его с помощью volatile asm инструкции, как в следующем примере PowerPC, не работает надёжно.
asm volatile("mtfsf 255, %0" : : "f" (fpenv));
sum = x + y;
Компилятор может перенести сложение назад перед volatile asm инструкцией. Чтобы заставить её работать как ожидается, добавьте искусственную зависимость к asm путём ссылки на переменную в последующем коде, например:
asm volatile ("mtfsf 255,%1" : "=X" (sum) : "f" (fpenv));
sum = x + y;
В определённых обстоятельствах GCC может дублировать (или удалять дубликаты) вашего ассемблерного кода при оптимизации. Это может привести к непредвиденным ошибкам дублирования символов во время компиляции, если ваш asm код определяет символы или метки. Использование ‘%=’ (см. AssemblerTemplate) может помочь решить эту проблему.
6.47.2.2 Шаблон ассемблера
Шаблон ассемблера — это строка-литерал, содержащая инструкции ассемблера. Компилятор заменяет маркеры в шаблоне, которые ссылаются на входные, выходные и метки перехода, и затем выводит результирующую строку в ассемблер. Строка может содержать любые инструкции, распознаваемые ассемблером, включая директивы. GCC не анализирует сами инструкции ассемблера и не знает их смысла или даже являются ли они корректным входом для ассемблера. Однако он считает инструкции (см. Размер asm).
Вы можете размещать несколько инструкций ассемблера вместе в одной asm строке, разделённые символами, обычно используемыми в коде ассемблера для данной системы. Комбинация, которая работает в большинстве случаев, — это перевод строки, плюс символ табуляции для перехода к полю инструкции (записано как ‘\n\t’). Некоторые ассемблеры позволяют использовать точку с запятой в качестве разделителя строк. Однако обратите внимание, что некоторые диалекты ассемблера используют точку с запятой для начала комментария.
Не ожидайте, что последовательность asm инструкций останется идеально последовательной после компиляции, даже когда вы используете квалификатор volatile. Если некоторые инструкции должны оставаться последовательными в выходном коде, поместите их в одну многоинструкционную asm инструкцию.
Доступ к данным из программ C без использования входных/выходных операндов (например, путём непосредственного использования глобальных символов из шаблона ассемблера) может не работать как ожидается. Аналогично, вызов функций непосредственно из шаблона ассемблера требует подробного понимания целевого ассемблера и ABI.
Поскольку GCC не анализирует шаблон ассемблера, он не имеет доступа к символам, на которые он ссылается. Это может привести к тому, что GCC отбросит эти символы как неиспользуемые, если они также не указаны в качестве входных, выходных или операндов перехода.
Специальные строки форматирования
Помимо маркеров, описанных входными, выходными и метками перехода, эти маркеры имеют специальное значение в шаблоне ассемблера:
- ‘%%’
-
Выводит одиночный ‘%’ в код ассемблера.
- ‘%=’
-
Выводит число, уникальное для каждой копии
asmинструкции в процессе компиляции. Этот вариант полезен при создании локальных меток и ссылках на них несколько раз в одном шаблоне, генерирующем несколько инструкций ассемблера. - ‘%{’
- ‘%|’
- ‘%}’
Выводит символы ‘{’, ‘|’, и ‘}’ (соответственно) в код ассемблера. Когда эти символы не экранированы, они имеют специальное значение для указания нескольких диалектов ассемблера, как описано ниже.
Несколько диалектов ассемблера в шаблонах asm
На таких целевых платформах, как x86, GCC поддерживает несколько диалектов ассемблера. Опция -masm управляет диалектом, используемым GCC по умолчанию для встроенного ассемблера. В документации целевой платформы для опции -masm содержится список поддерживаемых диалектов, а также диалект по умолчанию, если опция не указана. Эта информация может быть важна для понимания, так как ассемблерный код, который работает правильно при компиляции с одним диалектом, вероятно, не скомпилируется с другим. См. x86 Options.
Если ваш код должен поддерживать несколько диалектов ассемблера (например, если вы пишете общедоступные заголовки, которые должны поддерживать различные варианты компиляции), используйте конструкции такого вида:
{ dialect0 | dialect1 | dialect2... }
Эта конструкция выводит dialect0 при компиляции кода с диалектом #0, dialect1 для диалекта #1 и т. д. Если альтернатив в фигурных скобках меньше, чем количество диалектов, поддерживаемых компилятором, конструкция ничего не выводит.
Например, если компилятор x86 поддерживает два диалекта (‘att’, ‘intel’), шаблон ассемблера такого вида:
"bt{l %[Offset],%[Base] | %[Base],%[Offset]}; jc %l2"
эквивалентен одному из:
"btl %[Offset],%[Base] ; jc %l2" /* att dialect */ "bt %[Base],%[Offset]; jc %l2" /* intel dialect */
Используя тот же компилятор, этот код:
"xchg{l}\t{%%}ebx, %1"
соответствует либо:
"xchgl\t%%ebx, %1" /* att dialect */ "xchg\tebx, %1" /* intel dialect */
Вложенные альтернативы диалектов не поддерживаются.
6.47.2.3 Операнды вывода
В инструкции asm может быть ноль или более операндов вывода, указывающих имена переменных C, изменяемых кодом ассемблера.
В этом примере для i386, old (упоминаемое в строке шаблона как %0) и *Base (как %1) являются операндами вывода, а Offset (%2) — операндом ввода:
bool old;
__asm__ ("btsl %2,%1\n\t" // Turn on zero-based bit #Offset in Base.
"sbb %0,%0" // Use the CF to calculate old.
: "=r" (old), "+rm" (*Base)
: "Ir" (Offset)
: "cc");
return old;
Операнды разделяются запятыми. Каждый операнд имеет такой формат:
[ [asmSymbolicName] ] constraint (cvariablename)
- asmSymbolicName
Указывает символьное имя для операнда. Ссылайтесь на имя в шаблоне ассемблера, заключив его в квадратные скобки (например, ‘%[Value]’). Область действия имени — инструкция
asm, содержащая определение. Любое допустимое имя переменной C, включая имена, уже определённые в окружающем коде, допустимо. Два операнда в одной инструкцииasmне могут использовать одно и то же символьное имя.Если вы не используете asmSymbolicName, используйте (нулевой) порядковый номер операнда в списке операндов в шаблоне ассемблера. Например, если есть три операнда вывода, используйте ‘%0’ для первого, ‘%1’ для второго и ‘%2’ для третьего.
- constraint
Строковая константа, задающая ограничения на размещение операнда; подробнее см. Constraints.
Ограничения вывода должны начинаться с ‘=’ (переменная перезаписывает существующее значение) или ‘+’ (при чтении и записи). При использовании ‘=’, не предполагайте, что местоположение содержит текущее значение при входе в
asm, за исключением случаев, когда операнд связан с вводом; см. Операнды ввода.После префикса должны быть один или несколько дополнительных ограничений (см. Constraints), описывающих местоположение значения. Общие ограничения включают ‘r’ для регистра и ‘m’ для памяти. Когда вы перечисляете несколько возможных местоположений (например,
"=rm"), компилятор выбирает наиболее эффективный вариант в зависимости от текущего контекста. Если вы перечислите столько альтернатив, сколько позволяет инструкцияasm, вы позволите оптимизаторам сгенерировать наилучший возможный код. Если вам необходимо использовать определённый регистр, но ваши машинные ограничения не позволяют выбрать нужный вам регистр, локальные регистровые переменные могут предложить решение (см. Local Register Variables).- cvariablename
Указывает выражение C lvalue для хранения вывода, обычно имя переменной. Скобки являются обязательной частью синтаксиса.
При выборе компилятором регистров для представления операндов вывода он не использует ни один из регистров, помеченных как модифицируемые (см. Clobbers и регистры-временные переменные).
Выражения операндов вывода должны быть lvalue. Компилятор не может проверить, имеют ли операнды типы данных, которые приемлемы для выполняемой инструкции. Для выражений вывода, которые не являются непосредственно адресуемыми (например, поле битов), ограничение должно допускать регистр. В этом случае GCC использует регистр в качестве результата asm, а затем сохраняет этот регистр в выходной параметр.
Операнды с модификатором ограничения ‘+’ считаются двумя операндами (то есть, как вводом, так и выводом) с учётом максимального значения в 30 операндов в инструкции asm.
Используйте модификатор ограничения ‘&’ (см. Modifiers) для всех операндов вывода, которые не должны перекрывать входные параметры. В противном случае GCC может разместить операнд вывода в том же регистре, что и не связанный с ним операнд ввода, исходя из предположения, что ассемблерный код потребляет входные параметры до вывода результатов. Это предположение может быть ложным, если ассемблерный код на самом деле состоит из более чем одной инструкции.
Та же проблема может возникнуть, если один выходной параметр (a) допускает ограничение регистра, а другой выходной параметр (b) допускает ограничение памяти. Код, сгенерированный GCC для доступа к адресу памяти в b, может содержать регистры, которые могут быть совместно используемыми a, и GCC рассматривает эти регистры как входные для asm. Как и выше, GCC предполагает, что такие входные регистры потребляются до записи любых выходных данных. Это предположение может привести к неправильному поведению, если инструкция asm записывает в a до использования b. Объединение модификатора ‘&’ с ограничением регистра на a гарантирует, что изменение a не повлияет на адрес, к которому относится b. В противном случае расположение b неопределено, если a изменено до использования b.
asm поддерживает модификаторы операндов (например, ‘%k2’ вместо просто ‘%2’). Как правило, эти квалификаторы зависят от аппаратной части. Список поддерживаемых модификаторов для x86 находится в x86 Operand modifiers.
Если C-код, следующий за asm, не использует ни один из выходных операндов, используйте volatile для инструкции asm, чтобы предотвратить оптимизаторы от удаления инструкции asm как неиспользуемой (см. Volatile).
Этот код не использует необязательный asmSymbolicName. Поэтому он ссылается на первый выходной операнд как %0 (если был второй, он был бы %1, и т. д.). Номер первого входного операнда на единицу больше, чем номер последнего выходного операнда. В этом примере для i386, Mask указывается как %1:
uint32_t Mask = 1234;
uint32_t Index;
asm ("bsfl %1, %0"
: "=r" (Index)
: "r" (Mask)
: "cc");
Этот код перезаписывает переменную Index (‘=’), помещая значение в регистр (‘r’). Использование универсального ограничения ‘r’ вместо ограничения для конкретного регистра позволяет компилятору выбрать регистр, что может привести к более эффективному коду. Это может оказаться невозможным, если ассемблерная инструкция требует конкретного регистра.
Следующий пример для i386 использует синтаксис asmSymbolicName. Он производит тот же результат, что и код выше, но некоторые могут считать его более читаемым или более поддерживаемым, поскольку нет необходимости переупорядочивать индексы при добавлении или удалении операндов. Имена aIndex и aMask используются только в этом примере, чтобы подчеркнуть, где используются какие имена. Разрешается повторное использование имён Index и Mask.
uint32_t Mask = 1234;
uint32_t Index;
asm ("bsfl %[aMask], %[aIndex]"
: [aIndex] "=r" (Index)
: [aMask] "r" (Mask)
: "cc");
Вот ещё несколько примеров операндов вывода.
uint32_t c = 1;
uint32_t d;
uint32_t *e = &c;
asm ("mov %[e], %[d]"
: [d] "=rm" (d)
: [e] "rm" (*e));
Здесь d может находиться в регистре или в памяти. Поскольку компилятор, возможно, уже имеет текущее значение расположения uint32_t по адресу, указанному e, вы можете позволить ему выбрать лучшую позицию для d путём указания обоих ограничений.
6.47.2.4 Операнды вывода флагов
Некоторые целевые платформы имеют специальный регистр, содержащий «флаги» результата операции или сравнения. Как правило, содержимое этого регистра либо не изменяется инструкцией asm, либо считается, что инструкция asm уничтожает содержимое.
На некоторых целевых платформах существует специальная форма операнда вывода, посредством которой условия в регистре флагов могут быть результатом инструкции asm. Набор поддерживаемых условий специфичен для целевой платформы, но общее правило состоит в том, что переменная вывода должна быть скалярным целым числом, а значение — булевым. Если это поддерживается, целевая платформа определяет препроцессорную константу __GCC_ASM_FLAG_OUTPUTS__.
Из-за специфической природы операндов вывода флагов ограничение не может включать альтернативы.
Чаще всего целевая платформа имеет только один регистр флагов, что делает его неявным операндом многих инструкций. В этом случае операнд не должен упоминаться в шаблоне ассемблера через %0 и т.д., так как нет соответствующего текста в языке ассемблера.
- ARM
- AArch64
-
Ограничения на вывод флагов для семейства ARM имеют вид ‘=@cccond’, где cond — одно из стандартных условий, определенных в руководстве ARM по
ConditionHolds.eqФлаг Z установлен или равен
neФлаг Z сброшен или не равен
cshsФлаг C установлен или беззнаковое больше или равно
ccloФлаг C сброшен или беззнаковое меньше
miФлаг N установлен или «минус»
plФлаг N сброшен или «плюс»
vsФлаг V установлен или переполнение со знаком
vcФлаг V сброшен
hiбеззнаковое больше
lsбеззнаковое меньше или равно
geсо знаком больше или равно
ltсо знаком меньше
gtсо знаком больше
leсо знаком меньше или равно
Ограничения на вывод флагов не поддерживаются в режиме thumb1.
- x86 family
-
Ограничения на вывод флагов для семейства x86 имеют вид ‘=@cccond’, где cond — одно из стандартных условий, определенных в руководстве ISA для
jccилиsetcc.a«выше» или беззнаковое больше
ae«выше или равно» или беззнаковое больше или равно
b«ниже» или беззнаковое меньше
be«ниже или равно» или беззнаковое меньше или равно
cфлаг переноса установлен
ez«равно» или установлен флаг нуля
gсо знаком больше
geсо знаком больше или равно
lсо знаком меньше
leсо знаком меньше или равно
oфлаг переполнения установлен
pфлаг чётности установлен
sфлаг знака установлен
nanaenbnbencnengngenlnlenonpnsnz«не» flag, или инвертированные версии вышеперечисленных
6.47.2.5 Операнды-входы
Операнды-входы обеспечивают доступ к значениям из переменных и выражений C в ассемблерном коде.
Операнды разделяются запятыми. Каждый операнд имеет такой формат:
[ [asmSymbolicName] ] constraint (cexpression)
- asmSymbolicName
-
Указывает символическое имя для операнда. Ссылайтесь на имя в шаблоне ассемблера, заключив его в квадратные скобки (например, ‘%[Value]’). Область действия имени —
asmоператор, содержащий определение. Подходит любое допустимое имя переменной C, включая имена, уже определенные в окружающем коде. Два операнда в одномasmоператоре не могут использовать одно и то же символическое имя.Если не используется asmSymbolicName, используйте (нумерацию с нуля) позицию операнда в списке операндов в шаблоне ассемблера. Например, если есть два выходных операнда и три входных, в шаблоне используйте ‘%2’ для первого входного операнда, ‘%3’ для второго и ‘%4’ для третьего.
- constraint
-
Строковая константа, задающая ограничения на размещение операнда; подробнее см. Ограничения.
Строки ограничений ввода не могут начинаться с ‘=’ или ‘+’. Если вы перечисляете несколько возможных мест (например, ‘"irm"’), компилятор выбирает наиболее эффективный вариант, основываясь на текущем контексте. Если вам необходимо использовать определённый регистр, но ваши ограничения машины не обеспечивают достаточный контроль для выбора нужного регистра, локальные регистровые переменные могут предложить решение (см. Локальные регистровые переменные).
Ограничения ввода также могут быть цифрами (например,
"0"). Это указывает, что указанный вход должен находиться в том же месте, что и ограничение вывода с (нумерацией с нуля) индексом в списке ограничений вывода. При использовании синтаксиса asmSymbolicName для выходных операндов вы можете использовать эти имена (в квадратных скобках ‘[]’) вместо цифр. - cexpression
-
Это переменная или выражение C, передаваемые в
asmоператор в качестве входных данных. Скобки являются необходимой частью синтаксиса.
При выборе регистров для представления входных операндов компилятор не использует ни один из изменённых регистров (см. Изменяемые и вспомогательные регистры).
Если нет выходных операндов, но есть входные, поместите две последовательные двоеточия там, где были бы выходные операнды:
__asm__ ("some instructions"
: /* No outputs. */
: "r" (Offset / 8));
Предупреждение: Не изменяйте содержимое только входных операндов (за исключением входов, связанных с выходами). Компилятор предполагает, что при выходе из asm оператора эти операнды содержат те же значения, что и до выполнения оператора. Использование изменённых регистров для указания изменения значения ввода невозможно. Одним из распространённых решений является привязка изменяемой переменной ввода к переменной вывода, которая никогда не используется. Однако имейте в виду, что если код, следующий за asm оператором, не использует ни один из выходных операндов, оптимизаторы GCC могут отбросить asm оператор как не требуемый (см. Переменные volatile).
asm поддерживает модификаторы операндов в операндах (например, ‘%k2’ вместо просто ‘%2’). Обычно эти квалификаторы зависят от аппаратной части. Список поддерживаемых модификаторов для x86 находится по адресу Модификаторы операндов x86.
В этом примере, с использованием фиктивной инструкции combine, ограничение "0" для входного операнда 1 означает, что он должен занимать то же место, что и выходной операнд 0. Только входные операнды могут использовать числа в ограничениях, и каждый из них должен ссылаться на выходной операнд. Только число (или символическое имя ассемблера) в ограничении гарантирует, что один операнд находится в том же месте, что и другой. Просто то, что foo — значение обоих операндов, не достаточно для гарантии, что они находятся в одном месте в сгенерированном ассемблерном коде.
asm ("combine %2, %0"
: "=r" (foo)
: "0" (foo), "g" (bar));
Вот пример использования символических имён.
asm ("cmoveq %1, %2, %[result]"
: [result] "=r"(result)
: "r" (test), "r" (new), "[result]" (old));
6.47.2.6 Изменяемые и вспомогательные регистры
Хотя компилятор осведомлён о изменениях введённых в выходные операнды, встроенный asm ассемблерный код может изменять больше, чем просто выходы. Например, вычисления могут потребовать дополнительных регистров, или процессор может перезаписать регистр в качестве побочного эффекта конкретной ассемблерной инструкции. Чтобы проинформировать компилятор об этих изменениях, перечислите их в списке изменённых регистров. Элементы списка изменённых регистров — это имена регистров или специальные изменённые регистры (перечисленные ниже). Каждый элемент списка изменённых регистров — это строковая константа в двойных кавычках, разделённая запятыми.
Описание изменённых регистров не должно никоим образом перекрываться с входными или выходными операндами. Например, вы не можете иметь операнд, описывающий класс регистров с одним членом, когда вы перечисляете этот регистр в списке изменённых регистров. Переменные, объявленные в определенных регистрах (см. Явные переменные регистров) и используемые в качестве asm входных или выходных операндов не должны быть упомянуты в описании изменения. В частности, нет способа указать, что входные операнды изменяются, не указав их также в качестве выходных операндов.
Когда компилятор выбирает регистры для представления входных и выходных операндов, он не использует ни один из изменённых регистров. В результате изменённые регистры доступны для любого использования в ассемблерном коде.
Ещё одно ограничение заключается в том, что список изменённых регистров не должен содержать регистр стека. Это связано с тем, что компилятор требует, чтобы значение регистра стека после asm оператора было таким же, как и при входе в оператор. Однако предыдущие версии GCC не обеспечивали этой проверки и допускали наличие регистра стека в списке с неясными семантиками. Это поведение устарело, и указание регистра стека может стать ошибкой в будущих версиях GCC.
Вот реальный пример для VAX, показывающий использование изменённых регистров:
asm volatile ("movc3 %0, %1, %2"
: /* No outputs. */
: "g" (from), "g" (to), "g" (count)
: "r0", "r1", "r2", "r3", "r4", "r5", "memory");
Также есть два специальных аргумента для изменения:
"cc"-
Изменённый регистр
"cc"указывает, что ассемблерный код изменяет регистр флагов. На некоторых машинах GCC представляет коды условий как отдельный аппаратный регистр;"cc"служит для наименования этого регистра. На других машинах обработка кодов условий отличается, и указание"cc"не оказывает влияния. Но это допустимо независимо от целевого устройства. "memory"-
Изменённый регистр
"memory"сообщает компилятору, что ассемблерный код выполняет чтение или запись в память по адресам, отличным от перечисленных в входных и выходных операндах (например, доступ к памяти, на которую указывает один из входных параметров). Для обеспечения правильности содержимого памяти GCC может потребоваться очистить определённые значения в регистре в память перед выполнениемasm. Кроме того, компилятор не предполагает, что любые значения, прочитанные из памяти доasmоператора, останутся неизменными после этогоasm; он перезагружает их по мере необходимости. Использование изменённого регистра"memory"эффективно формирует барьер для чтения/записи в памяти для компилятора.Обратите внимание, что это изменение не препятствует процессору выполнять упреждающие чтения за пределами
asmоператора. Чтобы этого избежать, вам нужны инструкции-барьеры, специфичные для процессора.
Сброс регистров в память влечет за собой последствия для производительности и может стать проблемой для кода, чувствительного к времени. Вы можете предоставить GCC более подробную информацию, чтобы избежать этого, как показано в следующих примерах. Как минимум, правила алиасинга позволяют GCC знать, какие данные не нужно сбрасывать в память.
Вот фиктивная инструкция суммирования квадратов, которая принимает два указателя на значения с плавающей запятой в памяти и производит выходное значение с плавающей запятой в регистре. Обратите внимание, что x, и y оба появляются дважды в asm параметрах, один раз для указания памяти, к которой осуществляется доступ, и один раз для указания базового регистра, используемого asm. Обычно вы не будете тратить регистр на это, так как GCC может использовать один и тот же регистр для обеих целей. Однако было бы неразумно использовать как %1, так и %3 для x в этом asm и ожидать, что они будут одинаковыми. Фактически, %3 может и не быть регистром. Это может быть символическая ссылка на память на объект, на который указывает x.
asm ("sumsq %0, %1, %2"
: "+f" (result)
: "r" (x), "r" (y), "m" (*x), "m" (*y));
Вот фиктивная *z++ = *x++ * *y++ инструкция. Обратите внимание, что x, y и z регистры указателей должны быть указаны как вход/выход, потому что asm их изменяет.
asm ("vecmul %0, %1, %2"
: "+r" (z), "+r" (x), "+r" (y), "=m" (*z)
: "m" (*x), "m" (*y));
Пример x86, где строковый аргумент памяти неизвестной длины.
asm("repne scasb"
: "=c" (count), "+D" (p)
: "m" (*(const char (*)[]) p), "0" (-1), "a" (0));
Если вы знаете, что это будет только чтение массива из десяти байт, то вы можете использовать вход в память, такой как: "m" (*(const char (*)[10]) p).
Вот пример реализации масштабирования вектора PowerPC в ассемблере, с полным сбросом векторов и кода условия, и некоторыми инициализированными регистрами смещения, которые не изменяются asm.
void
dscal (size_t n, double *x, double alpha)
{
asm ("/* lots of asm here */"
: "+m" (*(double (*)[n]) x), "+&r" (n), "+b" (x)
: "d" (alpha), "b" (32), "b" (48), "b" (64),
"b" (80), "b" (96), "b" (112)
: "cr0",
"vs32","vs33","vs34","vs35","vs36","vs37","vs38","vs39",
"vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47");
}
Вместо выделения фиксированных регистров через сбросы для предоставления регистров-временных значений для инструкции asm, альтернативой является определение переменной и ее использование в качестве выхода с ранним сбросом, как в a2 и a3 в примере ниже. Это предоставляет компилятору планировщику регистров большую свободу. Вы также можете определить переменную и сделать ее выходом, связанным с входом, как в a0 и a1, связанными соответственно с ap и lda. Конечно, со связанными выходами ваш asm не может использовать значение входа после изменения регистра выхода, так как они являются одним и тем же регистром. Более того, если вы опустите ранний сброс для выхода, возможно, GCC может выделить тот же регистр для другого входа, если GCC сможет доказать, что они имели одинаковое значение при входе в asm. Вот почему у a1 есть ранний сброс. Его связанный вход lda возможно, известно, что он имеет значение 16, и без раннего сброса он может разделить один и тот же регистр с %11. С другой стороны, ap не может быть таким же, как любой из других входов, поэтому ранний сброс для a0 не нужен. В данном случае это также нежелательно. Ранний сброс для a0 заставит GCC выделить отдельный регистр для входа "m" (*(const double (*)[]) ap). Обратите внимание, что привязка входа к выходу — это способ настроить инициализированный временный регистр, изменяемый инструкцией asm. Вход, не связанный с выходом, предполагается GCC неизмененным, например, "b" (16) ниже устанавливает %11 в 16, и GCC может использовать этот регистр в последующем коде, если значение 16 окажется необходимым. Вы даже можете использовать обычный asm выход для временного значения, если все входы, которые могут использовать один и тот же регистр, будут использованы до использования временного значения. Регистры VSX, сброшенные инструкцией asm, могли бы использовать эту технику, за исключением ограничения GCC на количество asm параметров.
static void
dgemv_kernel_4x4 (long n, const double *ap, long lda,
const double *x, double *y, double alpha)
{
double *a0;
double *a1;
double *a2;
double *a3;
__asm__
(
/* lots of asm here */
"#n=%1 ap=%8=%12 lda=%13 x=%7=%10 y=%0=%2 alpha=%9 o16=%11\n"
"#a0=%3 a1=%4 a2=%5 a3=%6"
:
"+m" (*(double (*)[n]) y),
"+&r" (n), // 1
"+b" (y), // 2
"=b" (a0), // 3
"=&b" (a1), // 4
"=&b" (a2), // 5
"=&b" (a3) // 6
:
"m" (*(const double (*)[n]) x),
"m" (*(const double (*)[]) ap),
"d" (alpha), // 9
"r" (x), // 10
"b" (16), // 11
"3" (ap), // 12
"4" (lda) // 13
:
"cr0",
"vs32","vs33","vs34","vs35","vs36","vs37",
"vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47"
);
}
6.47.2.7 Метки перехода
asm goto позволяет коду ассемблера переходить к одной или нескольким меткам C. Раздел GotoLabels в инструкции asm goto содержит список меток C, к которым может перейти код ассемблера, разделённый запятыми. GCC предполагает, что выполнение asm переходит к следующей инструкции (если это не так, рассмотрите использование __builtin_unreachable intrin после инструкции asm). Оптимизация asm goto может быть улучшена с помощью атрибутов меток hot и cold (см. Атрибуты меток).
Если код ассемблера что-либо изменяет, используйте сброс "memory" для принудительного сброса всех значений регистров в память и их повторной загрузки при необходимости после инструкции asm.
Также обратите внимание, что инструкция asm goto всегда неявно считается переменной.
Будьте осторожны, когда вы устанавливаете выходные операнды внутри asm goto только на некоторых возможных путях управления. Если вы не настраиваете выход на заданном пути и никогда не используете его на этом пути, все в порядке. В противном случае вы должны использовать модификатор ограничения ‘+’, означающий, что операнд является входом и выходом. С этим модификатором у вас будут правильные значения на всех возможных путях от asm goto.
Для ссылки на метку в шаблоне ассемблера используйте префикс ‘%l’ (маленькая буква ‘L’) за которым следует её (нумерация с нуля) позиция в GotoLabels плюс количество входных и выходных операндов. Выходной операнд с модификатором ограничения ‘+’ подсчитывается как два операнда, потому что он рассматривается как один выходной и один входной операнд. Например, если у asm есть три входа, один выходной операнд с модификатором ограничения ‘+’ и один выходной операнд с модификатором ограничения ‘=’ и ссылаются на две метки, ссылайтесь на первую метку как ‘%l6’ и на вторую как ‘%l7’).
В качестве альтернативы, вы можете ссылаться на метки, используя фактическое имя метки C в скобках. Например, чтобы сослаться на метку с именем carry, вы можете использовать ‘%l[carry]’. Метка должна всё ещё быть указана в разделе GotoLabels при использовании этого подхода. Лучше использовать именованные ссылки для меток, так как в этом случае вы можете избежать подсчёта входных и выходных операндов и специального обращения с выходными операндами, имеющими модификатор ограничения ‘+’.
Вот пример asm goto для i386:
asm goto (
"btl %1, %0\n\t"
"jc %l2"
: /* No outputs. */
: "r" (p1), "r" (p2)
: "cc"
: carry);
return 0;
carry:
return 1;
Следующий пример показывает asm goto с использованием сброса памяти.
int frob(int x)
{
int y;
asm goto ("frob %%r5, %1; jc %l[error]; mov (%2), %%r5"
: /* No outputs. */
: "r"(x), "r"(&y)
: "r5", "memory"
: error);
return y;
error:
return -1;
}
Следующий пример показывает asm goto, использующий выходной операнд.
int foo(int count)
{
asm goto ("dec %0; jb %l[stop]"
: "+r" (count)
:
:
: stop);
return count;
stop:
return 0;
}
Следующий искусственный пример демонстрирует asm goto, устанавливающий выходной операнд только на одном пути внутри asm goto. Использование модификатора ограничения = вместо + было бы неправильно, так как factor используется на всех путях от asm goto.
int foo(int inp)
{
int factor = 0;
asm goto ("cmp %1, 10; jb %l[lab]; mov 2, %0"
: "+r" (factor)
: "r" (inp)
:
: lab);
lab:
return inp * factor; /* return 2 * inp or 0 if inp < 10 */
}
6.47.2.8 Модификаторы операндов x86
Ссылки на входные, выходные и метки перехода операнды в шаблоне ассемблера расширенных инструкций asm могут использовать модификаторы для влияния на способ форматирования операндов в генерируемом коде, отправляемом в ассемблер. Например, следующий код использует модификаторы ‘h’ и ‘b’ для x86:
uint16_t num;
asm volatile ("xchg %h0, %b0" : "+a" (num) );
Эти модификаторы генерируют следующий код ассемблера:
xchg %ah, %al
Остальная часть этого обсуждения использует следующий код для наглядности.
int main()
{
int iInt = 1;
top:
asm volatile goto ("some assembler instructions here"
: /* No outputs. */
: "q" (iInt), "X" (sizeof(unsigned char) + 1), "i" (42)
: /* No clobbers. */
: top);
}
Без модификаторов, вот какой вывод от операндов был бы для диалектов ассемблера ‘att’ и ‘intel’:
| Операнд | ‘att’ | ‘intel’ |
|---|---|---|
%0 |
%eax |
eax |
%1 |
$2 |
2 |
%3 |
$.L3 |
OFFSET FLAT:.L3 |
%4 |
$8 |
8 |
%5 |
%xmm0 |
xmm0 |
%7 |
$0 |
0 |
В следующей таблице показан список поддерживаемых модификаторов и их действия.
| Модификатор | Описание | Операнд | ‘att’ | ‘intel’ |
|---|---|---|---|---|
A |
Вывести абсолютную ссылку на память. | %A0 |
*%rax |
rax |
b |
Вывести имя регистра в формате QImode. | %b0 |
%al |
al |
B |
Вывести суффикс опкода для b. | %B0 |
b |
|
c |
Требовать константный операнд и выводить выражение константы без знаков препинания. | %c1 |
2 |
2 |
d |
Выводить дублированный операнд регистра для инструкции AVX. | %d5 |
%xmm0, %xmm0 |
xmm0, xmm0 |
E |
Выводить адрес в формате Double Integer (DImode) (8 байт), если целевой процессор 64-битный. В противном случае формат не определён (VOIDmode). | %E1 |
%(rax) |
[rax] |
g |
Вывести имя регистра в формате V16SFmode. | %g0 |
%zmm0 |
zmm0 |
h |
Вывести имя регистра в формате QImode для «верхнего» регистра. | %h0 |
%ah |
ah |
H |
Добавить 8 байт к смещаемой ссылке на память. Полезно при доступе к старшим 8 байтам значений SSE. Для memref в (%rax) генерируется | %H0 |
8(%rax) |
8[rax] |
k |
Вывести имя регистра в формате SImode. | %k0 |
%eax |
eax |
l |
Вывести имя метки без знаков препинания. | %l3 |
.L3 |
.L3 |
L |
Вывести суффикс опкода для l. | %L0 |
l |
|
N |
Вывести maskz. | %N7 |
{z} |
{z} |
p |
Вывести исходное имя символа (без синтаксических префиксов). | %p2 |
42 |
42 |
P |
Если используется для функции, добавить PLT-суффикс и сгенерировать код PIC. Например, вывести foo@PLT вместо ’foo’ для функции foo(). Если используется для константы, удалить все синтаксические префиксы и вывести константу без префиксов. См. p выше. |
|||
q |
Вывести имя регистра в формате DImode. | %q0 |
%rax |
rax |
Q |
Вывести суффикс опкода для q. | %Q0 |
q |
|
R |
Вывести встроенное округление и sae. | %R4 |
{rn-sae}, |
, {rn-sae} |
r |
Вывести только sae. | %r4 |
{sae}, |
, {sae} |
s |
Вывести сдвиг двойного счётчика, за которым следует разделитель аргументов ассемблера, вывести суффикс опкода для s. | %s1 |
$2, |
2, |
S |
Вывести суффикс опкода для s. | %S0 |
s |
|
t |
Вывести имя регистра в формате V8SFmode. | %t5 |
%ymm0 |
ymm0 |
T |
Вывести суффикс опкода для t. | %T0 |
t |
|
V |
Вывести полное имя целочисленного регистра без %. | %V0 |
eax |
eax |
w |
Вывести имя регистра в формате HImode. | %w0 |
%ax |
ax |
W |
Вывести суффикс опкода для w. | %W0 |
w |
|
x |
Вывести имя регистра в формате V4SFmode. | %x5 |
%xmm0 |
xmm0 |
y |
Вывести "st(0)" вместо "st" как регистр. | %y6 |
%st(0) |
st(0) |
z |
Вывести суффикс опкода, соответствующий размеру текущего целочисленного операнда (один из b/w/l/q). |
%z0 |
l |
|
Z |
Как z, с дополнительными суффиксами для инструкций x87. |
6.47.2.9 Операнды x86 с плавающей точкой в ассемблере
Для x86-целевых платформ существует несколько правил использования регистров стека в операндах asm. Эти правила применяются только к операндам, являющимся регистрами стека:
- Учитывая набор входных регистров, которые уничтожаются в
asm, необходимо знать, которые из них неявно извлекаютсяasm, а какие нужно явно извлечь GCC.Входной регистр, который неявно извлекается
asm, должен быть явно испорчен, если только он не ограничен для сопоставления с выходным операндом. - Для любого входного регистра, который неявно извлекается
asm, необходимо знать, как скорректировать стек, чтобы компенсировать извлечение. Если какой-либо не извлечённый входной операнд находится ближе к вершине стека регистров, чем неявно извлечённый регистр, будет невозможно определить, как выглядел стек — неясно, как «сдвигаются» остальные элементы стека.Все неявно извлекаемые входные регистры должны быть ближе к вершине стека регистров, чем любые входные, которые не извлекаются неявно.
Возможен случай, когда, если входной операнд уничтожается в
asm, компилятор может использовать входной регистр для перезагрузки выходного. Рассмотрим пример:asm ("foo" : "=t" (a) : "f" (b));Этот код говорит, что входной регистр
bне извлекаетсяasm, и чтоasmпомещает результат в стек регистров, т.е., стек становится глубже послеasmоперации, чем до неё. Но перезагрузка может предположить, что может использовать один и тот же регистр как для входного, так и для выходного значения.Чтобы этого избежать, если любой входной операнд использует ограничение ‘f’, все ограничения выходных регистров должны использовать модификатор «&» для раннего уничтожения.
Пример выше должен быть записан так:
asm ("foo" : "=&t" (a) : "f" (b)); - Некоторые операнды должны находиться в определённых местах стека. Все выходные операнды попадают в эту категорию — GCC не может узнать, в каких регистрах находятся выходные значения, если вы не укажете это в ограничениях.
Выходные операнды должны явно указывать, в каком регистре находится выходное значение после
asm. ‘=f’ недопустимо: ограничения операндов должны выбирать класс с единственным регистром. - Выходные операнды не могут «вставляться» между существующими регистрами стека. Поскольку ни одна 387-инструкция не использует операнд на чтение/запись, все выходные операнды уничтожаются до
asm, и помещаютсяasmоперацией. Нет смысла помещать значения не в самый верх стека регистров.Выходные операнды должны начинаться вверху стека регистров: выходные операнды не могут «пропускать» регистр.
- Некоторые
asmоператоры могут потребовать дополнительного места в стеке для внутренних вычислений. Это можно гарантировать, испортив регистры стека, не относящиеся к входным и выходным значениям.
Эта asm принимает один входной, который неявно извлекается, и даёт два выходных.
asm ("fsincos" : "=t" (cos), "=u" (sin) : "0" (inp));
Эта asm принимает два входных, которые извлекаются неявно fyl2xp1 операцией, и заменяет их одним выходным. st(1) необходимо для компилятора, чтобы знать, что fyl2xp1 извлекает оба входных значения.
asm ("fyl2xp1" : "=t" (result) : "0" (x), "u" (y) : "st(1)");
6.47.2.10 Модификаторы операндов MSP430
Ниже приведен список поддерживаемых модификаторов и их эффектов для MSP430.
| Модификатор | Описание |
|---|---|
A |
Выбор младших 16 бит константы/регистра/операнда памяти. |
B |
Выбор старших 16 бит константы/регистра/операнда памяти. |
C |
Выбор бит 32-47 константы/регистра/операнда памяти. |
D |
Выбор бит 48-63 константы/регистра/операнда памяти. |
H |
Эквивалентно B (для обратной совместимости). |
I |
Выводит инверсию (логическое NOT) значения константы. |
J |
Выводит целое число без префикса #. |
L |
Эквивалентно A (для обратной совместимости). |
O |
Смещение текущей рамки от вершины стека. |
Q |
Используйте постфикс инструкции A. |
R |
Инверсия кода условия, для беззнаковых сравнений. |
W |
Вычитает 16 из значения константы. |
X |
Используйте постфикс инструкции X. |
Y |
Вычитает 4 из значения константы. |
Z |
Вычитает 1 из значения константы. |
b |
Добавляет .B, .W или .A к инструкции, в зависимости от режима. |
d |
Смещение на 1 байт ссылки на память или значения константы. |
e |
Смещение на 3 байта ссылки на память или значения константы. |
f |
Смещение на 5 байтов ссылки на память или значения константы. |
g |
Смещение на 7 байтов ссылки на память или значения константы. |
p |
Выводит значение 2, возведённое в степень заданной константы. Используется для выбора заданной позиции бита. |
r |
Инверсия кода условия, для знаковых сравнений. |
x |
Эквивалентно X, но только для указателей. |
Далее: Ограничения, Предыдущее: Базовый ассемблер, Вверх: Использование ассемблерного языка с C [Содержание][Индекс]
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Extended-Asm.html