6.47.2 Расширенные инструкции ассемблера с операндами выражений 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 могут быть вне функций (см. Основные инструкции ассемблера без операндов). Функции, объявленные с атрибутом 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.
- Volatile
- Шаблон ассемблера
- Выходные операнды
- Выходные операнды флагов
- Входные операнды
- Clobbers и регистры-временные переменные
- Метки перехода
- Общие модификаторы операндов
- Модификаторы операндов x86
- Операнды x86 с плавающей точкой
asm - Модификаторы операндов MSP430
- Модификаторы операндов LoongArch
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.
Если вашему коду необходимо поддерживать несколько диалектов ассемблера (например, если вы пишете публичные заголовки, которые должны поддерживать различные варианты компиляции), используйте конструкции такого вида:
{ 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
-
Строковая константа, задающая ограничения на размещение операнда; см. Ограничения для операндов
asmдля деталей.Ограничения вывода должны начинаться с ‘=’ (перезапись существующего значения) или ‘+’ (чтение и запись). При использовании ‘=’, не предполагайте, что расположение содержит существующее значение при входе в
asm, за исключением случаев, когда операнд связан с вводом; см. Операнды ввода.После префикса должно быть одно или несколько дополнительных ограничений (см. Ограничения для операндов
asm), описывающих, где находится значение. Общие ограничения включают ‘r’ для регистра и ‘m’ для памяти. Если вы перечисляете несколько возможных расположений (например,"=rm"), компилятор выбирает наиболее эффективный вариант в зависимости от текущего контекста. Если вы перечислите все возможные варианты, разрешенные инструкциейasm, вы позволите оптимизаторам сгенерировать наилучший возможный код. Если вам необходим конкретный регистр, но ограничения вашей машины не обеспечивают достаточный контроль для выбора нужного регистра, локальные регистровые переменные могут предоставить решение (см. Указание регистров для локальных переменных). - cvariablename
-
Указывает выражение C lvalue для хранения вывода, обычно имя переменной. Вложенные скобки являются обязательной частью синтаксиса.
При выборе компилятором регистров для представления операндов вывода он не использует ни один из регистров, помеченных как модифицированные (см. Регистры модификаций и временные регистры).
Выражения операндов вывода должны быть lvalue. Компилятор не может проверить, соответствуют ли типы операндов инструкциям.
Для выражений вывода, которые не являются непосредственно адресуемыми (например, битовое поле), ограничение должно допускать регистр. В этом случае GCC использует регистр как вывод инструкции asm, а затем сохраняет этот регистр в вывод.
Операнды с модификатором ограничения ‘+’ считаются двумя операндами (то есть как входными, так и выходными) в отношении общего максимального значения в 30 операндов на инструкцию asm.
Используйте модификатор ограничения ‘&’ (см. Символы модификатора ограничения) для всех операндов вывода, которые не должны перекрываться с вводом. В противном случае GCC может разместить операнд вывода в том же регистре, что и не связанный с ним операнд ввода, исходя из предположения, что код ассемблера потребляет свои входные данные до выработки выходов. Это предположение может быть ложным, если код ассемблера на самом деле состоит из более одной инструкции.
Такая же проблема может возникнуть, если один параметр вывода (a) допускает ограничение регистра, а другой (b) — ограничение памяти. Код, сгенерированный GCC для доступа к адресу памяти в b, может содержать регистры, которые могут быть общими с a, и GCC считает эти регистры входными данными для инструкции asm. Как и выше, GCC предполагает, что такие регистры ввода потребляются до записи любых выходов. Это предположение может привести к неверному поведению, если инструкция asm записывает в a до использования b. Добавление модификатора ‘&’ к ограничению регистра для a гарантирует, что изменение a не повлияет на адрес, на который ссылается b. В противном случае расположение b является неопределённым, если a изменяется до использования b.
asm поддерживает модификаторы операндов (например, ‘%k2’ вместо просто ‘%2’). Общие модификаторы операндов перечисляет доступные модификаторы для всех целей. Другие модификаторы зависят от оборудования. Например, список поддерживаемых модификаторов для x86 находится в модификаторах операндов x86.
Если 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«не» флаг, или инвертированные версии вышеперечисленных
6.47.2.5 Входные операнды
Входные операнды предоставляют значения из переменных и выражений C для кода ассемблера.
Операнды разделяются запятыми. Каждый операнд имеет следующий формат:
[ [asmSymbolicName] ] constraint (cexpression)
- asmSymbolicName
-
Указывает символьное имя для операнда. Ссылайтесь на имя в шаблоне ассемблера, заключив его в квадратные скобки (например, ‘%[Value]’). Область действия имени — оператор
asm, содержащий определение. Любое допустимое имя C-переменной допустимо, включая имена, уже определённые в окружающем коде. Два операнда внутри одного оператораasmне могут использовать одно и то же символьное имя.Если asmSymbolicName не используется, используйте (нумерацию с нуля) позицию операнда в списке операндов в шаблоне ассемблера. Например, если есть два выходных операнда и три входных, используйте ‘%2’ для первого входного операнда, ‘%3’ для второго и ‘%4’ для третьего.
- constraint
-
Строковая константа, определяющая ограничения на размещение операнда; см. Ограничения для
asmоперандов для подробностей.Строки ограничений входных операндов не должны начинаться с ‘=’ или ‘+’. Когда вы указываете более одного возможного расположения (например, ‘"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 может изменять больше, чем только выходные данные. Например, вычисления могут потребовать дополнительных регистров, или процессор может перезаписать регистр как побочный эффект конкретной ассемблерной инструкции. Для того, чтобы проинформировать компилятор об этих изменениях, перечислите их в списке переменных. Элементы списка переменных — это либо имена регистров, либо специальные переменные (перечисленные ниже). Каждый элемент списка переменных — это строковая константа, заключенная в двойные кавычки и разделенная запятыми.
Описание переменных не должно никоим образом перекрываться с входными или выходными операндами. Например, у вас не может быть операнда, описывающего класс регистров с одним членом, при одновременном указании этого регистра в списке переменных. Переменные, объявленные как находящиеся в определённых регистрах (см. Переменные в указанных регистрах) и используемые в качестве входных или выходных операндов, не должны упоминаться в описании переменных. В частности, нет способа указать, что входные операнды изменяются, не указав их также в качестве выходных операндов.
Когда компилятор выбирает регистры для представления входных и выходных операндов, он не использует ни один из регистров, помеченных как переменные. В результате регистры, помеченные как переменные, доступны для любого использования в ассемблерном коде.
Другое ограничение состоит в том, что список переменных не должен содержать регистр стека. Это связано с тем, что компилятор требует, чтобы значение регистра стека было таким же после инструкции, как и при входе в неё. Тем не менее, предыдущие версии 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 Метки перехода Goto
asm goto позволяет коду ассемблера переходить к одной или нескольким меткам C. Раздел GotoLabels в инструкции asm goto содержит список меток C, разделенных запятыми, к которым код ассемблера может перейти. GCC предполагает, что выполнение asm переходит к следующей инструкции (если это не так, используйте атрибут __builtin_unreachable после инструкции 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 Общие модификаторы операндов
В следующей таблице показаны поддерживаемые всеми целевыми платформами модификаторы и их эффекты:
| Модификатор | Описание | Пример |
|---|---|---|
c |
Требуется константный операнд и выводит константное выражение без знаков препинания. | %c0 |
n |
Подобно ‘%c’, за исключением того, что значение константы меняет знак до вывода. | %n0 |
a |
Заменяет ссылку на память, при этом фактический операнд обрабатывается как адрес. Это может быть полезно при выводе инструкции «загрузки адреса», так как часто синтаксис ассемблера для такой инструкции требует записи операнда так, как будто он является ссылкой на память. | %a0 |
l |
Выводит имя метки без знаков препинания. | %l0 |
6.47.2.9 Модификаторы операндов 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.10 Операторы 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.11 Модификаторы операндов 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, но только для указателей. |
6.47.2.12 Модификаторы операндов LoongArch
Список ниже описывает поддерживаемые модификаторы и их эффекты для LoongArch.
| Модификатор | Описание |
|---|---|
d |
Аналогично c. |
i |
Вывод символа ”i” если операнд не является регистром. |
m |
Аналогично c, но выводимое значение — operand - 1. |
X |
Вывод константного целочисленного операнда в шестнадцатеричном формате. |
z |
Вывод операнда в исходном виде, после запятой. |
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-13.3.0/gcc/Extended-Asm.html