Tại sao các lệnh x86-64 trên thanh ghi 32-bit lại không có phần trên của thanh ghi 64-bit đầy đủ?


118

Trong Chuyến tham quan hướng dẫn sử dụng Intel x86-64 , tôi đã đọc

Có lẽ sự thật đáng ngạc nhiên nhất là một lệnh chẳng hạn như MOV EAX, EBXtự động lấy 32 bit trên của RAXthanh ghi.

Tài liệu Intel (3.4.1.1 Thanh ghi Mục đích Chung ở Chế độ 64-Bit trong Kiến trúc Cơ bản thủ công) được trích dẫn tại cùng một nguồn cho chúng ta biết:

  • Toán hạng 64 bit tạo ra kết quả 64 bit trong thanh ghi mục đích chung đích.
  • Toán hạng 32 bit tạo ra kết quả 32 bit, không được mở rộng thành kết quả 64 bit trong thanh ghi mục đích chung đích.
  • Toán hạng 8 bit và 16 bit tạo ra kết quả 8 bit hoặc 16 bit. 56 bit trên hoặc 48 bit (tương ứng) của thanh ghi mục đích chung không bị sửa đổi bởi hoạt động. Nếu kết quả của phép toán 8-bit hoặc 16-bit nhằm mục đích tính toán địa chỉ 64-bit, hãy ký-mở rộng thanh ghi một cách rõ ràng đến 64-bit đầy đủ.

Trong hợp ngữ x86-32 và x86-64, các lệnh 16 bit như

mov ax, bx

không hiển thị loại hành vi "kỳ lạ" này mà từ trên của eax là số 0.

Như vậy: lý do tại sao hành vi này được đưa ra? Thoạt nhìn nó có vẻ phi logic (nhưng lý do có thể là do tôi đã quen với những điều kỳ quặc của hội x86-32).


16
Nếu bạn Google cho "Gian hàng đăng ký một phần", bạn sẽ tìm thấy khá nhiều thông tin về vấn đề mà họ (gần như chắc chắn) đang cố gắng tránh.
Jerry Coffin


4
Không chỉ "hầu hết". AFAIK, tất cả các lệnh có r32toán hạng đích bằng không giá trị cao 32, thay vì hợp nhất. Ví dụ: một số trình lắp ráp sẽ thay thế pmovmskb r64, xmmbằng pmovmskb r32, xmm, lưu REX, vì phiên bản đích 64 bit hoạt động giống hệt nhau. Mặc dù phần Hoạt động của sổ tay liệt kê tất cả 6 tổ hợp của nguồn đích 32 / 64bit và nguồn 64/128 / 256b riêng biệt, phần mở rộng bằng không ngầm định của biểu mẫu r32 sao chép phần mở rộng bằng 0 rõ ràng của biểu mẫu r64. Tôi tò mò về việc triển khai HW ...
Peter Cordes

2
@HansPassant, tham chiếu vòng tròn bắt đầu.
kchoi

Câu trả lời:


97

Tôi không phải AMD hay nói thay cho họ, nhưng tôi sẽ làm theo cách tương tự. Bởi vì việc giảm nửa cao không tạo ra sự phụ thuộc vào giá trị trước đó, nên CPU sẽ phải đợi. Các đăng ký đổi tên cơ chế cơ bản sẽ bị đánh bại nếu nó không được thực hiện theo cách đó.

Bằng cách này, bạn có thể viết mã nhanh bằng cách sử dụng các giá trị 32-bit ở chế độ 64-bit mà không cần phải phá vỡ các phụ thuộc một cách rõ ràng mọi lúc. Nếu không có hành vi này, mọi lệnh 32-bit ở chế độ 64-bit sẽ phải chờ đợi điều gì đó đã xảy ra trước đó, mặc dù phần cao đó hầu như sẽ không bao giờ được sử dụng. (Tạo int64-bit sẽ lãng phí dấu chân bộ nhớ cache và băng thông bộ nhớ; x86-64 hỗ trợ hiệu quả nhất kích thước toán hạng 32 và 64-bit )

Hành vi cho các kích thước toán hạng 8 và 16 bit là một hành vi kỳ lạ. Sự điên rồ về sự phụ thuộc là một trong những lý do mà các lệnh 16-bit bị tránh khỏi hiện nay. x86-64 kế thừa điều này từ 8086 cho 8-bit và 386 cho 16-bit, và quyết định có các thanh ghi 8 và 16-bit hoạt động theo cùng một cách ở chế độ 64-bit như ở chế độ 32-bit.


Xem thêm Tại sao GCC không sử dụng thanh ghi từng phần? để biết chi tiết thực tế về cách ghi vào các thanh ghi từng phần 8 và 16 bit (và các lần đọc tiếp theo của thanh ghi đầy đủ) được xử lý bởi các CPU thực.


