Tại sao LLVM phân bổ một biến dự phòng?


9

Đây là một tệp C đơn giản với định nghĩa enum và mainhàm:

enum days {MON, TUE, WED, THU};

int main() {
    enum days d;
    d = WED;
    return 0;
}

Nó chuyển mã sang LLVM IR sau:

define dso_local i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store i32 2, i32* %2, align 4
  ret i32 0
}

%2hiển nhiên là dbiến, được 2 gán cho nó. Điều gì không %1tương ứng với nếu không được trả lại trực tiếp?


1
Những lá cờ nào bạn đã sử dụng để sản xuất IR này?
mũi tên

@arrowd, tôi đã cài đặt bộ LLVM ổn định mới nhất và chạyclang-9 -S -emit-llvm simple.c
macleginn

1
Tôi nghĩ rằng nó có liên quan đến việc khởi tạo trước đây main( godbolt.org/z/kEtS-s ). Liên kết cho thấy cách lắp ráp được ánh xạ tới nguồn
Pradeep Kumar

2
@PradeepKumar: Thật vậy, nếu bạn thay đổi tên của hàm thành một cái gì đó khác main, biến phụ bí ẩn sẽ biến mất. Thật thú vị, nó cũng biến mất nếu bạn bỏ qua returncâu lệnh hoàn toàn (đó là hợp pháp cho mainC và tương đương return 0;).
Nate Eldredge

1
@macleginn: Tôi không chắc lắm. Nếu bạn khai báo mainnhư int main(int argc, char **argv)bạn thấy argcargvsao chép vào ngăn xếp, nhưng biến zero bí ẩn vẫn còn đó ngoài chúng.
Nate Eldredge

Câu trả lời:


3

Thanh %1ghi này được tạo bởi clang để xử lý nhiều câu lệnh return trong một hàm . Hãy tưởng tượng bạn có một hàm để tính giai thừa của một số nguyên. Thay vì viết nó như thế này

int factorial(int n){
    int result;
    if(n < 2)
      result = 1;
    else{
      result = n * factorial(n-1);
    }
    return result;
}

Bạn có thể làm điều này

int factorial(int n){
    if(n < 2)
      return 1;
    return n * factorial(n-1);
}

Tại sao? Bởi vì Clang sẽ chèn resultbiến đó giữ giá trị trả về cho bạn. Yay Đó là mục đích chính xác của điều đó %1. Nhìn vào ir cho một phiên bản sửa đổi một chút của mã của bạn.

Mã sửa đổi,

enum days {MON, TUE, WED, THU};

int main() {
    enum days d;
    d = WED;
    if(d) return 1;
    return 0;
}

IR,

define dso_local i32 @main() #0 !dbg !15 {
    %1 = alloca i32, align 4
    %2 = alloca i32, align 4
    store i32 0, i32* %1, align 4
    store i32 2, i32* %2, align 4, !dbg !22
    %3 = load i32, i32* %2, align 4, !dbg !23
    %4 = icmp ne i32 %3, 0, !dbg !23
    br i1 %4, label %5, label %6, !dbg !25

 5:                                                ; preds = %0
   store i32 1, i32* %1, align 4, !dbg !26
   br label %7, !dbg !26

 6:                                                ; preds = %0
  store i32 0, i32* %1, align 4, !dbg !27
  br label %7, !dbg !27

 7:                                                ; preds = %6, %5
  %8 = load i32, i32* %1, align 4, !dbg !28
  ret i32 %8, !dbg !28
}

Bây giờ bạn thấy rằng %1làm cho mình hữu ích hả? Như những người khác đã chỉ ra, đối với các hàm chỉ có một câu lệnh return, biến này có thể sẽ bị tước bởi một trong những đường chuyền tối ưu của llvm.


1

Tại sao vấn đề này - vấn đề thực sự là gì?

Tôi nghĩ rằng câu trả lời sâu hơn mà bạn đang tìm kiếm có thể là: Kiến trúc của LLVM dựa trên các mặt trận khá đơn giản và nhiều đường chuyền. Các frontend phải tạo mã chính xác, nhưng nó không phải là mã tốt. Họ có thể làm điều đơn giản nhất mà làm việc.

Trong trường hợp này, Clang tạo ra một vài hướng dẫn hóa ra không được sử dụng cho bất cứ điều gì. Điều đó thường không phải là một vấn đề, bởi vì một số phần của LLVM sẽ thoát khỏi các hướng dẫn không cần thiết. Clang tin tưởng rằng sẽ xảy ra. Clang không cần tránh phát ra mã chết; việc thực hiện nó có thể tập trung vào tính đúng đắn, đơn giản, khả năng kiểm tra, v.v.


1

Vì Clang được thực hiện với phân tích cú pháp nhưng LLVM thậm chí chưa bắt đầu với tối ưu hóa.

Mặt trước Clang đã tạo ra IR (Đại diện trung gian) và không phải mã máy. Các biến đó là SSAs (Bài tập tĩnh đơn); họ chưa bị ràng buộc với các đăng ký và thực sự sau khi tối ưu hóa, sẽ không bao giờ vì chúng là dự phòng.

Mã đó là một đại diện theo nghĩa đen của nguồn. Đó là những gì clang tay cho LLVM để tối ưu hóa. Về cơ bản, LLVM bắt đầu với điều đó và tối ưu hóa từ đó. Thật vậy, đối với phiên bản 10 và x86_64, llc -O2 cuối cùng sẽ tạo ra:

main: # @main
  xor eax, eax
  ret

Tôi hiểu quá trình ở cấp độ này. Tôi muốn biết tại sao IR này được tạo ra để bắt đầu.
macleginn

Bạn có thể nghĩ về một trình biên dịch như là một vượt qua duy nhất. Có một đường dẫn bắt đầu với mặt trước Clang tạo ra IR. Nó thậm chí còn không tạo ra IR văn bản này mà thay vào đó ai đó đã yêu cầu clang -emit-llvm -S file.cpp Clang thực sự đã tạo ra một phiên bản bitcode tuần tự hóa nhị phân của IR. LLVM được cấu trúc thành nhiều lượt, mỗi lần lấy và tối ưu hóa IR. Pass LLVM đầu tiên lấy IR từ Clang. Phải mất IR vì bạn có thể thay thế Clang bằng Fortran FE để hỗ trợ ngôn ngữ khác có cùng trình tạo mã + trình tối ưu hóa.
Olsonist
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.