2012-11-29 44 views
5

Tôi đang cố gắng viết assembly x86-64 nội tuyến cho GCC để sử dụng hiệu quả lệnh MULQ. MULQ nhân RAX đăng ký 64 bit với một giá trị 64 bit khác. Giá trị khác có thể là bất kỳ thanh ghi 64 bit nào (thậm chí là RAX) hoặc một giá trị trong bộ nhớ. MULQ đặt 64 bit cao của sản phẩm vào RDX và 64 bit thấp vào RAX.GCC có thể phát ra các phép ghi nhớ hướng dẫn khác nhau khi lựa chọn giữa nhiều ràng buộc toán hạng thay thế của assembly nội tuyến?

Bây giờ, thật dễ dàng, đủ để thể hiện một mulq đúng như lắp ráp nội tuyến:

#include <stdint.h> 
static inline void mulq(uint64_t *high, uint64_t *low, uint64_t x, uint64_t y) 
{ 
    asm ("mulq %[y]" 
      : "=d" (*high), "=a" (*low) 
      : "a" (x), [y] "rm" (y)  
     ); 
} 

Mã này là đúng, nhưng không phải là tối ưu. MULQ là giao hoán, vì vậy nếu y xảy ra ở trong RAX, thì sẽ là chính xác để rời khỏi y vị trí của nó và nhân lên. Nhưng GCC không biết điều đó, vì vậy nó sẽ phát ra các hướng dẫn bổ sung để di chuyển các toán hạng vào các vị trí được xác định trước của chúng. Tôi muốn nói với GCC rằng nó có thể đặt đầu vào ở một trong hai vị trí, miễn là một đầu vào trong RAX và MULQ tham chiếu đến vị trí khác. GCC có một cú pháp cho điều này, được gọi là "nhiều ràng buộc thay thế". Lưu ý dấu phẩy (nhưng tổng số asm() bị hỏng; xem bên dưới):

asm ("mulq %[y]" 
     : "=d,d" (*high), "=a,a" (*low) 
     : "a,rm" (x), [y] "rm,a" (y)  
    ); 

Thật không may, điều này là sai. Nếu GCC chọn ràng buộc thay thế thứ hai, nó sẽ phát ra "mulq% rax". Để được rõ ràng, hãy xem xét chức năng này:

uint64_t f() 
{ 
    uint64_t high, low; 
    uint64_t rax; 
    asm("or %0,%0": "=a" (rax)); 
    mulq(&high, &low, 7, rax); 
    return high; 
} 

Biên soạn với gcc -O3 -c -fkeep-inline-functions mulq.c, GCC phát ra lắp ráp này:

0000000000000010 <f>: 
    10: or  %rax,%rax 
    13: mov $0x7,%edx 
    18: mul %rax 
    1b: mov %rdx,%rax 
    1e: retq 

Các "mul% rax" cần được "mul% RDX".

Làm cách nào có thể viết lại nội tuyến này để tạo ra kết quả chính xác trong mọi trường hợp?

+0

Cách giải quyết là đặt macro lắp ráp trong assembly nội tuyến, một cái gì đó như 'MULQ_FIX_OPERANDS (% 2,% 3)', chọn toán hạng không phải RAX, nhưng trông thực sự xấu với tôi. Tôi hy vọng ai đó có một giải pháp tốt hơn. – staufk

+0

GCC hỗ trợ ràng buộc '%' để thể hiện các toán hạng giao hoán giữa các ràng buộc thay thế, nhưng điều đó không giải quyết được vấn đề vì MULQ chỉ nhận toán hạng. Nếu chỉ có 'gas' nghĩ 'MULQ RAX, r/m64' là một định dạng hợp lệ cho lệnh này! – staufk

Trả lời

2
__asm__ ("mulq %3" : "=a,a" (*low), "=d,d" (*high) : "%0,0" (x), "r,m" (y)) 

