Справочник по Whale IR

Описывает типы, идентификаторы, допустимость функции, порядок оценки и формат обмена.

Wave Foundation

Модули и идентификаторы

Модуль состоит из целевой информации, глобальных определений и функций. Значения имеют явные типы. Интерфейс обрабатывает имя, тип, перегрузку и дженерики исходного языка и генерирует typed IR.

Функции и глобальные переменные используют разные внутренние пространства имен. Поэтому функции и переменные могут иметь одно и то же имя. Внутренний идентификатор отличается от имени внешнего соединения link_name, а внешнее имя задается внешним интерфейсом. Whale не разрешает внешние конфликты путем автоматического создания новых имен. Пожалуйста, обратитесь к Символы и ссылки.

Каждое определение значения имеет идентификатор. Определения не могут дублироваться, а метаданные типа должны соответствовать типу, указанному в определении. Само по себе имя не идентифицирует определения, даже если объявления с одинаковыми именами затеняют друг друга.

IR Конфигурация и чтение

Ниже приведен полный пример Rust, который создает и проверяет функцию с помощью ящика ir, а затем выводит typed IR.

use ir::{ModuleBuilder, Target, Type};

fn main() {
    let target = Target::lookup("x86_64-whale-linux").unwrap();
    let mut module = ModuleBuilder::new(target.name(), target.data_layout());
    let mut function = module.begin_function("answer", vec![], Type::I32);
    let left = function.const_i32(40);
    let right = function.const_i32(2);
    let answer = function.add(Type::I32, left, right);
    function.ret(Some(answer));
    function.finish();
    let module = module.finish();
    ir::verify_module(&module).unwrap();
    print!("{}", ir::print_module(&module));
}

Принтер выводит IR:

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

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

  fn @answer() -> i32, id @f0 {
  entry:
    %v0: i32 = const i32 40
    %v1: i32 = const i32 2
    %v2: i32 = add i32 %v0, %v1
    ret i32 %v2
  }

}

%v0 и %v1 являются определениями константы i32. Используйте %v2, определенный add, в качестве возвращаемого значения i32 функции. Если вы измените возвращаемое значение на Bool, это вызовет ошибку проверки, поскольку оно не соответствует сигнатуре функции. Даже если оба операнда являются константами, O0 поддерживает инструкцию сложения.

Этот код представляет собой фактический вывод принтера, а не входной файл для передачи в анализатор текста. Анализ текста и выполнение IR пока не поддерживаются, в настоящее время этот модуль можно настроить как Rust builder.

тип

тип смысл
bool Логическое значение false или true
i1, i8, i16, i32, i64, i128 целое число со знаком указанной разрядности
u1, u8, u16, u32, u64, u128 целое число без знака указанной разрядности
f16, f32, f64 Значение с плавающей запятой с указанной разрядностью
ptr<T> T Указатель на тип значения.
fnptr<signature> Вызываемый указатель с точными типами параметров/результатов и соглашением о вызовах
array<T, N> N элементы одного типа
struct{T, ...} Поле упорядоченной структуры
tuple<T, ...> упорядоченные элементы кортежа
void Нет результатов

bool, i1 и u1 — это разные типы. signed i1 представляет −1 и 0, а unsigned u1 представляет 0 и 1. Целое число 1 не является неявным логическим условием. Условный переход, условие Select, trap_if требует операнда Bool.

Размер хранилища не определяется исключительно количеством битов в значении и соответствует целевой макет. Например, значение i1 составляет 1 бит, но занимает в памяти не менее 1 байта.

Функции и вызовы

Функция определяет все параметры, типы результатов, соглашение о вызовах и linkage. Прямые и косвенные вызовы должны совпадать с подписью звонящего. Вызов void не дает результата ID. Вызов nonvoid сохраняет определение результата, даже если O0 не использует результат.

Возвращаемый результат должен соответствовать типу результата функции. Возврат void не несет значения, а возврат nonvoid содержит значение объявленного типа результата.

Заявления, личности и звонки

Module.declarations записывает FunctionId каждой функции, имя, полную подпись, ссылку и имя внешней ссылки. Определение относится к этой идентичности; типы параметров и возвращаемых значений должны соответствовать его объявлению. Идентичные повторяющиеся объявления разрешаются к одному и тому же идентификатору через declare_function; конфликты и повторяющиеся определения являются ошибками. Внутреннему объявлению необходимо тело в модуле. Внешнее объявление может быть неразрешенным до момента связывания или иметь экспортированное тело. Внутренние функции не имеют link_name; внешние функции требуют явного непустого имени без NUL. Два разных объявления функции не могут претендовать на одно и то же внешнее имя. Глобальные объекты и функции по-прежнему используют отдельные внутренние пространства имен.

