Chỉ dẫn PHI chính xác là gì và cách sử dụng nó trong LLVM


87

LLVM có hướng dẫn phi với giải thích khá kỳ lạ:

Lệnh 'phi' được sử dụng để triển khai nút φ trong biểu đồ SSA biểu diễn hàm.

Thông thường, nó được sử dụng để thực hiện phân nhánh. Nếu tôi hiểu đúng, cần phải thực hiện phân tích phụ thuộc và trong một số trường hợp, nó có thể giúp tránh tải không cần thiết. Tuy nhiên, vẫn khó để hiểu chính xác nó làm gì.

Ví dụ về Kaleidoscope giải thích nó khá độc đáo cho iftrường hợp. Tuy nhiên, nó không rõ ràng như thế nào để thực hiện các phép toán logic như &&||. Nếu tôi nhập nội dung sau vào trình biên dịch llvm trực tuyến :

void main1(bool r, bool y) {
    bool l = y || r;
}

Vài dòng cuối cùng hoàn toàn làm tôi bối rối:

; <label>:10                                      ; preds = %7, %0
%11 = phi i1 [ true, %0 ], [ %9, %7 ]
%12 = zext i1 %11 to i8

Có vẻ như nút phi tạo ra một kết quả có thể được sử dụng. Và tôi có ấn tượng rằng nút phi chỉ xác định giá trị đường dẫn nào đến.

Ai đó có thể giải thích nút Phi là gì và cách triển khai ||với nó không?


1
Các phinút là một giải pháp của vấn đề trong trình biên dịch để chuyển đổi IR vào dạng "tĩnh đơn chuyển nhượng". Để hiểu rõ hơn giải pháp tôi sẽ đề nghị hiểu rõ hơn vấn đề. Vì vậy, tôi sẽ giới thiệu cho bạn " Tại sao lại là phinút ".
Vraj Pandya,

Câu trả lời:


76

Một nút phi là một lệnh được sử dụng để chọn một giá trị tùy thuộc vào khối tiền nhiệm của khối hiện tại (Hãy xem ở đây để xem toàn bộ hệ thống phân cấp - nó cũng được sử dụng như một giá trị, là một trong những lớp mà nó kế thừa).

Các nút Phi là cần thiết do cấu trúc của kiểu SSA (phép gán đơn tĩnh) của mã LLVM - ví dụ: hàm C ++ sau

void m(bool r, bool y){
    bool l = y || r ;
}

được dịch sang IR sau: (được tạo thông qua clang -c -emit-llvm file.c -o out.bc- và sau đó được xem qua llvm-dis)

define void @_Z1mbb(i1 zeroext %r, i1 zeroext %y) nounwind {
entry:
  %r.addr = alloca i8, align 1
  %y.addr = alloca i8, align 1
  %l = alloca i8, align 1
  %frombool = zext i1 %r to i8
  store i8 %frombool, i8* %r.addr, align 1
  %frombool1 = zext i1 %y to i8
  store i8 %frombool1, i8* %y.addr, align 1
  %0 = load i8* %y.addr, align 1
  %tobool = trunc i8 %0 to i1
  br i1 %tobool, label %lor.end, label %lor.rhs

lor.rhs:                                          ; preds = %entry
  %1 = load i8* %r.addr, align 1
  %tobool2 = trunc i8 %1 to i1
  br label %lor.end

lor.end:                                          ; preds = %lor.rhs, %entry
  %2 = phi i1 [ true, %entry ], [ %tobool2, %lor.rhs ]
  %frombool3 = zext i1 %2 to i8
  store i8 %frombool3, i8* %l, align 1
  ret void
}

Vậy điều gì xảy ra ở đây? Không giống như mã C ++, trong đó biến bool lcó thể là 0 hoặc 1, trong LLVM IR, nó phải được xác định một lần . Vì vậy, chúng tôi kiểm tra xem %toboolcó đúng không, và sau đó chuyển đến lor.endhoặc lor.rhs.

Trong lor.endchúng ta cuối cùng có giá trị của || nhà điều hành. Nếu chúng tôi đến từ khối mục nhập - thì đó là sự thật. Nếu không, nó bằng giá trị của %tobool2- và đó chính xác là những gì chúng ta nhận được từ dòng IR sau:

%2 = phi i1 [ true, %entry ], [ %tobool2, %lor.rhs ]

6
Nút TL; DR φ là một biểu thức bậc ba. Người ta có thể tranh luận rằng nó không chứa điều kiện, nhưng thực sự, khi chuyển đổi sang mã cuối cùng, bạn không thể xác định nếu không thì một trong các đối số đang tồn tại, vì vậy φ cũng phải có điều kiện.
Hi-Angel

31

Bạn không cần phải sử dụng phi ở tất cả. Chỉ cần tạo một loạt các biến tạm thời. Các đường chuyền tối ưu hóa LLVM sẽ đảm nhận việc tối ưu hóa các biến tạm thời và sẽ tự động sử dụng nút phi cho việc đó.

Ví dụ: nếu bạn muốn làm điều này:

x = 4;
if (something) x = x + 2;
print(x);

Bạn có thể sử dụng nút phi cho điều đó (trong mã giả):

  1. gán 4 cho x1
  2. if (! something) rẽ nhánh thành 4
  3. tính x2 từ x1 bằng cách thêm 2
  4. gán x3 phi từ x1 và x2
  5. gọi in với x3

Nhưng bạn có thể làm mà không có nút phi (trong mã giả):

  1. phân bổ biến cục bộ trên ngăn xếp được gọi là x
  2. tải vào nhiệt độ x1 giá trị 4
  3. lưu trữ x1 đến x
  4. if (! something) rẽ nhánh thành 8
  5. tải x đến nhiệt độ x2
  6. thêm x2 với 4 vào nhiệt độ x3
  7. lưu trữ x3 thành x
  8. tải x đến nhiệt độ x4
  9. gọi in với x4

Bằng cách chạy các bước tối ưu hóa với llvm, mã thứ hai này sẽ được tối ưu hóa cho mã đầu tiên.


4
Từ những gì tôi đã đọc, có vẻ như có một số hạn chế cần ghi nhớ ở đây. mem2reg là thẻ tối ưu hóa được đề cập và nó có một vài hạn chế được chỉ ra trong ví dụ về Kaleidoscope . Tuy nhiên, có vẻ như đây là cách xử lý vấn đề ưa thích và được sử dụng bởi Clang.
Matthew Sanders
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.