Встроенная сборка 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). Например:С или без "U" в качестве ограничения можно использовать "H" и "L" в качестве модификаторов в шаблоне, чтобы получить регистры High и Low пары, используемой для 64-битного значения. Ограничение "h" выделяет регистр, из которого, согласно ABI v8 +, можно безопасно использовать все 64 бита (только для глобальных или выходных регистров). Следующий (искусственный) пример демонстрирует ограничение "h" и модификаторы "H" и "L":void atomic64_set(volatile uint64_t *p, uint64_t v) { asm volatile ( "std %1, %0" : "=m"(*p) : "U"(v) ); }
Отказ от ответственности: эти примеры были написаны на месте и, возможно, не являются правильными в отношении раннего срыва и подобных вопросов.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
операнды.