Mô hình bộ nhớ

Theo dõi phân bổ, đọc các giá trị khởi tạo, số học con trỏ, bố cục và quy tắc lưu trữ chuỗi.

Wave Foundation

Theo dõi phân bổ

Một con trỏ được theo dõi liên kết một địa chỉ với ID phân bổ, thế hệ, giới hạn, độ lệch và quyền truy cập. Mô hình bộ nhớ cũng theo dõi thời gian tồn tại và trạng thái khởi tạo của phân bổ. Quyền truy cập phải đáp ứng các điều kiện này; vi phạm gây ra bẫy.

Mặc dù cùng một địa chỉ vật lý được sử dụng lại nhưng các thế hệ vẫn tách biệt nhau trong suốt cuộc đời. Sự hiện diện đơn thuần của một địa chỉ không xác định được con trỏ hợp lệ hay người gọi có quyền truy cập vào không gian lưu trữ đó.

Địa chỉ native duy trì 64 bit. Riêng shadow metadata được chuyển cùng với con trỏ thông qua sao chép/lưu/gọi/trả lại. Phạm vi bộ nhớ native ban đầu là ngăn xếp có thể theo dõi và phân bổ toàn cầu. C Các ranh giới yêu cầu bộ điều hợp rõ ràng và việc chuyển quyền sở hữu bộ nhớ ngoài tùy ý không được bao gồm trong phạm vi này.

Khởi tạo và đọc

Khai báo không gian lưu trữ không khởi tạo giá trị. Kiểm tra xem phạm vi byte thực sự được đọc đã được khởi tạo hay chưa. Nếu thậm chí một phần của giá trị được đọc ở trạng thái chưa được khởi tạo thì đó là trap. Nó không thay thế kết quả đọc bằng 0 hoặc giá trị không xác định.

Kiểm tra khởi tạo trên giá trị đọc không bao gồm byte padding. Ví dụ: nếu tất cả các trường của cấu trúc được khởi tạo, các giá trị đọc sẽ không sai đơn giản vì các byte trống do căn chỉnh trường chưa được khởi tạo.

Bản sao bộ nhớ mang trạng thái khởi tạo cùng với các byte. Sao chép không gian lưu trữ chưa được khởi tạo sẽ không thay đổi nó thành không gian lưu trữ được khởi tạo. Khi đọc các giá trị từ đích sau này, các bước kiểm tra tương tự sẽ được áp dụng như khi đọc nguồn.

Thực tế là byte vật lý của BSS là 0 không cho phép khởi tạo biến IR.

IR trong không gian lưu trữ vô hướng được khởi tạo

Mô-đun sau được định cấu hình là builder và đã vượt qua trình xác minh. store trước khi đọc giá trị và cả ba hướng dẫn bộ nhớ đều chỉ định cách sắp xếp lũy thừa 2 khác 0.

module {
  format_version 2
  semantics_version 1
  target "x86_64-whale-linux"
  datalayout { ptr=64, endian=little }

  declare @f0 "initialized_local": whale () -> i32, linkage internal

  fn @initialized_local() -> i32, id @f0 {
  entry:
    %v0: ptr<i32> = alloca i32, align 4
    %v1: i32 = const i32 42
    store i32 %v1, ptr<i32> %v0, align 4
    %v2: i32 = load i32, ptr<i32> %v0, align 4
    ret i32 %v2
  }

}

alloca tạo bộ lưu trữ i32, store ghi 42 và load xác định giá trị cần trả về. Đây là đầu ra từ máy in IR đã nhập chứ không phải dấu vết thực thi gốc. Căn chỉnh 3 là lỗi xác thực. Việc xóa cửa hàng sẽ yêu cầu bẫy đọc chưa được khởi tạo trong mô hình bộ nhớ này, nhưng tính năng theo dõi khởi tạo và kiểm tra bẫy thời gian chạy tương ứng vẫn chưa được triển khai. Việc vượt qua trình xác minh hiện tại không chứng minh rằng lần đọc này là an toàn.

So sánh với số học con trỏ

Tính toán địa chỉ overflow là trap. Bạn có thể tạo con trỏ one-past trỏ ngay sau phạm vi phân bổ, nhưng bạn không thể truy cập bộ nhớ thông qua nó.

Đẳng thức con trỏ sử dụng nhận dạng phân bổ chứ không chỉ là địa chỉ số. Việc sắp xếp hoặc trừ đi các con trỏ từ các lần phân bổ khác nhau sẽ gây ra bẫy. Việc xây dựng lại địa chỉ từ một số nguyên không khôi phục quyền truy cập.

GEP

GEP tính toán địa chỉ trên cơ sở phần tử và trường. Đây không phải là lệnh để đọc giá trị.

Chỉ mục đầu tiên là đơn vị phần tử offset của loại được trỏ đến bởi con trỏ cơ sở. Chỉ mục tiếp theo chọn một phần tử mảng hoặc trường cấu trúc/bộ dữ liệu. Chỉ mục trường của cấu trúc/bộ dữ liệu là số thứ tự trường tại thời điểm biên dịch, không phải byte offset. Kiểu được chọn sẽ xác định kiểu con trỏ kết quả.

