避免在汇编中对灵活的第二操作数进行硬数字移位。

8

这个问题涉及到 ARM 汇编语言。

我的问题是是否可以使用宏来替换 ASM 代码中的立即数,以便于我不必硬编码数字。

我不确定上面的问题是否清晰明了,因此我将提供一些带有 asm 代码的示例:

ARM 上存在一些指令,如 ror 指令(https://developer.arm.com/documentation/dui0473/m/arm-and-thumb-instructions/ror),其中可以使用一个寄存器值来旋转值,使其达到我们想要的效果:

#define rotate(number, ptr) ({                       \
  asm volatile( \
    "ror %[output], %[output], %1\n" \ 
    : [output]"+r"(ptr) \ // this is the in/output 
    : "r"(number)); \ // this is the rotator
})

现在,让我们来看一下 ARM(https://developer.arm.com/documentation/dui0473/m/arm-and-thumb-instructions/orr)中的 orr 指令。

语法如下:ORR{S}{cond} Rd, Rn, Operand2

其中Operand2是一个灵活的操作数,意味着它可以是一个常量或者带有可选移位的寄存器 (来源: https://www.keil.com/support/man/docs/armasm/armasm_dom1361289851539.htm)

所以像这样的东西就可以工作:

#define orr_test(ptr) ({                       \
  uint64_t __result;                    \
  asm volatile (\
    "orr %0, %1, #4 << 60\n"\
    : "=r" (__result) : "r" (ptr)); \
  __result;                 \
})

然而,我质疑代码行中的#4是否可以被替换为宏。这样一来,我就不必硬编码#4了。但我认为这是不可能的(因为如果处理不当可能会引起严重问题),但我仍然想问一下,因为我在网上找不到相关信息。
如果我上面提到的任何内容不清楚,请告诉我;谢谢。
编辑(根据要求):
以下是我希望的伪代码实现:
#define orr_test(ptr, number) ({                       \
  uint64_t __result;                    \
  asm volatile (\
    "orr %0, %1, %[num] << 60\n"\
    : "=r" (__result) : "r" (ptr), [num]"r"(number)); \
  __result;                 \
})

换句话说,我希望使用宏来确定要移动的数字,而不是需要在硬编码中写入#4

@NateEldredge 对不起,我没有澄清。这是针对64位ARM的。然而,我认为这与我的问题无关紧要?(如果有影响,请告诉我) - Jay
1
听起来您想要orr %0, %1, %[num], LSL #60. - Jester
1
@Jay:顺便提一下,你的链接是指32位指令参考。你可能想看的是合并的架构参考手册的64位部分。不幸的是,它只能作为一个巨大的PDF文件提供,而不是每个指令单独的HTML页面。 - Nate Eldredge
你接受了 Nate 的回答,其中描述了一个常量方法。我不确定你想要什么。无论如何,我在 godbolt 上测试过,在 clang 上可以工作。你得到了什么错误?还要注意编译器完全能够从 C 代码生成这个,你根本不需要汇编。 - Jester
1
@Jester,对于混淆我感到抱歉,我的意思是在我的Godbolt上也能够工作,就像你说的那样,但它并没有工作。我回去仔细检查了我的代码,正如你所提到的,这是一个打字错误,我成功地让它工作了。非常感谢你。 - Jay
显示剩余7条评论
1个回答

9
ARM64的orr立即指令采用位掩码立即数,请参考ARMv8 A64汇编中立即数的范围进行解释。 GCC为此类型的操作数设置了一个约束条件: L
#define MASK (4UL << 60)

inline uint64_t set_bit(uint64_t x) {
    uint64_t result;
    asm("orr %0, %1, %2"
        : "=r" (result)
        : "r" (x), "L" (MASK));
    return result;
}

在 godbolt 上尝试

我进行了一些其他修复/改进:

  • 您在orr的目标和第一个源操作数中都使用了%0,这只有在幸运的情况下编译器选择相同的寄存器用于xresult时才有效,而这并非必须。您应该使用匹配输入的 x 的操作数,即这里的 %1。如果编译器认为有利,它仍然可以选择为两者选择相同的寄存器(在其他不适当的设置中,您可以使用 & 修饰符来禁止)。

  • 尽可能使用内联函数而不是宏。

请注意,如果将MASK的值更改为无法编码为位掩码立即数的值,例如 #define MASK (4UL << 60 | 1),则会收到编译器错误。 如果您希望此继续正常工作,可以将 L 约束更改为 Lr。 如果表达式MASK是适合作为位掩码立即数的编译时常量,则编译器将继续发出立即数。 如果不是,它将发出额外的代码来加载该值到寄存器中,然后发出带有寄存器操作数的orr试一下。

(当然,这个内联 asm 的示例有些荒谬。GCC 为 C 等价的代码 return x | MASK; 生成了 非常好的代码。但我认为这是更复杂代码的一个例子。即使如此,您也可以考虑是否可以提取 | 操作,并将其仅用作实际需要在 asm 中的输入。像往常一样,请始终考虑是否应用不要使用内联 asm)。


只是一个小细节:如果 x 也可能是一个常量,你可以用 if __builtin_constant_p(x) 包装那个汇编块,并把 return x | MASK 放在 else 中。如果有任何方法可以在编译时计算出值(而不是运行时),那很可能会获得胜利。 - David Wohlferd
谢谢您的建议,我从答案中学到了很多。回答您的另一个问题,是的,我的问题只是更复杂的代码片段中需要使用其他架构相关的汇编指令以及 orr - Jay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接