6.45.2 Расширенный Asm — Инструкции ассемблера с операндами выражений C
С расширенным asm вы можете читать и записывать переменные C из ассемблера и выполнять переходы из ассемблерного кода к меткам C. Расширенный asm синтаксис использует двоеточия (‘:’) для разграничения параметров операндов после шаблона ассемблера:
asm asm-qualifiers ( AssemblerTemplate
: OutputOperands
[ : InputOperands
[ : Clobbers ] ])
asm asm-qualifiers ( AssemblerTemplate
:
: InputOperands
: Clobbers
: GotoLabels)
где в последней форме asm-qualifiers содержит goto (а в первой форме — нет).
Ключевое слово asm является расширением GNU. При написании кода, который может быть скомпилирован с -ansi и различными опциями -std, используйте __asm__ вместо asm (см. Альтернативные ключевые слова).
Квалификаторы
volatile-
Типичное использование расширенных
asmоператоров заключается в манипулировании входными значениями для получения выходных значений. Однако вашиasmоператоры также могут иметь побочные эффекты. В таком случае, вам может потребоваться использовать квалификаторvolatileдля отключения определённых оптимизаций. См. Volatile. inline-
Если вы используете квалификатор
inline, то для целей встраивания размер asm принимается как наименьший возможный размер (см. Размер asm). gotoЭтот квалификатор сообщает компилятору, что оператор
asmможет выполнить переход к одной из меток, перечисленных в GotoLabels. См. GotoLabels.
Параметры
- AssemblerTemplate
-
Это строка-литерал, являющаяся шаблоном для кода ассемблера. Он представляет собой комбинацию фиксированного текста и токенов, которые ссылаются на входные, выходные и параметры перехода. См. AssemblerTemplate.
- OutputOperands
-
Список C-переменных, модифицируемых инструкциями в AssemblerTemplate, разделённый запятыми. Разрешен пустой список. См. OutputOperands.
- InputOperands
-
Список C-выражений, считываемых инструкциями в AssemblerTemplate, разделённый запятыми. Разрешен пустой список. См. InputOperands.
- Clobbers
-
Список регистров или других значений, изменяемых AssemblerTemplate, помимо перечисленных в качестве выходов. Разрешен пустой список. См. Clobbers.
- GotoLabels
-
Когда вы используете форму
gotoasm, эта секция содержит список всех C-меток, на которые код в AssemblerTemplate может перейти. См. GotoLabels.asmоператоры не могут выполнять переходы в другиеasmоператоры, только в перечисленные GotoLabels. Оптимизаторы GCC не знают о других переходах; поэтому они не могут учитывать их при принятии решения об оптимизации.
Общее количество входных + выходных + переходов операндов ограничено 30.
Примечания
Оператор asm позволяет включать инструкции ассемблера непосредственно в код C. Это может помочь вам максимизировать производительность в коде с критическими временными требованиями или получить доступ к инструкциям ассемблера, которые не доступны программам C.
Обратите внимание, что расширенные asm операторы должны быть внутри функции. Только базовые asm могут находиться вне функций (см. Базовый 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.45.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.45.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.45.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 оптимизаторами как ненужного (см. Переменные 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.45.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.45.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 как ненужный (см. Переменные с атрибутом 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.45.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.45.2.7 Метки перехода
asm goto позволяет коду ассемблера переходить к одной или нескольким меткам C. Раздел GotoLabels в операторе asm goto содержит список всех меток C, к которым может перейти код ассемблера. GCC предполагает, что выполнение asm продолжается по следующему оператору (если это не так, рассмотрите использование атрибута __builtin_unreachable после оператора asm). Оптимизация оператора asm goto может быть улучшена с помощью атрибутов меток hot и cold (см. Атрибуты меток).
Оператор asm goto не может иметь выходы. Это связано с внутренним ограничением компилятора: инструкции передачи управления не могут иметь выходов. Если код ассемблера изменяет что-либо, используйте изменение регистра "memory" для принудительного сброса всех значений регистров в память и их перезагрузки при необходимости после оператора asm.
Также обратите внимание, что оператор asm goto всегда неявно считается переменной volatile.
Для ссылки на метку в шаблоне ассемблера добавьте префикс ‘%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.45.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’:
| Операнд | ‘att’ | ‘intel’ |
|---|---|---|
%0 |
%eax |
eax |
%1 |
$2 |
2 |
%2 |
$.L2 |
OFFSET FLAT:.L2 |
Нижеприведённая таблица показывает список поддерживаемых модификаторов и их эффекты.
| Модификатор | Описание | Операнд | ‘att’ | ‘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.45.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-7.5.0/gcc/Extended-Asm.html