Files
2026-10-13 15:37:05 +08:00

8.6 KiB
Raw Permalink Blame History

隐式转换与整数提升(Implicit Conversions & Integer Promotions)

本文记录 Viper(现行编译器 VPC / ViperC)的隐式类型转换规则。 总原则:允许无损拓宽,禁止有损收窄;一切禁止项 fail-fast(报错并终止),不做静默截断、不做兜底推断。

主要实现位置:

  • ViperC/App/lib/core/Gen/Expr/ExprGenBase.vp — coerce_to_type(统一转换入口)
  • ViperC/App/lib/core/Gen/Expr/ExprGenOps.vp — 提升/符号判定(should_zext_widen、_node_type_unsigned、translate_binop、一元运算)
  • ViperC/App/lib/core/Gen/AssignGenMixin.vp — define_min_width、复合赋值(x op= y)
  • ViperC/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp — range() 的边界/循环变量位宽

1. 整数:拓宽与收窄

1.1 拓宽(低 → 高):允许

方向按源操作数的符号性决定:

源 指令
u 类(无符号) zext
i 类(有符号) sext
未标注的 i8 默认按无符号字节(zext);其余未标注按有符号(sext)

判据:ExprGenOps.should_zext_widen(内部走 _node_type_unsigned)。

1.2 收窄(高 → 低):禁止

报 CastError: implicit narrowing is forbidden。仅两类豁免:

  1. 目标为 i1:真值归一化 x != 0(不是位截断)。
  2. 源是编译期常量(val.IsConst == 1)且值落进目标位宽的两种解释区间 [-(2^(b-1)), 2^b - 1]。超界即便只是「刚好越界一点」也报 OverflowError。

1.3 表达式的工作位宽(translate_binop)

  • work_bits = max(上下文目标位宽(赋值/形参/返回,若已知), 两侧操作数位宽),下限 32(int)。
    • 移位(<< >>)不把右值(计数)宽度计入 work_bits。
  • 对齐时:
    • 位运算 & | ^:额外允许「常量无损适配」——能无损落进另一侧的常量按另一侧宽度参与。
    • 算术 + - * / %:不做常量适配,按 C 语义把窄侧拓宽(窄位宽下会回绕,例如 (32 + 1) * 8 在 i8 中溢出成 8)。
  • 移位:只提升左操作数到 work_bits;计数不参与类型提升,改为静态范围检查(0 <= c < 左操作数位宽,无法静态确定则留待运行期)。

1.4 define / 整数字面量的物化宽度

  • 显式注解 define X: T = V:按 T 的位宽;值放不下 → 报错。
  • 自动推导:W_min = 8 / 16 / 32 / 64 中最小可容纳宽度(AssignGenMixin.define_min_width)。
    • 负数按有符号解释(必须留符号位);
    • 非负按无符号位模式(上界 2^w - 1;例如 0xFFFFFFFF → 32 位位型)。
  • 使用处宽度 w_ctx:w_ctx >= W_min ⇒ 就地按 w_ctx 物化(无损);w_ctx < W_min ⇒ 报错。
  • 符号性(决定拓宽方向):
    • 负值 CDefine → sext(有符号语义;如 define Z_DEFAULT_COMPRESSION = (-1) 必须是 i32 -1,而非 255);
    • 非负 → zext(无符号位模式;如 define WINDOW_SIZE = (1 << 15) 必须是 32768,而非 -32768);
    • 判定与引用形态无关:裸名 N 与模块属性 mod.N 同口径。

2. 整数提升(Promotions)

上下文 规则
二元算术 + - * / // % 仿 C 拓宽到 work_bits(见 §1.3)
二元位运算 & | ^ 对齐到较宽侧;仅常量可无损适配到另一侧宽度
移位 << >> 只提升左操作数到 work_bits;计数只做静态范围检查
比较 == != < <= > >= 两侧对齐到较宽者;无符号用 ult/ugt,有符号用 slt/sgt
一元 +x / -x / ~x u 类不提升(保持原位宽);i 类提升到 i32(防负数陷阱,如 int8 -128 取负会溢出,提升后 = 128);未标注按 i 类处理
not x 结果为 i1,与操作数位宽无关

一元运算内部的常量(-x 的 0、~x 的 -1)与当前操作数同宽, 因此「u 类不提升」后 ~uint16 是纯 16 位取反(结果仍是 uint16),-uint8 也在 uint8 内取反。


3. 浮点

  • float → double:允许;double → float:禁止(有损)。
  • 整数 → 浮点:要求浮点位宽 ≥ 整数位宽(无损);否则禁止。
  • 浮点 → 整数:禁止(有损)。

