6.47.2 Расширенный Asm — Инструкции ассемблера с операндами выражений C ¶
С помощью расширенного asm вы можете читать и записывать переменные C из ассемблера и выполнять переходы из кода ассемблера к меткам C. Синтаксис расширенного asm использует двоеточия (‘:’) для разделения параметров операндов после шаблона ассемблера:
asm asm-qualifiers ( AssemblerTemplate
: OutputOperands
[ : InputOperands
[ : Clobbers ] ])
asm asm-qualifiers ( AssemblerTemplate
: OutputOperands
: InputOperands
: Clobbers
: GotoLabels)
где в последней форме asm-qualifiers содержит goto (и в первой форме — нет).
Ключевое слово asm является расширением GNU. При написании кода, который может быть скомпилирован с -ansi и различными -std опциями, используйте __asm__ вместо asm (см. Альтернативные ключевые слова).
Квалификаторы ¶
volatile-
Типичное использование инструкций расширенного
asmзаключается в манипулировании входными значениями для получения выходных значений. Однако ваши инструкцииasmтакже могут иметь побочные эффекты. В таком случае вам может потребоваться использовать квалификаторvolatileдля отключения определенных оптимизаций. См. Volatile. inline-
Если вы используете квалификатор
inline, то для целей инлайнинга размер инструкцииasmпринимается как наименьший возможный размер (см. Размер инструкцииasm). gotoЭтот квалификатор сообщает компилятору, что инструкция
asmможет выполнить переход к одной из меток, перечисленных в GotoLabels. См. GotoLabels.
Параметры ¶
- AssemblerTemplate
-
Это строковая литерал, являющаяся шаблоном для кода ассемблера. Она представляет собой комбинацию фиксированного текста и токенов, которые ссылаются на входные, выходные и параметры перехода. См. AssemblerTemplate.
- OutputOperands
-
Список переменных C, изменяемых инструкциями в AssemblerTemplate, разделённый запятыми. Разрешен пустой список. См. OutputOperands.
- InputOperands
-
Список выражений C, считываемых инструкциями в AssemblerTemplate, разделённый запятыми. Разрешен пустой список. См. InputOperands.
- Clobbers
-
Список регистров или других значений, изменяемых AssemblerTemplate, помимо указанных в качестве выходов. Разрешен пустой список. См. Clobbers и регистры-временные переменные.
- GotoLabels
-
Когда вы используете форму
gotoинструкцииasm, этот раздел содержит список всех меток C, в которые код в AssemblerTemplate может перейти. См. GotoLabels.Инструкции
asmне могут выполнять переходы в другие инструкцииasm, только в перечисленные GotoLabels. Оптимизаторы GCC не знают о других переходах; поэтому они не могут учесть их при принятии решений об оптимизации.
Общее количество входных + выходных + меток перехода операндов ограничено 30.
Замечания ¶
Инструкция asm позволяет включить инструкции ассемблера непосредственно в код C. Это может помочь вам максимизировать производительность в коде, зависящем от времени, или получить доступ к инструкциям ассемблера, которые не доступны для программ C.
Обратите внимание, что расширенные инструкции asm должны находиться внутри функции. Только базовые инструкции 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
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 может дублировать (или удалять дубликаты) вашего ассемблерного кода при оптимизации. Это может привести к неожиданным ошибкам дублирования символов во время компиляции, если ваш код ассемблера определяет символы или метки. Использование ‘%=’ (см. AssemblerTemplate) может помочь решить эту проблему.
6.47.2.2 Шаблон ассемблера ¶
Шаблон ассемблера — это строка, содержащая инструкции ассемблера. Компилятор заменяет в шаблоне токены, которые ссылаются на входные, выходные данные и метки goto, а затем выводит полученную строку в ассемблер. Строка может содержать любые инструкции, распознаваемые ассемблером, включая директивы. GCC не анализирует сами ассемблерные инструкции и не знает, что они означают, или даже являются ли они допустимым вводом ассемблера. Однако он считает операторы (см. Размер asm).
Вы можете поместить несколько ассемблерных инструкций вместе в одной строке asm, разделенные символами, обычно используемыми в ассемблерном коде для системы. Комбинация, которая работает в большинстве случаев, — это перевод строки, плюс символ табуляции для перехода к полю инструкции (записано как ‘\n\t’). Некоторые ассемблеры допускают использование точек с запятой в качестве разделителя строк. Однако обратите внимание, что некоторые диалекты ассемблера используют точки с запятой для начала комментария.
Не ожидайте, что последовательность asm операторов останется идеально последовательной после компиляции, даже когда вы используете квалификатор volatile. Если определённые инструкции должны оставаться последовательными в выводе, поместите их в один многоинструкционный оператор asm.
Доступ к данным из программ C без использования операндов ввода/вывода (например, с использованием глобальных символов непосредственно из шаблона ассемблера) может не работать так, как ожидается. Аналогичным образом, вызов функций непосредственно из шаблона ассемблера требует глубокого понимания целевого ассемблера и ABI.
Поскольку GCC не анализирует шаблон ассемблера, он не видит какие-либо символы, на которые он ссылается. Это может привести к тому, что GCC отбросит эти символы как неиспользуемые, если они также не указаны как входные, выходные или операнды goto.
Специальные строковые форматы ¶
В дополнение к токенам, описанным операндами ввода, вывода и goto, эти токены имеют специальное значение в шаблоне ассемблера:
- ‘%%’
-
Выводит одиночный символ ‘%’ в код ассемблера.
- ‘%=’
-
Выводит число, уникальное для каждого экземпляра оператора
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 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«не» флаг, или инвертированные версии вышеперечисленных
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 может изменять не только выходные данные. Например, для вычислений могут потребоваться дополнительные регистры, или процессор может перезаписать регистр в качестве побочного эффекта конкретной инструкции ассемблера. Для информирования компилятора об этих изменениях, перечислите их в списке захватов. Элементы списка захватов — это имена регистров или специальные захваты (перечисленные ниже). Каждый элемент списка захватов — строковая константа в двойных кавычках, разделенная запятыми.
Описание захватов не должно никоим образом перекрываться с входным или выходным операндом. Например, вы не можете иметь операнд, описывающий класс регистров с одним членом, при одновременном указании этого регистра в списке захватов. Переменные, объявленные для хранения в определённых регистрах (см. Переменные в указанных регистрах) и используемые в качестве входных или выходных операндов, не должны быть упомянуты в описании захватов. В частности, нет способа указать, что входные операнды изменяются, не указав их также как выходные операнды.
При выборе компилятором регистров для представления входных и выходных операндов он не использует ни один из захваченных регистров. В результате захваченные регистры доступны для любого использования в коде ассемблера.
Ещё одно ограничение заключается в том, что список захватов не должен содержать регистр указателя стека. Это связано с тем, что компилятор требует, чтобы значение указателя стека было одинаковым после инструкции 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инструкции. Для предотвращения этого вам потребуются инструкции ограждения (fence) процессора.
Сброс регистров в память влечёт за собой последствия для производительности и может быть проблемой для кода, чувствительного к времени. Вы можете предоставить GCC более подробную информацию, чтобы избежать этого, как показано в следующих примерах. Как минимум, правила алиасинга позволяют GCC знать, какая память не должна быть сброшена.
Вот фиктивная инструкция суммирования квадратов, принимающая два указателя на значения с плавающей запятой в памяти и производящая выходное значение с плавающей запятой в регистре. Обратите внимание, что x, и y оба появляются дважды в параметрах asm, один раз для указания доступа к памяти, и один раз для указания базового регистра, используемого инструкцией asm. Обычно вы не будете тратить регистр, делая это, так как GCC может использовать один и тот же регистр для обеих целей. Однако было бы глупо использовать как %1, так и %3 для x в этом asm и ожидать, что они будут одинаковыми. Фактически, %3 может не быть регистром. Это может быть символическая ссылка на память на объект, на который указывает x.
asm ("sumsq %0, %1, %2"
: "+f" (result)
: "r" (x), "r" (y), "m" (*x), "m" (*y));
Вот фиктивная инструкция *z++ = *x++ * *y++. Обратите внимание, что регистры указателей x, y и z должны быть указаны как входные/выходные, потому что инструкция asm изменяет их.
asm ("vecmul %0, %1, %2"
: "+r" (z), "+r" (x), "+r" (y), "=m" (*z)
: "m" (*x), "m" (*y));
Пример для x86, где длина строки в памяти неизвестна.
asm("repne scasb"
: "=c" (count), "+D" (p)
: "m" (*(const char (*)[]) p), "0" (-1), "a" (0));
Если вы знаете, что это будет только чтение массива из десяти байтов, то вы можете вместо этого использовать вход в память, как "m" (*(const char (*)[10]) p).
Вот пример реализации масштабирования вектора PowerPC в ассемблере, с захватами вектора и кода условия, а также некоторыми регистровыми смещениями, которые не изменяются инструкцией asm.
void
dscal (size_t n, double *x, double alpha)
{
asm ("/* lots of asm here */"
: "+m" (*(double (*)[n]) x), "+&r" (n), "+b" (x)
: "d" (alpha), "b" (32), "b" (48), "b" (64),
"b" (80), "b" (96), "b" (112)
: "cr0",
"vs32","vs33","vs34","vs35","vs36","vs37","vs38","vs39",
"vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47");
}
Вместо выделения фиксированных регистров через захваты для предоставления регистров-заготовок для инструкции asm, альтернативой является определение переменной и назначение ей раннего захвата как вывода, как в a2 и a3 в примере ниже. Это даёт компилятору-аллокатору регистров большую свободу. Вы также можете определить переменную и назначить ей вывод, связанный с входом, как в a0 и a1, связанные соответственно с ap и lda. Конечно, при связанных выходах ваше asm не может использовать значение входа после изменения выходного регистра, поскольку они представляют собой один и тот же регистр. Более того, если вы опустите ранний захват на выходе, возможно, GCC присвоит тот же регистр другому входу, если GCC сможет доказать, что у них было одинаковое значение при входе в asm. Вот почему a1 имеет ранний захват. Его связанный вход, lda может предположительно иметь значение 16, и без раннего захвата разделять тот же регистр с %11. С другой стороны, ap не может быть таким же, как любой из других входов, поэтому ранний захват на a0 не нужен. В данном случае это также нежелательно. Ранний захват на a0 заставит GCC выделить отдельный регистр для входа "m" (*(const double (*)[]) ap). Обратите внимание, что связывание входа с выходом — это способ установить инициализированный временный регистр, изменённый инструкцией asm. Вход, не связанный с выходом, предполагается GCC неизменным, например, "b" (16) ниже устанавливает %11 в 16, и GCC может использовать этот регистр в последующем коде, если значение 16 окажется необходимым. Вы даже можете использовать обычный asm вывод для заготовки, если все входы, которые могут использовать тот же регистр, потребляются до использования заготовки. Регистры VSX, захваченные инструкцией asm, могли бы использовать эту технику, за исключением ограничения GCC на количество asm параметров.
static void
dgemv_kernel_4x4 (long n, const double *ap, long lda,
const double *x, double *y, double alpha)
{
double *a0;
double *a1;
double *a2;
double *a3;
__asm__
(
/* lots of asm here */
"#n=%1 ap=%8=%12 lda=%13 x=%7=%10 y=%0=%2 alpha=%9 o16=%11\n"
"#a0=%3 a1=%4 a2=%5 a3=%6"
:
"+m" (*(double (*)[n]) y),
"+&r" (n), // 1
"+b" (y), // 2
"=b" (a0), // 3
"=&b" (a1), // 4
"=&b" (a2), // 5
"=&b" (a3) // 6
:
"m" (*(const double (*)[n]) x),
"m" (*(const double (*)[]) ap),
"d" (alpha), // 9
"r" (x), // 10
"b" (16), // 11
"3" (ap), // 12
"4" (lda) // 13
:
"cr0",
"vs32","vs33","vs34","vs35","vs36","vs37",
"vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47"
);
}
6.47.2.7 Метки перехода ¶
asm goto позволяет коду ассемблера переходить к одной или нескольким меткам C. Раздел GotoLabels в инструкции asm goto содержит список всех меток C, на которые может перейти код ассемблера, разделённый запятыми. GCC предполагает, что выполнение asm происходит по следующей инструкции (если это не так, рассмотрите использование intrinsika __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 Модификаторы операндов x86 ¶
Ссылки на операнды входных, выходных и операторов goto в шаблоне ассемблера расширенных 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 |
Вывести адрес в режиме двойного целого (DImode) (8 байт), когда целевая архитектура 64-битная. В противном случае режим не определен (VOIDmode). | %E1 |
%(rax) |
[rax] |
g |
Вывести имя регистра типа V16SFmode. | %g0 |
%zmm0 |
zmm0 |
h |
Вывести имя регистра типа QImode для «верхнего» регистра. | %h0 |
%ah |
ah |
H |
Добавить 8 байт к смещаемой ссылке на память. Полезно при доступе к старшим 8 байтам значений SSE. Для memref в (%rax) генерируется | %H0 |
8(%rax) |
8[rax] |
k |
Вывести имя регистра типа SImode. | %k0 |
%eax |
eax |
l |
Вывести имя метки без знаков препинания. | %l3 |
.L3 |
.L3 |
L |
Вывести суффикс опкода для l. | %L0 |
l |
|
N |
Вывести maskz. | %N7 |
{z} |
{z} |
p |
Вывести имя символа в сыром виде (без префиксов, специфичных для синтаксиса). | %p2 |
42 |
42 |
P |
Если используется для функции, вывести суффикс PLT и сгенерировать код PIC. Например, вывести foo@PLT вместо ’foo’ для функции foo(). Если используется для константы, удалить все префиксы, специфичные для синтаксиса, и вывести саму константу. См. p выше. |
|||
q |
Вывести имя регистра типа DImode. | %q0 |
%rax |
rax |
Q |
Вывести суффикс опкода для q. | %Q0 |
q |
|
R |
Вывести встроенные округлении и sae. | %R4 |
{rn-sae}, |
, {rn-sae} |
r |
Вывести только sae. | %r4 |
{sae}, |
, {sae} |
s |
Вывести сдвиг двойного счета, за которым следует разделитель аргументов ассемблераВывести суффикс опкода для s. | %s1 |
$2, |
2, |
S |
Вывести суффикс опкода для s. | %S0 |
s |
|
t |
Вывести имя регистра типа V8SFmode. | %t5 |
%ymm0 |
ymm0 |
T |
Вывести суффикс опкода для t. | %T0 |
t |
|
V |
Вывести полное имя целого регистра без %. | %V0 |
eax |
eax |
w |
Вывести имя регистра типа HImode. | %w0 |
%ax |
ax |
W |
Вывести суффикс опкода для w. | %W0 |
w |
|
x |
Вывести имя регистра типа V4SFmode. | %x5 |
%xmm0 |
xmm0 |
y |
Вывести «st(0)» вместо «st» как регистр. | %y6 |
%st(0) |
st(0) |
z |
Вывести суффикс опкода для размера текущего целочисленного операнда (один из b/w/l/q). |
%z0 |
l |
|
Z |
Как z, со специальными суффиксами для инструкций x87. |
6.47.2.9 Операнды x86 с плавающей точкой в ассемблере ¶
На целевых платформах x86 существуют определенные правила использования регистров, похожих на стек, в операндах asm. Эти правила применяются только к операндам, которые являются регистрами, похожими на стек:
- Учитывая набор входных регистров, которые умирают в
asm, необходимо знать, какие из них неявно извлекаются из стекаasm, а какие должны быть явно извлечены GCC.Входной регистр, который неявно извлекается из стека
asm, должен быть явно помечен как модифицированный, если он не ограничен соответствием выходному операнду. - Для любого входного регистра, который неявно извлекается из стека
asm, необходимо знать, как настроить стек, чтобы компенсировать извлечение. Если какой-либо не извлекаемый входной регистр ближе к вершине стека регистров, чем неявно извлекаемый регистр, невозможно определить, как выглядел стек — неясно, как «сдвинулись» остальные элементы стека.Все неявно извлекаемые входные регистры должны быть ближе к вершине стека регистров, чем любой не извлекаемый входной регистр.
Возможно, если входной операнд умирает в
asm, компилятор может использовать входной регистр для загрузки выходного значения. Рассмотрим этот пример:asm ("foo" : "=t" (a) : "f" (b));Этот код указывает, что вход
bне извлекается из стекаasm, и чтоasmпомещает результат в стек регистров, т.е. стек глубже послеasmчем до. Однако, возможна ситуация, когда загрузка может предположить, что она может использовать тот же регистр как для входных, так и для выходных данных.Чтобы предотвратить это, если любой входной операнд использует ограничение ‘f’, все ограничения выходных регистров должны использовать модификатор «&» раннего изменения.
Приведенный выше пример должен быть написан так:
asm ("foo" : "=&t" (a) : "f" (b)); - Некоторые операнды должны находиться в определенных местах стека. Все выходные операнды попадают в эту категорию — GCC не может знать, в каких регистрах находятся выходные значения, пока вы не укажете это в ограничениях.
Выходные операнды должны указывать, в каком регистре находится выходной результат после
asm. ‘=f’ не допускается: ограничения операндов должны выбирать класс с единственным регистром. - Выходные операнды не могут быть «вставлены» между существующими регистрами стека. Поскольку ни одна из инструкций 387 не использует операнд для чтения/записи, все выходные операнды мертвы до
asm, и они помещаются в стекasm. Нет смысла помещать их не в вершину стека регистров.Выходные операнды должны начинаться с вершины стека регистров: выходные операнды не могут «пропустить» регистр.
- Некоторые
asmинструкции могут потребовать дополнительного места в стеке для внутренних вычислений. Это можно гарантировать, пометив как модифицированные регистры стека, не связанные с входными и выходными значениями.
Эта asm принимает один вход, который неявно извлекается, и генерирует два выхода.
asm ("fsincos" : "=t" (cos), "=u" (sin) : "0" (inp));
Эта asm принимает два входа, которые извлекаются из стека fyl2xp1 операцией, и заменяет их одним выходом. st(1) модификатор необходим для того, чтобы компилятор понимал, что fyl2xp1 извлекает оба входа.
asm ("fyl2xp1" : "=t" (result) : "0" (x), "u" (y) : "st(1)");
6.47.2.10 Модификаторы операндов MSP430 ¶
Ниже приведен список поддерживаемых модификаторов и их эффекты для MSP430.
| Модификатор | Описание |
|---|---|
A |
Выбор младших 16 бит операнда-константы/регистра/памяти. |
B |
Выбор старших 16 бит операнда-константы/регистра/памяти. |
C |
Выбор битов 32-47 операнда-константы/регистра/памяти. |
D |
Выбор битов 48-63 операнда-константы/регистра/памяти. |
H |
Эквивалентно B (для обратной совместимости). |
I |
Вывести инверсию (логическое отрицание) значения константы. |
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, но только для указателей. |
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-11.4.0/gcc/Extended-Asm.html