尝试使用带有多个替代约束的内联汇编实现128位add amd64

时间:2013-09-19 06:46:33

标签: gcc assembly x86-64

尝试在amd64上的GCC中获得可用的128位操作,我实现了一些内联函数。喜欢add_128_128_128。我想让编译器决定哪些寄存器用作输入和输出以获得最大的灵活性。所以,我使用了多种替代约束。

inline __uint128_t add_128_128_128(__uint128_t a, __uint128_t b) {
        uint64_t a_hi = a >> 64;
        uint64_t a_lo = a;
        uint64_t b_hi = b >> 64;
        uint64_t b_lo = b;
        uint64_t retval_hi;
        uint64_t retval_lo;

        asm (
                "\n"
                "       add     %2, %0\n"
                "       adc     %3, %1\n"
                : "=r,r,r,r" (retval_lo)
                , "=r,r,r,r" (retval_hi)
                : "r,0,r,0" (a_lo)
                , "0,r,0,r" (b_lo)
                , "r,1,1,r" (a_hi)
                , "1,r,r,1" (b_hi)
        );

        return ((__uint128_t)retval_hi) << 64 | retval_lo;
}

现在,生成的汇编器输出为:

_Z11add_128_128oo:
        movq    %rdx, %rax
        movq    %rcx, %rdx
        add     %rdi, %rax
        adc     %rax, %rdx
        ret

让我感到困惑的是如何修复adc指令。考虑到这一点,我得出了临时结论,即使匹配约束也会得到“新”数字,这可以解释%rax是%3 ==%0 ==%rax。那么,有没有办法告诉GCC只计算“r”约束? (我知道我可以通过放弃多个替代约束来使这个内联汇编工作。)

顺便说一句:有没有关于GCC内联汇编的有用文档?关于有趣的东西的零例子的官方手册是我在这种情况下称之为有用的东西。使用Google搜索并没有让我发现任何问题。所有的howtos和东西只是谈论琐碎的基本事物,但完全省略了更多高级的东西,如完全多个替代约束。

3 个答案:

答案 0 :(得分:2)

查看GMP和GCC等项目中包含的longlong.h标头。你会发现像:

这样的宏
#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
  __asm__ ("addq %5,%q1\n\tadcq %3,%q0"                                 \
           : "=r" (sh), "=&r" (sl)                                      \
           : "0"  ((UDItype)(ah)), "rme" ((UDItype)(bh)),               \
             "%1" ((UDItype)(al)), "rme" ((UDItype)(bl)))

应该很容易变成__uint128_t类型的内联函数。您可能希望添加类似:__attribute__ ((__always_inline__))的内容来强制内联,无论编译器标志如何。


此外,您是否看过为表达式生成的代码:a + b?我希望它能产生你想要的add/adc指令对,这是这种扩展类型的动机的一部分。


以下是u128 x u64 -> u128函数调用的结果(gcc-4.8.1):

    imulq   %rdx, %rsi
    movq    %rdx, %rax
    mulq    %rdi
    addq    %rsi, %rdx
    ret

u128 x u128 -> u128

imulq   %rdx, %rsi
movq    %rdi, %rax
imulq   %rdi, %rcx
mulq    %rdx
addq    %rcx, %rsi
addq    %rsi, %rdx
ret

答案 1 :(得分:1)

首先想到的是:

inline __uint128_t add_128_128_128(__uint128_t a, __uint128_t b) {
    asm("add %1, %%rax\n\t"
        "adc %2, %%rdx"
        : "+A"(a)
        : "r"((uint64_t)(b >> 64)), "r"((uint64_t)b)
        : "cc");
    return a;
}

这是因为GCC可以将RDX:RAX视为具有"A"约束的双倍大小寄存器对。这是次优的,特别是对于内联,因为它没有考虑到两个操作数是可互换的,并且总是在RDX中返回:RAX它也限制了寄存器选择。

要获得交换性,您可以使用%约束修饰符:

inline __uint128_t add_128_128_128(__uint128_t a, __uint128_t b) {
    uint64_t a_lo = a, a_hi = a >> 64, b_lo = b, b_hi = b >> 64;
    uint64_t r_lo, r_hi;
    asm("add %3, %0\n\t"
        "adc %5, %1"
        : "=r"(r_lo), "=r"(r_hi)
        : "%0" (a_lo), "r"(b_lo), "%1"(a_hi), "r"(b_hi) :
        : "cc");
    return ((__uint128_t)r_hi) << 64 | r_lo;
}

%向GCC表明该操作数和下一个操作数是可互换的 这将创建以下代码(非内联):

Disassembly of section .text:

0000000000000000 <add_128_128_128>:
   0:   48 89 f8                mov    %rdi,%rax
   3:   48 01 d0                add    %rdx,%rax
   6:   48 11 ce                adc    %rcx,%rsi
   9:   48 89 f2                mov    %rsi,%rdx
   c:   c3                      retq

看起来非常像你想要的?

答案 2 :(得分:0)

对GCC没有帮助,但也许CLANG的某些人可能会对此发现感到高兴: http://clang.llvm.org/docs/LanguageExtensions.html

这使您可以实现所需的功能,而无需了解目标Assembler。我找不到GCC的这样的东西:(

相关问题