1

大きな整数用に非常に優れた整数ライブラリをコーディングしましたが、512ビットに制限されています(さまざまな理由でGMPよりも高速です)。libを大きなサイズに一般化しようとしています。したがって、adcq命令をループする必要があります。

// long addition little indian order due the technique incq-jnz
// I can not use compare because it destroy the Carry Bit
template<int n>
void test_add(boost::uint64_t*, boost::uint64_t* ){    
    asm volatile (
        "clc                                     \n"
        "movq %0, %%rcx                          \n"
    "loop:                                       \n"
        "movq 8(%%rsi,%%rcx,8), %%rax            \n"  /* original -8(%%rsi,%%rbx,8) */
        "adcq %%rax           , 8(%%rdi,%%rcx,8) \n"  /* original -8(%%rsi,%%rbx,8) */
        "incq %%rcx                              \n"  /* original decq */
    "jnz loop                                    \n"
        :   
        :"g"(n)
        :"rax","rcx","cc","memory"
    );  
}


int main(int argc, char* argv[]) {
boost::uint64_t c[4],d[4];

c[0] = -1; 
c[1] = -1; 
c[2] = -1; 
c[3] =  0;  

d[0] = 1;
d[1] = 0;
d[2] = 0;
d[3] = 0;

test_add<-4>(&d[3],&c[3]); // <-- BigEndian to LittleEndian

これはデバッグモード-O0でうまく機能しますが、最適化を使用するとすぐにsegfault /

私はrsiとrdiのABIを尊重し、クローバーレジスターを使用し、適切なレジスターを使用しているため、よくわかりません。そのため、GCC-O0-Sと-O2-Sを使用してコンパイルしました。

-O0 -SIの場合、

 3 .globl main
 4         .type   main, @function
 5 main:
 6 .LFB1:
 7         .cfi_startproc
 8         .cfi_personality 0x3,__gxx_personality_v0
 9         pushq   %rbp
 10         .cfi_def_cfa_offset 16
 11         .cfi_offset 6, -16
 12         movq    %rsp, %rbp
 13         .cfi_def_cfa_register 6
 14         subq    $80, %rsp
 15         movl    %edi, -68(%rbp)
 16         movq    %rsi, -80(%rbp)
 17         movq    $-1, -32(%rbp)
 18         movq    $-1, -24(%rbp)
 19         movq    $-1, -16(%rbp)
 20         movq    $0, -8(%rbp)
 21         movq    $1, -64(%rbp)
 22         movq    $0, -56(%rbp)
 23         movq    $0, -48(%rbp)
 24         movq    $0, -40(%rbp)
 25         leaq    -32(%rbp), %rax
 26         leaq    24(%rax), %rdx
 27         leaq    -64(%rbp), %rax
 28         addq    $24, %rax
 29         movq    %rdx, %rsi
 30         movq    %rax, %rdi
 31         call    _Z8test_addILin4EEvPyS0_
 32         movl    $0, %eax
 33         leave
 34         .cfi_def_cfa 7, 8
 35         ret
 36         .cfi_endproc
 37 .LFE1:
 38         .size   main, .-main
 39         .section              .   enter code here  `enter code here`text._Z8test_addILin4EEvPyS0_,"axG",@progbits,_Z8test_addILin4EEvPyS0_,comdat
 40         .weak   _Z8test_addILin4EEvPyS0_
 41         .type   _Z8test_addILin4EEvPyS0_, @function
 42 _Z8test_addILin4EEvPyS0_:
 43 .LFB2:
 44         .cfi_startproc
 45         .cfi_personality 0x3,__gxx_personality_v0
 46         pushq   %rbp
 47         .cfi_def_cfa_offset 16
 48         .cfi_offset 6, -16
 49         movq    %rsp, %rbp
 50         .cfi_def_cfa_register 6
 51         movq    %rdi, -8(%rbp)
 52         movq    %rsi, -16(%rbp)
 53 #APP
 54 # 14 "test.cpp" 1
 55         clc
 56 movq $-4, %rcx
 57 loop:
 58 movq 8(%rsi,%rcx,8), %rax
 59 adcq %rax           , 8(%rdi,%rcx,8)
 60 incq %rcx
 61 jnz loop
 62 
 63 # 0 "" 2
 64 #NO_APP
 65         leave
 66         .cfi_def_cfa 7, 8
 67         ret
 68         .cfi_endproc
 69 .LFE2:
 70         .size   _Z8test_addILin4EEvPyS0_, .-_Z8test_addILin4EEvPyS0_
 71         .ident  "GCC: (GNU) 4.4.6 20120305 (Red Hat 4.4.6-4)"
 72         .section        .note.GNU-stack,"",@progbits

20〜30行目では、コンパイラがスタックを再編成して、argをrsiとrdiに渡し(29〜30行目)、呼び出しを行っています。ABIのように完璧

今私が得る最適化バージョンを見ると

  1         .file   "test.cpp"
  2         .text
  3         .p2align 4,,15
  4 .globl main
  5         .type   main, @function
  6 main:
  7 .LFB1:
  8         .cfi_startproc
  9         .cfi_personality 0x3,__gxx_personality_v0
  10 #APP
  11 # 14 "test.cpp" 1
  12         clc
  13 movq $-4, %rcx
  14 loop:
  15 movq 8(%rsi,%rcx,8), %rax
  16 adcq %rax           , 8(%rdi,%rcx,8)
  17 incq %rcx
  18 jnz loop
  19 
  20 # 0 "" 2
  21 #NO_APP
  22         xorl    %eax, %eax
  23         ret
  24         .cfi_endproc
  25 .LFE1:
  26         .size   main, .-main
  27         .ident  "GCC: (GNU) 4.4.6 20120305 (Red Hat 4.4.6-4)"
  28         .section        .note.GNU-stack,"",@progbits

さようならABI、わかりません。スタックは何によって管理されますか????

ASMの第一人者はアイデアを持っていますか?私は関数を独立したファイルに入れることを拒否します。メタプログラミングの精神です。

乾杯。

- - - - 編集 :

単一のループに入れると、ソリューションにバグが見つかりました:

#include <boost/cstdint.hpp> //boost type

template<long n>
void test_add(boost::uint64_t* x, boost::uint64_t const* y) {
    boost::uint64_t dummy;
    boost::uint64_t loop_index(n);
    __asm__ __volatile__ (
        "clc\n\t"
        "1:\n\t"
        "movq (%[y],%[counter],8), %[dummy]\n\t"
        "adcq %[dummy], (%[x], %[counter], 8)\n\t"
        "incq %[counter]\n\t"
        "jnz 1b\n\t"
        : [dummy] "=&r" (dummy)
        : [x] "r" (x), [y] "r" (y), [counter] "r" (loop_index)
        : "memory", "cc");
 }


int main(int argc, char* argv[]) {
    boost::uint64_t c[3],d[3];

    c[0] = -1; 
    c[1] = -1; 
    c[2] = -1; 
    c[3] =  0;  

    d[0] = 1;
    d[1] = 0;
    d[2] = 0;
    d[3] = 0;

for(int i=0; i < 0xfff; ++i)
    test_add<-4>(&c[4],&d[4]);

 return 0;

}

次のASMを提供します:

      movq    $-4, %rdx <---------------------template parameter
      leaq    -32(%rsp), %rcx
      movq    $-1, -32(%rsp)
      movq    $-1, -24(%rsp)
      movq    $-1, -16(%rsp)
      movq    $0, -8(%rsp)
      movq    $1, -64(%rsp)
      movq    $0, -56(%rsp)
      movq    $0, -48(%rsp)
      movq    $0, -40(%rsp)
      .p2align 4,,10
      .p2align 3
  .L2: <-------- OUPUT loop
#APP
# 16 "main.cpp" 1
       clc
       1: <-------- INPUT loop
       movq (%rcx,%rdx,8), %rsi
       adcq %rsi, (%rsp, %rdx, 8)
       incq %rdx <------------ rdx++ -> (-4)++ (for the @nd iteration of L2 it is not reset to -4)
       jnz 1b

 # 0 "" 2
 #NO_APP
       addl    $1, %eax
       cmpl    $4095, %eax <----- test second loop
       jne     .L2

出力ループの2回目の反復では、rdxは-4にリフラッシュされないため、movq命令は誤った読み取り値(segfault)を返します。私はそれを非常にひどくパッチします(私は-4で手動でリセットします)、jnzの後に「movq $ -4、%[counter] \ n \ t」を追加するだけですが、もっと一般的なものが必要です。カウンターをテンプレートパラメーター値にリセットする制約はありますか?

現在、修正は次のとおりです。

template<long n>
void test_add(boost::uint64_t* x, boost::uint64_t const* y) {
    boost::uint64_t dummy;
    __asm__ __volatile__ (
        "clc\n\t"
        "movq %[counter_value], %[counter]\n\t" // set the counter to the template value, it's not sure if the function is reused
        "1:\n\t"
        "movq (%[y],%[counter],8), %[dummy]\n\t"
        "adcq %[dummy], (%[x], %[counter], 8)\n\t"
        "incq %[counter]\n\t"
        "jnz 1b\n\t"
        : [dummy] "=&r" (dummy)
        : [x] "r" (x), [y] "r" (y), [counter] "r" (n), [counter_value] "i" (n)
        : "memory", "cc");
}
4

1 に答える 1

3

引数にアクセスするには、制約を使用する必要があります。gcc内部機能のためにABIに従う必要はありません。また、従う場合でも、asmブロックの実行時に初期状態をそのままにしておく必要はありません。もちろん、インラインasmのポイントは、コンパイラーにインライン化させることです。そうすれば、関数呼び出しも行われません。(多くの人は、インラインは「Cソースファイルに埋め込まれている」という意味であると誤解しており、実際のコードのインライン化が不要な場合でも便利な機能として使用します。)

gccまた、必要なレジスターに物を入れることもできます(ここでは、カウンターが特に気になるわけではありませんrcx)。また、レジスタ割り当て、ループ展開、およびその他の最適化を実行できるように、コンパイラにできるだけ多くを任せることも一般的には良い考えです。残念ながらgcc、生成できなかったADCので、今回はasmブロックが残ります。フラグが部分的に更新されているため、使用incはお勧めしませんが、現時点ではそれを回避する明確な方法はわかりません。

最後に、あなたの住所を渡すと、あなたが望むものではないd[3]アイテムにアクセスd[-1]します。d[2]合格する必要がありd[4]ます。

修正されたバージョンは次のようになります(名前付き引数を使用):

template<long n>
void test_add(boost::uint64_t* x, boost::uint64_t* y) {
    boost::uint64_t dummy, dummy2;
    __asm__ __volatile__ (
        "clc\n\t"
        "1:\n\t"
        "movq (%[y], %[counter], 8), %[dummy]\n\t"
        "adcq %[dummy], (%[x], %[counter], 8)\n\t"
        "incq %[counter]\n\t"
        "jnz 1b\n\t"
        : [dummy] "=&r" (dummy), "=r" (dummy2)
        : [x] "r" (x), [y] "r" (y), [counter] "1" (n)
        : "memory", "cc");
}

変数は、特定のレジスタを使用するように強制するのではなく、適切なレジスタを選択dummyできるようにしながら最適化されることに注意してください。gcc


更新:これは、コンパイラーが完全に展開し、その他の方法で最適化できる純粋なC ++バージョンです(コンパイル時の計算を含む)。一般的なケースでは、コンパイラのコードは手書きのコードほど効率的ではありませんが、前述の最適化により、状況によっては改善される可能性があります。注:gccインラインasmを使用しているため、コードはすでに具体的であり、使用gccはそれ以上の制限ではありません(実際、これは128ビット整数をサポートするすべてのアーキテクチャで機能します)。x86-64__uint128_tgcc

template<long n>
void test_add(boost::uint64_t* x, boost::uint64_t* y) {
    __uint128_t work = 0;
    for(long i = n; i < 0; i += 1) {
        work = work + x[i] + y[i];
        x[i] = work; // automatic truncation
        work >>= 64;
    }
}
于 2013-01-18T00:26:23.713 に答える