Зарегистрируйте объявления перед созданием тел с помощью begin_declared_function для поддержки переадресации вызовов и рекурсии. begin_function остаётся удобным для новой внутренней функции Whale. Проверенные API declare_function, begin_declared_function, function_addr, null_function и call возвращают Result; отклоненный вызов не добавляет инструкцию и не выделяет ее идентификатор результата.

Следующая полная программа на Rust объявляет внешнюю функцию, принимает ее типизированный адрес и выполняет как прямые, так и косвенные вызовы:

use ir::{Callee, CallingConvention, DataLayout, FunctionSignature, Linkage, ModuleBuilder, Type};

fn main() {
    let mut module = ModuleBuilder::new("x86_64-whale-linux", DataLayout::default_64bit_le());
    let signature = FunctionSignature {
        params: vec![Type::I32], ret: Type::I32,
        convention: CallingConvention::SysV64, variadic: false,
    };
    let identity = module.declare_function(
        "identity", signature, Linkage::External, Some("identity_i32".into()),
    ).unwrap();
    let mut function = module.begin_function("answer", vec![], Type::I32);
    let input = function.const_i32(42);
    let callback = function.function_addr(identity).unwrap();
    // The direct call's result remains defined even though it is unused.
    function.call(Callee::Direct(identity), vec![input]).unwrap();
    let result = function.call(Callee::Indirect(callback), vec![input]).unwrap().unwrap();
    function.ret(Some(result));
    function.finish();
    let module = module.finish();
    ir::verify_module(&module).unwrap();
    print!("{}", ir::print_module(&module));
}
module {
  format_version 2
  semantics_version 1
  target "x86_64-whale-linux"
  datalayout { ptr=64, endian=little }

  declare @f0 "identity": sysv64 (i32) -> i32, linkage external, link_name "identity_i32"
  declare @f1 "answer": whale () -> i32, linkage internal

  fn @answer() -> i32, id @f1 {
  entry:
    %v0: i32 = const i32 42
    %v1: fnptr<sysv64 (i32) -> i32> = function_addr @f0
    %v2: i32 = call sysv64 i32 @f0(%v0)
    %v3: i32 = call sysv64 i32 indirect %v1(%v0)
    ret i32 %v3
  }

}

Callee::Direct(FunctionId) разрешается через таблицу объявлений; Для Callee::Indirect(ValueId) требуется значение Type::FnPtr(FunctionSignature). Подпись включает все типы параметров, тип результата и CallingConvention::{Whale, SysV64}. Он сохраняется через копии, хранилище, параметры, возвраты, фи и выбор. Указатели данных и целочисленные значения не подлежат вызову. Приведения, включающие типы указателей на функции, отклоняются; изменение аннотации типа не может изменить вызываемую подпись. Указатель функции имеет 64-битное адресное хранилище на этой цели; это само по себе не реализует теневые метаданные во время выполнения.

Арность, точные типы аргументов/результатов, наличие идентификатора результата и соглашение о вызовах должны совпадать. Неявных преобразований нет. Косвенный вызываемый объект должен доминировать над вызовом, как и его аргументы. variadic: true, параметры типа void и SysV64 агрегированные подписи параметров/результатов отклоняются. Совокупные подписи Whale могут быть представлены в IR; Собственная классификация ABI и передача машинных вызовов пока недоступны ни для одного соглашения.

null_function(signature) представляет собой типизированный нулевой указатель на функцию. Вызов правильно напечатан IR с обязательной ловушкой времени выполнения перед входом в вызываемый объект. Ненулевая цель, которая недействительна, просрочена или несовместима с проверяемой подписью, также должна быть перехвачена. Эти проверки во время выполнения и управление временем существования внешнего обратного вызова ожидают уровня интерпретатора/собственного выполнения; Успех верификатора не означает, что произвольные внешние адреса безопасны.

Формы вызовов AST

Это фрагменты выражений внутри программы формата AST:

{"Call":{"callee":{"Direct":"increment"},"args":[{"Lit":{"Int":{"bits":32,"signed":true,"value":"41"}}}]}}
{"Call":{"callee":{"Indirect":{"FunctionRef":"increment"}},"args":[{"Lit":{"Int":{"bits":32,"signed":true,"value":"41"}}}]}}