8
Tôi không nghĩ đó là điều lạ, tôi nghĩ rằng họ không muốn phá vỡ quá nhiều và giữ nguyên cách cư xử cũ ở đó.
Alexey Frunze

5
@Alex khi họ giới thiệu chế độ 32bit, không có hành vi cũ cho phần cao. Trước đây không có phần cao .. Tất nhiên sau đó không thể thay đổi được nữa.
harold

1
Tôi đã nói về toán hạng 16 bit, tại sao các bit trên cùng không bị 0 trong trường hợp đó. Chúng không ở chế độ không phải 64-bit. Và nó cũng được giữ ở chế độ 64-bit.
Alexey Frunze

3
Tôi đã giải thích "Hành vi đối với các lệnh 16 bit là một điều kỳ lạ" là "thật kỳ lạ khi phần mở rộng bằng 0 không xảy ra với các toán hạng 16 bit ở chế độ 64 bit". Do đó, nhận xét của tôi về việc giữ nguyên nó ở chế độ 64-bit để tương thích tốt hơn.
Alexey Frunze

8
@Alex ơi, tôi hiểu rồi. Đồng ý. Tôi không nghĩ nó lạ từ góc độ đó. Chỉ từ một phân tích "nhìn lại, có lẽ đó không phải là một ý kiến ​​hay". Đoán tôi cần phải có được rõ ràng hơn :)
harold

9

Nó chỉ đơn giản là tiết kiệm không gian trong các hướng dẫn và tập lệnh. Bạn có thể di chuyển các giá trị nhỏ tức thời vào thanh ghi 64 bit bằng cách sử dụng các hướng dẫn hiện có (32 bit).

Nó cũng giúp bạn không phải mã hóa các giá trị 8 byte cho MOV RAX, 42, khiMOV EAX, 42 có thể được sử dụng lại.

Việc tối ưu hóa này không quan trọng đối với các ops 8 và 16 bit (vì chúng nhỏ hơn), và việc thay đổi các quy tắc ở đó cũng sẽ phá vỡ mã cũ.


7
Nếu điều đó đúng, có phải nó sẽ hợp lý hơn khi ký-gia hạn thay vì gia-hạn-0 không?
Damien_The_Un Believer

16
Tiện ích mở rộng dấu hiệu chậm hơn, ngay cả trong phần cứng. Phần mở rộng bằng 0 có thể được thực hiện song song với bất kỳ phép tính nào tạo ra nửa dưới, nhưng không thể thực hiện mở rộng dấu cho đến khi (ít nhất là dấu của) nửa dưới được tính.
Jerry Coffin

13
Một thủ thuật liên quan khác là sử dụng XOR EAX, EAXXOR RAX, RAXsẽ cần tiền tố REX.
Neil

3
@Nubok: Chắc chắn rồi, họ có thể đã thêm mã hóa movzx / movsx để có một đối số ngay lập tức. Hầu hết thời gian, thuận tiện hơn khi để các bit trên bằng 0, vì vậy bạn có thể sử dụng một giá trị làm chỉ số mảng (vì tất cả các reg phải có cùng kích thước trong một địa chỉ hiệu dụng: [rsi + edx]không được phép). Tất nhiên, tránh phụ thuộc sai / gian hàng đăng ký một phần (câu trả lời còn lại) là một lý do chính khác.
Peter Cordes

4
và thay đổi các quy tắc ở đó cũng sẽ phá vỡ mã cũ. Mã cũ không thể chạy ở chế độ 64 bit (ví dụ: 1 byte inc / dec là tiền tố REX); điều này không liên quan. Lý do không làm sạch mụn cóc của x86 là có ít sự khác biệt hơn giữa chế độ dài và chế độ compat / kế thừa, do đó, ít hướng dẫn phải giải mã khác nhau tùy thuộc vào chế độ. AMD không biết AMD64 sẽ bắt kịp và không may là họ rất thận trọng nên sẽ cần ít bóng bán dẫn hơn để hỗ trợ. Về lâu dài, sẽ ổn nếu trình biên dịch và con người phải nhớ những thứ nào hoạt động khác nhau ở chế độ 64-bit.
Peter Cordes

1

Nếu không có số không mở rộng đến 64 bit, điều đó có nghĩa là một lệnh đọc từ raxsẽ có 2 phụ thuộc cho raxtoán hạng của nó (lệnh ghi vào eaxvà lệnh ghi vào raxtrước nó), điều này có nghĩa là 1) ROB sẽ phải có các mục nhiều phụ thuộc cho một toán hạng duy nhất, có nghĩa là ROB sẽ yêu cầu nhiều logic và bóng bán dẫn hơn, đồng thời chiếm nhiều không gian hơn và việc thực thi sẽ chậm hơn khi chờ đợi phụ thuộc thứ hai không cần thiết có thể mất nhiều thời gian để thực thi; hoặc cách khác là 2), mà tôi đoán sẽ xảy ra với các lệnh 16 bit, giai đoạn cấp phát có thể dừng lại (nghĩa là nếu RAT có phân bổ hoạt động cho một lần axghi và một lần eaxđọc xuất hiện, nó sẽ dừng lại cho đến khi việc axghi ngừng hoạt động ).

