Встроенная сборка GCC для SPARC: Как обрабатывать пары целых двойных слов?

Из того, что я понимаю, в SPARC 32-разрядные целочисленные величины хранятся в отдельных регистрах, а 64-разрядные целочисленные величины хранятся в смежных парах регистров, причем четный регистр содержит старшие 32 бита, а нечетный регистр содержит младшие 32 бита.

Мне нужно написать несколько специализированных макросов встроенной сборки SPARC (функции встроенной сборки тоже подойдут), которые работают с 64-битными целочисленными парами двойных слов, и я не могу понять, как ссылаться в общем (используя расширенную встроенную сборку GCC) на две половинки пары в моей встроенной сборке. Хотя мои макросы сборки будут немного сложнее, чем макрос MULTIPLY(), показанный ниже, пример умножения, если он сработает, продемонстрирует, как работать с двумя половинками 64-битной пары двойных слов. Может кто-нибудь сказать мне, как исправить мой макрос MULTIPLY()?

В случае, если это имеет значение, я на...

bash-2.03 $ uname -a
SunOS [...] 5.8 Generic_117350-39 sun4u sparc SUNW, Ultra-80

Вот мой тривиальный пример программы (на С):

#include <stdio.h>
//#include <stdint.h>
#define uint32 unsigned long int
#define uint64 unsigned long long int


#define MULTIPLY(r, a, b)  /* (r = a * b) */   \
   asm("umul %1, %2, %0;"  /* unsigned mul */  \
       : /* regs out */  "=h"(r)               \
       : /* regs in  */  "r"(a),   "r"(b));
#if 0
       : /* clobbers */  "%y" );
#endif


int main(int argc, char** argv)
{
   uint64 r;
   uint32 a=0xdeadbeef, b=0xc0deba5e;

   // loses the top 32 bits of the multiplication because the result is
   // truncated at 32 bits which then gets assigned to the 64-bit 'r'...
   r = a * b;
   printf("u64=u32*u32  ---->  r=a*b           "
          "---->  0x%016llx = 0x%x * 0x%x\n",
          r, a, b);

   // force promotion of 'a' to uint64 to get 64-bit multiplication
   // (could cast either a or b as uint64, which one doesn't matter,
   // as one explicit cast causes the other to be promoted as well)...
   r = ((uint64)a) * b;
   printf("u64=u64*u32  ---->  r=((u64)a)*b    "
          "---->  0x%016llx = 0x%x * 0x%x\n",
          r, a, b);

   MULTIPLY(r, a, b);
   printf("u64=u64*u32  ---->  MULTIPLY(r,a,b) "
          "---->  0x%016llx = 0x%x * 0x%x\n",
          r, a, b);

   return 0;
}

Который, когда компилируется с gcc-3.2-sun4u/bin/gcc -o mult -mcpu=ultrasparc mult.c, производит этот вывод:

u64=u32*u32  ---->  r=a*b           ---->  0x00000000d3c7c1c2 = 0xdeadbeef * 0xc0deba5e  
u64=u64*u32  ---->  r=((u64)a)*b    ---->  0xa7c40bfad3c7c1c2 = 0xdeadbeef * 0xc0deba5e  
u64=u64*u32  ---->  MULTIPLY(r,a,b) ---->  0xd3c7c1c2deadbeef = 0xdeadbeef * 0xc0deba5e  

Я посмотрел на -S -fverbose-asm вывод gcc, и он делает странное смещение регистра результата (который является четным) и записывает в соседний нечетный регистр. Моя проблема в том, что я не знаю, как в общем случае ссылаться на соседний нечетный регистр в расширенном синтаксисе asm. Я подумал, что, возможно, ограничение "h" в "=h"(r) может иметь к этому какое-то отношение, но я не могу найти примеров того, как его использовать.

3 ответа

Решение

Прежде всего, большое спасибо Крису Додду, Тореку и Гбулмеру за ваши усилия и помощь. Мне удалось выяснить, как это сделать, с некоторыми комментариями, которые я нашел здесь, частично воспроизведенными (и слегка отредактированными для формы, но не для содержания) ниже:

Поток: RFE: ограничения "h" и "U" и модификаторы "H" и "L".
[...] следующие два ограничения (цитата из gcc.info) для некоторого встроенного asm v8+ ABI:
'h' 64-битный глобальный или внешний регистр для архитектуры SPARC-V8+.
'U' Даже зарегистрироваться
"U" требуется для выделения регистра (ов) для ldd/std (он выделяет четную + нечетную пару для uint64_t). Например:
    void atomic64_set(volatile uint64_t *p, uint64_t v) {
        asm volatile ( "std %1, %0" : "=m"(*p) : "U"(v) );
    }