Điều này tương tự như những gì bạn sẽ tìm thấy trong longlong.h được bao gồm trong các gói GNU khác nhau; "r,m" thay vì "rm" thực sự là lợi ích của clang. Cú pháp ràng buộc nhiều vẫn có vẻ quan trọng đối với tiếng kêu, như được thảo luận here. Đó là một sự xấu hổ, nhưng tôi vẫn thấy rằng clang làm một công việc tồi tệ hơn của sự ràng buộc ràng buộc (đặc biệt là trên x86 [-86]) hơn gcc. đối với gcc:

__asm__ ("mulq %3" : "=a" (*low), "=d" (*high) : "%0" (x), "rm" (y)) 

sẽ đủ và sẽ ưu tiên giữ (y) trong sổ đăng ký, trừ khi áp lực đăng ký quá cao; nhưng clang luôn luôn dường như tràn trong nhiều trường hợp. Các thử nghiệm của tôi cho thấy nó sẽ chọn tùy chọn đầu tiên "r" trong cú pháp ràng buộc nhiều.

"%3" như một số bị nhơn trong hướng dẫn phép hoặc một thanh ghi (ưa thích) hoặc vị trí bộ nhớ, như aliased bởi thứ ba toán hạng, so với zero, đó là (y). "0" bí danh toán hạng 'zero-th': (*low), rõ ràng là "a", tức là %rax cho 64 bit. Ký tự % hàng đầu trong "%0" là toán tử giao hoán: nghĩa là, (x) có thể đi làm với (y) nếu điều đó giúp đăng ký phân bổ. Rõ ràng, mulq có tính giao hoán là: x * y == y * x.

Chúng tôi thực sự khá hạn chế ở đây. mulq nhân toán hạng 64 bit %3 với giá trị trong %rax để tạo ra sản phẩm 128 bit: %rdx:%rax. "0" (x) có nghĩa là (x) phải được tải vào %rax(y) phải được tải vào đăng ký 64 bit hoặc địa chỉ bộ nhớ. Tuy nhiên %0 có nghĩa là (x) và đầu vào sau (y) có thể đi làm.

Tôi cũng sẽ tham khảo best practical inline assembly tutorial Tôi đã tìm thấy. Trong khi các tài liệu tham khảo gcc là 'có thẩm quyền', chúng tạo ra một hướng dẫn kém.


Cảm ơn Chris để chọn lỗi trong yêu cầu ràng buộc ban đầu của tôi.

+0

Khi tôi thử mã, nó tính y \ * y thay vì x \ * y. – Chris

+0

Khi tôi thực thi 'uint64_t hi, lo, a = 20, b = 30; mulq (& hi, & lo, a, b); ', tôi nhận được lo = 900. Asm tôi nhận được là: '_mulq: pushq% rbp \\ movq% rsp,% rbp \\ movq% rdi, -8 (% rbp) \\ movq% rsi, -16 (% rbp) \\ movq% rdx, -24 (% rbp) \\ movq% rcx, -32 (% rbp) \\ movq -8 (% rbp),% rax \\ movq -16 (% rbp),% rcx \\ movq -24 (% rbp),% rdx \\ movq -32 (% rbp),% rsi \\ movq% rax, -40 (% rbp) \\ ## InlineAsm Bắt đầu \\ mulq% rsi \\ ## InlineAsm End \\ movq -40 (% rbp),% rsi \\ movq% rdx, (% rsi) \\ movq% rax, (% rcx) \\ popq% rbp \\ ret.' Xin lỗi vì sự lộn xộn, nhưng tôi mới làm quen với nội tuyến asm, tôi không chắc bạn muốn thông tin nào. – Chris

+0

Có, cả hai dòng hoạt động hoàn hảo ngay bây giờ! :) (Tôi đã thử nghiệm với gcc) Chỉ có một lỗi đánh máy nhỏ: một thiếu "sau khi% 3 trong mã cho gcc. Rất cám ơn cho câu trả lời tuyệt vời này! – Chris

0

Sử dụng lừa như thế này:

void multiply(unsigned& rhi, unsigned& rlo, unsigned a, unsigned b) 
{ 
__asm__(
" mull %[b]\n" 
:"=d"(rhi),"=a"(rlo) 
:"1"(a),[b]"rm"(b)); 
} 

