6.44.2 Расширенный Asm — Инструкции ассемблера с операндами выражений C
С расширенным asm можно читать и записывать переменные C из ассемблера и выполнять переходы из ассемблерного кода к меткам C. Синтаксис расширенного asm использует двоеточия (‘:’) для разграничения параметров операндов после шаблона ассемблера:
asm [volatile] ( AssemblerTemplate : OutputOperands [ : InputOperands [ : Clobbers ] ]) asm [volatile] goto ( AssemblerTemplate : : InputOperands : Clobbers : GotoLabels)
Ключевое слово asm является расширением GNU. При написании кода, который можно компилировать с -ansi и различными -std опциями, используйте __asm__ вместо asm (см. Альтернативные ключевые слова).
Квалификаторы
volatile-
Типичное использование расширенных
asmоператоров заключается в манипулировании входными значениями для получения выходных значений. Однако вашиasmоператоры также могут иметь побочные эффекты. В таком случае, вам может потребоваться использовать квалификаторvolatileдля отключения определенных оптимизаций. См. Volatile. gotoЭтот квалификатор сообщает компилятору, что оператор
asmможет выполнить переход к одной из меток, указанных в GotoLabels. См. GotoLabels.
Параметры
- AssemblerTemplate
-
Это строковая константа, являющаяся шаблоном для ассемблерного кода. Она представляет собой комбинацию фиксированного текста и токенов, которые ссылаются на входные, выходные и параметры перехода. См. AssemblerTemplate.
- OutputOperands
-
Список переменных C, изменяемых инструкциями в AssemblerTemplate, разделённый запятыми. Разрешен пустой список. См. OutputOperands.
- InputOperands
-
Список выражений C, считываемых инструкциями в AssemblerTemplate, разделённый запятыми. Разрешен пустой список. См. InputOperands.
- Clobbers
-
Список регистров или других значений, измененных AssemblerTemplate, помимо тех, которые указаны как выходные. Разрешен пустой список. См. Clobbers.
- GotoLabels
-
Когда вы используете форму
gotoasm, этот раздел содержит список всех меток C, к которым код в AssemblerTemplate может перейти. См. GotoLabels.Операторы
asmне могут выполнять переходы в другиеasmоператоры, только в указанные GotoLabels. Оптимизаторы GCC не знают об остальных переходах; поэтому они не могут учесть их при принятии решений об оптимизации.
Общее число входных + выходных + переходных операндов ограничено 30.
Замечания
Оператор asm позволяет включать инструкции ассемблера непосредственно в код C. Это может помочь вам максимизировать производительность в коде, чувствительном к времени, или получить доступ к инструкциям ассемблера, которые нелегко доступны для программ C.
Обратите внимание, что расширенные операторы asm должны находиться внутри функции. Только базовые asm могут находиться вне функций (см. Базовый Asm). Функции, объявленные с атрибутом naked, также требуют базового asm (см. Атрибуты функций).
Хотя использования asm многообразны, полезно рассматривать оператор asm как серию низкоуровневых инструкций, преобразующих входные параметры в выходные параметры. Так, простой (хотя и не очень полезный) пример для i386 с использованием asm может выглядеть так:
int src = 1;
int dst;
asm ("mov %1, %0\n\t"
"add $1, %0"
: "=r" (dst)
: "r" (src));
printf("%d\n", dst);
Этот код копирует src в dst и добавляет 1 к dst.
6.44.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.44.2.2 Шаблон ассемблера
Шаблон ассемблера — это строковая константа, содержащая ассемблерные инструкции. Компилятор заменяет токены в шаблоне, которые ссылаются на входные, выходные и метки переходов, и затем выводит результирующую строку в ассемблер. Строка может содержать любые инструкции, распознаваемые ассемблером, включая директивы. GCC не анализирует сами ассемблерные инструкции и не знает их смысла или даже того, являются ли они корректным входом ассемблера. Однако он считает операторы (см. Размер asm).
Вы можете разместить несколько ассемблерных инструкций вместе в одной строке asm , разделяя их символами, обычно используемыми в ассемблерном коде для данной системы. Сочетание, работающее в большинстве случаев, — это перевод строки, плюс табуляция для перемещения к полю инструкции (записанное как ‘\n\t’). Некоторые ассемблеры разрешают использование точки с запятой в качестве разделителя строк. Однако обратите внимание, что некоторые диалекты ассемблера используют точку с запятой для начала комментария.
Не ожидайте, что последовательность операторов asm останется идеально последовательной после компиляции, даже при использовании квалификатора volatile. Если определённые инструкции должны оставаться последовательными в выходе, поместите их в один оператор asm с несколькими инструкциями.
Доступ к данным из программ C без использования входных/выходных операндов (например, путём прямого использования глобальных символов из шаблона ассемблера) может не работать как ожидается. Аналогично, вызовы функций непосредственно из шаблона ассемблера требуют подробного понимания целевого ассемблера и ABI.
Поскольку GCC не анализирует шаблон ассемблера, у него нет информации о любых символах, на которые он ссылается. Это может привести к тому, что GCC отбросит эти символы как неиспользуемые, если они не указаны также как входные, выходные или переходные операнды.
Специальные строки форматирования
В дополнение к токенам, описанным входными, выходными и переходными операндами, эти токены имеют специальное значение в шаблоне ассемблера:
- ‘%%’
-
Выводит одиночную ‘%’ в ассемблерный код.
- ‘%=’
-
Выводит число, уникальное для каждого экземпляра оператора
asmво всей компиляции. Эта опция полезна при создании локальных меток и их многократном использовании в одном шаблоне, генерирующем несколько ассемблерных инструкций. - ‘%{’
- ‘%|’
- ‘%}’
Выводит символы ‘{’, ‘|’, и ‘}’ (соответственно) в ассемблерный код. Когда не экранированы, эти символы имеют специальное значение для указания нескольких диалектов ассемблера, как описано ниже.
Несколько диалектов ассемблера в шаблонах asm
На таких платформах, как x86, GCC поддерживает несколько диалектов ассемблера. Опция -masm управляет диалектом, который GCC использует по умолчанию для встроенного ассемблера. В документации к целевой платформе для опции -masm содержится список поддерживаемых диалектов, а также диалект по умолчанию, если опция не указана. Эта информация может быть важной для понимания, так как ассемблерный код, правильно работающий при компиляции с одним диалектом, вероятно, потерпит неудачу при компиляции с другим. См. Опции x86.
Если ваш код должен поддерживать несколько диалектов ассемблера (например, если вы пишете общедоступные заголовки, которые должны поддерживать различные варианты компиляции), используйте конструкции такого вида:
{ dialect0 | dialect1 | dialect2... }
Данный конструкт выводит dialect0 при использовании диалекта #0 для компиляции кода, dialect1 для диалекта #1 и т. д. Если в фигурных скобках содержится меньше альтернатив, чем количество диалектов, поддерживаемых компилятором, конструкт ничего не выводит.
Например, если компилятор x86 поддерживает два диалекта (‘att’, ‘intel’), шаблон ассемблера, такой как:
"bt{l %[Offset],%[Base] | %[Base],%[Offset]}; jc %l2"
эквивалентен одному из:
"btl %[Offset],%[Base] ; jc %l2" /* att dialect */ "bt %[Base],%[Offset]; jc %l2" /* intel dialect */
Используя тот же компилятор, этот код:
"xchg{l}\t{%%}ebx, %1"
соответствует либо:
"xchgl\t%%ebx, %1" /* att dialect */ "xchg\tebx, %1" /* intel dialect */
Вложенные альтернативы диалектов не поддерживаются.
6.44.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, за исключением случаев, когда операнд связан с вводом; см. Операнды ввода.После префикса должно быть одно или несколько дополнительных ограничений (см. Ограничения), которые описывают местоположение значения. Общие ограничения включают ‘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 как ненужного (см. Переменные).
В данном коде не используется необязательный 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.44.2.4 Операнды вывода флагов
Некоторые целевые платформы имеют специальный регистр, который содержит «флаги» результата операции или сравнения. Как правило, содержимое этого регистра либо не изменяется asm, либо asm считается уничтожающим содержимое.
На некоторых целевых платформах существует специальная форма операнда вывода, позволяющая использовать условия в регистре флагов в качестве выходов asm. Набор поддерживаемых условий специфичен для каждой платформы, но общим правилом является то, что переменная вывода должна быть скалярным целым числом, а значение — булевым. Если это поддерживается, целевая платформа определяет препроцессорную метку __GCC_ASM_FLAG_OUTPUTS__.
Из-за специфики операндов вывода флагов, ограничение не может включать альтернативы.
Чаще всего на целевой платформе есть только один регистр флагов, и он, таким образом, является неявным операндом многих инструкций. В этом случае операнд не должен ссылаться в шаблоне ассемблера через %0 и т. д., так как соответствующий текст в языке ассемблера отсутствует.
- Семейство x86
-
Ограничения вывода флагов для семейства x86 имеют вид ‘=@cccond’, где cond — одно из стандартных условий, определенных в руководстве ISA для
jccилиsetcc.a«выше» или больше по значению без учёта знака
ae«выше или равно» или больше или равно по значению без учёта знака
b«ниже» или меньше по значению без учёта знака
be«ниже или равно» или меньше или равно по значению без учёта знака
cфлаг переноса установлен
ez«равно» или флаг нуля установлен
gбольше по значению с учётом знака
geбольше или равно по значению с учётом знака
lменьше по значению с учётом знака
leменьше или равно по значению с учётом знака
oфлаг переполнения установлен
pфлаг чётности установлен
sфлаг знака установлен
nanaenbnbencnengngenlnlenonpnsnz«не» флаг, или инвертированные версии вышеперечисленных
6.44.2.5 Операнды ввода
Операнды ввода предоставляют значения из C-переменных и выражений для кода ассемблера.
Операнды разделяются запятыми. Каждый операнд имеет следующий формат:
[ [asmSymbolicName] ] constraint (cexpression)
- asmSymbolicName
-
Указывает символическое имя для операнда. Ссылайтесь на имя в шаблоне ассемблера, заключив его в квадратные скобки (например, ‘%[Значение]’). Область действия имени — это
asmоператор, содержащий определение. Приемлемы любые допустимые имена C-переменных, включая имена, уже определенные в окружающем коде. Два операнда в одномasmоператоре не могут использовать одно и то же символическое имя.Если не используется asmSymbolicName, используйте (нулево-базовую) позицию операнда в списке операндов в шаблоне ассемблера. Например, если есть два выходных операнда и три входных, используйте ‘%2’ в шаблоне для ссылки на первый входной операнд, ‘%3’ для второго и ‘%4’ для третьего.
- constraint
-
Строковая константа, задающая ограничения на размещение операнда; см. Ограничения для подробностей.
Строки ограничений для входных операндов не должны начинаться с ‘=’ или ‘+’. При перечислении нескольких возможных расположений (например, ‘"irm"’), компилятор выбирает наиболее эффективный вариант на основе текущего контекста. Если вам необходимо использовать конкретный регистр, но ваши ограничения для машины не предоставляют достаточный контроль для выбора нужного регистра, локальные переменные регистров могут предложить решение (см. Локальные переменные регистров).
Входные ограничения также могут быть цифрами (например,
"0"). Это указывает, что указанный входной операнд должен находиться в том же месте, что и выходное ограничение с (нулево-базовым) индексом в списке выходных ограничений. При использовании синтаксиса asmSymbolicName для выходных операндов вы можете использовать эти имена (включенные в квадратные скобки ‘[]’) вместо цифр. - cexpression
-
Это C-переменная или выражение, передаваемое в
asmоператор в качестве входных данных. Закрывающиеся скобки — обязательная часть синтаксиса.
Когда компилятор выбирает регистры для представления входных операндов, он не использует ни один из регистров, помеченных как «измененные» (см. Изменения).
Если нет выходных операндов, но есть входные, поместите две последовательные точки с двоеточием там, где должны быть выходные операнды:
__asm__ ("some instructions"
: /* No outputs. */
: "r" (Offset / 8));
Предупреждение: не изменяйте содержимое только входных операндов (кроме входных, связанных с выходными). Компилятор предполагает, что по завершении asm оператора эти операнды содержат те же значения, что и до выполнения оператора. Невозможно использовать «изменения» для информирования компилятора о том, что значения в этих входных данных меняются. Одним из распространенных обходных путей является связывание изменяемой входной переменной с выходной переменной, которая никогда не используется. Однако, если код, следующий за asm оператором, не использует ни один из выходных операндов, оптимизаторы GCC могут отбросить asm оператор как ненужный (см. Неизменные).
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.44.2.6 Изменения
Хотя компилятор осведомлён о изменениях в записях, перечисленных в выходных операндах, встроенный asm код может изменять не только выходы. Например, вычисления могут потребовать дополнительных регистров, или процессор может перезаписать регистр как побочный эффект конкретной инструкции ассемблера. Для информирования компилятора об этих изменениях перечислите их в списке изменений. Элементы списка изменений — это имена регистров или специальные изменения (перечисленные ниже). Каждый элемент списка изменений — строковая константа, заключённая в двойные кавычки и разделенные запятыми.
Описание изменений не должно пересекаться с входными или выходными операндами. Например, у вас не может быть операнда, описывающего класс регистров с одним членом, при одновременном перечислении этого регистра в списке изменений. Переменные, объявленные в определённых регистрах (см. Явные переменные регистров) и используемые в качестве asm входных или выходных операндов, не должны упоминаться в описании изменений. В частности, нет способа указать, что входные операнды изменяются, не указав их также как выходные операнды.
Когда компилятор выбирает регистры для представления входных и выходных операндов, он не использует ни один из регистров, помеченных как «изменённые». В результате, помеченные регистры доступны для использования в ассемблерном коде.
Вот реалистичный пример для VAX, демонстрирующий использование изменённых регистров:
asm volatile ("movc3 %0, %1, %2"
: /* No outputs. */
: "g" (from), "g" (to), "g" (count)
: "r0", "r1", "r2", "r3", "r4", "r5");
Также существует два специальных аргумента изменений:
"cc"-
Изменение
"cc"указывает, что ассемблерный код изменяет регистр флагов. На некоторых машинах GCC представляет кодовые состояния как определённый регистр аппаратного обеспечения;"cc"служит для именования этого регистра. На других машинах обработка кодовых состояний отличается, и указание"cc"не имеет эффекта. Но оно допустимо независимо от целевой системы. "memory"-
Изменение
"memory"сообщает компилятору, что ассемблерный код выполняет чтение или запись в память элементов, отличных от указанных в входных и выходных операндах (например, доступ к памяти, на которую указывает один из входных параметров). Для обеспечения корректности памяти GCC может потребоваться сбросить определённые значения регистров в память перед выполнениемasm. Кроме того, компилятор не предполагает, что любые значения, считанные из памяти доasmоператора, останутся неизменными после этогоasmоператора; они перезагружаются по мере необходимости. Использование изменения"memory"эффективно формирует барьер чтения/записи памяти для компилятора.Обратите внимание, что это изменение не препятствует процессору выполнять упреждающее чтение за пределами
asmоператора. Для предотвращения этого вам нужны инструкции барьера, специфичные для процессора.Сброс регистров в память имеет последствия для производительности и может быть проблемой для кода с жёсткими временными ограничениями. Вы можете использовать трюк, чтобы избежать этого, если размер памяти, к которой осуществляется доступ, известен во время компиляции. Например, если осуществляется доступ к десяти байтам строки, используйте вход памяти, как:
{"m"( ({ struct { char x[10]; } *p = (void *)ptr ; *p; }) )}.
6.44.2.7 Метки перехода
asm goto позволяет ассемблерному коду переходить к одной или нескольким меткам C. Раздел GotoLabels в asm goto операторе содержит список всех меток C, к которым может перейти ассемблерный код, разделённый запятыми. GCC предполагает, что выполнение asm продолжается по следующему оператору (если это не так, рассмотрите использование __builtin_unreachable интринсика после asm оператора). Оптимизация asm goto может быть улучшена с помощью атрибутов меток hot и cold (см. Атрибуты меток).
asm goto оператор не может иметь выходов. Это обусловлено внутренним ограничением компилятора: инструкции передачи управления не могут иметь выходов. Если ассемблерный код изменяет что-либо, используйте изменение "memory" для принудительного сброса всех значений регистров в память и перезагрузки их при необходимости после asm оператора.
Также обратите внимание, что asm goto оператор всегда неявно считается изменяемым.
Для ссылки на метку в шаблоне ассемблера добавьте префикс ‘%l’ (маленькая буква ‘L’) за которым следует её (нулево-базовая) позиция в GotoLabels плюс количество входных операндов. Например, если в asm есть три входных и две метки, ссылайтесь на первую метку как ‘%l3’ и на вторую как ‘%l4’.
В качестве альтернативы вы можете ссылаться на метки, используя фактическое имя 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;
}
6.44.2.8 Модификаторы операндов x86
Ссылки на входные, выходные и метки перехода в шаблоне ассемблера расширенных asm операторов могут использовать модификаторы для изменения способа форматирования операндов в коде вывода в ассемблер. Например, следующий код использует модификаторы ‘h’ и ‘b’ для x86:
uint16_t num;
asm volatile ("xchg %h0, %b0" : "+a" (num) );
Эти модификаторы генерируют такой ассемблерный код:
xchg %ah, %al
Остальная часть этого обсуждения использует следующий код в качестве иллюстрации.
int main()
{
int iInt = 1;
top:
asm volatile goto ("some assembler instructions here"
: /* No outputs. */
: "q" (iInt), "X" (sizeof(unsigned char) + 1)
: /* No clobbers. */
: top);
}
Без модификаторов, вот что будет результатом вывода операндов для диалектов ассемблера ‘att’ и ‘intel’:
| Операнд | masm=att | masm=intel |
|---|---|---|
%0 |
%eax |
eax |
%1 |
$2 |
2 |
%2 |
$.L2 |
OFFSET FLAT:.L2 |
Ниже представлен список поддерживаемых модификаторов и их эффектов.
| Модификатор | Описание | Операнд | masm=att | masm=intel |
|---|---|---|---|---|
z |
Выводит суффикс кода операции для размера текущего целочисленного операнда (один из b/w/l/q). |
%z0 |
l |
|
b |
Выводит имя регистра в QImode. | %b0 |
%al |
al |
h |
Выводит имя регистра в QImode для «высокого» регистра. | %h0 |
%ah |
ah |
w |
Выводит имя регистра в HImode. | %w0 |
%ax |
ax |
k |
Выводит имя регистра в SImode. | %k0 |
%eax |
eax |
q |
Выводит имя метки без знаков препинания. | %q0 |
%rax |
rax |
l |
Требует константного операнда и выводит выражение константы без знаков препинания. | %c1 |
2 |
2 |
V — это специальный модификатор, который печатает имя полного целочисленного регистра без %.
6.44.2.9 x86 Плавающие точки asm операндов
На 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)");
Далее: Ограничения, Предыдущее: Базовый Asm, Вверх: Использование языка ассемблера с C [Оглавление][Индекс]
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-6.5.0/gcc/Extended-Asm.html