Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Inline asm for x86

                                       
    Colin Plumb (colin@nyx.net)
    Mon, 20 Apr 1998 18:56:24 -0600 (MDT)
    colin@nyx.net, 20 April 1998
 

Краткое руководство GCC inline asm

Рассматривается один из инструментов ускорения работы ядра - расширенный ассемблер.

Рассмотрим пример :


   asm("foo %1,%2,%0" : "=r" (output) : "r" (input1), "r" (input2));
 
Ее можно усложнить следующим образом :

   asm("foo %1,%2,%0" : "=r" (ptr->vtable[3](a,b,c)->foo.bar[baz]) :
       : "r" (gcc(is) + really(damn->cool)), "r" (42));
 
GCC интерпретирует это следующим образом :

 register int t0, t1, t2;
   t1 = gcc(is) + really(damn->cool);
   t2 = 42;
   asm("foo %1,%2,%0" : "=r" (t0) : "r" (t1), "r" (t2));
   ptr->vtable[3](a,b,c)->foo.bar[baz] = t0;
 
Основная форма asm() :

   asm( "code" : outputs : inputs : clobbers);
 
Внутри "code" , %0 - это обычно 1-й аргумент output. %1 - второй аргумент , и т.д. Их может быть не более %9. Если вы хотите в "code" втиснуть несколько операторов , прийдется их разделять "\n\t". Но разделять так необязательно - можно все записать и в одной строке - для этого достаточно расставить";" .

Как output , так и input состоят из 2-х частей - "constraints" и (value). Для outputs (value) - возвращаемое значение.

outputs должен быть промаркирован знаком равенства "=".

"r" - или "rm" - буквально - register or memory. "ri" - register или immediate value. "g" - "general" - может быть и тем и другим. "o" похоже на "m" - "offsettable", означающее , что вы можете прибавить к нему смещение. Для x86 все операнды в памти - offsettable, поддерживающее индексацию.

* Несколько слов о inputs

input может быть временной переменной.

GCC может разместить output в том же регистре, в котором приходит input ,если последний более не нужен.

* x86 assembly code

GNU использует AT&T-синтаксис,который отличается от Intel-синтаксиса. В AT&T например отсутствует DWORD PTR . Intel использует "op dest,src", AT&T использует "op src,dest".

AT&T использует знак процента для обозначения регистров - %eax, %ebx. Не нужно ставить символ подчеркивания перед переменными и именами функций.

Отличие также в том , что в AT&T размер операнда зашит в саму инструкцию - например , вместо inc нужно - "incb", "incw" ,"incl" соответственно для 8, 16 или 32 бит. Иногда размер операнда указывать необязательно - например можно написать "inc %eax" - и так понятно,что речь идет о 2-х байтах. Но если вы работаете с операндами в памяти , нужно писать"incl foo", а не "inc foo",иначе будет ошибка. "incl %al" - это конечно тоже ошибка.

Перед именем внешней си-шной переменной можно ставить символ доллара - $.
При этом , "movl foo,%eax" копирует содержимое памяти, расположенной по адресу foo , в регистр %eax.
А "movl $foo,%eax" даст совершенно другой эффект - будет скопирован адрес переменной foo.
"movl 42,%eax" - копирование абсолютного значения.

Адресные режимы реализованы с помощью формата offset(base,index,scale). Например , -44(%ebx,%eax) будет эквивалентно -44(%ebx,%eax,1). В качестве шкалы можно использовать 1, 2 ,4 , 8.

* Equivalence constraints

Иногда может возникнуть ситуация,когда для ввода и вывода нужен один и тот же регистр. Для этого можно использовать специальный constraint "0":


   asm("foo %1,%0" : "=r" (output) : "r" (input1), "0" (input2));
 
Поскольку здесь %2 опущен, output и input2 будут использовать один и тот же регистр . Противоречия здесь нет , поскольку GCC сам сгенерит временные регистры.

* Constraints on the x86

i386 имеет достаточно типов регистров для прикладного использования. Основные типы :


 g - general effective address
 m - memory effective address
 r - register
 i - immediate value, 0..0xffffffff
 n - immediate value known at compile time.
 
