Числовые операции
Описывает целочисленные операции wrap, checked, сдвиг, ошибки преобразования типов и результаты с плавающей запятой.
Wave Foundation
целочисленное представление
Битовое целое число N имеет биты значения N. Целые числа без знака находятся в диапазоне от 0 до 2^N - 1, а целые числа со знаком - от -2^(N-1) до 2^(N-1) - 1. signedness операции определяет интерпретацию битовой строки.
В таблице ниже поясняются результаты расчетов. IR В примере кода используется текущее представление принтера.
Сложение, вычитание, умножение
Базовые целые числа add·sub·mul содержат младшие биты N результата. overflow не вызывает trap. Операция checked возвращает тот же результат wrap, а также Bool, который указывает, находится ли математический результат за пределами диапазона соответствующего signed или unsigned.
| операция | Wrap Результат | Checked overflow |
|---|---|---|
| u8: 255 + 1 | 0 | true |
| i8: 127 + 1 | −128 | true |
| u8: 0 − 1 | 255 | true |
| i8: 12 × 3 | 36 | false |
Интерфейсы для языков, которые прерывают выполнение на overflow, должны использовать явный trap_if для результата overflow операции checked. Операция по умолчанию не применяет неявно политику overflow исходного языка.
Wrap и IR выражают явную проверку
Следующие модули настроены как Rust builder и представляют собой текущие выходные данные принтера, прошедшие проверку. Анализатор текста и механизм выполнения пока недоступны.
module {
format_version 2
semantics_version 1
target "x86_64-whale-linux"
datalayout { ptr=64, endian=little }
declare @f0 "add_u8": whale () -> u8, linkage internal
declare @f1 "require_no_overflow": whale () -> u8, linkage internal
fn @add_u8() -> u8, id @f0 {
entry:
%v0: u8 = const u8 255
%v1: u8 = const u8 1
%v2: u8 = add u8 %v0, %v1
ret u8 %v2
}
fn @require_no_overflow() -> u8, id @f1 {
entry:
%v3: u8 = const u8 255
%v4: u8 = const u8 1
%v5: tuple<u8, bool> = uadd_chk u8 %v3, %v4
%v6: u8 = extract %v5, 0
%v7: bool = extract %v5, 1
trap_if bool %v7, reason="integer overflow"
ret u8 %v6
}
}
В соответствии с арифметическим контрактом add_u8 преобразует 256 в 8-битный результат 0. require_no_overflow извлекает завернутый результат с помощью extract ..., 0 и флага переполнения Bool с помощью extract ..., 1. Если флаг true, trap_if останавливает выполнение перед возвратом. Это указанные результаты, а не вывод реализованного интерпретатора.
Деление и остаток
Целочисленное деление или остаток на ноль вызывает ловушку. Разделив минимальное значение со знаком на -1, мы получим минимальное значение. Остаток в этом случае равен нулю.
| операция | результат |
|---|---|
| i8: −128 / −1 | −128 |
| i8: −128 % −1 | 0 |
| Разделить на целое число 0 | trap |
| остаток для целого числа 0 | trap |
сдвиг
При сдвиге битового значения N битовая строка count интерпретируется как unsigned, и используется остаток, разделенный на N. Он не генерирует trap только потому, что count находится вне диапазона от 0 до N-1.
В 8-битных значениях count 0·8·16 все представляют собой 0-битные сдвиги. Битовая строка 11111111 из 8 бит count сдвигается на 7 бит. Это то же самое, даже если эта битовая строка представляет собой signed -1. unsigned Анализ выполняется перед остальными расчетами.
Исходные языки, которые отвергают отрицательные или чрезмерные count, должны поставить явную проверку перед сдвигом.
преобразование типов
| преобразование | смысл |
|---|---|
| Zero extension | Увеличьте ширину, заполнив старшие биты 0. |
| Sign extension | Увеличьте ширину, дублируя знаковый бит. |
| резка бит | Сохраняйте только младшие биты, соответствующие ширине назначения. |
| Ударная реинтерпретация | Интерпретация одной и той же битовой строки как разных типов |
| Числовое преобразование без потерь | Если его невозможно выразить, сохранив числовое значение, trap |
Например, преобразование 8-битного 11111111 в 16-битное zero extension становится 0000000011111111, а sign extension становится 1111111111111111. Даже если входные биты одинаковы, это разные операции.
Преобразование float-to-int отбрасывает дробную часть в направлении нуля, затем проверяется диапазон целых чисел. NaN и бесконечность вызывают ловушку. При преобразовании в i8 127,9 становится 127, а 128,0 — ловушкой. Bool преобразуется в целое число 0 или 1. Знак i1 не может представлять 1, поэтому он не может быть местом назначения этого преобразования.
Преобразование адреса в целое число не восстанавливает доступ указателя к этому целому числу. Пожалуйста, обратитесь к достоверность указателя.
арифметика с плавающей запятой
Значения с плавающей запятой имеют точную битовую строку f16·f32·f64. Операция округляет до ближайшего значения в объявленной ширине и, если ровно посередине, использует ties-to-even, которое выбирает значение с четными младшими битами значащих цифр.
Операция по умолчанию — fast-math, неявная FMA, которая не позволяет принудительно обнулять небольшие значения. Умножение, за которым следует сложение, сохраняет соответствующие шаги округления, и серверная часть не должна неявно объединять их в одну операцию.
Результатом числовой операции может быть NaN или бесконечность. NaN от математических операций и изменений ширины нормализуются до одного фиксированного количества тихих NaN на ширину. Сохраните и скопируйте исходные биты NaN. Поэтому поведение различается при передаче полезных данных NaN в память без арифметических операций и при их вычислении.
Он не предоставляет флаги состояния с плавающей запятой. Несмотря на то, что базовая арифметика с плавающей запятой позволяет получить результат NaN·бесконечность, преобразование float→int применяет приведенные выше правила trap.
Храните точные константы
Используйте FloatBits variant или любую строку шестнадцатеричных бит точной ширины. Равенство сохраненных значений сравнивается с битовой строкой, содержащей отрицательные 0 и NaN payload. Верификатор отклоняет, если ширина типов IR и payload различна.
use ir::{FloatBits, ModuleBuilder, Target, Type};
fn main() {
let bits = FloatBits::parse(32, "0xffc01234").unwrap();
assert_eq!(bits, FloatBits::F32(0xffc01234));
let target = Target::X86_64WhaleLinux;
let mut module = ModuleBuilder::new(target.name(), target.data_layout());
let mut function = module.begin_function("payload", vec![], Type::F32);
let value = function.const_float_bits(Type::F32, bits);
function.ret(Some(value));
function.finish();
let module = module.finish();
ir::verify_module(&module).unwrap();
assert!(ir::print_module(&module).contains("const f32 0xffc01234"));
println!("{}", bits);
}
0xffc01234
Битовая строка f16, f32 или f64 начинается с 0x, за которой следуют ровно 4, 8 или 16 шестнадцатеричных цифр соответственно. 0x80000000 представляет собой f32 отрицательный ноль; 0x7f800000 представляет положительную бесконечность. const_float преобразует значение хоста f64 в числовую форму; используйте const_float_bits, чтобы сохранить исходные биты. Точное представление хранилища не подразумевает полную обработку операций с плавающей запятой. Арифметика времени компиляции по-прежнему использует промежуточные звенья f64 хоста, поэтому контракт полного округления для каждой объявленной ширины еще не реализован.