2013-03-07 39 views
9

Trong khi biên soạn một dự án lớn hơn với tiếng lóng, tôi tình cờ gặp một lỗi khó chịu.Lỗi tối ưu hóa LLVM hoặc hành vi không xác định?

xem xét ví dụ nhỏ sau:

unsigned long int * * fee(); 

void foo(unsigned long int q) 
{ 
    unsigned long int i,j,k,e; 
    unsigned long int pows[7]; 
    unsigned long int * * table; 

    e = 0; 
    for (i = 1; i <= 256; i *= q) 
    pows[e++] = i; 
    pows[e--] = i; 

    table = fee(); // need to set table to something unknown 
        // here, otherwise the compiler optimises 
        // parts of the loops below away 
        // (and no bug occurs) 

    for (i = 0; i < q; i++) 
    for (j = 0; j < e; j++) 
     ((unsigned char*)(*table) + 5)[i*e + j] = 0; // bug here 
} 

Để theo sự hiểu biết của tôi mã này không vi phạm các tiêu chuẩn C trong bất kỳ cách nào, mặc dù dòng cuối cùng dường như lúng túng (trong dự án thực tế, mã như thế này xuất hiện do sử dụng quá nhiều macro tiền xử lý).

Biên dịch điều này bằng tiếng kêu (phiên bản 3.1 hoặc cao hơn) ở cấp tối ưu hóa -O1 hoặc cao hơn dẫn đến việc viết mã sai vị trí trong bộ nhớ.

Các bộ phận quan trọng của tập tin lắp ráp sản xuất bởi kêu vang/LLVM đọc như sau: (Đây là cú pháp GAS, như vậy để những người bạn của những người đang sử dụng để Intel: Hãy coi chừng!)

[...] 
    callq _fee 
    leaq 6(%rbx), %r8   ## at this point, %rbx == e-1 
    xorl %edx, %edx 
LBB0_4: 
    [...] 
    movq %r8, %rsi 
    imulq %rdx, %rsi 
    incq %rdx 
LBB0_6: 
    movq (%rax), %rcx   ## %rax == fee() 
    movb $0, (%rcx,%rsi) 
    incq %rsi 
    [conditional jumps back to LBB0_6 resp. LBB0_4] 
    [...] 

Trong khác từ, hướng dẫn làm

(*table)[i*(e+5) + j] = 0; 

thay vì dòng cuối cùng được viết ở trên. Sự lựa chọn của + 5 là tùy ý, thêm (hoặc trừ) các số nguyên khác dẫn đến cùng một hành vi. Vì vậy - đây có phải là lỗi trong tối ưu hóa của LLVM hay không có hành vi không xác định xảy ra ở đây?

Chỉnh sửa: Cũng lưu ý rằng lỗi biến mất nếu tôi bỏ dàn diễn viên (unsigned char*) ở dòng cuối cùng. Nói chung, lỗi có vẻ khá nhạy cảm với bất kỳ thay đổi nào.

+1

Không thể thấy phép nhân bằng 5 trong mã lắp ráp ở trên (nhưng sau đó tôi được sử dụng để lắp ráp ARM hơn Intel, nếu là Intel :-)), nhưng dòng cuối cùng của mã C dịch thành '* ((unsigned char *) (* table) + 5 + i * e + j) ', vì vậy ... bạn có chắc chắn rằng bạn đặt những niềng răng xung quanh" e + 5 "trong giải thích của bạn về đầu ra lắp ráp một cách chính xác? – user2116939

+0

Vâng, tôi khá chắc chắn. Đây là cú pháp GAS, không phải Intel, do đó, 'movq% r8,% rsi' và' imulq% rdx,% rsi' có nghĩa là '% rsi' sẽ giữ' (% rbx + 6) *% rdx = (e + 5) *% rdx'. –

+0

Có, bây giờ tôi có thể thấy điều này. Nó trông giống như một lỗi trình tối ưu hóa vì mã là đủ kosher ngay cả khi một chút lạ (nhưng sau đó các macro có thể tạo ra đầu ra lạ). – user2116939

Trả lời

5

Tôi khá chắc chắn đây là lỗi trình tối ưu hóa. Nó repro trong LLVM-2.7 và LLVM-3.1, phiên bản duy nhất tôi có quyền truy cập.

Tôi đã đăng a bug vào LLVM Bugzilla.

Các lỗi được thể hiện bởi SSCCE này:

#include <stdio.h> 

unsigned long int * table; 

void foo(unsigned long int q) 
{ 
    unsigned long int i,j,e; 

    e = 0; 
    for (i = 1; i <= 256; i *= q) 
    e++; 
    e--; 

    for (i = 0; i < q; i++) 
    for (j = 0; j < e; j++) 
     ((unsigned char*)(table) + 13)[i*e + j] = 0; // bug here 
} 

int main() { 
    unsigned long int v[8]; 
    int i; 
    memset(v, 1, sizeof(v)); 

    table = v; 
    foo(2); 

    for(i=0; i<sizeof(v); i++) { 
     printf("%d", ((unsigned char*)v)[i]); 
    } 
    puts(""); 
    return 0; 
} 

Nó nên in

1111111111111000000000000000011111111111111111111111111111111111 

thuộc GCC và "kêu vang -O0". Sản lượng không chính xác quan sát được với LLVM là

0000000011111111111110000000011111111111111111111111111111111111 

Cảm ơn vì đã chú ý điều này!

Các vấn đề liên quan