7.1 KiB
7.1 KiB
隐式转换与整数提升(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。仅两类豁免:
- 目标为
i1:真值归一化x != 0(不是位截断)。 - 源是编译期常量(
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 常量按无符号位模式参与宽化与比较
(
define N = 50000是 16 位位型0xC350,须zext成 50000,而非sext成 -15536)。
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. 值 ↔ 指针(聚合类型)
- 非基本类型的
Foo* → Foo(指针 → 值)禁止,仅两处豁免:- 构造物化(
Foo(...)直接产出聚合值); - 值语义槽拷贝(源是
x: Foo这类「槽自身持有聚合物、Spec.PtrLevel == 0」)。
- 构造物化(
- 聚合值 → 指针:在栈上溢出临时(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)——同一个语义两种写法不该一错一对。
- 动机: