clang汇编程序的奇怪行为

时间:2016-04-21 08:01:08

标签: gcc assembly clang llvm

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

null

将这个结果集合在一起:

#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)

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

2 个答案:

答案 0 :(得分:5)

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

  

对所有不能与输入重叠的输出操作数使用'&'约束修饰符(请参阅修饰符)。否则,GCC可以将输出操作数分配到同一寄存器中作为无关输入操作数,前提是汇编代码在产生输出之前消耗其输入。如果汇编程序代码实际上包含多个指令,则此假设可能为false。

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

答案 1 :(得分:3)

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

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

另请参阅代码wiki,以及我的内联asm文档和SO答案at the bottom of this answer的集合。

#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)

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

You can convince gcc to broadcast the sign bit with an arithmetic right shift using a ternary operator。所以希望你可以哄骗编译器以这种方式执行sub。然后,它可以使用subs设置sub的标记,而无需在usedval上单独测试insn。

如果您无法让目标编译器生成您想要的代码,那么请确保将内联作为一个镜头。但要注意,我已经看到clang比使用内联asm的gcc差很多。它倾向于使内联的代码更糟糕 asm on x86。