4. 指针

  • 级数必须相同。
  • pointee 兼容性(_ptr_cast_reason):
    • 任一侧是 void* / rawptr → 允许;
    • pointee 相同 → 允许;
    • 两侧都是基本数值类型 → 按整数规则;
    • 结构体(非基本类型)只允许上转型(源类派生自目标类);不相关结构体 / 向下转型 / 结构体↔基本类型 → 禁止。
  • 单级指针 ↔ void* 双向允许;多级禁止。
  • 显式转换不受上述限制。

5. 值 ↔ 指针(聚合类型)

  • 指向同型聚合的指针 → 同型聚合值:允许,做隐式值拷贝(load)。 覆盖裸类形参(elem: T,ABI 即 T*)直接赋给同型值字段/变量,以及 x: T 这类值语义槽。 拷贝后目标独立、不会悬垂 —— 与「传参用指针」的 ABI 选择互不冲突。
  • 其余 Foo* → Foo 禁止(不同型聚合 / 多级指针),须显式 *p / c.deref(p) 或显式 cast; 唯一另一处豁免是构造物化(Foo(...) 直接产出聚合值)。
  • 聚合值 → 指针:在栈上溢出临时(VPC 不按值传聚合)。
  • 裸类形参 ABI = T*:VPC 里对象本身即指针,def f(node: T) 的形参按对象地址传入; 但赋给同型值目标时按上面的值拷贝处理(交出的是拷贝,不是地址)。

6. 复合赋值 x op= y

  • y 的有效宽度必须 ≤ x 的宽度(变量按 LLVM 类型宽度;常量按 W_min)。 否则报 augmented assignment operand too wide,须显式收窄右值(如 BYTE(...) / uint8(...))。
    • 例:u8 += 1 合法;u8 += 300 报错;buf[i] |= BYTE(UINT(1) << bit_pos) 合法。
  • 移位(<<= / >>=)豁免:计数不是写回目标的值,改为静态范围检查。
  • 右值显式有符号而左值无符号不单独拦截:拓宽方向由 §1.1 统一决定(i 类 sext、u 类 zext), 与二元形态 x = x op y 口径一致(如 sizet += int 里 int 先拓宽到 u64 再相加)。

7. range()

  • 边界(start / stop / step)统一提升到至少 i32(u64 边界保持 i64,避免截断);
  • 循环变量(未声明时)与边界同口径:≥ i32(不再有 u16→i16 / u8→i8 档);
  • 边界若是非负 CDefine 常量,按无符号位模式比较(icmp ult)。

变更记录

  • 2026-10-12:一元 +x / -x / ~x 提升规则由「一律提升到 i32」改为按符号性分档(见 §2)。
    • 动机:ncomp: uint16 = ~n(n: uint16)此前会被提升成 i32,再赋回 uint16 触发「隐式窄化禁止」误报; 现在 u 类保持原位宽,位图/掩码写法能同宽运算,同时 i 类仍提升到 i32 守住有符号负数陷阱。
    • 配套:-x 的 0、~x 的 -1 常量改为按当前操作数位宽物化(否则会出现 xor i16, i32 这类非法 IR)。
  • 2026-10-12:复合赋值取消「右值有符号而左值无符号」的单独拦截(见 §6)。
    • 动机:pos: sizet; pos += match_len(match_len: int)此前被拦,而等价的 pos = pos + match_len 本就允许(match_len 在二元路径里拓宽到 u64)——同一个语义两种写法不该一错一对。
  • 2026-10-12:CDefine 常量的拓宽方向单独判定,且覆盖属性形态 mod.N(见 §1.4)。
    • 此前只有裸名会走「非负 → zext」的判定,mod.N 形态(如 zdef.ZDEFLATE_WINDOW_SIZE)落到 「i8/i16 默认」档:(1 << 15) 物化成 i16 位型 -32768 后被 sext 成天文数字 → malloc 失败 → zlib compress 返回 None。
    • 现在:负值 → sext,非负 → zext(引用形态无关);同时修掉 pyzlib.Z_DEFAULT_COMPRESSION (-1)被 zext 成 255 的问题。
  • 2026-10-12:Ptr(同型聚合) → 同型聚合值 允许隐式值拷贝(见 §5)。
    • 动机:裸类形参(elem: T)的 ABI 已定为 T*,但 self.field = elem(同型值字段)应是值拷贝; 此前要求显式解引用会误报。VPC 里裸类形参与 p: T* 的槽同构,故判据落在「pointee 与目标同型」 上;拷贝后目标独立,不会悬垂。