Thông báo "1" luận đặc điểm kỹ thuật cho đầu vào toán hạng a. Điều này có nghĩa là "đặt" một "vào cùng một vị trí mà đối số # 1 là".

0

Brett Hale's answer tạo mã tối ưu trong một số trường hợp (ít nhất là trên GCC 5.4.0).

Given:

static inline void mulq(uint64_t *high, uint64_t *low, uint64_t x, uint64_t y) { 
    __asm__ ("mulq %3" : "=a" (*low), "=d" (*high) : "%0" (x), "rm" (y) : "cc"); 
} 

uint64_t foo(); 

Sau đó mulq(&high, &low, foo(), 42) biên dịch để:

call foo 
    movl $42, %edx 
    mulq %rdx 

... đó là tối ưu.

Nhưng bây giờ đảo ngược thứ tự của các toán hạng:

mulq(&high, &low, 42, foo()); 

... và nhìn vào những gì xảy ra với mã biên dịch:

call foo 
    movq %rax, %rdx 
    movl $42, %eax 
    mulq %rdx 

Oops! Chuyện gì đã xảy ra? Trình biên dịch nhấn mạnh vào việc đưa 42 vào rax, và vì vậy nó phải di chuyển giá trị trả về từ foo() ra khỏi rax. Rõ ràng ràng buộc toán hạng % (giao hoán) có lỗi.

Có cách nào để tối ưu hóa điều này không? Hóa ra là, mặc dù hơi lộn xộn một chút.

static inline void mulq(uint64_t *high, uint64_t *low, uint64_t x, uint64_t y) { 
    __asm__ (
     ".ifnc %2,%%rax\n\t" 
     "mulq %2\n\t" 
     ".else\n\t" 
     "mulq %3\n\t" 
     ".endif" 
     : "=a,a" (*low), "=d,d" (*high) 
     : "a,rm" (x), "rm,a" (y) 
     : "cc"); 
} 

Bây giờ mulq(&high, &low, foo(), 42) biên dịch để:

call foo 
    movl $42, %edx 
    .ifnc %rax,%rax 
    mulq %rax 
    .else 
    mulq %rdx 
    .endif 

mulq(&high, &low, 42, foo()) biên dịch để:

call foo 
    movl $42, %edx 
    .ifnc %rdx,%rax 
    mulq %rdx 
    .else 
    mulq %rax 
    .endif 

Mã này sử dụng một thủ thuật lắp ráp để có được xung quanh giới hạn đó GCC không cho phép chúng ta phát ra mã lắp ráp khác nhau tùy thuộc vào các hạn chế thay thế mà nó đã chọn.Trong mỗi trường hợp, người lắp ráp sẽ chỉ phát ra một trong hai hướng dẫn mulq có thể, tùy thuộc vào việc trình biên dịch đã chọn đặt x hoặc y trong rax.

Đáng buồn thay, thủ thuật này là tối ưu nếu chúng ta nhân giá trị trả về của foo() bởi giá trị tại một vị trí bộ nhớ:

extern uint64_t bar; 

Bây giờ mulq(&high, &low, bar, foo()) biên dịch để:

call foo 
    .ifnc bar(%rip),%rax 
    mulq bar(%rip) 
    .else 
    mulq %rax 
    .endif 

... đó là tối ưu , nhưng mulq(&high, &low, foo(), bar) biên dịch thành:

movq bar(%rip), %rbx 
    call foo 
    .ifnc %rax,%rax 
    mulq %rax 
    .else 
    mulq %rbx 
    .endif 

… mà không cần sao chép bar vào rbx.

Tôi không thể tìm ra cách để tạo mã tối ưu đầu ra GCC trong mọi trường hợp, thật không may. Buộc hệ số là toán hạng bộ nhớ, vì mục đích điều tra, chỉ khiến GCC tải bar(%rip) vào sổ đăng ký và sau đó lưu sổ đăng ký đó vào vị trí ngăn xếp tạm thời sau đó chuyển đến mulq.