6.43.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
-
При использовании
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.
6.43.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.43.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.43.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
-
Указывает символическое имя операнда. Ссылайтесь на имя в шаблоне ассемблера, заключив его в квадратные скобки (т.е. ‘%[Значение]’). Область действия имени — оператор
asm, содержащий определение. Любое допустимое имя C-переменной допустимо, включая имена, уже определенные в окружающем коде. Два операнда в одном и том же оператореasmне могут использовать одно и то же символическое имя.Если asmSymbolicName не используется, используйте позицию операнда (с нуля) в списке операндов в шаблоне ассемблера. Например, если есть три операнда вывода, используйте ‘%0’ для первого, ‘%1’ для второго и ‘%2’ для третьего.
- constraint
-
Строковая константа, определяющая ограничения размещения операнда; см. Ограничения для получения подробностей.
Ограничения вывода должны начинаться либо с ‘=’ (перезаписывающая переменная существующее значение) или ‘+’ (при чтении и записи). При использовании ‘=’, не предполагайте, что расположение содержит существующее значение при входе в оператор
asm, за исключением случаев, когда операнд связан с входом; см. Операнды ввода.После префикса должно быть одно или несколько дополнительных ограничений (см. Ограничения), описывающих расположение значения. Распространённые ограничения включают ‘r’ для регистра и ‘m’ для памяти. Если вы указываете более одного возможного расположения (например,
"=rm"), компилятор выбирает наиболее эффективный вариант на основе текущего контекста. Если вы указываете столько альтернатив, сколько позволяет операторasm, вы позволяете оптимизаторам генерировать наилучший возможный код. Если вам нужно использовать конкретный регистр, но ограничения вашей машины не обеспечивают достаточный контроль для выбора нужного вам регистра, локальные переменные регистров могут предложить решение (см. Локальные переменные регистров). - cvariablename
-
Указывает выражение C lvalue для хранения вывода, обычно имя переменной. Заключающие скобки являются обязательной частью синтаксиса.
При выборе регистров для представления операндов вывода компилятор не использует ни один из регистров, помеченных как используемые (см. Помеченные как используемые).
Выражения операндов вывода должны быть lvalues. Компилятор не может проверить, имеют ли операнды типы данных, приемлемые для выполняемой инструкции. Для выражений вывода, которые не являются непосредственно адресоваемыми (например, битовое поле), ограничение должно позволять регистр. В этом случае 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.43.2.4 Операнды ввода
Операнды ввода предоставляют значения из 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.43.2.5 Clobbers
Хотя компилятор осведомлён об изменениях в записях, указанных в выходных операндах, встроенный asm код может изменять больше, чем просто выходные значения. Например, вычисления могут потребовать дополнительных регистров, или процессор может перезаписать регистр как побочный эффект конкретной ассемблерной инструкции. Для того, чтобы проинформировать компилятор об этих изменениях, перечислите их в списке clobber. Элементы списка clobber — это имена регистров или специальные clobbers (перечисленные ниже). Каждый элемент списка clobber — это строковая константа, заключенная в двойные кавычки и разделенная запятыми.
Описание clobber не должно никоим образом перекрываться с входным или выходным операндом. Например, вы не можете иметь операнд, описывающий класс регистров с одним членом, при одновременном указании этого регистра в списке clobber. Переменные, объявленные для размещения в определённых регистрах (см. Explicit Reg Vars) и используемые как asm входные или выходные операнды, не должны упоминаться в описании clobber. В частности, нет способа указать, что входные операнды изменяются, не указав их также в качестве выходных операндов.
При выборе компилятором регистров для представления входных и выходных операндов он не использует ни один из clobbered регистров. В результате clobbered регистры доступны для любого использования в коде ассемблера.
Вот реальный пример для VAX, демонстрирующий использование clobbered регистров:
asm volatile ("movc3 %0, %1, %2"
: /* No outputs. */
: "g" (from), "g" (to), "g" (count)
: "r0", "r1", "r2", "r3", "r4", "r5");
Также существуют два специальных аргумента clobber:
"cc""cc"clobber указывает, что код ассемблера изменяет регистр флагов. На некоторых машинах GCC представляет коды условий как определённый регистр аппаратного обеспечения;"cc"служит для именования этого регистра. На других машинах обработка кодов условия отличается, и указание"cc"не оказывает влияния. Но это допустимо независимо от целевой платформы."memory""memory"clobber сообщает компилятору, что код ассемблера выполняет чтение или запись в память для элементов, отличных от перечисленных в входных и выходных операндах (например, доступ к памяти, на которую указывает один из входных параметров). Для обеспечения корректности данных в памяти GCC может потребоваться сбросить определённые значения регистров в память перед выполнениемasm. Кроме того, компилятор не предполагает, что любые значения, считанные из памяти до инструкцииasm, останутся неизменными после этойasm; они загружаются заново по мере необходимости. Использование"memory"clobber фактически создаёт барьер для чтения/записи памяти для компилятора.Обратите внимание, что этот clobber не предотвращает процессору выполнение спекулятивных чтений после
asmинструкции. Для предотвращения этого вам нужны инструкции fence, специфичные для процессора.Сброс регистров в память имеет последствия для производительности и может быть проблемой для кода с жёсткими временными требованиями. Вы можете использовать уловку, чтобы избежать этого, если размер памяти, к которой осуществляется доступ, известен во время компиляции. Например, если происходит доступ к десяти байтам строки, используйте входной операнд памяти, подобный этому:
{"m"( ({ struct { char x[10]; } *p = (void *)ptr ; *p; }) )}.
6.43.2.6 Goto Labels
asm goto позволяет коду ассемблера переходить к одному или нескольким меткам C. Раздел GotoLabels в инструкции asm goto содержит список, разделённый запятыми, всех меток C, к которым может перейти код ассемблера. GCC предполагает, что выполнение asm переходит к следующей инструкции (если это не так, рассмотрите использование __builtin_unreachable intrinsic после инструкции asm). Оптимизация asm goto может быть улучшена путём использования атрибутов меток hot и cold (см. Label Attributes).
Инструкция asm goto не может иметь выходных значений. Это связано с внутренним ограничением компилятора: инструкции передачи управления не могут иметь выходных значений. Если код ассемблера изменяет что-либо, используйте "memory" clobber, чтобы заставить оптимизаторы сбросить все значения регистров в память и загрузить их заново при необходимости после инструкции 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, который использует clobber памяти.
int frob(int x)
{
int y;
asm goto ("frob %%r5, %1; jc %l[error]; mov (%2), %%r5"
: /* No outputs. */
: "r"(x), "r"(&y)
: "r5", "memory"
: error);
return y;
error:
return -1;
}
6.43.2.7 x86 Operand Modifiers
Ссылки на входные, выходные и 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)
: /* 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 | Вывести имя DImode регистра. | %q0 | %rax | rax |
l | Вывести имя метки без знаков препинания. | %l2 | .L2 | .L2 |
c | Требовать константный операнд и выводить константное выражение без знаков препинания. | %c1 | 2 | 2 |
6.43.2.8 x86 Floating-Point 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)");
Далее: Ограничения, Предыдущее: Базовый Ассемблер, Наверх: Использование Ассемблера с С [Содержание][Индекс]
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-5.5.0/gcc/Extended-Asm.html