Direct и FunctionRef используют пространство имен функции, даже если переменная имеет одно и то же имя. Indirect сначала оценивает свое выражение, затем оценивает аргументы слева направо. Вызов void действителен как ExprStmt, но не как инициализатор переменной, аргумент, операнд или возвращаемое значение. Вызовы и ссылки на функции не являются числовыми константными выражениями времени компиляции. NullFunction принимает объект подписи с полями params, ret, convention и variadic.

Полный пример JSON сохраняет обратный вызов и вызывает его перед внешним вызовом. Опустите его с помощью:

cargo run --locked --features socket-cli -- ir lower ir/tests/fixtures/ast-v2-calls.json

Его ожидаемый IR проверяется в тестах на понижение. Имена функций и ссылок представлены на границе IR; сохранение их посредством генерации собственных объектов и связывания по-прежнему является отдельной работой.

Наличие блоков и значений

Каждый блок имеет уникальный идентификатор и ровно один terminator. Цели ветвей должны принадлежать одной и той же функции. Входной блок должен существовать и не может иметь переднюю кромку и phi. При создании цикла перейдите от входного блока к отдельному заголовку цикла.

Определение значения в пути к исполняемому файлу должно определять его типичную точку использования. Это означает, что все пути от точки входа до точки использования должны проходить через это определение. В том же блоке определения должны предшествовать использованию. Порядок хранения блоков не определяет доминирование.

Предположим, что точка входа разветвляется на left или right, а затем присоединяется к join. Значения, определенные только в left, не могут использоваться в качестве общих значений в join. Это связано с тем, что путь, проходящий через right, не определен. Значения каждого предыдущего блока необходимо объединить в phi, который принимается на вход.

Недоступные блоки также сохраняются в модуле. Верификатор постоянно проверяет идентификатор, тип, операнд и структуру ветвления блока. Определение недостижимого блока не может обеспечить ценность нормального использования достижимого пути.

Phi команда

phi ставится перед всеми обычными командами в блоке. Для каждого предыдущего блока требуется ровно один вход. Входное значение должно иметь тип phi и должно быть доступно в конце соответствующего предыдущего блока.

Даже если в одном предыдущем блоке имеется несколько ребер, вход будет только один. Цикл phi может относиться к значению блока, который появляется позже в порядке хранения модуля, если это значение, рассчитанное на повторяющемся фронте. Отсутствующие, дублированные или нерелевантные предыдущие блоки, а также неправильно введенные входные данные являются ошибками проверки.

Оценка и отбор

Whale AST оценивает цели вызова и подвыражения слева, где они указаны. Интерфейсный интерфейс выражает краткую оценку как ветвь потока управления.

Select выбирает одно из уже рассчитанных значений. Он не пропускает вычисление ни одного из входных данных. Например, даже если вы выберете безопасное значение, вы не сможете избежать trap, встречающегося при вычислении других входных данных. Вычисления, которые необходимо выполнить только по определенному пути, следует размещать внутри условного блока.

Отдел проверки trap

Недействительный код IR будет отклонен на этапе проверки. Нарушения условий выполнения обрабатываются с помощью определенных trap, а undef и poison являются неприемлемыми значениями. Неправильное использование builder, дублирование определения, добавление второго terminator должно быть возвращено как структурная ошибка.

trap содержит причину·местоположение·IR ID, а затем останавливает выполнение. Запуск native завершает работу программы, а интерпретатор API возвращает ошибку Trap. Сохраняет предыдущие побочные эффекты, но не гарантирует буфер flush·стек вызовов деструктора unwinding.

Данная гарантия распространяется на проверенную IR и память трассировки. Внешняя встроенная сборка C·необработанный адрес· имеет отдельный контракт и не всегда обнаруживает нарушения за ее пределами. Пожалуйста, обратитесь к Модель памяти.

Формат обмена и представление текста

AST и typed IR используют соответствующие format version и общие semantics version. Читатель должен отклонить неверсионную/неизвестную версию/поле/функцию/дубликат ключа JSON. Конструкторам не следует предполагать, что неподдерживаемые свойства будут молча игнорироваться.

Целые числа передаются как числа разрядности ·signedness·. Константы с плавающей запятой передаются как ширина и точная битовая строка. Текст round-trip в IR должен сохранять имя·ID·тип·константу·последовательность·свойство·метаданные. Пространства и размещение комментариев сохранению не подлежат.

Вы можете использовать скалярные контракты AST JSON ниже. Вывод typed IR включает информацию о версии, но полный двусторонний обмен с анализатором текста пока не поддерживается.

Указана версия AST JSON