i386-specific (i386.h):

 q - регистры с байтовой адресацией (eax, ebx, ecx, edx)
 A - eax или edx
 a, b, c, d, S, D - eax, ebx, ecx, edx, esi, edi соответственно
 
 I - immediate 0..31
 J - immediate 0..63
 K - immediate 255
 L - immediate 65535
 M - immediate 0..3 (shifts that can be done with lea)
 N - immediate 0..255 (one-byte immediate value)
 O - immediate 0..32
 
Пример использования "I" для rotate left:

 
   asm("roll %1,%0" : "=g" (result) : "cI" (rotate), "0" (input));
 

* Advanced constraints

= используется для маркировки output.  

% говорит о том , что данный и следующий операнды могут
 обменять свои приоритеты

, разделитель constraints.  
 x86 разрешает операции типа register-memory и  memory-register , 
 но запрещает memory-memory:

     asm("add %1,%0" : "=r,rm" (output) : "%g,ri" (input1), "0,0" (input2));
 
Если output - register, input1 может быть чем угодно. Но если output - memory, input может быть только register либо immediate value. И input2 должен быть в одном месте с output

& output operand пишется перед inputs , поэтому output и input 
 должны быть в разных регистрах.

* const and volatile

Есть 2 хинта , которые можно применить к asm- выражениям.

asm volatile(...) - ключевое слово volatile говорит компилятору , что он не может его оптимизировать и обязан выполнять именно так , как оно записано.

asm const() - может быть оптимизировано как обычное subexpression optimization.


 Пример:
   int foo(int x);
   {
     int i, y, total;
 
     total = 0;
     for (i = 0; i < 100; i++) {
       asm volatile("foo %1,%0" : "=r" (y) : "g" (x));
       total += y;
     }
     return total;
   }
 
Можно поставить ключевое слово "const". В первом случае , без "const" , будет сгенерирован код :

 func1:
         xorl %ecx,%ecx
         pushl %ebx
         movl %ecx,%edx
         movl 8(%esp),%ebx
         .align 4
 .L7:
 #APP
         foo %ebx,%eax
 #NO_APP
         addl %eax,%ecx
         incl %edx
         cmpl $99,%edx
         jle .L7
         movl %ecx,%eax
         popl %ebx
         ret
 >
С использованием const будет сгенеирован другой код :

 func2:
         xorl %edx,%edx
 #APP
         foo 4(%esp),%ecx
 #NO_APP
         movl %edx,%eax
         .align 4
 .L13:
         addl %ecx,%edx
         incl %eax
         cmpl $99,%eax
         jle .L13
         movl %edx,%eax
         ret
 

* Alternate keywords

__asm__() - еще одна альтернатива для asm(), при том что не генерирует варнинги.

* Output substitutions

Иногда возникает необходимость включить какую-то нестандартную конструкцию типа :

   asm("lea %1(%2,%3,1<<%4),%0" : "=r" (out)
       : "%i" (in1), "r" (in2), "r" (in3), "M" (logscale));
 
При этом GCC может сгенерировать что-то подобное :

         lea $-44(%ebx,%eax,1<<$2),%ecx
 
на самом деле это синтаксическая ошибка. Символ $ для констант в данном случае бесполезен. Существуют символы-модификаторы. Один из них - "c", который проигнорирует immediate value. Правильный вариант :

   asm("lea %c1(%2,%3,1<<%c4),%0" : "=r" (out)
       : "%i" (in1), "r" (in2), "r" (in3), "M" (logscale));
 
будет сгенерировано

         lea -44(%ebx,%eax,1<<2),%ecx
 
 Еще один символ-модификатор - n
 %n0 работает аналогично %c0, но делает значение отрицательным
 %l0 аналогично %c0, для jump target.
 
 %k0 выводит  32-битную форму операнда.  %eax, etc.
 %w0 выводит  16-битную форму операнда.  %ax, etc.
 %b0 выводит  8-битную форму операнда.  %al, etc.
 %h0 выводит старшие  8 бит регистра.  %ah, etc.
 %z0 выводит opcode операнда, b, w или l.
 
По умолчанию %0 выводит регистр в форме соответствующей размеру аргумента. Т.е.

