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 行と 2 番目の行の間では保存されません。ループが i をインクリメントして CB I を「削除」するため、2 つの ASM 呼び出しで CB を保存するための GAS 制約が存在する必要があるため、論理的である可能性があります。残念ながらそのような情報は見つかりません。

何か案が ?

ありがとう、メルシー!

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");

ただし、ホット コードの場合、タイト ループは最適ではありません。adc1 つには、命令には依存関係があり、インライン/展開されたシーケンスよりも反復あたりの命令が大幅に多くなります。より良いシーケンスは次のようなものです ( %rbpresp.%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 

そのようなブロックの周りだけをループします。利点は、ロードがブロックされ、ループカウント/終了条件を1回だけ処理できることです。

正直なところ、一般的なビット幅を特に「きちんとした」ものにするのではなく、2 のべき乗のビット幅など、明示的にアンロールされたコードを特殊なケースにしようとします。むしろ、最適化されていないテンプレートのインスタンス化にフラグ/コンストラクター メッセージを追加して、ユーザーに「2 の累乗を使用する」ように伝えますか?

于 2012-12-07T13:43:16.393 に答える