数值运算

描述整数 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 处中断执行的语言的前端必须对 checked 操作的 overflow 结果使用显式 trap_if。默认操作不会隐式应用源语言的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提取包装后的结果,并使用extract ..., 1提取Bool溢出标志。如果标志为true,则trap_if在返回之前停止执行。这些是指定的结果,而不是实现的解释器的输出。

除法和余数

整数除以零或余数会导致陷阱。将最小有符号值除以 -1 即可得到最小值。在这种情况下余数为零。

操作 结果
i8: −128 / −1 −128
i8: −128 % −1 0
除以整数 0 trap
整数 0 的余数 trap

转变

在移位N位值时,count的位串被解释为unsigned,并且使用除以N的余数。它不会仅仅因为 count 超出 0 到 N−1 的范围而生成 trap。

在 8 位值中,count 0·8·16 均为 0 位移位。 8 位count 的位串11111111 移位了 7 位。即使该位串表示signed-1,这也是相同的。 unsigned 分析在其余计算之前进行。

拒绝负数或过多count的源语言必须在转换前进行显式检查。

类型转换

转换 意义
Zero extension 通过用 0 填充高位来增加宽度
Sign extension 通过复制符号位来增加宽度
钻头切削 仅保留目标宽度对应的低位
节拍重新诠释 将相同的位串解释为不同的类型
无损数值转换 如果无法在保留数值的情况下表达,trap

例如,将 8 位 11111111 转换为 16 位 zero extension 变为 0000000011111111,sign extension 变为 1111111111111111。即使输入位相同,也是不同的操作。

float 到 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和NaNpayload的位串进行比较。如果类型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中间体,因此每个声明宽度的完整舍入契约尚未实现。