Макрос препроцессора сборки GCC ARM

Я пытаюсь использовать макрос сборки (ARM) для умножения с фиксированной точкой:

    #define MULT(a,b) __asm__ __volatile__ ( \
        "SMULL r2, r3, %0, %1\n\t" \
        "ADD r2, r2, #0x8000\n\t" \
        "ADC r3, r3, #0\n\t" \
        "MOV %0, r2, ASR#16\n\t" \
        "ORR %0, %0, r3, ASL#16" \
        : "=r" (a) : "0"(a), "1"(b) : "r2", "r3" );

но при попытке скомпилировать я получаю ошибку(и): ожидаемое выражение перед 'asm'

(Вы можете игнорировать все, что ниже, если вы цените свое время, но было бы неплохо, если бы вы взглянули на это, главный вопрос здесь заключается в том, как заставить все вышеперечисленное работать)

Я пробовал это:

    static inline GLfixed MULT(GLfixed a, GLfixed b){
       asm volatile(
        "SMULL r2, r3, %[a], %[b]\n"
        "ADD r2, r2, #0x8000\n"
        "ADC r3, r3, #0\n"
        "MOV %[a], r2, ASR#16\n"
        "ORR %[a], %[a], r3, ASL#16\n"
        : "=r" (a)
        : [a] "r" (a), [b] "r" (b)
        : "r2", "r3");
     return a; }

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

GLfixed m[16];
m[0]=costab[player_ry];//1(65536 integer representation)
m[5]=costab[player_rx];//1(65536 integer representation)
m[6]=-sintab[player_rx];//0
m[8]=-sintab[player_ry];//0
LOG("%i,%i,%i",m[6],m[8],MULT(m[6],m[8]));
m[1]=MULT(m[6],m[8]);
m[2]=MULT(m[5],-m[8]);
m[9]=MULT(-m[6],m[0]);
m[10]=MULT(m[5],m[0]);
m[12]=MULT(m[0],0)+MULT(m[8],0);
m[13]=MULT(m[1],0)+MULT(m[5],0)+MULT(m[9],0);
m[14]=MULT(m[2],0)+MULT(m[6],0)+MULT(m[10],0);
m[15]=0x00010000;//1(65536 integer representation)

int i=0;
while(i<16)
{
    LOG("%i,%i,%i,%i",m[i],m[i+1],m[i+2],m[i+3]);
    i+=4;
}

Приведенный выше код будет печатать (LOG здесь похож на printf):

0,0,-1411346156
65536,65536,65536,440
-2134820096,65536,0,-1345274311
0,65536,22,220
65536,196608,131072,65536

Когда правильный результат будет (очевидно, много мусора в приведенном выше):

0,0,0
65536,0,0,0
0,65536,0,0
0,0,65536,0
0,0,0,65536

person Jonathan    schedule 24.05.2011    source источник


Ответы (2)


Вы пробовали простой C-код вместо сборки? В моей системе с GCC 4.5.3 компилятор генерирует код, который, по крайней мере, так же хорош, как ваш написанный от руки ассемблер:

int mul (int a, int b)
{
  long long x = ((long long)a * b + 0x8000);
  return x>>16;
}

компилируется в следующий asm-код:

# input: r0, r1
mov    r3, #32768
mov    r4, #0
smlal  r3, r4, r0, r1
mov    r0, r3, lsr #16
orr    r0, r0, r4, asl #16
# result in r0

(Эпилог вызова функции и пролог удалены)

Код становится еще лучше, если у вас есть несколько умножений в одной функции, потому что компилятор удалит избыточные инструкции mov r3, #32768.

person Nils Pipenbrinck    schedule 25.05.2011

Первая часть достаточно проста: проблема в том, что блок __asm__ является оператором, а не выражением.

Вы можете использовать расширение GCC выражения операторов для достижения того, что вы хотите - что-то так:

#define MULT(a,b) \
  ({ \
    __asm__ __volatile__ ( \
      /* ... asm stuff here ... */
    ); \
    a; \
  })

Вторая часть связана с проблемами в спецификациях входных и выходных операндов. У вас тут две разные версии, и обе неверны. В версии макроса вы сказали:

: "=r" (a) : "0"(a), "1"(b) : "r2", "r3"

что сдерживает

  • вывод a в регистр (это операнд 0);
  • вход a должен быть таким же, как операнд 0, т.е. тот же регистр (это операнд 1);
  • вход b должен быть таким же, как операнд 1, то есть снова таким же (это операнд 2).

Здесь вам нужно "r"(b), и вы можете обращаться к нему как %2.

Во встроенной версии вы сказали:

: "=r" (a) : [a] "r" (a), [b] "r" (b) : "r2", "r3"

который ограничивает выход a и вход a и b регистрами, но

  • он не объявляет о каких-либо отношениях между ними;
  • ассемблер никогда явно не ссылается на выходной операнд (вы не дали имени выходному операнду, а ассемблерный код не ссылается на %0).

Вы должны быть в состоянии исправить исходную версию с помощью:

: "=r" (a) : "0" (a), "r" (b) : "r2", "r3"

и ссылайтесь на a как на %0 или %1, а на b как на %2.

Встроенную версию можно исправить следующим образом:

: [a] "=r" (a) : "[a]" (a), [b] "r" (b) : "r2", "r3"

и ссылайтесь на операнды как %[a] и %[b].

Если вы хотите использовать имена в версии макроса, вам понадобится что-то вроде строк

: [arg_a] "=r" (a) : "[arg_a]" (a), [arg_b] "r" (b) : "r2", "r3"

(и обратитесь к %[arg_a] и %[arg_b]), потому что в противном случае препроцессор расширит a и b внутри [a] и [b].

Обратите внимание на тонкость в случаях с именованными аргументами: когда аргументу присваивается имя (как в выводе a), вы пишете [a] — без кавычек, — но когда вы ссылаетесь на имя другого уже именованного операнда (как в выводе a). введите a) вам нужно поместить его в кавычки: "[a]".

person Matthew Slattery    schedule 25.05.2011