Сохраните следующее как program.json. Все четыре поля конверта являются обязательными. program содержит обязательные массивы declarations, globals и functions, которые могут быть пустыми. Требуются имя функции, параметры, тип возвращаемого значения, тело, convention и linkage. link_name может отсутствовать или быть нулевым для внутренних функций и должен быть непустой строкой без NUL для внешних функций. Каждое перечисление использует либо свое имя модуля, либо один объект с ключом варианта. Варианты модуля также принимают объект с нулевым значением, например {"Void":null}; кодер выдает имя устройства "Void". VarDecl.init может отсутствовать или быть нулевым; другие обязательные поля должны присутствовать.

{
  "format_version": 2,
  "semantics_version": 1,
  "features": [],
  "program": {
    "globals": [],
    "functions": [
      {
        "name": "answer",
        "parameters": [],
        "return_type": {
          "Int": {
            "bits": 128,
            "signed": false
          }
        },
        "body": [
          {
            "Return": {
              "Lit": {
                "Int": {
                  "bits": 128,
                  "signed": false,
                  "value": "340282366920938463463374607431768211455"
                }
              }
            }
          }
        ],
        "convention": "Whale",
        "linkage": "Internal",
        "link_name": null
      }
    ],
    "declarations": []
  }
}
cargo run --locked --features socket-cli -- ir lower program.json
module {
  format_version 2
  semantics_version 1
  target "x86_64-whale-linux"
  datalayout { ptr=64, endian=little }

  declare @f0 "answer": whale () -> u128, linkage internal

  fn @answer() -> u128, id @f0 {
  entry:
    %v0: u128 = const u128 340282366920938463463374607431768211455
    ret u128 %v0
  }

}

Целое число value представляет собой десятичную строку. Signed После необязательного минуса целого числа используется число. Пробелы, плюсы, показатели степени и разделители не допускаются. Допустимый диапазон определяется заявленной шириной и signedness. Вышеуказанный u128::MAX сохраняется в неизменном виде до JSON и lowering. Отрицательные значения unsigned или значения, выходящие за пределы диапазона, не являются wrap и являются ошибками. Значение Float использует шестнадцатеричную битовую строку точной ширины, описанной в Числовые операции.

format_version равен 2 для этого формата AST; semantics_version равен 1. features должен быть пустым массивом. Неизвестные поля, версии, функции, повторяющиеся необработанные ключи JSON (включая экранированные эквивалентные ключи) и конечные значения являются ошибками, даже с --no-verify. Точка входа в библиотеку — ir::lower_ast::interchange::decode; encode излучает конверт. decode по умолчанию имеет ограничение на исходный байт в 8 МБ; decode_with_limit принимает ограничение на количество вызывающих абонентов. Вложенность JSON ограничена. Используйте этот необработанный декодер вместо синтаксического анализа общей карты, которая уже может отбрасывать повторяющиеся ключи.

Полная схема JSON определяет формы, обязательные поля и варианты. Дополнительно применяются проверки диапазона/типа и обнаружение дубликатов ключей. Подмножество скалярного понижения включает в себя литералы, переменные/константы, добавление/под/муль, сравнения, присваивание, if/ while, возврат и разрыв/продолжение. Поддерживаются ссылки на функции, прямые и косвенные вызовы; агрегатные выражения не поддерживаются. Opaque представим на схеме, но не поддерживается понижением.

Миграция требует переноса старых пустых полезных данных программы и замены числовых литералов JSON строками десятичных целых чисел или строками битов с плавающей запятой. Старые неверсионные полезные данные отклоняются. Полезные данные формата 1 необходимо перенести в формат 2: добавьте program.declarations (пустой массив, если он не используется) и явные convention/linkage в определениях. AST и введенные номера версий IR независимы; оба теперь 2, с семантикой версии 1.

Ввод отклонен и восстановление CLI

Сохраните следующий полный ввод как invalid.json.

{
  "format_version": 99,
  "semantics_version": 1,
  "features": [],
  "program": {
    "globals": [],
    "functions": [],
    "declarations": []
  }
}
cargo run --locked --features socket-cli -- ir lower invalid.json -o rejected.wir
Failed to parse socket JSON: unsupported AST format_version 99; expected 2

Команда завершает работу с ненулевым статусом и не создает новые выходные данные и не перезаписывает существующие файлы. Несоответствие типов также не будет выполнено перед публикацией вывода. Двоичные файлы, созданные без socket-cli, завершаются со статусом 2 и выводят команду восстановления, содержащую --features socket-cli.