Rust đang nổi lên như một thế lực khiến một Amater như mình không thể không để tâm. Sau vài ngày dig deeper vào Rust, mình cho rằng Rust là một ngôn ngữ khá hay để “học”.

MIR - Tinh hoa của chú cua bé nhỏ Rust image 1

Lý do:

  • Rust giống C/C++, học Rust các bạn có thể giải các bài toán liên quan tới vùng nhớ- điều mà các Rubylist, Pythonist không quan tâm.
  • Có ownership + borrow system để hỗ trợ memory safety.
  • Traits để generic.
  • Và đặc biệt là, Rust là một ngôn ngữ compiler driven, tức là khi cái app bạn build nó không bị compiler nhả lỗi, bạn đã có một chương trình safe + performance. Suy ra cái đáng để tìm hiểu ở đây là compiler :troll:

Vì vậy mình sẽ giới thiệu các bạn một chút về cái làm nên tên tuổi của và chỉ có ở Rust - MIR.

Để tìm hiểu rõ hơn Rust bạn hãy click vào link sau: Rust là gì ?

MIR thì có gì khác biệt?

MIR - Tinh hoa của chú cua bé nhỏ Rust image image 2

Vậy MIR trong Rust được sinh ra để làm gì? MIR là key của các việc sau:

  • Compile nhanh hơn: MIR được thiết kế để giúp compiler của Rust có thể incremental. Tức là nó sẽ calculate được phần nào mới và chỉ build lại phần đó, giúp cut down được một khoản thời gian đáng kể.
  • Execute nhanh hơn: Các bạn nhìn vào hình bên trái, khi mà lúc trước chỉ có một mình thằng LLVM làm nhiệm vụ optimization, thì với MIR, một số bước optimization cho riêng Rust sẽ được thực thi.
  • Type checking chính xác hơn.

MIR biến Rust thành đơn nhân

MIR sẽ remove toàn bộ keywords for, match dùng trong loop và expression, và cả method call. Sau đó thay thế bằng primitive objects.

Ví dụ, một đoạn code Rust như sau:

for elem in vec {
    process(elem);
}

Việc gọi for chỉ đơn giản là iterator gọi next liên tục cho tới khi hết phần tử. Nên nó sẽ được viết lại như này:

let mut iterator = vec.into_iter();
while let Some(elem) = iterator.next() {
    process(elem);
}

và cuối cùng được tranlaste bởi MIR thành:

 let mut iterator = IntoIterator::into_iter(vec);

loop:
    match Iterator::next(&mut iterator) {
        Some(elem) => { process(elem); goto loop; }
        None => { goto break; }
    }

break:
    ...

Vậy thì tại sao?

Thứ nhất, đích đến tiếp theo của MIR sẽ là LLVM, mục tiêu là để:

  • Borrow checking
  • Optimize performance

MIR primitive sau khi thay thế sẽ xịn hơn các construct ban đầu. Điểm đặc biệt ở đây là, formatch sẽ được replace thành goto. Việc đưa xuống LLVM với số lượng construct càng ít sẽ được quy về các pattern càng nhỏ, dẫn đến việc optimize dễ dàng hơn.

Thứ hai, cấu trúc trong MIR là type driven. Ví dụ iterator.next() sẽ được desugar thành Iterator::next(&mut iterator). Các bạn có thể thấy, MIR sẽ provide thêm đầy đủ trait và type information cho interator để biết được hàm next() từ đâu gọi.

Thứ ba, MIR làm tường minh mọi type trong Rust. Việc tường minh này giúp LLVM analyse borrow checking tốt hơn.

Khái niệm control-flow graph.

Nhìn ở trên ta thấy MIR được translate ra dưới dạng text, nhưng thực tế bên trong compiler, MIR được biểu diễn thành một luồng điều khiển ở dạng graph, gọi tắt là CFG.

Ví dụ trên sẽ được vẽ thành:

MIR - Tinh hoa của chú cua bé nhỏ Rust image image 1

Thực ra, bất kì compiler nào cũng đều translate ra CFG và đưa xuống cho LLVM, nhưng MIR khác ở chỗ: cái CFG mà nó translate ra match một cách hoàn hảo với cấu trúc của LLVM IR(cũng là CFG), trong khi các compiler khác không chú trọng chuyện này => có thể xảy ra sự không chính xác.

Tối giản biểu thức match

MIR sẽ đơn giản hóa biểu thức match ở trên thành những operations nhỏ. Cụ thể:

match Iterator::next(&mut iterator) {
    Some(elem) => process(elem),
    None => break,
}

Ở đoạn code trên khi nó đã wrap lại 2 bước thành 1 bước, một là check xem thử có Some(tức là còn elem nào không), hai là extract cái giá trị của elem đó ra(trong Rust gọi là downcasting).

Khi qua MIR, nó sẽ trở thành:

loop:
    // Put the value we are matching on into a temporary variable.
    let tmp = Iterator::next(&mut iterator);

    // Next, we "switch" on the value to determine which it has.
    switch tmp {
        Some => {
            // If this is a Some, we can extract the element out
            // by "downcasting". This effectively asserts that
            // the value `tmp` is of the Some variant.
            let elem = (tmp as Some).0;

            // The user's original code:
            process(elem);

            goto loop;
        }
        None => {
            goto break;
        }
    }

break:
....

Các bạn có thể thấy, match đã bị replace thành switchdowncasting Tại sao lại tách ra? Lí do là vì match quá phức tạp, và mục tiêu vẫn là làm sao để LLVM có thể phát huy tối đa khả năng optimization, nên dùng switch sẽ đơn giản hơn.

Drops và Panic tường minh

Trên ví dụ trên, ta đã vô hình assume rằng mọi chuyện đều xảy ra như ý muốn. Nhưng trong thực tế thì… hên xui. Đừng lo, MIR sẽ thêm thắt vào các drops(hay còn gọi là destruction) và panic operation vào trong CFG.

Nó thành thế này đây

MIR - Tinh hoa của chú cua bé nhỏ Rust image image 1

Conclusion

Trên đây là overview thiết kế của MIR. Các bạn có thể thấy, MIR có ý nghĩa trong việc giảm độ phức tạp của từng câu lệnh, lại gần với LLVM IR hơn. Việc này giúp dễ phát triển các pattern cho việc optimize.

Tóm lại thì, MIR được Rust đầu tư với hi vọng trở thành một ngọn cờ đầu của compiler evolution, khi mà tất cả các lý thuyết của nó đều quá hoàn hảo. Mục tiêu ban đầu của MIR là sẽ ôm xô một số công việc từ HIR và LLVM.

MIR sắp ra đời rồi, mọi người đón chờ xem nhé

Viết câu trả lời

Drop Images

0 Bình luận