2013-01-17 48 views
1

我編寫了一個非常漂亮的大整數的大整數庫,但限於512位(由於各種原因比GMP更快)。我正在嘗試將lib的大小進行概括。所以我必須循環adcq指令。ASM塊功能和ABI x86-64

// 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但只要我使用的優化,段錯誤/

我真的不明白,因爲我尊重ABI的RSI和RDI,撞註冊,使用良好的寄存器,所以我用GCC -O0 -S編譯和-02 -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行,我們看到編譯整理堆棧傳遞AR g到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 

好用於輸出迴路的第二次迭代,RDX不reflush至-4,因此,米ovq指令給出了不好的讀數,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"); 
} 

回答

3

你應該使用約束來訪問這些參數。 gcc不需要遵循內部函數的ABI,並且即使這樣做,也不需要在執行asm塊時保持初始狀態不變。當然,內聯asm的要點是讓編譯器內聯它,甚至不發生函數調用。 (很多peole誤以爲內嵌的意思是「嵌入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++版本,編譯器可以充分展開否則優化(包括在編譯時計算的東西!)。雖然在通用情況下,編譯器的代碼不如手寫代碼那樣高效,但是所提及的優化可能會使情況更好。注意:由於您使用的是內嵌asm,因此您的代碼已經是gccx86-64,因此使用__uint128_t不是進一步的限制(事實上,這適用於任何架構,其中gcc支持128位整數)。

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; 
    } 
} 
+0

命名參數是一個很好的舉措:這確實有助於確定代碼生成。 – wallyk

+0

1)如果我插入一個循環,解決方案是不穩定的,segfault再次:/。我只想做的是:adcq x [0] + = y [0]; adcq x [1] + = y [1]; adcq x [2] + = y [2]; adcq x [3] + = y [3]; 那爲什麼我使用incq。目前我將所有內核手動編寫在.cpp文件中,直到x86-64,power64的512位。我有很多線路,它不是一個完整的內聯解決方案。 2)對於通用,我仍然有一個。 –

+0

我意外地遺漏了'CLC',很抱歉。不應該導致segfault,但我從來沒有得到任何。你確定錯誤在那裏,而不是由編譯器挑選一組特定的寄存器觸發的嗎?您是否使用過調試器來查明問題? – Jester