Khi loại cơ sở là ptr<array<i32, 4>>, chỉ mục [0, 2] chọn phần tử i32 thứ ba của mảng, dẫn đến ptr<i32>. Chỉ định chỉ mục đầu tiên là 1 di chuyển i32 theo một phần tử, thay vì theo một phần tử trong mảng. Ví dụ này minh họa ngữ nghĩa chỉ mục chứ không phải cú pháp lệnh văn bản.

Mục tiêu native ban đầu từ chối số học con trỏ trên các phần tử có kích thước 0. Việc tính toán địa chỉ không loại bỏ thời gian tồn tại, phạm vi, khởi tạo và kiểm tra quyền cần thiết cho lần truy cập tiếp theo.

bố cục dữ liệu

Mục tiêu đầu ra xác định kích thước·căn chỉnh·trường offset·mảng stride. Thứ tự của các trường trong cấu trúc và bộ dữ liệu giữ nguyên thứ tự khai báo của chúng. Bố cục của máy chủ đang chạy trình biên dịch không được coi là bố cục của mục tiêu đầu ra.

giá trị Lưu quy tắc
Bool, signed i1, unsigned u1 Ít nhất 1 byte
Cấu trúc/bộ dữ liệu trống Kích thước 0, căn chỉnh 1
Mảng Bố cục mục tiêu xác định phần tử stride
Cấu trúc/bộ dữ liệu Giữ nguyên thứ tự khai báo và yêu cầu căn chỉnh của mục tiêu

Căn chỉnh hoàn chỉnh của IR là lũy thừa của 2, không phải 0. Việc tự động căn chỉnh phải được xác định trước khi tạo IR này. Bố cục packed·union·bitfield không được hỗ trợ trong hồ sơ này và nên bị từ chối.

Yêu cầu bố cục đầu ra

Rust API tính toán bố cục lưu trữ độc lập với máy chủ xây dựng. Trong ví dụ sau, có 7 byte padding trước trường u64 và 6 byte ở cuối padding.

use ir::{allocation_align, layout_of, Target, Type};

fn main() {
    let target = Target::X86_64WhaleLinux;
    let record = Type::Struct(vec![Type::U8, Type::U64, Type::U16]);
    let layout = layout_of(&record, target).unwrap();
    assert_eq!((layout.size, layout.align), (24, 8));
    assert_eq!(layout.field_offsets, [0, 8, 16]);

    let array = Type::Array(Box::new(record), 3);
    let layout = layout_of(&array, target).unwrap();
    assert_eq!((layout.size, layout.align), (72, 8));
    assert_eq!(layout.element_stride, Some(24));
    assert_eq!(allocation_align(&array, target).unwrap(), 16);
}
struct{u8, u64, u16}: size 24, natural alignment 8
field 0: byte 0
field 1: byte 8
field 2: byte 16
array of 3: size 72, element stride 24
standalone array placement alignment: 16

Kích thước được trả về bởi layout_of và bước tiến của mảng bao gồm phần đệm ở cuối. Cấu trúc và bộ dữ liệu sử dụng cùng một quy tắc thứ tự trường. Bool, i1 và u1 mỗi cái chiếm một byte. Các cấu trúc và bộ dữ liệu trống có kích thước 0 và căn chỉnh 1; mảng có độ dài bằng 0 vẫn giữ được sự liên kết tự nhiên của phần tử. void không có bố cục lưu trữ, trong khi ptr<void> chiếm 8 byte.

Sử dụng tính năng sắp xếp tự nhiên cho các trường và phần tử mảng. allocation_align áp dụng quy tắc SysV AMD64, quy tắc này yêu cầu căn chỉnh ít nhất 16 byte, cho các mảng cục bộ/toàn cầu độc lập từ 16 byte trở lên. Nó không làm tăng sự liên kết của các trường mảng hoặc phần tử stride. AST lowering sử dụng tính năng tra cứu sắp xếp hàng loạt này cho bộ nhớ cục bộ.

Phép nhân cường độ, phép cộng trường offset, overflow trong phép tính padding trả về LayoutError::Overflow. layout_of có giới hạn lồng kiểu phức tạp là 128 cấp độ và layout_of_with_limit cho phép người gọi chỉ định giới hạn. Nó không phân bổ không gian lưu trữ bằng số phần tử mảng. pointer_stride từ chối pointee với kích thước 0 trong số học con trỏ native, nhưng bản thân bố cục lưu trữ của loại đó là hợp lệ. Packed·union·bitfield không có biểu thức loại được hỗ trợ. Tra cứu bố cục được lưu trữ này không triển khai các quy ước gọi kiểu phức tạp hoặc kiểm tra giới hạn thời gian chạy.

Chuỗi và ranh giới C

Chuỗi là một chuỗi byte bất biến có độ dài được chỉ định. Mã hóa mặc định là UTF-8. Cho phép NUL nội bộ và không tự động thêm phần kết thúc NUL. O0 không tự động kết hợp các đối tượng chuỗi có cùng nội dung.

Do đó, độ dài của chuỗi byte bao gồm A, NUL và B là 3. Không thể sử dụng cho các chuyển đổi chuỗi C rõ ràng, từ chối NUL nội bộ. Giao diện người dùng không được cắt ngắn phần này thành A.

Bên ngoài C·Địa chỉ thô·Hội nội tuyến là ranh giới hợp đồng riêng biệt. Việc kiểm tra thời gian chạy của bộ nhớ theo dõi không được đảm bảo để phát hiện tất cả hành vi không chính xác trong mã bên ngoài.