asm("inc %0" : "=r" (out) : "0" (in))

выведет "inc %al", "inc %ax" или "inc %eax" в зависимости от типа "out".

Можно использовать %w и %b для обьектов не-регистрового типа. Рассмотрим пример:


   #define xchg(m, in, out)      \
     asm("xchg%z0 %2,%0" : "=g" (*(m)), "=r" (out) : "1" (in))
 
   int
   bar(void *m, int x)
   {
     xchg((char *)m, (char)x, x);
     xchg((short *)m, (short)x, x);
     xchg((int *)m, (int)x, x);
     return x;
   }
 
Будет сгенерирован код :

 
 .globl bar
         .type    bar,@function
 bar:
         movl 4(%esp),%eax
         movb 8(%esp),%dl
 #APP
         xchgb %dl,(%eax)
         xchgw %dx,(%eax)
         xchgl %edx,(%eax)
 #NO_APP
         movl %edx,%eax
         ret
 

* Extra % patterns

Избыточный % не несет в себе никакой дополнительной информации - примером является %%.

Еще одним примером является %=, генерирующий уникальное число для asm()-блока. Это может быть использовано для меток-шаблонов.

* Examples

Пример кода из include/asm-i386/system.h:

 
 #define _set_tssldt_desc(n,addr,limit,type) \
 __asm__ __volatile__ ("movw %3,0(%2)\n\t" \
         "movw %%ax,2(%2)\n\t" \
         "rorl $16,%%eax\n\t" \
         "movb %%al,4(%2)\n\t" \
         "movb %4,5(%2)\n\t" \
         "movb $0,6(%2)\n\t" \
         "movb %%ah,7(%2)\n\t" \
         "rorl $16,%%eax" \
         : "=m"(*(n)) : "a" (addr), "r"(n), "ri"(limit), "i"(type))
 
Этот пример можно переписать с использованием любого другого регистра , отличного от %eax:

 #define _set_tssldt_desc(n,addr,limit,type) \
 __asm__ __volatile__ ("movw %w3,0(%2)\n\t" \
         "movw %w1,2(%2)\n\t" \
         "rorl $16,%1\n\t" \
         "movb %b1,4(%2)\n\t" \
         "movb %4,5(%2)\n\t" \
         "movb $0,6(%2)\n\t" \
         "movb %h1,7(%2)\n\t" \
         "rorl $16,%1" \
         : "=m"(*(n)) : "q" (addr), "r"(n), "ri"(limit), "ri"(type))
 
Проблема в том , что нет возможности закодировать смещение для данного адреса. Если адрес равен "40(%eax)" то смещение на 2 может быть сделано как "2+" . Но если адрес "(%eax)" то "2+(%eax)" будет неверно.

Пример:


 #define _set_tssldt_desc(n,addr,limit,type) \
 __asm__ __volatile__ ("movw %w2,%0\n\t" \
         "movw %w1,2+%0\n\t" \
         "rorl $16,%1\n\t" \
         "movb %b1,4+%0\n\t" \
         "movb %3,5+%0\n\t" \
         "movb $0,6+%0\n\t" \
         "movb %h1,7+%0\n\t" \
         "rorl $16,%1" \
         : "=o"(*(n)) : "q" (addr), "ri"(limit), "i"(type))
 
constraint "o" - то же самое что и "m";

Пример:


 __asm__ __volatile__ ("movw %w7,%0\n\t" \
         "movw %w6,%1\n\t" \
         "rorl $16,%6\n\t" \
         "movb %b6,%2\n\t" \
         "movb %b8,%3\n\t" \
         "movb $0,%4\n\t" \
         "movb %h6,%5\n\t" \
         "rorl $16,%6" \
         : "=m"(*(n)), \
           "=m"((n)[2]), \
           "=m"((n)[4]), \
           "=m"((n)[5]), \
           "=m"((n)[6]), \
           "=m"((n)[7]) \
        : "q" (addr), "g"(limit), "iqm"(type))
 

Пример


 {
         int a=10, b;
         asm ("movl %1, %%eax;
               movl %%eax, %0;"
                 :"=r"(b)        /* output */
                 :"r"(a)         /* input */
                 :"%eax");       /* clobbered register */
 }
 
