Trình biên dịch - Tạo mã trung gian

Một mã nguồn có thể được dịch trực tiếp thành mã máy đích của nó, vậy tại sao chúng ta cần dịch mã nguồn thành một mã trung gian và sau đó được dịch sang mã đích của nó? Hãy để chúng tôi xem lý do tại sao chúng tôi cần một mã trung gian.

  • Nếu một trình biên dịch dịch ngôn ngữ nguồn sang ngôn ngữ máy đích của nó mà không có tùy chọn tạo mã trung gian, thì đối với mỗi máy mới, cần có một trình biên dịch gốc đầy đủ.

  • Mã trung gian loại bỏ sự cần thiết của một trình biên dịch đầy đủ mới cho mọi máy duy nhất bằng cách giữ nguyên phần phân tích cho tất cả các trình biên dịch.

  • Phần thứ hai của trình biên dịch, tổng hợp, được thay đổi theo máy đích.

  • Việc áp dụng các sửa đổi mã nguồn để cải thiện hiệu suất mã trở nên dễ dàng hơn bằng cách áp dụng các kỹ thuật tối ưu hóa mã trên mã trung gian.

Đại diện trung gian

Mã trung gian có thể được biểu diễn theo nhiều cách khác nhau và chúng có những lợi ích riêng.

  • High Level IR- Biểu diễn mã trung gian bậc cao rất gần với chính ngôn ngữ nguồn. Chúng có thể được tạo dễ dàng từ mã nguồn và chúng ta có thể dễ dàng áp dụng các sửa đổi mã để nâng cao hiệu suất. Nhưng để tối ưu hóa máy mục tiêu, nó ít được ưu tiên hơn.

  • Low Level IR - Cái này gần với máy đích, phù hợp cho việc cấp phát thanh ghi và bộ nhớ, lựa chọn tập lệnh, v.v. Nó tốt cho việc tối ưu hóa phụ thuộc vào máy.

Mã trung gian có thể là ngôn ngữ cụ thể (ví dụ: Mã Byte cho Java) hoặc ngôn ngữ độc lập (mã ba địa chỉ).

Mã ba địa chỉ

Bộ tạo mã trung gian nhận đầu vào từ pha tiền nhiệm của nó, bộ phân tích ngữ nghĩa, dưới dạng cây cú pháp có chú thích. Sau đó, cây cú pháp đó có thể được chuyển đổi thành một biểu diễn tuyến tính, ví dụ: ký hiệu hậu tố. Mã trung gian có xu hướng là mã độc lập với máy. Do đó, bộ tạo mã giả định có số lượng bộ nhớ lưu trữ (thanh ghi) không giới hạn để tạo mã.

Ví dụ:

a = b + c * d;

Bộ tạo mã trung gian sẽ cố gắng chia biểu thức này thành các biểu thức con và sau đó tạo mã tương ứng.

r1 = c * d;
r2 = b + r1;
a = r2

r được sử dụng như các thanh ghi trong chương trình đích.

Mã ba địa chỉ có nhiều nhất ba vị trí địa chỉ để tính toán biểu thức. Mã ba địa chỉ có thể được biểu diễn dưới hai dạng: bộ bốn và bộ ba.

Tứ giác

Mỗi lệnh trong bản trình bày tứ phân được chia thành bốn trường: toán tử, arg1, arg2 và kết quả. Ví dụ trên được trình bày dưới đây ở định dạng tứ phân:

Op tranh luận 1 tranh luận 2 kết quả
* c d r1
+ b r1 r2
+ r2 r1 r3
= r3 a

Bộ ba

Mỗi lệnh trong bản trình bày bộ ba có ba trường: op, arg1 và arg2. Kết quả của các biểu thức con tương ứng được biểu thị bằng vị trí của biểu thức. Các bộ ba thể hiện sự tương tự với DAG và cây cú pháp. Chúng tương đương với DAG trong khi biểu diễn các biểu thức.

Op tranh luận 1 tranh luận 2
* c d
+ b (0)
+ (1) (0)
= (2)

Bộ ba phải đối mặt với vấn đề về tính bất động của mã trong khi tối ưu hóa, vì kết quả có vị trí và việc thay đổi thứ tự hoặc vị trí của một biểu thức có thể gây ra vấn đề.

Bộ ba gián tiếp

Biểu diễn này là một sự cải tiến so với biểu diễn ba lần. Nó sử dụng con trỏ thay vì vị trí để lưu trữ kết quả. Điều này cho phép các trình tối ưu hóa tự do định vị lại biểu thức con để tạo ra một mã được tối ưu hóa.

Tuyên bố

Một biến hoặc thủ tục phải được khai báo trước khi nó có thể được sử dụng. Khai báo liên quan đến việc phân bổ không gian trong bộ nhớ và nhập kiểu và tên trong bảng ký hiệu. Một chương trình có thể được mã hóa và thiết kế để lưu ý đến cấu trúc máy đích, nhưng không phải lúc nào cũng có thể chuyển đổi chính xác mã nguồn sang ngôn ngữ đích của nó.

Lấy toàn bộ chương trình làm tập hợp các thủ tục và thủ tục con, có thể khai báo tất cả các tên cục bộ cho thủ tục. Việc cấp phát bộ nhớ được thực hiện liên tiếp và các tên được cấp phát cho bộ nhớ theo trình tự mà chúng được khai báo trong chương trình. Chúng tôi sử dụng biến offset và đặt nó thành 0 {offset = 0} biểu thị địa chỉ cơ sở.

Ngôn ngữ lập trình nguồn và kiến ​​trúc máy đích có thể khác nhau trong cách lưu trữ tên, do đó, việc sử dụng địa chỉ tương đối. Trong khi tên đầu tiên được cấp phát bộ nhớ bắt đầu từ vị trí bộ nhớ 0 {offset = 0}, tên tiếp theo được khai báo sau, sẽ được cấp phát bộ nhớ bên cạnh tên đầu tiên.

Example:

Chúng ta lấy ví dụ về ngôn ngữ lập trình C trong đó một biến số nguyên được gán 2 byte bộ nhớ và một biến float được gán 4 byte bộ nhớ.

int a;
float b;

Allocation process:
{offset = 0}

   int a;
   id.type = int
   id.width = 2

offset = offset + id.width 
{offset = 2}

   float b;
   id.type = float
   id.width = 4
   
offset = offset + id.width 
{offset = 6}

Để nhập chi tiết này vào bảng ký hiệu, có thể sử dụng một thủ tục nhập . Phương thức này có thể có cấu trúc sau:

enter(name, type, offset)

Thủ tục này sẽ tạo một mục nhập trong bảng ký hiệu, cho tên biến , có kiểu của nó được đặt thành kiểu và độ lệch địa chỉ tương đối trong vùng dữ liệu của nó.