Как получить младшие и старшие 32 бита 64-битного целого для gcc inline asm?(Платформа ARMV5) - PullRequest
0 голосов
/ 28 декабря 2018

У меня есть проект на платформе armv5te, и я должен переписать некоторые функции и использовать ассемблерный код для использования улучшенных инструкций DSP.Я часто использую тип int64_t для аккумуляторов, но у меня нет идеи, как передать его для команды постановки на охрану SMULL (http://www.keil.com/support/man/docs/armasm/armasm_dom1361289902800.htm).

Как передать 32-разрядные младшие или более высокие значения из 32 переменных в 32регистр? (я знаю, что могу использовать промежуточную переменную int32_t, но она выглядит не очень хорошо).

Я знаю, что компилятор сделает это за меня, но я просто пишу небольшую функцию дляпример.

int64_t testFunc(int64_t acc, int32_t x, int32_t y)
{
   int64_t tmp_acc;

   asm("SMULL %0, %1, %2, %3"
      : "=r"(tmp_acc), "=r"(tmp_acc) // no idea how to pass tmp_acc;
      : "r"(x), "r"(y)
      );

return tmp_acc + acc;
}

1 Ответ

0 голосов
/ 31 декабря 2018

Вам не нужно и не следует использовать встроенный ассемблер для этого.Компилятор может работать даже лучше, чем smull, и использовать smlal для умножения с одной инструкцией:

int64_t accum(int64_t acc, int32_t x, int32_t y) {
    return acc + x * (int64_t)y;
}

, которая компилируется ( с gcc8.2 -O3 -mcpu=arm10e на компиляторе GodboltИсследователь ) к этому asm: (ARM10E - это микроархитектура ARMv5, которую я выбрал из списка Википедии )

accum:
    smlal   r0, r1, r3, r2        @, y, x
    bx      lr  @

В качестве бонуса этот чистый C также эффективно компилируется для AArch64.

https://gcc.gnu.org/wiki/DontUseInlineAsm


Если вы настаиваете на том, чтобы выстрелить себе в ногу и использовать встроенный ассм:

Или в общем случае с другими инструкциями, естьЭто может быть случай, когда вам нужно это.

Во-первых, помните, что smull выходные регистры не могут перекрывать первый входной регистр, поэтому вы должны сообщить об этом компилятору. Ограничение раннего клоббера выходного операнда (ов) позволит сообщить компилятору, что он не может иметь входы в этих регистрах.Я не вижу ясного способа сообщить компилятору, что 2-й вход может быть в том же регистре, что и выход.

Это ограничение снято в ARMv6 и более поздних версиях (см. эту документацию Keil ) " Rn должен отличаться от RdLo и RdHi в архитектурах до ARMv6 ", но для совместимости с ARMv5 необходимо убедиться, что компилятор не нарушает это при заполнении шаблона inline-asm.

Оптимизирующие компиляторы могут оптимизировать сдвиг / ИЛИ, который объединяет 32-битные переменные C в 64-битную переменную C, при нацеливании на 32-битную платформу.Они уже хранят 64-битные переменные в виде пары регистров, и в обычных случаях могут выяснить, что в asm нет никакой реальной работы.

Таким образом, вы можете указать 64-битный вход иливывод в виде пары 32-битных переменных.

#include <stdint.h>

int64_t testFunc(int64_t acc, int32_t x, int32_t y)
{
   uint32_t prod_lo, prod_hi;

   asm("SMULL %0, %1, %2, %3"
      : "=&r" (prod_lo), "=&r"(prod_hi)  // early clobber for pre-ARMv6
      : "r"(x), "r"(y)
      );

    int64_t prod = ((int64_t)prod_hi) << 32;
    prod |= prod_lo;        // + here won't optimize away, but | does, with gcc
    return acc + prod;
}

К сожалению, ранний клоббер означает, что нам нужно всего 6 регистров, но соглашение о вызовах ARM имеет только 6 регистров с закрытым вызовом (r0..r3, lr и ip (он же r12) ).И одним из них является LR, у которого есть обратный адрес, поэтому мы не можем потерять его значение.Вероятно, нет ничего страшного, если встроить в обычную функцию, которая уже сохраняет / восстанавливает несколько регистров.

Снова от Godbolt :

@ gcc -O3 output with early-clobber, valid even before ARMv6
testFunc:
    str     lr, [sp, #-4]!    @,         Save return address (link register)
    SMULL ip, lr, r2, r3    @ prod_lo, prod_hi, x, y
    adds    r0, ip, r0      @, prod, acc
    adc     r1, lr, r1        @, prod, acc
    ldr     pc, [sp], #4      @          return by popping the return address into PC


@ gcc -O3 output without early-clobber (&) on output constraints:
@ valid only for ARMv6 and later
testFunc:
    SMULL r3, r2, r2, r3    @ prod_lo, prod_hi, x, y
    adds    r0, r3, r0      @, prod, acc
    adc     r1, r2, r1        @, prod, acc
    bx      lr  @

Или вы можетеиспользуйте ограничение "=r"(prod64) и используйте модификаторы, чтобы выбрать, какую половину %0 вы получите. К сожалению, gcc и clang испускают менее эффективный asm по некоторым причинам, сохраняя больше регистров (и поддерживая 8-байтовое выравнивание стека).2 вместо 1 для gcc, 4 вместо 2 для лязга.

// using an int64_t directly with inline asm, using %Q0 and %R0 constraints
// Q is the low half, R is the high half.
int64_t testFunc2(int64_t acc, int32_t x, int32_t y)
{
   int64_t prod;    // gcc and clang seem to want more free registers this way

   asm("SMULL %Q0, %R0, %1, %2"
      : "=&r" (prod)         // early clobber for pre-ARMv6
      : "r"(x), "r"(y)
      );

    return acc + prod;
}

снова скомпилировано с gcc -O3 -mcpu=arm10e.(clang сохраняет / восстанавливает 4 регистра)

@ gcc -O3 with the early-clobber so it's safe on ARMv5
testFunc2:
    push    {r4, r5}        @
    SMULL r4, r5, r2, r3    @ prod, x, y
    adds    r0, r4, r0      @, prod, acc
    adc     r1, r5, r1        @, prod, acc
    pop     {r4, r5}  @
    bx      lr  @

Поэтому, по некоторым причинам, кажется более эффективным вручную обрабатывать половинки 64-битного целого числа с текущими значениями gcc и clang.Это явно пропущенная ошибка оптимизации.

...