5

据我了解,在 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-asmgcc 的输出,它正在对结果寄存器(偶数)进行一些奇怪的移位并写入相邻的奇数寄存器。我的问题是我不知道如何在扩展 asm 语法中泛指相邻的奇数寄存器。我想也许 'h' asm 约束"=h"(r)可能与它有关,但我找不到任何如何使用它的例子。

4

3 回答 3

0

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_torunsigned long long操作数。

于 2012-03-27T05:15:29.130 回答
0

我认为您收到旧umul指令是因为您使用-mcpu=的是-march=. 根据文档,后者已更改为-mtune=:为“最通用架构”生成指令,但优化它们以在给定架构上使用。所以-mcpu=ultrasparc意思是“为 V8 sparc 生成,但为 Ultrasparc 优化”。使用-march=ultrasparc应该可以得到一个原始的 64 位乘法。


编辑:根据所有讨论和其他答案,配置的 gcc 3.2 似乎不适用于-m64,这迫使一个人在 Solaris 2 上以“v8plus”模式运行(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
...

有了所有这些,您应该能够在普通 C 代码中将两个 64 位值相乘以获得 64 位结果,而无需使用内联汇编。

(否则,您将需要类似于您最终为 gcc 3.2 提出的代码。)

于 2012-03-27T07:41:55.193 回答
0

首先,非常感谢 Chris Dodd、torek 和 gbulmer 的努力和帮助。我设法通过我在这里找到的一些评论来弄清楚如何做到这一点,部分转载(并针对形式而不是内容进行了略微编辑)如下:

主题:RFE:“h”和“U”asm 约束以及“H”和“L”修饰符。
[...]以下两个限制(引自 gcc.info)对于一些 v8+ ABI 内联 asm:
SPARC-V8+ 架构的 'h' 64 位全局或输出寄存器。
'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”作为修饰符来获得用于 64 位值的对的高和低寄存器。“h”约束分配一个寄存器,根据 v8+ ABI,可以安全地使用所有 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
于 2012-03-29T14:52:33.660 回答