mov rdx, 1
mov rax, 6
imul rax, rdx
mov rbx, rax
mov eax, 7 //retires before add rax, 6
mov rdx, rax // has to wait for both imul rax, rdx and mov eax, 7 to finish before dispatch to the execution units, even though the higher order bits are identical anyway

Lợi ích duy nhất của việc mở rộng không bằng 0 là đảm bảo raxbao gồm các bit thứ tự cao hơn , ví dụ: nếu ban đầu nó chứa 0xffffffffffffffff, kết quả sẽ là 0xffffffff00000007, nhưng có rất ít lý do để ISA thực hiện đảm bảo này với chi phí như vậy, và nhiều khả năng lợi ích của phần mở rộng bằng không sẽ thực sự được yêu cầu nhiều hơn, vì vậy nó tiết kiệm thêm dòng mã mov rax, 0. Bằng cách đảm bảo rằng nó sẽ luôn bằng 0 được mở rộng đến 64 bit, các trình biên dịch có thể làm việc với tiên đề này trong khi thực hiện mov rdx, rax, raxchỉ phải đợi một phụ thuộc duy nhất của nó, có nghĩa là nó có thể bắt đầu thực thi nhanh hơn và rút lui, giải phóng các đơn vị thực thi. Hơn nữa, nó cũng cho phép các thành ngữ số 0 hiệu quả hơn như xor eax, eaxsố 0 raxmà không yêu cầu byte REX.


Cờ một phần trên Skylake ít nhất hoạt động bằng cách có đầu vào riêng biệt cho CF so với bất kỳ SPAZO nào. (Như vậy cmovbelà 2 uops nhưng cmovblà 1). Nhưng không có CPU nào thực hiện đổi tên thanh ghi từng phần theo cách bạn đề xuất. Thay vào đó, họ chèn một uop hợp nhất nếu một đăng ký được đổi tên riêng biệt với đăng ký đầy đủ (tức là "bẩn"). Xem Tại sao GCC không sử dụng thanh ghi từng phần? Chính xác thì các thanh ghi từng phần trên Haswell / Skylake hoạt động như thế nào? Viết AL dường như có sự phụ thuộc sai vào RAX và AH không nhất quán
Peter Cordes

Các CPU thuộc họ P6 hoặc bị đình trệ trong ~ 3 chu kỳ để chèn uop hợp nhất (Core2 / Nehalem) hoặc họ P6 trước đó (PM, PIII, PII, PPro) chỉ dừng lại trong (ít nhất?) ~ 6 chu kỳ. Có lẽ điều đó giống như bạn đã đề xuất trong phần 2, đợi giá trị reg đầy đủ có sẵn thông qua ghi lại vào tệp đăng ký vĩnh viễn / kiến ​​trúc.
Peter Cordes

@PeterCordes ồ, tôi đã biết về việc hợp nhất các uops ít nhất là cho một phần cờ xí. Có lý, nhưng tôi đã quên nó hoạt động như thế nào trong một phút; nó đã nhấp một lần nhưng tôi quên ghi chú
Lewis Kelsey

@PeterCordes microarchitecture.pdf: This gives a delay of 5 - 6 clocks. The reason is that a temporary register has been assigned to AL to make it independent of AH. The execution unit has to wait until the write to AL has retired before it is possible to combine the value from AL with the value of the rest of EAXTôi không thể tìm thấy ví dụ về 'hợp nhất uop' sẽ được sử dụng để giải quyết vấn đề này, mặc dù vậy, tương tự đối với tình trạng treo cờ một phần
Lewis Kelsey

Đúng, P6 đầu chỉ dừng lại cho đến khi viết lại. Core2 và Nehalem chèn một uop hợp nhất sau / trước? chỉ dừng giao diện người dùng trong thời gian ngắn hơn. Sandybridge chèn các uops hợp nhất mà không bị đình trệ. (Nhưng hợp nhất AH phải tự phát hành theo chu kỳ, trong khi hợp nhất AL có thể là một phần của một nhóm đầy đủ.) Haswell / SKL hoàn toàn không đổi tên AL riêng biệt với RAX, do đó, mov al, [mem]tải vi hợp nhất + ALU- hợp nhất, chỉ đổi tên AH và uop hợp nhất AH vẫn còn vấn đề riêng lẻ. Cơ chế hợp nhất cờ từng phần trong các CPU này khác nhau, ví dụ như Core2 / Nehalem vẫn chỉ dừng lại đối với cờ từng phần, không giống như đăng ký từng phần.
Peter Cordes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.