В этом примере переменная b приравнивается к a .
      * "b" - output operand, ссылка на %0 , и "a" - input operand, ссылка на %1.
      * "r" - constraint , указывает на то , что переменные  "a" и "b" 
        хранятся в регистрах.
      * Регистр %eax идет с двойным префиксом %
      * clobbered register %eax говорит GCC о том , что значение %eax модифицируется
        внутри"asm", поэтому GCC не использует этот регистр для хранения
        чего-то еще
      * movl %1, %%eax - копируем "a" в %eax, и movl %%eax, %0 -копируем
         %eax в "b".
      * после выполнения "asm" изменеие "b" внутри блока становится
        видно снаружи , потому что b определено как output-операнд	
 

Пример

В следующем примере инструкция cpuid получает параметр из регистра %eax и результат выводит в 4 регистрах: %eax, %ebx, %ecx, %edx. input - переменная "op" - передается в блок asm через регистр eax . a,b, c, d - constraints.

    
     asm ("cpuid"
                 : "=a" (_eax),
                   "=b" (_ebx),
                   "=c" (_ecx),
                   "=d" (_edx)
                 : "a" (op));
 
Ниже приводится код , который генерирует компилятор :

    
     movl -20(%ebp),%eax /* store 'op' in %eax -- input */
 #APP
         cpuid
 #NO_APP
         movl %eax,-4(%ebp)      /* store %eax in _eax -- output */
         movl %ebx,-8(%ebp)      /* store other registers in
         movl %ecx,-12(%ebp)        respective output variables */
         movl %edx,-16(%ebp)
 

Пример

Функция strcpy может быть реализована с помощью "S" и "D" constraints :

     asm ("cld\n
               rep\n
               movsb"
               : /* no input */
               :"S"(src), "D"(dst), "c"(count));
 
Источник src копируется в %esi с помощью "S" constraint, приемник dst копируется в %edi с помощью "D" constraint. Счетчик копируется в %ecx.

Пример

Увеличим значение переменной i на единицу :

         int i = 0;
         __asm__("
           pushl %%eax\n
           movl %0, %%eax\n
           addl $1, %%eax\n
           movl %%eax, %0\n
           popl %%eax"
           :
           : "g" (i)
         );
 
В следующем примере мы складываем 2 переменных и результат храним в первой переменной - i :

         int i=0, j=1;
         __asm__ __volatile__("
           pushl %%eax\n
           movl %0, %%eax\n
           addl %1, %%eax\n
           movl %%eax, %0\n
           popl %%eax"
           :
           : "g" (i), "g" (j)
         );
           /* i = i + j; */
 
В следующем примере переменной i мы присвоим 1, используя = :

         int i=0;
         __asm__ __volatile__("
           pushl %%eax\n
           movl $1, %%eax\n
           movl %%eax, %0\n
           popl %%eax"
           : "=g" (i)
         );
           /* i=1; */
 
В следующем примере мы результат сложения 2-х переменных положим в 3-ю - переменную k :
 
         int i=0, j=1, k=0;
         __asm__ __volatile__("
           pushl %%eax\n
           movl %1, %%eax\n
           addl %2, %%eax\n
           movl %%eax, %0\n
           popl %%eax"
           : "=g" (k)
           : "g" (i), "g" (j)
         );
           /* k = i + j; */
 
В следующем примере мы говорим компилятору , чтобы он сохранил значение регистра ax после входа в asm-блок и после выхода из него , т.е. ничего специально сами не делаем для его восстановления :

         int i=0, j=1, k=0;
         __asm__ __volatile__("
           movl %1, %%eax\n
           addl %2, %%eax\n
           movl %%eax, %0"
           : "=g" (k)
           : "g" (i), "g" (j)
           : "ax", "memory"
         );
           /* k = i + j; */
 
Локальные метки внутри инлайна нужно заканчивать "b" или "f" , в зависимости от того , спереди или сзади они стоят :

         __asm__ __volatile__("
         0:\n
           ...
           jmp 0b\n
           ...
           jmp 1f\n
           ...
         1:\n
           ...
         );
 
Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье