2

我正在 GAS 内联汇编中编写汇编长加法,

template <std::size_t NumBits>
void inline KA_add(vli<NumBits> & x, vli<NumBits> const& y);

如果我专攻,我可以:

template <>
void inline KA_add<128>(vli<128> & x, vli<128> const& y){
     asm("addq  %2, %0; adcq  %3, %1;" :"+r"(x[0]),"+r"(x[1]):"g"(y[0]),"g"(y[1]):"cc");
}

很好,现在如果我尝试概括以允许模板内联,并让我的编译器工作任何长度......

template <std::size_t NumBits>
void inline KA_add(vli<NumBits> & x, vli<NumBits> const& y){
    asm("addq  %1, %0;" :"+r"(x[0]):"g"(y[0]):"cc");
    for(int i(1); i < vli<NumBits>::numwords;++i)
        asm("adcq  %1, %0;" :"+r"(x[i]):"g"(y[i]):"cc");
};

好吧,它不起作用我不能保证进位位(CB)被传播。第一个 asm 行和第二个 asm 行之间不守恒。这可能是合乎逻辑的,因为循环增加了 i 并因此“删除”了 CB I 事物,它应该存在 GAS 约束以在两个 ASM 调用中保存 CB。不幸的是,我没有找到这样的信息。

任何的想法 ?

谢谢你,谢谢!

PS 我重写了我的函数以删除 C++ 意识形态

template <std::size_t NumBits>
inline void KA_add_test(boost::uint64_t* x, boost::uint64_t const* y){
    asm ("addq  %1, %0;" :"+r"(x[0]):"g"(y[0]):"cc");
        for(int i(1); i < vli<NumBits>::numwords;++i)
            asm ("adcq  %1, %0;" :"+r"(x[i]):"g"(y[i]):"cc");
};

asm 给出(GCC 调试模式),

应用程序

    addq  %rdx, %rax; 

NO_APP

    movq    -24(%rbp), %rdx
    movq    %rax, (%rdx)

.LBB94:.loc 9 55 0

    movl    $1, -4(%rbp)
    jmp     .L323 

.L324:

    .loc 9 56 0

    movl    -4(%rbp), %eax
    cltq  
    salq    $3, %rax
    movq    %rax, %rdx
    addq    -24(%rbp), %rdx <----------------- Break the carry bit
    movl    -4(%rbp), %eax
    cltq  
    salq    $3, %rax
    addq    -32(%rbp), %rax
    movq    (%rax), %rcx
    movq    (%rdx), %rax

应用程序

    adcq  %rcx, %rax; 

NO_APP

正如我们所读到的,还有额外的 addq,它破坏了 CB 的传播

4

1 回答 1

1

我看不出有办法明确告诉编译器必须在没有影响C标志的指令的情况下创建循环代码。

这样做肯定是可能的 - 用于lea向上计数数组地址,dec向下计数循环并测试Z结束条件。这样,除了实际的数组总和外,循环中的任何内容都不会更改C标志。

你必须做一个手动的事情,比如:

long long tmp; // hold a register

__asm__("0:
    movq (%1), %0
    lea 8(%1), %1
    adcq  %0, (%2)
    lea 8(%2), %2
    dec %3
    jnz 0b"
    : "=r"(tmp)
    : "m"(&x[0]), "m"(&y[0]), "r"(vli<NumBits>::numwords)
    : "cc", "memory");

对于热代码,紧密的循环并不是最优的;一方面,指令具有依赖性,并且每次迭代的指令比内联/展开adc序列要多得多。更好的序列将类似于(%rbp分别%rsi具有源和目标数组的起始地址):

0:

lea  64(%rbp), %r13
lea  64(%rsi), %r14
movq   (%rbp), %rax
movq  8(%rbp), %rdx
adcq   (%rsi), %rax
movq 16(%rbp), %rcx
adcq  8(%rsi), %rdx
movq 24(%rbp), %r8
adcq 16(%rsi), %rcx
movq 32(%rbp), %r9
adcq 24(%rsi), %r8
movq 40(%rbp), %r10
adcq 32(%rsi), %r9
movq 48(%rbp), %r11
adcq 40(%rsi), %r10
movq 56(%rbp), %r12
adcq 48(%rsi), %r10
movq %rax,   (%rsi)
adcq 56(%rsi), %r10
movq %rdx,  8(%rsi)
movq %rcx, 16(%rsi)
movq %r8,  24(%rsi)
movq %r13, %rbp     // next src
movq %r9,  32(%rsi)
movq %r10, 40(%rsi)
movq %r11, 48(%rsi)
movq %r12, 56(%rsi)
movq %r14, %rsi     // next tgt
dec  %edi           // use counter % 8 (doing 8 words / iteration)
jnz 0b              // loop again if not yet zero 

并且只在这些块周围循环。这样做的好处是负载被阻塞,并且您每次只处理一次循环计数/终止条件。

老实说,我会尽量不要使一般位宽特别“整洁”,而是明确展开代码的特殊情况,例如,二次幂的位宽。而是将标志/构造函数消息添加到非优化模板实例中,告诉用户“使用二的幂”?

于 2012-12-07T13:43:16.393 回答