Встроенная сборка GCC для SPARC: как обрабатывать пары целых двойных слов? - PullRequest
5 голосов
/ 27 марта 2012

Насколько я понимаю, в 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

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

#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' asm в "=h"(r) может иметь к этому какое-то отношение, но я не могу найти примеров того, как его использовать.

Ответы [ 3 ]

0 голосов
/ 27 марта 2012

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


Редактировать: на основании всех обсуждений и других ответов, похоже, что gcc 3.2, как настроено, не работает с -m64, что заставляет его работать " v8plus "в Solaris 2 (32-разрядное адресное пространство и, по большей части, 32-разрядные регистры, за исключением значения, хранящегося в регистрах %g и %o). Достаточно новая НКУ должна позволять компиляцию с -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.)

0 голосов
/ 29 марта 2012

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

Thread: RFE: ограничения asm "h" и "U" и модификаторы "H" и "L".
[...] следующие два ограничения (цитата из gcc.info) для некоторого встроенного asm v8 + ABI:
64-разрядный регистр 'h' глобального или внешнего регистра для архитектуры 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) &lt&lt 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
0 голосов
/ 27 марта 2012

Инструкция 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 операнды.

...