Модель памяти

Отслеживание выделения, чтение инициализированных значений, арифметика указателей, правила размещения и хранения строк.

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 адреса и инициализацию; передавать состояние копии.