6.11.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 and Scratch Registers.
- GotoLabels
-
При использовании формы
gotoдляasmэтот раздел содержит список всех меток C, к которым может перейти код в AssemblerTemplate. См. GotoLabels.Операторы
asmне могут выполнять переходы в другие операторыasm, а могут переходить только к перечисленным меткам GotoLabels. Оптимизаторы GCC не знают о других переходах, поэтому не могут учитывать их при выборе способа оптимизации.
Общее количество входных, выходных операндов и операндов перехода ограничено 30.
Примечания
Оператор asm позволяет включать инструкции ассемблера непосредственно в код C. Это может помочь повысить производительность кода, чувствительного ко времени выполнения, или получить доступ к инструкциям ассемблера, которые недоступны для программ на C.
Как и обычные операторы asm, операторы расширенного asm можно использовать как внутри функции C, так и на уровне файла («на верхнем уровне»). Этот приём позволяет выводить директивы ассемблера, определять макросы на языке ассемблера для последующего вызова в файле или писать целые функции на языке ассемблера. Операторы расширенного asm вне функций не могут использовать квалификаторы, указывать clobbers, использовать модификаторы %, + или & в ограничениях и могут использовать только те ограничения, которые не допускают использование регистров.
Для функций, объявленных с атрибутом 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 и увеличивает dst на 1.
- Volatile
- Шаблон ассемблера
- Выходные операнды
- Выходные операнды флагов
- Входные операнды
- Clobbers и временные регистры
- Метки перехода
- Общие модификаторы операндов
- Модификаторы операндов AArch64
- Модификаторы операндов x86
- Операнды
asmс плавающей точкой x86 - Модификаторы операндов MSP430
- Модификаторы операндов LoongArch
- Модификаторы операндов RISC-V
- Модификаторы операндов SH
6.11.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.11.2.2 Шаблон ассемблера
Шаблон ассемблера — это строковый литерал, содержащий инструкции ассемблера. В C++ с -std=gnu++11 или более поздней версией шаблон ассемблера также может быть константным выражением в скобках (см. Константные выражения C++11 вместо строковых литералов).
Компилятор заменяет в шаблоне токены, ссылающиеся на входные, выходные параметры и метки перехода, а затем передаёт полученную строку ассемблеру. Строка может содержать любые распознаваемые ассемблером инструкции, включая директивы. GCC не анализирует сами инструкции ассемблера и не знает, что они означают и являются ли они допустимым вводом для ассемблера. Однако он подсчитывает инструкции (см. Размер asm).
В одну строку asm можно поместить несколько инструкций ассемблера, разделив их символами, обычно используемыми в ассемблерном коде данной системы. В большинстве случаев подходит сочетание символа новой строки для переноса строки и символа табуляции для перехода к полю инструкции (записывается как ‘\n\t’). В некоторых ассемблерах в качестве разделителя строк используются точки с запятой. Однако учтите, что в некоторых диалектах ассемблера точка с запятой начинает комментарий.
Не рассчитывайте, что последовательность операторов asm останется совершенно непрерывной после компиляции, даже если используется квалификатор volatile. Если некоторые инструкции должны оставаться в выходном коде подряд, поместите их в один оператор asm с несколькими инструкциями.
Доступ к данным из программ C без использования входных/выходных операндов (например, прямое обращение к глобальным символам из шаблона ассемблера) может работать не так, как ожидается. Аналогично, прямой вызов функций из шаблона ассемблера требует подробного знания целевого ассемблера и ABI.
Поскольку GCC не анализирует шаблон ассемблера, он не видит символы, на которые тот ссылается. В результате GCC может отбросить такие символы как неиспользуемые, если они также не перечислены во входных, выходных операндах или операндах перехода.
Строки специального формата
Помимо токенов, описанных для входных, выходных операндов и операндов перехода, следующие токены в шаблоне ассемблера имеют специальное значение:
- ‘%%’
-
Выводит в код ассемблера один символ ‘%’.
- ‘%=’
-
Выводит число, уникальное для каждого экземпляра оператора
asmво всей компиляции. Этот параметр полезен для создания локальных меток и многократных ссылок на них в одном шаблоне, генерирующем несколько инструкций ассемблера. - ‘%{’
- ‘%|’
- ‘%}’
Выводит в код ассемблера символы ‘{’, ‘|’ и ‘}’ соответственно. Если не экранировать эти символы, они имеют специальное значение и обозначают несколько диалектов ассемблера, как описано ниже.
Несколько диалектов ассемблера в шаблонах asm
Для таких целевых архитектур, как x86, GCC поддерживает несколько диалектов ассемблера. Параметр -masm задаёт диалект, используемый GCC по умолчанию для встроенного ассемблера. В документации по целевой архитектуре для параметра -masm перечислены поддерживаемые диалекты, а также диалект по умолчанию, если параметр не указан. Эта информация может быть важна для понимания: код ассемблера, корректно работающий при компиляции с одним диалектом, скорее всего, завершится ошибкой при компиляции с другим. См. Параметры x86.
Если ваш код должен поддерживать несколько диалектов ассемблера (например, если вы пишете общедоступные заголовочные файлы, которые должны поддерживать различные параметры компиляции), используйте конструкции следующего вида:
{ dialect0 | dialect1 | dialect2... }
При компиляции кода с диалектом № 0 эта конструкция выводит dialect0, с диалектом № 1 — dialect1 и так далее. Если в фигурных скобках указано меньше вариантов, чем поддерживает компилятор, конструкция ничего не выводит.
Например, если компилятор 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.11.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
-
Задаёт необязательное символьное имя операнда. Буквальные квадратные скобки ‘[]’ вокруг asmSymbolicName обязательны как в спецификации операнда, так и в ссылках на него в шаблоне ассемблера, например ‘%[Value]’. Область видимости имени ограничена оператором
asm, содержащим его определение. Допускается любой допустимый идентификатор C, в том числе имена, уже определённые в окружающем коде. В одном оператореasmнельзя использовать одинаковые символьные имена для разных операндов.Если asmSymbolicName не используется, указывайте позицию операнда в списке операндов шаблона ассемблера (нумерация начинается с нуля). Например, если есть три выходных операнда, для ссылки на первый в шаблоне используйте ‘%0’, для второго — ‘%1’, а для третьего — ‘%2’.
- constraint
-
Строковая константа, задающая ограничения на размещение операнда. Подробнее см. Ограничения для операндов
asm. В C++ с -std=gnu++11 или более поздней версией ограничение также может быть константным выражением в скобках (см. Константные выражения C++11 вместо строковых литералов).Выходные ограничения должны начинаться с ‘=’ (переменная заменяет существующее значение) или ‘+’ (при чтении и записи). При использовании ‘=’ не следует считать, что при входе в
asmв указанном месте находится существующее значение, кроме случая, когда операнд связан с входным; см. Входные операнды.После префикса должно быть указано одно или несколько дополнительных ограничений (см. Ограничения для операндов
asm), описывающих, где находится значение. Часто используются ограничения ‘r’ для регистра и ‘m’ для памяти. Если указано несколько возможных мест (например,"=rm"), компилятор выбирает наиболее эффективное с учётом текущего контекста. Если перечислить столько альтернатив, сколько допускает операторasm, это позволит оптимизаторам сгенерировать наилучший код. Если необходимо использовать конкретный регистр, но машинные ограничения не позволяют выбрать нужный регистр с достаточной точностью, решить эту задачу могут локальные регистровые переменные (см. Указание регистров для локальных переменных). - cvariablename
-
Задаёт выражение lvalue языка C для хранения выходного значения, обычно имя переменной. Окружающие скобки являются обязательной частью синтаксиса.
Выбирая регистры для представления выходных операндов, компилятор не использует регистры, помеченные как clobbered (см. Clobbers и временные регистры).
Выражения выходных операндов должны быть 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.11.2.4 Выходные операнды флагов
В некоторых целевых архитектурах есть специальный регистр, в котором хранятся «флаги» результата операции или сравнения. Обычно содержимое этого регистра либо не изменяется asm, либо оператор asm считается затирающим его содержимое.
В некоторых целевых архитектурах имеется специальная форма выходного операнда, позволяющая использовать условия из регистра флагов в качестве выходных значений asm. Набор поддерживаемых условий зависит от целевой архитектуры, но общее правило состоит в том, что выходная переменная должна быть целым скалярным типом, а её значение — логическим. Если такая форма поддерживается, целевая архитектура определяет символ препроцессора __GCC_ASM_FLAG_OUTPUTS__.
Из-за особой природы выходных операндов флагов ограничение не может содержать альтернативы.
Чаще всего в целевой архитектуре есть только один регистр флагов, поэтому он является подразумеваемым операндом многих инструкций. В этом случае на операнд не следует ссылаться в шаблоне ассемблера через %0 и т. п., поскольку в языке ассемблера ему не соответствует никакой текст.
- ARM
- AArch64
-
Ограничения выходных флагов для семейства ARM имеют вид ‘=@cccond’, где cond — одно из стандартных условий, определённых в ARM 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
-
Ограничения выходных флагов для семейства x86 имеют вид ‘=@cccond’, где cond — одно из стандартных условий, определённых в руководстве по ISA для
jccилиsetcc.a«выше» или беззнаковое значение больше
ae«выше или равно» или беззнаковое значение больше или равно
b«ниже» или беззнаковое значение меньше
be«ниже или равно» или беззнаковое значение меньше или равно
cфлаг переноса установлен
ez«равно» или флаг нуля установлен
gзнаковое значение больше
geзнаковое значение больше или равно
lзнаковое значение меньше
leзнаковое значение меньше или равно
oфлаг переполнения установлен
pфлаг чётности установлен
sфлаг знака установлен
nanaenbnbencnengngenlnlenonpnsnz«не» flag или инвертированные варианты перечисленных выше условий
- s390
-
Ограничение выходных флагов для s390 — ‘=@cc’. Допускается только одно такое ограничение. Значение должно сохраняться в переменной типа ‘int’.
6.11.2.5 Входные операнды
Входные операнды делают значения переменных и выражений C доступными для кода на ассемблере.
Операнды разделяются запятыми. Каждый операнд имеет следующий формат:
[ [asmSymbolicName] ] constraint (cexpression)
- asmSymbolicName
-
Задает необязательное символическое имя операнда. Литеральные квадратные скобки ‘[]’ вокруг asmSymbolicName обязательны как в спецификации операнда, так и в ссылках на операнд в шаблоне ассемблера, например ‘%[Value]’. Область видимости имени — оператор
asm, содержащий определение. Допустим любой идентификатор C, в том числе имена, уже определенные в окружающем коде. В одном оператореasmнельзя использовать одно и то же символическое имя для двух операндов.Если asmSymbolicName не используется, ссылайтесь на операнд по его позиции (с нуля) в списке операндов в шаблоне ассемблера. Например, если имеются два выходных операнда и три входных, используйте в шаблоне ‘%2’ для ссылки на первый входной операнд, ‘%3’ — на второй, а ‘%4’ — на третий.
- constraint
-
Строковая константа, задающая ограничения на размещение операнда; подробности см. в разделе Ограничения для операндов
asm. В C++ с -std=gnu++11 или новее ограничение также может быть константным выражением в круглых скобках (см. Константные выражения C++11 вместо строковых литералов).Строки ограничений для входных операндов не могут начинаться ни с ‘=’, ни с ‘+’. Если вы перечисляете несколько возможных мест размещения (например, ‘"irm"’), компилятор выбирает наиболее эффективное с учетом текущего контекста. Если необходимо использовать определенный регистр, но машинные ограничения не позволяют выбрать нужный регистр с достаточной точностью, решением могут стать локальные регистровые переменные (см. Задание регистров для локальных переменных).
Ограничения для входных операндов также могут быть цифрами (например,
"0"). Это означает, что указанный входной операнд должен находиться в том же месте, что и выходной операнд с индексом (начиная с нуля) в списке ограничений выходных операндов. Если для выходных операндов используется синтаксис asmSymbolicName, вместо цифр можно использовать эти имена (заключенные в квадратные скобки ‘[]’). - cexpression
-
Переменная или выражение C, передаваемое оператору
asmв качестве входных данных. Обрамляющие круглые скобки являются обязательной частью синтаксиса.
При выборе регистров для представления входных операндов компилятор не использует регистры, указанные в списке clobber (см. Регистры clobber и временные регистры).
Если выходных операндов нет, но есть входные, поставьте два двоеточия подряд на месте выходных операндов:
__asm__ ("some instructions"
: /* No outputs. */
: "r" (Offset / 8));
Предупреждение: Не изменяйте содержимое операндов, предназначенных только для входных данных (за исключением входных операндов, связанных с выходными). Компилятор предполагает, что при выходе из оператора asm эти операнды содержат те же значения, что и до выполнения оператора. Использовать clobber, чтобы сообщить компилятору об изменении значений этих входных операндов, невозможно. Один из распространенных способов обойти это ограничение — связать изменяемую входную переменную с выходной переменной, которая нигде не используется. Однако обратите внимание: если в коде после оператора 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.11.2.6 Регистры clobber и временные регистры
Компилятор учитывает изменения в элементах, перечисленных среди выходных операндов, однако встроенный код asm может изменять не только выходные операнды. Например, вычисления могут потребовать дополнительных регистров, или процессор может перезаписать регистр как побочный эффект определенной инструкции ассемблера. Чтобы сообщить компилятору об этих изменениях, перечислите их в списке clobber. Элементами этого списка могут быть имена регистров или специальные элементы clobber (перечисленные ниже). Каждый элемент списка clobber — это строковая константа в двойных кавычках; элементы разделяются запятыми. В C++ с -std=gnu++11 или новее элемент списка clobber также может быть константным выражением в круглых скобках (см. Константные выражения C++11 вместо строковых литералов).
Описания clobber ни в коем случае не должны пересекаться с входными или выходными операндами. Например, нельзя описать операнд как класс регистров с единственным элементом, одновременно указывая этот регистр в списке clobber. Переменные, объявленные как размещенные в определенных регистрах (см. Переменные в заданных регистрах) и используемые в качестве входных или выходных операндов asm, не должны ни в какой части упоминаться в описании clobber. В частности, невозможно указать, что входные операнды изменяются, не указав их также в качестве выходных операндов.
Выбирая регистры для представления входных и выходных операндов, компилятор не использует регистры, перечисленные в clobber. Поэтому эти регистры можно свободно использовать в ассемблерном коде.
Еще одно ограничение: список clobber не должен содержать регистр указателя стека. Это связано с тем, что компилятор требует, чтобы после оператора asm значение указателя стека совпадало со значением при входе в этот оператор. Однако в предыдущих версиях GCC это правило не соблюдалось: регистр указателя стека разрешалось указывать в списке, а семантика такого использования была неясной. Такое поведение объявлено устаревшим, и в будущих версиях GCC указание указателя стека может стать ошибкой.
Вот реалистичный пример для VAX, демонстрирующий использование регистров clobber:
asm volatile ("movc3 %0, %1, %2"
: /* No outputs. */
: "g" (from), "g" (to), "g" (count)
: "r0", "r1", "r2", "r3", "r4", "r5", "memory");
Также существуют три специальных аргумента clobber:
"cc"-
Элемент clobber
"cc"указывает, что ассемблерный код изменяет регистр флагов. На некоторых машинах GCC представляет коды условий как определенный аппаратный регистр;"cc"служит для обозначения этого регистра. На других машинах обработка кодов условий устроена иначе, и указание"cc"не имеет эффекта. Однако оно допустимо независимо от целевой платформы. "memory"-
Элемент clobber
"memory"сообщает компилятору, что ассемблерный код выполняет чтение или запись в память для объектов, не перечисленных среди входных и выходных операндов (например, обращается к памяти, на которую указывает один из входных параметров). Чтобы обеспечить корректное содержимое памяти, GCC может потребоваться перед выполнениемasmсбросить в память значения определенных регистров. Кроме того, компилятор не предполагает, что значения, прочитанные из памяти доasm, останутся неизменными после этогоasm; при необходимости он загружает их повторно. Использование clobber"memory"фактически создает для компилятора барьер чтения и записи памяти.Обратите внимание: этот элемент clobber не мешает процессору выполнять спекулятивное чтение после оператора
asm. Чтобы предотвратить это, нужны специфичные для процессора инструкции барьера. "redzone"-
Элемент clobber
"redzone"сообщает компилятору, что ассемблерный код может записывать в красную зону стека — область под указателем стека, которая в некоторых архитектурах и соглашениях о вызовах гарантированно не изменяется обработчиками сигналов, прерываниями или исключениями, поэтому компилятор может хранить в ней временные данные в листовых функциях. На целевых платформах, где понятия красной зоны стека нет, этот элемент clobber игнорируется. Его, например, следует использовать, если ассемблерный код вызывает функции или помещает что-либо в стек, не учитывая красную зону: перед этим не вычитает размер красной зоны из указателя стека и не восстанавливает его после.
Сброс регистров в память влияет на производительность и может быть проблемой для кода, чувствительного ко времени выполнения. Чтобы избежать этого, можно предоставить 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 на ассемблере: он включает clobber векторных регистров и кодов условий, а также несколько инициализированных регистров смещения, которые не изменяются оператором 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");
}
Вместо выделения фиксированных регистров через clobber для предоставления временных регистров оператору asm можно объявить переменную и задать для нее выходной операнд с ранним clobber, как для a2 и a3 в примере ниже. Это дает распределителю регистров компилятора больше свободы. Можно также объявить переменную и задать для нее выходной операнд, связанный с входным, как для a0 и a1, связанных соответственно с ap и lda. Разумеется, при связанных выходных операндах asm не может использовать входное значение после изменения выходного регистра, поскольку это один и тот же регистр. Кроме того, если не указать для выходного операнда ранний clobber, GCC может выделить тот же регистр для другого входного операнда, если сможет доказать, что при входе в asm они имеют одинаковое значение. Именно поэтому для a1 задан ранний clobber. Его связанный входной операнд, lda, теоретически может иметь значение 16; без раннего clobber он мог бы использовать тот же регистр, что и %11. С другой стороны, ap не может совпадать ни с одним из других входных операндов, поэтому ранний clobber для a0 не нужен. Более того, в данном случае он нежелателен. Ранний clobber для a0 заставил бы GCC выделить отдельный регистр для входного операнда "m" (*(const double (*)[]) ap). Обратите внимание: связывание входного операнда с выходным — это способ задать инициализированный временный регистр, изменяемый оператором asm. GCC предполагает, что входной операнд, не связанный с выходным, остается неизменным. Например, в приведенном ниже коде "b" (16) задает для %11 значение 16, и GCC может использовать этот регистр в следующем коде, если там потребуется значение 16. Для временного регистра можно использовать и обычный выходной операнд asm, если все входные операнды, которые могут использовать тот же регистр, потребляются до того, как этот временный регистр будет использован. Регистры VSX, указанные в clobber оператора 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.11.2.7 Метки goto
asm goto позволяет ассемблерному коду переходить к одной или нескольким меткам C. Раздел GotoLabels оператора asm goto содержит разделенный запятыми список всех меток C, к которым может перейти ассемблерный код. GCC предполагает, что выполнение asm продолжается со следующего оператора (если это не так, рассмотрите возможность использования встроенной функции __builtin_unreachable после оператора asm). Оптимизацию asm goto можно улучшить с помощью атрибутов меток hot и cold (см. Атрибуты меток).
Если ассемблерный код что-либо изменяет, используйте clobber "memory", чтобы заставить оптимизаторы сбросить все значения регистров в память и при необходимости загрузить их повторно после оператора asm.
Также обратите внимание, что оператор asm goto всегда неявно считается volatile.
Будьте осторожны, задавая выходные операнды внутри 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, использующий clobber памяти.
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.11.2.8 Универсальные модификаторы операндов
В следующей таблице показаны модификаторы, поддерживаемые всеми целевыми платформами, и их действие:
| Модификатор | Описание | Пример |
|---|---|---|
c |
Требует константный операнд и выводит константное выражение без знаков пунктуации. | %c0 |
cc |
Аналогичен ‘%c’, но с более настойчивой попыткой вывести значение без знаков пунктуации. Например, на некоторых архитектурах ‘%c’ может не суметь вывести константные адреса в позиционно-независимом коде. | %cc0 |
n |
Аналогичен ‘%c’, но перед выводом значение константы меняется на противоположное. | %n0 |
a |
Подставляет ссылку на память, рассматривая сам операнд как адрес. Это может быть полезно при выводе инструкции «загрузить адрес», поскольку синтаксис ассемблера для таких инструкций часто требует записывать операнд так, как если бы он был ссылкой на память. | %a0 |
l |
Выводит имя метки без знаков пунктуации. | %l0 |
6.11.2.9 Модификаторы операндов AArch64
В следующей таблице показаны модификаторы, поддерживаемые AArch64, и их действие:
| Модификатор | Описание |
|---|---|
w |
Выводит имя 32-разрядного регистра общего назначения или, если операндом является константный ноль, 32-разрядный нулевой регистр (wzr). |
x |
Выводит имя 64-разрядного регистра общего назначения или, если операндом является константный ноль, 64-разрядный нулевой регистр (xzr). |
b |
Выводит имя регистра FP/SIMD с префиксом b (байт, 8 бит). |
h |
Выводит имя регистра FP/SIMD с префиксом h (полуслово, 16 бит). |
s |
Выводит имя регистра FP/SIMD с префиксом s (слово, 32 бита). |
d |
Выводит имя регистра FP/SIMD с префиксом d (двойное слово, 64 бита). |
q |
Выводит имя регистра FP/SIMD с префиксом q (четверное слово, 128 бит). |
Z |
Выводит имя регистра FP/SIMD как регистр SVE (то есть с префиксом z). Для операндов-регистров SVE ничего не меняет. |
6.11.2.10 Модификаторы операндов x86
В ссылках на входные, выходные операнды и операнды goto в шаблоне ассемблера расширенных операторов asm можно использовать модификаторы, влияющие на форматирование операндов в коде, выводимом для ассемблера. Например, в следующем коде для x86 используются модификаторы ‘h’ и ‘b’:
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 |
В таблице ниже перечислены поддерживаемые модификаторы и их действие.
| Модификатор | Описание | Операнд | ‘att’ | ‘intel’ |
|---|---|---|---|---|
A |
Выводит абсолютную ссылку на память. | %A0 |
*%rax |
rax |
b |
Выводит имя регистра в режиме QImode. | %b0 |
%al |
al |
c |
Требует константный операнд и выводит константное выражение без знаков пунктуации. | %c1 |
2 |
2 |
E |
Если целевая платформа 64-разрядная, выводит адрес в режиме Double Integer (DImode, 8 байт). В противном случае режим не определен (VOIDmode). | %E1 |
%(rax) |
[rax] |
h |
Выводит имя регистра QImode для «старшего» регистра. | %h0 |
%ah |
ah |
H |
Добавляет 8 байт к смещаемой ссылке на память. Полезно для доступа к старшим 8 байтам значений SSE. Для ссылки на память вида (%rax) генерируется | %H0 |
8(%rax) |
8[rax] |
k |
Выводит имя регистра в режиме SImode. | %k0 |
%eax |
eax |
l |
Выводит имя метки без знаков пунктуации. | %l3 |
.L3 |
.L3 |
p |
Выводит имя символа без изменений (без префиксов, специфичных для синтаксиса). | %p2 |
42 |
42 |
P |
Если используется для функции, выводит суффикс PLT и генерирует PIC-код. Например, вместо ’foo’ для функции foo() выводит foo@PLT. Если используется для константы, удаляет все префиксы, специфичные для синтаксиса, и выводит константу без изменений. См. раздел p выше. |
|||
q |
Выводит имя регистра в режиме DImode. | %q0 |
%rax |
rax |
w |
Выводит имя регистра в режиме HImode. | %w0 |
%ax |
ax |
z |
Выводит суффикс кода операции, соответствующий размеру текущего целочисленного операнда (один из b/w/l/q). |
%z0 |
l |
V — специальный модификатор, который выводит имя полного целочисленного регистра без %.
6.11.2.11 Операнды asm с плавающей точкой x86
Для целевых платформ x86 существуют несколько правил использования регистров, организованных как стек, в операндах asm. Эти правила применяются только к операндам, являющимся регистрами стека:
- Для набора входных регистров, значения которых становятся ненужными в
asm, необходимо знать, какие регистры неявно извлекаются операторомasm, а какие GCC должен извлечь явно.Входной регистр, неявно извлекаемый оператором
asm, должен быть явно указан в clobber, если только он не ограничен соответствием выходному операнду. - Для каждого входного регистра, неявно извлекаемого оператором
asm, необходимо знать, как скорректировать стек с учетом извлечения. Если невыталкиваемый входной регистр находится ближе к вершине стека регистров, чем неявно извлекаемый регистр, определить состояние стека будет невозможно — неясно, как остальные элементы стека «сдвигаются вверх».Все неявно извлекаемые входные регистры должны находиться ближе к вершине стека регистров, чем любой входной регистр, который не извлекается неявно.
Если входной операнд становится ненужным в
asm, компилятор может использовать входной регистр для повторной загрузки выходного значения. Рассмотрим этот пример:asm ("foo" : "=t" (a) : "f" (b));Этот код указывает, что входной операнд
bне извлекается операторомasm, аasmпомещает результат в стек регистров, то есть послеasmстек становится на один элемент глубже, чем до него. Однако при повторной загрузке компилятор может решить, что один и тот же регистр можно использовать и для входного, и для выходного значения.Чтобы этого не произошло, если для какого-либо входного операнда используется ограничение ‘f’, во всех ограничениях выходных регистров необходимо использовать модификатор раннего clobber ‘&’.
Пример выше следует записать так:
asm ("foo" : "=&t" (a) : "f" (b)); - Некоторые операнды должны находиться в определенных местах стека. К этой категории относятся все выходные операнды: иначе GCC не сможет определить, в каких регистрах находятся выходные значения, если это не указано в ограничениях.
Для выходных операндов необходимо явно указать, в каком регистре окажется выходное значение после
asm. Запись ‘=f’ недопустима: ограничения операнда должны выбирать класс, состоящий из одного регистра. - Выходные операнды нельзя «вставлять» между существующими регистрами стека. Поскольку ни один код операции 387 не использует операнд для чтения и записи, все выходные операнды становятся ненужными до
asm, а операторasmпомещает их в стек. Нет смысла помещать их куда-либо, кроме вершины стека регистров.Выходные операнды должны начинаться с вершины стека регистров: пропускать регистр нельзя.
- Некоторым операторам
asmможет потребоваться дополнительное место в стеке для внутренних вычислений. Это можно гарантировать, указав в clobber регистры стека, не связанные с входными и выходными операндами.
Этот оператор asm принимает один входной операнд, который извлекается внутри оператора, и формирует два выходных операнда.
asm ("fsincos" : "=t" (cos), "=u" (sin) : "0" (inp));
Этот оператор asm принимает два входных операнда, которые извлекаются кодом операции fyl2xp1, и заменяет их одним выходным операндом. Элемент clobber st(1) необходим, чтобы компилятор знал, что fyl2xp1 извлекает оба входных операнда.
asm ("fyl2xp1" : "=t" (result) : "0" (x), "u" (y) : "st(1)");
6.11.2.12 Модификаторы операндов 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.11.2.13 Модификаторы операндов LoongArch
В приведенном ниже списке описаны поддерживаемые модификаторы и их действие для LoongArch.
| Модификатор | Описание |
|---|---|
d |
То же, что и c. |
i |
Вывести символ ”i”, если операнд не является регистром. |
m |
То же, что и c, но выводимое значение — operand - 1. |
u |
Вывести регистр LASX. |
w |
Вывести регистр LSX. |
X |
Вывести целочисленный операнд-константу в шестнадцатеричном формате. |
z |
Вывести операнд в исходном виде, а затем запятую. |
Ссылки на входные и выходные операнды в шаблоне ассемблера операторов extended asm могут использовать модификаторы, влияющие на форматирование операндов в коде, передаваемом ассемблеру. Например, в следующем коде используется модификатор ’w’ для LoongArch:
test-asm.c:
#include <lsxintrin.h>
__m128i foo (void)
{
__m128i a,b,c;
__asm__ ("vadd.d %w0,%w1,%w2\n\t"
:"=f" (c)
:"f" (a),"f" (b));
return c;
}
Команда компиляции тестового примера выглядит следующим образом:
gcc test-asm.c -mlsx -S -o test-asm.s
Оператор ассемблера создает следующий ассемблерный код:
vadd.d $vr0,$vr0,$vr1
Это инструкция сложения 128-битных векторов, c (указанный в строке шаблона как %0) является выходным операндом, а a (%1) и b (%2) — входными. __m128i — это векторный тип данных, определенный в файле lsxintrin.h (см. Векторные встроенные функции LoongArch SX). Символ ’=f’ обозначает ограничение, указывающее на использование регистра с плавающей точкой в качестве выходного операнда, а ’f’ во входном операнде обозначает ограничение, указывающее на использование операнда-регистра с плавающей точкой; определение ограничения можно найти в gcc (см. Ограничения для операндов asm).
6.11.2.14 Модификаторы операндов RISC-V
В приведенном ниже списке описаны поддерживаемые модификаторы и их действие для RISC-V.
| Модификатор | Описание |
|---|---|
z |
Вывести ”zero” вместо 0, если операнд является непосредственным значением, равным нулю. |
i |
Вывести символ ”i”, если операнд является непосредственным значением. |
N |
Вывести кодировку регистра в виде целого числа (0–31). |
6.11.2.15 Модификаторы операндов SH
В приведенном ниже списке описаны поддерживаемые модификаторы и их действие для семейства процессоров SH.
| Модификатор | Описание |
|---|---|
. |
Вывести ”.s”, если инструкции нужен слот задержки. |
, |
Вывести ”LOCAL_LABEL_PREFIX”. |
@ |
Вывести ”trap”, ”rte” или ”rts” в зависимости от используемой директивы interrupt. |
# |
Вывести ”nop”, если в слот задержки нечего помещать. |
' |
Вывести суффикс вероятности (”/u” для маловероятного случая). |
> |
Вывести адрес перехода, если ”-fverbose-asm”. |
O |
Требовать операнд-константу и вывести константное выражение без знаков пунктуации. |
R |
Вывести ”LSW” значения dp — меняется при использовании порядка байтов от младшего к старшему. |
S |
Вывести ”MSW” значения dp — меняется при использовании порядка байтов от младшего к старшему. |
T |
Вывести следующее слово значения dp — то же, что ”R” в режиме порядка байтов от старшего к младшему. |
M |
Вывести суффикс ”.b ”, ”.w”, ”.l”, ”.s”, ”.d”, если операнд — MEM. |
N |
Вывести ”r63”, если операнд — ”const_int 0”. |
d |
Вывести ”V2SF” как ”dN” вместо ”fpN”. |
m |
Вывести пару ”base,offset” или ”base,index” для LD и ST. |
U |
Подобно ”%m” для ”LD” и ”ST”, ”HI” и ”LO”. |
V |
Вывести позицию единственного установленного бита. |
W |
Вывести позицию единственного сброшенного бита. |
t |
Вывести адрес памяти, заданный регистром. |
u |
Вывести младшие 16 бит ”CONST_INT” как беззнаковое значение. |
o |
Вывести оператор. |
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-15.3.0/gcc/Extended-Asm.html