fix(viperc): 静态常量求值对 + - * << 按结果值加宽,杜绝 define 算式静默回绕

- 现象:Gargantua 的 define V3_POOL_SIZE=8192 / V3_POOL_BYTES=V3_POOL_SIZE*24+512 在运行时变成 512(正好是表达式里的 +512),等价于 8192*24=196608 被按 16 位回绕成 0 → 池只剩 512B → 每像素 trace 立刻 OOM 刷屏

- 根因:extract_cdefine_int_value_bits 的 BinOp 位宽规则只“取较宽”,而 + - * << 会增长结果位宽 → define 物化时按 16 位截断

- 现在:操作数的声明宽度仍保持最小(不提升);基准取较宽者;若精确结果装不进基准宽度,则按结果值加宽(向上取 8/16/32/64)—— 静态计算不再回绕;基准未知(纯字面量算式)时保持“未知”,仍由物化端按值推断

- 顺带:未知 BinOp 算子恢复 fail-fast(重构后原落空分支若留着会静默返回 0)
This commit is contained in:
TermiNexus committed 2026-10-14 19:18:52 +08:00
1 parent 813b59e25f
commit 61f23d99a1
1 file changed
+45 -18
+45 -18
View File
@@ -54,7 +54,9 @@ novirtual class AssignGenMixin:
位宽规则(供 define 物化用): 位宽规则(供 define 物化用):
- 显式强转(Call 的 callee 是整型/typedef,如 WORD / int / t.CUInt16T)→ 该位宽; - 显式强转(Call 的 callee 是整型/typedef,如 WORD / int / t.CUInt16T)→ 该位宽;
- Name 引用其它 define → 沿用其位宽; - Name 引用其它 define → 沿用其位宽;
- BinOp:两侧位宽**相等** → 保持该位宽(**不提升**);不等 → 取较宽;任一未知 → 未知; - BinOp:操作数的**声明宽度**保持最小(不提升),基准取较宽者;`+ - * <<` 的结果若装不进
基准宽度,就**按结果值加宽**(向上取到 8/16/32/64)—— 静态计算不允许回绕;
基准未知(如纯字面量算式)→ 保持未知,仍由物化端按值推断;
- 字面量/一元 → 未知(物化时按值推断)。 - 字面量/一元 → 未知(物化时按值推断)。
pool 为 None 时无法解析 typedef(仅旧调用方走此路),强转按"取实参值、位宽未知"处理。 pool 为 None 时无法解析 typedef(仅旧调用方走此路),强转按"取实参值、位宽未知"处理。
""" """
@@ -79,31 +81,56 @@ novirtual class AssignGenMixin:
rb: int = 0 rb: int = 0
left_val: int64 = self.extract_cdefine_int_value_bits(bop.left, pool, &lb) left_val: int64 = self.extract_cdefine_int_value_bits(bop.left, pool, &lb)
right_val: int64 = self.extract_cdefine_int_value_bits(bop.right, pool, &rb) right_val: int64 = self.extract_cdefine_int_value_bits(bop.right, pool, &rb)
if bits_out is not None: # 操作数位宽(各自的**声明宽度**,保持最小、不提升):两侧不等取较宽,一侧未知则沿用另一侧
if lb != 0 and rb != 0: bits_out[0] = lb if lb > rb else rb wbase: int = 0
elif lb != 0: bits_out[0] = lb if lb != 0 and rb != 0: wbase = lb if lb > rb else rb
elif rb != 0: bits_out[0] = rb elif lb != 0: wbase = lb
if bop.op == ast.OpKind.BitOr: return left_val | right_val elif rb != 0: wbase = rb
if bop.op == ast.OpKind.BitAnd: return left_val & right_val res: int64 = 0
if bop.op == ast.OpKind.BitXor: return left_val ^ right_val if bop.op == ast.OpKind.BitOr: res = left_val | right_val
elif bop.op == ast.OpKind.BitAnd: res = left_val & right_val
elif bop.op == ast.OpKind.BitXor: res = left_val ^ right_val
# 移位:求值器内部按 64 位算,计数为负或 >= 64 时结果无意义(宿主侧是 poison/未定义), # 移位:求值器内部按 64 位算,计数为负或 >= 64 时结果无意义(宿主侧是 poison/未定义),
# 与 require_shift_count_in_range 同一口径直接 fail-fast,不产出垃圾常量。 # 与 require_shift_count_in_range 同一口径直接 fail-fast,不产出垃圾常量。
if bop.op == ast.OpKind.LShift or bop.op == ast.OpKind.RShift: elif bop.op == ast.OpKind.LShift or bop.op == ast.OpKind.RShift:
if right_val < 0 or right_val >= 64: if right_val < 0 or right_val >= 64:
self.Gen.TypeGenErr.fatal_error(val_node, f"constant expression shift count out of range: the shift count evaluates to {right_val} at compile time, " "but the evaluator works on 64-bit integers (valid range 0..63); the result would be undefined. " "Fix: use a count in 0..63, or restructure the expression.", "ValueError") self.Gen.TypeGenErr.fatal_error(val_node, f"constant expression shift count out of range: the shift count evaluates to {right_val} at compile time, " "but the evaluator works on 64-bit integers (valid range 0..63); the result would be undefined. " "Fix: use a count in 0..63, or restructure the expression.", "ValueError")
if bop.op == ast.OpKind.LShift: return left_val << right_val if bop.op == ast.OpKind.LShift: res = left_val << right_val
return left_val >> right_val else: res = left_val >> right_val
if bop.op == ast.OpKind.Add: return left_val + right_val elif bop.op == ast.OpKind.Add: res = left_val + right_val
if bop.op == ast.OpKind.Sub: return left_val - right_val elif bop.op == ast.OpKind.Sub: res = left_val - right_val
if bop.op == ast.OpKind.Mult: return left_val * right_val elif bop.op == ast.OpKind.Mult: res = left_val * right_val
# 编译期除零:绝不把除法交给宿主(会直接把编译器打挂),fail-fast 给出可读诊断。 # 编译期除零:绝不把除法交给宿主(会直接把编译器打挂),fail-fast 给出可读诊断。
if bop.op == ast.OpKind.Div or bop.op == ast.OpKind.FloorDiv or bop.op == ast.OpKind.Mod: elif bop.op == ast.OpKind.Div or bop.op == ast.OpKind.FloorDiv or bop.op == ast.OpKind.Mod:
if right_val == 0: if right_val == 0:
self.Gen.TypeGenErr.fatal_error(val_node, "constant expression division by zero: the right operand of '/', '//' or '%' evaluates to 0 at compile time.", "ValueError") self.Gen.TypeGenErr.fatal_error(val_node, "constant expression division by zero: the right operand of '/', '//' or '%' evaluates to 0 at compile time.", "ValueError")
if bop.op == ast.OpKind.Mod: return left_val % right_val if bop.op == ast.OpKind.Mod: res = left_val % right_val
return left_val // right_val else: res = left_val // right_val
# Unknown BinOp operator: fail fast else:
# Unknown BinOp operator: fail fast(不能静默返回 0)
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError")
if bits_out is not None:
# 静态计算**自行拓宽**:字面量/define 的声明宽度仍保持最小(不提升),但 `+ - * <<`
# 的精确结果可能比操作数宽 —— 若照操作数宽度物化就会静默回绕
# (现场:define A=8192(最小 16 位)的 `A*24+512` 被按 16 位物化成 512)。
# 规则:以操作数的“较宽”为基准,再用**结果值**算精确可容纳位宽,只有装不下才加宽
# (不无故提升,避免把 `CDefine + 1` 这类算式整体抬成 i64 引出新的“隐式窄化”报错);
# 基准未知(wbase==0,如纯字面量算式)时保持“未知”,仍由物化端按值推断。
w: int = wbase
if w > 0 and res != 0:
t: int64 = res
if res < 0: t = res ^ int64(-1)
vw: int = 1 # 符号位
while t != 0:
vw += 1
t = t >> 1
if vw > w: w = vw
if w > 0:
if w <= 8: bits_out[0] = 8
elif w <= 16: bits_out[0] = 16
elif w <= 32: bits_out[0] = 32
else: bits_out[0] = 64
return res
# Case 3: Name - a registered CDefine constant or True/False literal # Case 3: Name - a registered CDefine constant or True/False literal
if k == ast.ASTKind.Name: if k == ast.ASTKind.Name: