Встроенный ассемблер GCC, размеры регистров микширования (x86) - PullRequest
12 голосов
/ 23 сентября 2008

Кто-нибудь знает, как мне избавиться от следующего предупреждения на ассемблере?

Код x86, 32 бита:

int test (int x)
{
  int y;
  // do a bit-rotate by 8 on the lower word. leave upper word intact.
  asm ("rorw $8, %0\n\t": "=q"(y) :"0"(x));
  return y;
}

Если я скомпилирую его, я получу следующее (очень правильное) предупреждение:

Warning: using `%ax' instead of `%eax' due to `w' suffix

То, что я ищу, это способ сообщить компилятору / ассемблеру, что я хочу получить доступ к нижнему 16-битному подрегистру% 0. Доступ к байтовым подрегистрам (в данном случае AL и AH) также был бы полезен.

Я уже выбрал модификатор "q", поэтому компилятор вынужден использовать EAX, EBX, ECX или EDX. Я убедился, что компилятор должен выбрать регистр с подрегистрами.

Я знаю, что могу заставить asm-код использовать определенный регистр (и его подрегистры), но я хочу оставить задание на распределение регистров до компилятора.

Ответы [ 5 ]

19 голосов
/ 23 сентября 2008

Вы можете использовать %w0, если я правильно помню. Я только что проверил это тоже. : -)

int
test(int x)
{
    int y;
    asm ("rorw $8, %w0" : "=q" (y) : "0" (x));
    return y;
}

Изменить: В ответ на ОП, вы также можете сделать следующее:

int
test(int x)
{
    int y;
    asm ("xchg %b0, %h0" : "=Q" (y) : "0" (x));
    return y;
}

В настоящее время единственное место (о котором я знаю), в котором это задокументировано, это gcc/config/i386/i386.md, а не в какой-либо стандартной документации.

9 голосов
/ 24 июля 2013

Давным-давно, но мне, вероятно, понадобится это для моего будущего использования ...

В добавление к прекрасному ответу Криса говорится, что ключ использует модификатор между '%' и номером выходного операнда. Например, "MOV %1, %0" может стать "MOV %q1, %w0".

Я не смог найти ничего в constraints.md, но / gcc / config / i386 / i386.c содержал этот потенциально полезный комментарий в источнике для print_reg():

/* Print the name of register X to FILE based on its machine mode and number.
   If CODE is 'w', pretend the mode is HImode.
   If CODE is 'b', pretend the mode is QImode.
   If CODE is 'k', pretend the mode is SImode.
   If CODE is 'q', pretend the mode is DImode.
   If CODE is 'x', pretend the mode is V4SFmode.
   If CODE is 't', pretend the mode is V8SFmode.
   If CODE is 'h', pretend the reg is the 'high' byte register.
   If CODE is 'y', print "st(0)" instead of "st", if the reg is stack op.
   If CODE is 'd', duplicate the operand for AVX instruction.
 */

Комментарий ниже для ix86_print_operand() предлагает пример:

b - вывести имя QImode регистра для указанного операнда.

% b0 напечатает% al, если операнды [0] равны 0. 0. 1019 *

Еще несколько полезных опций перечислены в Шаблон вывода документации GCC Internals :

«% cdigit» может использоваться для замены операнда, который является константой значение без синтаксиса, который обычно указывает на непосредственный операнд.

«% ndigit» похож на «% cdigit», за исключением того, что значение константы равно перед печатью отрицается.

ad% adigit ’может использоваться для замены операнда, как если бы это была память ссылка, с фактическим операндом, рассматриваемым как адрес. Это может быть полезно при выводе инструкции «адрес загрузки», потому что часто синтаксис ассемблера для такой инструкции требует, чтобы вы написали операнд, как будто это ссылка на память.

l% ldigit ’используется для замены label_ref в инструкцию перехода.

‘% =’ выводит число, уникальное для каждой инструкции в Весь сборник. Это полезно для создания локальных меток упоминается более одного раза в одном шаблоне, который генерирует несколько инструкций на ассемблере.

Конструкция '%c2' позволяет правильно отформатировать инструкцию LEA, используя смещение:

#define ASM_LEA_ADD_BYTES(ptr, bytes)                            \
    __asm volatile("lea %c1(%0), %0" :                           \
                   /* reads/writes %0 */  "+r" (ptr) :           \
                   /* reads */ "i" (bytes));

Обратите внимание на важное, но редко документированное «c» в «%c1». Этот макрос эквивалентен

ptr = (char *)ptr + bytes

но без использования обычных целочисленных арифметических портов выполнения.

Изменить, чтобы добавить:

Выполнение прямых вызовов в x64 может быть затруднено, поскольку для этого требуется еще один недокументированный модификатор: '%P0' (что, похоже, для PIC)

#define ASM_CALL_FUNC(func)                                         \
    __asm volatile("call %P0") :                                    \
              /* no writes */ :                                     \
              /* reads %0 */ "i" (func))                           

Модификатор «p» в нижнем регистре, похоже, также работает в GCC, хотя ICC распознает только заглавную букву «P». Более подробная информация, вероятно, доступна по адресу / gcc / config / i386 / i386.c . Поиск "'p'".

1 голос
/ 23 сентября 2008

Пока я думаю об этом ... вы должны заменить ограничение "q" на заглавную "Q" во втором решении Криса:

int
test(int x)
{
    int y;
    asm ("xchg %b0, %h0" : "=Q" (y) : "0" (x));
    return y;
}

«q» и «Q» немного отличаются в 64-битном режиме, где вы можете получить младший байт для всех целочисленных регистров (ax, bx, cx, dx, si, di, sp, bp, r8 -r15). Но вы можете получить только второй младший байт (например, ах) для четырех оригинальных 386 регистров (ax, bx, cx, dx).

0 голосов
/ 23 сентября 2008

Гоча. Ну, если это примитивная рутина, которую вы собираетесь использовать снова и снова, у меня нет с этим никаких аргументов ... трюк с именами регистров, на который указал Крис, хорош, который мне придется запомнить.

Было бы неплохо, если бы он также вошел в стандартную документацию GCC!

0 голосов
/ 23 сентября 2008

Так что, очевидно, есть хитрости, чтобы сделать это ... но это может быть не так эффективно. 32-битные процессоры x86 обычно медленнее при манипулировании 16-битными данными в регистрах общего назначения. Вы должны сравнить его, если производительность важна.

Если это (а) не критично для производительности и (б) не намного быстрее, я избавил бы себя от необходимости технического обслуживания и просто сделал бы это в C:

uint32_t y, hi=(x&~0xffff), lo=(x&0xffff);
y = hi + (((lo >> 8) + (lo << 8))&0xffff);

С GCC 4.2 и -O2 это оптимизируется до шести инструкций ...

...