【问题标题】:PC-relative jump in gcc inline assemblygcc 内联汇编中相对于 PC 的跳转
【发布时间】:2011-02-05 17:04:20
【问题描述】:

我有一个 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 内联汇编可以实现这样的事情吗?

【问题讨论】:

    标签: gcc x86-64 inline-assembly


    【解决方案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);
    }
    

    这是一回事。为了避免乘法,您可以做的另一件事是让汇编器 align 为您设置块,例如,以 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 位值,并且指令块将一起增长.通过填充指令流,仍然可以使用跳转表技术。

    【讨论】:

      【解决方案2】:

      这不是一个直接的答案,但您是否考虑过使用 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");
      }
      

      【讨论】:

      • 我现在正在使用 Duff 设备的变体,它可以正常工作,但我发布此内容是因为我想切换到仅 asm 的方式
      【解决方案3】:

      对不起,我无法提供 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 
      

      【讨论】:

        猜你喜欢
        • 2023-03-22
        • 1970-01-01
        • 2010-10-19
        • 2015-03-16
        • 2012-10-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多