Модель памяти
Отслеживание выделения, чтение инициализированных значений, арифметика указателей, правила размещения и хранения строк.
Wave Foundation
Отслеживание распределения
Отслеживаемый указатель связывает адрес с идентификатором выделения, генерацией, границами, смещением и разрешениями на доступ. Модель памяти также отслеживает время жизни выделения и состояние инициализации. Доступ должен удовлетворять этим условиям; нарушение вызывает ловушку.
Несмотря на то, что один и тот же физический адрес используется повторно, каждую жизнь разделяют поколения. Само наличие адреса не определяет, что указатель действителен или что вызывающая сторона имеет доступ к этому пространству памяти.
Адрес native поддерживает 64 бита. Отдельные shadow metadata передаются вместе с указателем при копировании/сохранении/вызове/возврате. Начальная область памяти native представляет собой отслеживаемый стек и глобальные выделения. C Для границ требуется явный адаптер, и передача владения произвольной внешней памятью не включена в эту область.
Инициализация и чтение
Объявление места для хранения не инициализирует значение. Проверяет, был ли инициализирован диапазон фактически читаемых байтов. Если хотя бы часть значения читается в неинициализированном состоянии, это trap. Он не заменяет результат чтения на 0 или неопределенное значение.
Проверка инициализации считанного значения исключает байт padding. Например, если все поля структуры инициализированы, значения чтения не будут неверными просто потому, что пустые байты, возникающие в результате выравнивания полей, не инициализированы.
Копия памяти содержит статус инициализации вместе с байтами. Копирование неинициализированного пространства хранения не превращает его в инициализированное пространство хранения. При чтении значений из места назначения в дальнейшем применяются те же проверки, что и при чтении источника.
Тот факт, что физический байт BSS равен 0, не позволяет инициализировать переменную IR.
IR в инициализированной скалярной памяти.
Следующий модуль был настроен как builder и прошел проверку. store перед чтением значения, и все три инструкции памяти определяют ненулевую сортировку по степени 2.
module {
format_version 4
semantics_version 1
target "x86_64-whale-linux"
datalayout { ptr=64, endian=little }
declare @f0 "initialized_local": whale () -> i32, linkage internal
fn @f0 "initialized_local"() -> i32, entry %b0 {
%b0 "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
}
}
Сохраните полный модуль как initialized.wir: исполнение выводит i32 42. Без store чтение load вызывает trap неинициализированной памяти. Выравнивание 3 — ошибка проверки. Физические нули alloca не означают инициализацию.
Сравнение с арифметикой указателей
Расчет адреса overflow равен trap. Вы можете создать указатель one-past, который будет указывать сразу после диапазона выделения, но вы не сможете через него получить доступ к памяти.
Для равенства указателей используется идентификатор выделения, а не просто числовой адрес. Упорядочение или вычитание указателей из разных распределений приводит к ловушке. Восстановление адреса из целого числа не восстанавливает права доступа.
GEP
GEP вычисляет адреса на основе элементов и полей. Это не команда для чтения значения.
Первый индекс — это единица элемента offset типа, на который указывает базовый указатель. Последующий индекс выбирает элемент массива или поле структуры/кортежа. Индекс поля структуры/кортежа — это порядковый номер поля на момент компиляции, а не байт offset. Выбранный тип определяет результирующий тип указателя.
Если базовым типом является ptr<array<i32, 4>>, индекс [0, 2] выбирает третий элемент i32 массива, в результате чего получается ptr<i32>. Указание первого индекса равным 1 перемещает i32 на один элемент, а не на один элемент в массиве. Этот пример иллюстрирует семантику индекса, а не синтаксис текстовых команд.
Начальная цель native отклоняет арифметику указателей на элементах размера 0. Вычисление адреса не исключает проверки времени жизни, области действия, инициализации и разрешений, необходимые для последующего доступа.
расположение данных
Цель вывода определяет размер поля выравнивания offset·массива stride. Порядок полей в структурах и кортежах сохраняет порядок их объявления. Макет хоста, на котором работает компилятор, не следует считать макетом цели вывода.
| ценность | Сохранить правила |
|---|---|
| Bool, signed i1, unsigned u1 | Минимум 1 байт |
| Пустая структура/кортеж | Размер 0, выравнивание 1 |
| Массив | Целевой макет определяет элемент stride |
| Структура/кортеж | Сохранять порядок объявления и требования к выравниванию цели. |
Завершенное выравнивание IR представляет собой степень 2, а не 0. Перед созданием этого IR необходимо определить автоматическое выравнивание. Макеты packed·union·bitfield не поддерживаются в этом профиле и должны быть отклонены.
Запрос макета вывода
Rust API вычисляет структуру хранилища независимо от хоста сборки. В следующем примере имеется 7 байтов padding перед полем u64 и 6 байтов в конце 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
Размер, возвращаемый layout_of, и шаг массива включают завершающее заполнение. Структуры и кортежи используют одни и те же правила порядка полей. Bool, i1 и u1 каждый занимает один байт. Пустые структуры и кортежи имеют размер 0 и выравнивание 1; Массив нулевой длины сохраняет естественное выравнивание своих элементов. void не имеет структуры хранения, а ptr<void> занимает 8 байт.
Используйте естественную сортировку для полей и элементов массива. allocation_align применяет правило SysV AMD64, которое требует выравнивания как минимум по 16 байтам, к независимым локальным/глобальным массивам размером 16 байт и более. Это не увеличивает выравнивание полей массива или элемента stride. AST lowering использует этот поиск пакетной сортировки для локального хранилища.
Умножение величины, сложение поля offset, overflow в вычислении padding возвращает LayoutError::Overflow. layout_of имеет ограничение вложенности сложных типов в 128 уровней, а layout_of_with_limit позволяет вызывающему объекту указать ограничение. Он не выделяет пространство для хранения, равное количеству элементов массива. pointer_stride отклоняет pointee с размером 0 в арифметике указателей native, но сама структура хранения этого типа действительна. Packed·union·bitfield не имеет поддерживаемого выражения типа. Этот сохраненный поиск макета не реализует соглашения о вызовах сложных типов или проверку границ во время выполнения.
Строка и граница C
Строка — это неизменяемая строка байтов заданной длины. Кодировка по умолчанию — UTF-8. Разрешает внутренний NUL и не добавляет окончание NUL автоматически. O0 не объединяет автоматически строковые объекты с одинаковым содержимым.
Следовательно, длина строки байтов, состоящей из A, NUL и B, равна 3. Невозможно использовать для явных преобразований строк C, которые отвергают внутренний NUL. Интерфейс не должен молча обрезать это значение до A.
Внешний C·Необработанный адрес·Встроенная сборка — это отдельная граница контракта. Проверка памяти трассировки во время выполнения не гарантирует обнаружение всего неправильного поведения во внешнем коде.
Исполнение с отслеживаемой стековой памятью
Интерпретатор по умолчанию исполняет целые/Bool, поток управления, стековые выделения, хранение и чтение указателей данных, typed GEP, memcpy и memset. Чтение checked-пар исключает padding. Адреса — синтетические 64-битные значения без разыменования памяти хоста. Аргументы и результаты ограничены целыми/Bool или void; float, вызовы, указатели функций, общие агрегатные значения, глобальные адреса и native-исполнение не поддержаны. Стековые выделения живут до возврата. Завершение лексического времени жизни, передача указателей при вызове/возврате, адаптеры внешней памяти и native shadow metadata требуют дальнейшей реализации.
--max-memory задаёт бюджет логических байтов выделения, по умолчанию 64 MiB. InterpreterOptions::memory_limits также ограничивает число выделений до 16384, метаданные байтов указателя до 262144 фрагментов и обработку байтов/метаданных до 256 Mi единиц. Превышение возвращает MemoryLimit с позицией IR отдельно от trap программы. Проверка также отвергает overflow известных размеров хранения целевой платформы до исполнения.
whale ir run initialized.wir --function @f0
i32 42
Копирование байтов и сброс инициализации
Сохраните следующий полный модуль как tracked-memory.wir. @f0 копирует байты указателя и отдельные метаданные, затем читает 42. В @f1 uninit помечает диапазон хранения типа как неинициализированный и очищает метаданные указателей, не меняя исходных байтов. memcpy может копировать неинициализированные байты; позднее чтение значения проверяет состояние назначения. Частичные копии сохраняют полномочия только при наличии всех восьми согласованных фрагментов метаданных. Запись тех же битов целыми или memset не восстанавливает полномочия.
memcpy/memset нулевой длины успешны без проверки доступа или выравнивания, включая null и one-past. Непустое перекрывающееся memcpy вызывает trap. memset инициализирует записанные байты и очищает их метаданные указателей. Bool хранится как 0 или 1; чтение другого представления вызывает trap.
module {
format_version 4
semantics_version 1
target "x86_64-whale-linux"
datalayout { ptr=64, endian=little }
declare @f0 "copied_pointer": whale () -> u32, linkage internal
declare @f1 "uninitialized": whale () -> u32, linkage internal
fn @f0 "copied_pointer"() -> u32, entry %b0 {
%b0 "entry":
%v0: ptr<u32> = alloca u32, align 4
%v1: u32 = const u32 42
store u32 %v1, ptr<u32> %v0, align 4
%v2: ptr<ptr<u32>> = alloca ptr<u32>, align 8
%v3: ptr<ptr<u32>> = alloca ptr<u32>, align 8
store ptr<u32> %v0, ptr<ptr<u32>> %v2, align 8
%v4: ptr<u8> = bitcast ptr<ptr<u32>> %v2 to ptr<u8>
%v5: ptr<u8> = bitcast ptr<ptr<u32>> %v3 to ptr<u8>
%v6: u64 = const u64 8
memcpy ptr<u8> %v5, ptr<u8> %v4, u64 %v6, align 8
%v7: ptr<u32> = load ptr<u32>, ptr<ptr<u32>> %v3, align 8
%v8: u32 = load u32, ptr<u32> %v7, align 4
ret u32 %v8
}
fn @f1 "uninitialized"() -> u32, entry %b0 {
%b0 "entry":
%v0: ptr<u32> = alloca u32, align 4
uninit u32, ptr<u32> %v0, align 4
%v1: u32 = load u32, ptr<u32> %v0, align 4
ret u32 %v1
}
}
whale ir run tracked-memory.wir --function @f0
whale ir run tracked-memory.wir --function @f1
u32 42
Error: tracked-memory.wir: trap at @f1 %b0 instruction 2 (%v1): uninitialized byte at allocation offset 0 (after 3 steps)
Обязанности проверки, lowering и исполнения
| Этап | Обязанность |
|---|---|
| Verifier | Проверять операнды/типы указателей, форму выравнивания и размеры хранения целевой платформы; отвергать undef. |
| O0 lowering | Сохранять uninit в исполняемом объявлении, записывать инициализаторы и сохранять чтения. |
| Runtime | Проверять идентичность выделения, поколение/время жизни, диапазон, права, выравнивание, overflow адреса и инициализацию; передавать состояние копии. |