PC相对跳转gcc内联汇编

时间:2011-02-05 17:04:21

标签: gcc x86-64 inline-assembly

我有一个asm循环保证不会超过128次迭代,我想用PC相对跳转展开。我们的想法是以相反的顺序展开每个迭代,然后跳转到它需要的循环中。代码如下所示:

#define __mul(i) \
    "movq -"#i"(%3,%5,8),%%rax;" \
    "mulq "#i"(%4,%6,8);" \
    "addq %%rax,%0;" \
    "adcq %%rdx,%1;" \
    "adcq $0,%2;"

asm("jmp (128-count)*size_of_one_iteration" // I need to figure this jump out
    __mul(127)
    __mul(126)
    __mul(125)
    ...
    __mul(1)
    __mul(0)
    : "+r"(lo),"+r"(hi),"+r"(overflow)
    : "r"(a.data),"r"(b.data),"r"(i-k),"r"(k)
    : "%rax","%rdx");

使用gcc内联汇编是否可以这样?

3 个答案:

答案 0 :(得分:1)

很抱歉,我无法用ATT语法提供答案,希望您能轻松完成翻译。

如果您在RCX中有计数,并且您可以在__mul(0)之后有一个标签,那么您可以这样做:

; rcx must be in [0..128] range.
    imul ecx, ecx, -size_of_one_iteration ; Notice the multiplier is negative (using ecx is faster, the upper half of RCX will be automatically cleared by CPU)
    lea  rcx, [rcx + the_label] ; There is no memory read here
    jmp  rcx

希望这有帮助。

编辑: 我昨天弄错了。我假设引用[rcx + the_label]中的标签被解析为[rcx + rip + disp],但它不是因为没有这样的寻址模式(仅存在[rip + disp32])

此代码应该可以工作,另外它会保持rcx不受影响并且会破坏rax和rdx(但是你的代码在首先写入它们之前似乎没有读取它们):

; rcx must be in [0..128] range.
    imul edx, ecx, -size_of_one_iteration ; Notice the multiplier is negative (using ecx is faster, the upper half of RCX will be automatically cleared by CPU)
    lea  rax, [the_label] ; PC-relative addressing (There is no memory read here)
    add  rax, rdx
    jmp  rax 

答案 1 :(得分:1)

这不是一个直接的答案,但你考虑使用的变体 Duff's Device而不是内联 部件?这将采用switch语句的形式:

switch(iterations) {
  case 128: /* code for i=128 here */
  case 127: /* code for i=127 here */
  case 126: /* code for i=126 here */
  /* ... */
  case 1:   /* code for i=1 here*/
  break;
  default: die("too many cases");
}

答案 2 :(得分:1)

在gcc内联汇编中,您可以使用标签并让汇编程序为您排序跳转目标。像(人为的例子):

int max(int a, int b)
{
    int result;
    __asm__ __volatile__(
        "movl %1, %0\n"
        "cmpl %2, %0\n"
        "jeq  a_is_larger\n"
        "movl %2, %0\n"
        "a_is_larger:\n" : "=r"(result), "r"(a), "r"(b));
    return (result);
}

这是一回事。你可以做的另一件事是避免乘法是让汇编程序对齐你的块,比如说,32个字节的倍数(我不认为指令序列符合16个字节),像:

#define mul(i)                     \
    ".align 32\n"                  \
    ".Lmul" #i ":\n"               \
    "movq -" #i "(%3,%5,8),%%rax\n"\
    "mulq " #i "(%4,%6,8)\n"       \
    "addq %%rax,%0\n"              \
    "adcq %%rdx,%1\n"              \
    "adcq $0,%2\n"

这将简单地用nop填充指令流。如果您确实选择不对齐这些块,您仍然可以在主表达式中使用生成的本地标签来查找装配块的大小:

#ifdef UNALIGNED
__asm__ ("imul $(.Lmul0-.Lmul1), %[label]\n"
#else
__asm__ ("shlq $5, %[label]\n"
#endif
    "leaq .Lmulblkstart, %[dummy]\n"        /* this is PC-relative in 64bit */
    "jmp (%[dummy], %[label])\n"
    ".align 32\n"
    ".Lmulblkstart:\n"
    __mul(127)
    ...
    __mul(0)
    : ... [dummy]"=r"(dummy) : [label]"r"((128-count)))

对于count是编译时常量的情况,你甚至可以这样做:

__asm__("jmp .Lmul" #count "\n" ...);

最后几点说明:

如果自动生成的_mul()事物可以创建不同长度的序列,那么对齐块是个好主意。对于你使用的常量0..127,情况并非如此,因为它们都适合一个字节,但是如果你将它们扩大到更大,它将会转到16位或32位值,指令块会和睦相处。通过填充指令流,仍然可以使用跳转技术。