携带位,GAS约束

时间:2012-11-30 12:06:15

标签: x86-64 inline-assembly

我正在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。不幸的是我没有找到这样的信息。

有什么想法吗?

谢谢你,Merci!

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调试模式),

APP

    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

APP

    adcq  %rcx, %rax; 

NO_APP

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

1 个答案:

答案 0 :(得分: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 resp。%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 

并仅围绕这些块循环。优点是负载被阻塞,并且您只需处理一次循环计数/终止条件。

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