Lỗi GCC có thể xảy ra khi trả về struct từ hàm


133

Tôi tin rằng tôi đã tìm thấy một lỗi trong GCC khi triển khai PCG PRNG của O'Neill. ( Mã ban đầu trên Godbolt's Compiler Explorer )

Sau khi nhân oldstatevới MULTIPLIER, (kết quả được lưu trữ trong rdi), GCC không thêm kết quả đó vào INCREMENT, INCREMENTthay vào đó, hãy chuyển sang ndx, sau đó được sử dụng làm giá trị trả về của rand32_ret.state

Một ví dụ tái sản xuất tối thiểu ( Trình biên dịch Explorer ):

#include <stdint.h>

struct retstruct {
    uint32_t a;
    uint64_t b;
};

struct retstruct fn(uint64_t input)
{
    struct retstruct ret;

    ret.a = 0;
    ret.b = input * 11111111111 + 111111111111;

    return ret;
}

Lắp ráp được tạo (GCC 9.2, x86_64, -O3):

fn:
  movabs rdx, 11111111111     # multiplier constant (doesn't fit in imm32)
  xor eax, eax                # ret.a = 0
  imul rdi, rdx
  movabs rdx, 111111111111    # add constant; one more 1 than multiplier
     # missing   add rdx, rdi   # ret.b=... that we get with clang or older gcc
  ret
# returns RDX:RAX = constant 111111111111 : 0
# independent of input RDI, and not using the imul result it just computed

Thật thú vị, sửa đổi cấu trúc để có uint64_t là thành viên đầu tiên tạo mã chính xác , cũng như thay đổi cả hai thành viên thành uint64_t

x86-64 System V không trả về các cấu trúc nhỏ hơn 16 byte trong RDX: RAX, khi chúng có thể sao chép một cách tầm thường. Trong trường hợp này, thành viên thứ 2 nằm trong RDX vì một nửa RAX cao là phần đệm để căn chỉnh hoặc .bkhi nào .alà loại hẹp hơn. ( sizeof(retstruct)là 16 theo cách nào đó; chúng tôi không sử dụng __attribute__((packed))vì vậy nó tôn trọng alignof (uint64_t) = 8.)

Mã này có chứa bất kỳ hành vi không xác định nào sẽ cho phép GCC phát ra cụm "không chính xác" không?

Nếu không, điều này sẽ được báo cáo trên https://gcc.gnu.org/ormszilla/


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Samuel Liew

Câu trả lời:


102

Tôi không thấy bất kỳ UB nào ở đây; các loại của bạn không được ký nên UB không tràn được ký là không thể, và không có gì lạ. (Và ngay cả khi đã ký, nó sẽ phải tạo đầu ra chính xác cho các đầu vào không gây ra tràn UB, như rdi=1). Nó cũng bị hỏng với front-end C ++ của GCC.

Ngoài ra, GCC8.2 biên dịch chính xác cho AArch64 và RISC-V (thành một maddhướng dẫn sau khi sử dụng movkđể xây dựng các hằng số, hoặc RISC-V mul và thêm sau khi tải các hằng số). Nếu đó là UB mà GCC đang tìm kiếm, chúng tôi thường mong đợi nó sẽ tìm thấy nó và phá vỡ mã của bạn cho các ISA khác, ít nhất là các mã có độ rộng loại và độ rộng đăng ký tương tự.

Clang cũng biên dịch nó một cách chính xác.

Điều này dường như là một hồi quy từ GCC 5 đến 6; Biên dịch GCC5.4 là chính xác, 6.1 trở lên thì không. ( Thần thánh ).

Bạn có thể báo cáo điều này trên bugzilla của GCC bằng cách sử dụng MCVE từ câu hỏi của bạn.

Nó thực sự trông giống như đó là một lỗi trong xử lý trả lại cấu trúc System V x86-64, có lẽ là các cấu trúc có chứa phần đệm. Điều đó sẽ giải thích tại sao nó hoạt động khi nội tuyến và khi mở rộng athành uint64_t (tránh đệm).



11
@vitorhnn Hình như nó đã được sửa master.
SS Anne

19

Điều này đã được cố định vào trunk/ master.

Đây là cam kết có liên quan .

Và đây là một bản vá để khắc phục vấn đề.

Dựa trên một nhận xét trong bản vá, reload_combine_recognize_patternchức năng đã cố gắng điều chỉnh nội dung USE .


14

Mã này có chứa bất kỳ hành vi không xác định nào sẽ cho phép GCC phát ra cụm "không chính xác" không?

Hành vi của mã được trình bày trong câu hỏi được xác định rõ đối với các tiêu chuẩn ngôn ngữ C99 và sau này. Cụ thể, C cho phép các hàm trả về giá trị cấu trúc mà không bị hạn chế.


2
GCC không tạo ra một định nghĩa độc lập về chức năng; đó là những gì chúng tôi đang xem xét, bất kể đó là những gì chạy khi bạn biên dịch nó trong một đơn vị dịch cùng với các chức năng khác. Bạn có thể dễ dàng kiểm tra nó mà không cần thực sự sử dụng bằng __attribute__((noinline))cách tự biên dịch nó trong một đơn vị dịch thuật và liên kết mà không có LTO, hoặc bằng cách biên dịch với -fPICngụ ý tất cả các biểu tượng toàn cầu (theo mặc định) có thể thay thế cho người gọi. Nhưng thực sự vấn đề có thể phát hiện được chỉ từ việc nhìn vào mã asm được tạo ra, bất kể người gọi.
Peter Cordes

Đủ công bằng, @PeterCordes, mặc dù tôi khá tự tin rằng chi tiết đó đã được thay đổi từ dưới tôi trong Godbolt.
John Bollinger

Phiên bản 1 của câu hỏi được liên kết với Godbolt chỉ bằng chức năng trong một đơn vị dịch thuật, giống như trạng thái của chính câu hỏi khi bạn trả lời. Tôi đã không kiểm tra tất cả các sửa đổi hoặc nhận xét mà bạn có thể đã xem xét. Nước dưới cầu, nhưng tôi không nghĩ đã từng có một tuyên bố rằng định nghĩa asm độc lập chỉ bị phá vỡ khi nguồn được sử dụng __attribute__((noinline)). (Điều đó sẽ gây sốc, không chỉ đáng ngạc nhiên về cách sửa lỗi GCC). Có lẽ điều đó chỉ được đề cập để thực hiện một cuộc gọi thử nghiệm in kết quả.
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.