clang 汇编程序的奇怪行为

Strange behaviour of clang assembler

我尝试编译了这个Zend引擎的溢出检测宏:

#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do {   \
    long __tmpvar;                                                  \
    __asm__( \
        "mul %0, %2, %3\n"                                      \
        "smulh %1, %2, %3\n"                                        \
        "sub %1, %1, %0, asr #63\n"                                 \
            : "=X"(__tmpvar), "=X"(usedval)                         \
            : "X"(a), "X"(b));                                      \
    if (usedval) (dval) = (double) (a) * (double) (b);              \
    else (lval) = __tmpvar;                                         \
} while (0)

并在汇编中得到了这个结果:

; InlineAsm Start
mul     x8, x8, x9
smulh   x9, x8, x9
sub x9, x9, x8, asr #63

; InlineAsm End

编译器只用了2个寄存器用于宏的输入和输出,我认为至少是3个,导致计算结果错误(例如-1 * -1)。有什么建议吗?

汇编代码有问题。来自 GCC 关于 extended asm 的文档:

Use the ‘&’ constraint modifier (see Modifiers) on all output operands that must not overlap an input. Otherwise, GCC may allocate the output operand in the same register as an unrelated input operand, on the assumption that the assembler code consumes its inputs before producing outputs. This assumption may be false if the assembler code actually consists of more than one instruction.

这基本上是说,从您写入未标有&符号的输出参数的那一刻起,您就不能再使用输入参数,因为它们可能已被覆盖。

语法是围绕包装单个 insn 的概念设计的,该 insn 在写入其输出之前读取其输入。

当你使用多个insn时,你经常需要在约束上使用一个early-clobber修饰符("=&x")来让编译器知道你在读取所有输入之前写了一个输出或读写寄存器.然后它将确保输出寄存器与任何输入寄存器都不是同一个寄存器。

另见 tag wiki, and my collection of inline asm docs and SO answers

#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do {   \
    long __tmpvar;                                                  \
    __asm__( \
        "mul   %[tmp], %[a], %[b]\n\t"                              \
        "smulh %[uv], %[a], %[b]\n\t"                               \
        "sub   %[uv], %[uv], %[tmp], asr #63\n"                     \
            : [tmp] "=&X"(__tmpvar), [uv] "=&X"(usedval)            \
            : [a] "X"(a), [b] "X"(b));                              \
    if (usedval) (dval) = (double) (a) * (double) (b);              \
    else (lval) = __tmpvar;                                         \
} while (0)

你真的需要所有这些指令都在内联汇编中吗?你不能让 long tmp = a * b 成为输入操作数吗?然后如果编译器在函数的其他地方需要 a*b,CSE 可以看到它。

。所以希望你能哄编译器那样做 sub 。然后它可以使用 subssub 设置标志,而不需要在 usedval.

上单独测试 insn

如果您不能让您的目标编译器生成您想要的代码,那么可以试试内联汇编。但要注意,我已经看到 clang 比带有内联 asm 的 gcc 差很多。它往往会使内联代码变得更糟 x86 上的汇编。