С или без "U" в качестве ограничения можно использовать "H" и "L" в качестве модификаторов в шаблоне, чтобы получить регистры High и Low пары, используемой для 64-битного значения. Ограничение "h" выделяет регистр, из которого, согласно ABI v8 +, можно безопасно использовать все 64 бита (только для глобальных или выходных регистров). Следующий (искусственный) пример демонстрирует ограничение "h" и модификаторы "H" и "L":
    void ex_store64(uint64_t *p, uint64_t v) {  
       register int tmp; // Don't say uint64_t or GCC thinks we want 2 regs  
       asm volatile (  
          "sllx %H2,32,%1 \n\t" // tmp = HI32(v) << 32  
          "or %1,%L2,%1 \n\t" // tmp |= LO32(v)  
          "stx %0, %1" // store 64-bit tmp  
          :  "=m"(*p),  "=&h"(tmp)  :  "r"(v));  
      }
Отказ от ответственности: эти примеры были написаны на месте и, возможно, не являются правильными в отношении раннего срыва и подобных вопросов.
-Павел

Исходя из этого, я смог выяснить, как переписать свой собственный макрос "MULTIPLY" из моей постановки задачи:

#define MULTIPLY(r, a, b)     /* r = a * b          */\
   asm("umul %1, %2, %L0;"    /* umul a,b,r         */\
       "srlx %L0, 32, %H0;"                           \
       : /* regs out */   "=r"(r)                     \
       : /* regs in  */   "r"(a),   "r"(b));
       /* re: clobbbers "none": I tried specifying :"%y"
        *     in various ways but GCC kept telling me
        *     there was no y, %y, or %%y register. */

Мои результаты сейчас:

u64=u32*u32  ---->  r=a*b           ---->  0x00000000d3c7c1c2 = 0xdeadbeef * 0xc0deba5e  
u64=u64*u32  ---->  r=((u64)a)*b    ---->  0xa7c40bfad3c7c1c2 = 0xdeadbeef * 0xc0deba5e  
u64=u64*u32  ---->  MULTIPLY(r,a,b) ---->  0xa7c40bfad3c7c1c2 = 0xdeadbeef * 0xc0deba5e

Я думаю, что вы получаете старое umul инструкция, потому что вы используете -mcpu= вместо -march=, Согласно документации, последний был изменен, чтобы быть синонимом -mtune=: генерировать инструкции для "самой общей архитектуры", но оптимизировать их для использования в данной архитектуре. Так -mcpu=ultrasparc означает "генерировать для V8 Sparc, но оптимизировать для Ultrasparc". С помощью -march=ultrasparc должен получить вам сырое 64-битное умножение.


Изменить: на основе всех обсуждений и других ответов, кажется, что gcc 3.2, как настроено, не работает с -m64, который заставляет работать в режиме "v8plus" в Solaris 2 (32-разрядное адресное пространство и, по большей части, 32-разрядные регистры, за исключением значения, хранящегося в %g а также %o регистры). Достаточно новый GCC должен позволять компилировать с -m64, Который сделает всю ситуацию более или менее спорный вопрос. (И вы можете добавить -march=niagara2 или что-то еще, в зависимости от вашего конкретного целевого оборудования.) Вам также может понадобиться установить полный набор binutils, как указано ниже в gcc 4.7.0 config/sparc/sparc.h:

#if TARGET_CPU_DEFAULT == TARGET_CPU_v9
/* ??? What does Sun's CC pass?  */
#define CPP_CPU64_DEFAULT_SPEC "-D__sparc_v9__"
/* ??? It's not clear how other assemblers will handle this, so by default
   use GAS.  Sun's Solaris assembler recognizes -xarch=v8plus, but this case
   is handled in sol2.h.  */
#define ASM_CPU64_DEFAULT_SPEC "-Av9"
#endif
#if TARGET_CPU_DEFAULT == TARGET_CPU_ultrasparc
#define CPP_CPU64_DEFAULT_SPEC "-D__sparc_v9__"
#define ASM_CPU64_DEFAULT_SPEC "-Av9a"
#endif
...

Имея все это на месте, вы сможете просто умножить два 64-битных значения, чтобы получить 64-битный результат в обычном C-коде, не прибегая к встроенной сборке.

(В противном случае вам понадобится что-то вроде кода, который вы в конечном итоге придумали для gcc 3.2.)

umul инструкция умножает два 32-битных (без знака целочисленных) значения в нижних половинах двух регистров и помещает нижнюю половину 64-битного результата в регистр назначения. Верхняя половина результата записывается в регистр Y. Верхняя половина регистра назначения очищается. Так что вы, вероятно, хотите, чтобы использовать это что-то вроде:

#define MULTIPLY(u, r, a, b) /* (u,r = a * b) */     \
asm("umul %2, %3, %0;"   /* unsigned mul */          \
    "rd %%y, %1;"        /* get hi word of result */ \
    : /* regs out */  "=r"(r), "=r"(u)               \
    : /* regs in  */  "r" (a), "r" (b)               \
    : /* clobbers */  "%y" );

Обратите внимание, однако, что вам почти наверняка лучше просто написать умножение в C, используя uint64_t или же unsigned long long операнды.

Другие вопросы по тегам