3

为了在 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 的内联汇编有什么有用的文档吗?当涉及到有趣的东西时,带有零示例的官方手册在这种情况下没有任何用处。用谷歌搜索并没有让我找到任何东西。所有的howtos和东西都只是谈论琐碎的基本东西,但完全省略了更高级的东西,比如多个替代约束。

4

3 回答 3

2

查看GMP和 GCClonglong.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
于 2013-09-19T10:00:53.120 回答
2

首先想到的是:

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"这是次优的,但特别是对于内联来说,因为它没有考虑到两个操作数是可互换的,并且总是返回 in RDXRAX它也限制了寄存器的选择。

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

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 指示此操作数和下一个操作数是可互换的。
这将创建以下代码(非内联):

部分.text的反汇编:

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

这看起来很像你想要的?

于 2013-09-19T08:56:14.987 回答
0

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

这使您无需知道目标汇编程序即可实现您想要的。不过,我在 GCC 中找不到类似的东西:(

于 2013-12-08T22:51:35.437 回答