Files
ViperC/App/lib/core/Gen/AssignGenMixin.vp
T

3777 lines
253 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import stdlib
import viperlib
import win32.fileio as fileio
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import lib.core.CompilerFactory as CompilerFactory
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.core.Gen.GenApi as GenApi
# AssignGenMixin - assignment translation mixin
novirtual class AssignGenMixin:
Gen: Generator.Generator*
"""Assignment translation mixin."""
RecoverFound: int
# Check whether an annotation is the CDefine marker (module-level function)
def is_cdefine_annotation(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation is the CDefine marker, else 0."""
if annot is None: return 0
k: int = annot.kind()
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(annot)
if nm.id is not None and string.strcmp(nm.id, "CDefine") == 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if self.is_cdefine_annotation(bop.left) != 0: return 1
if self.is_cdefine_annotation(bop.right) != 0: return 1
return 0
# extract_cdefine_int_value - extract an integer constant from AnnAssign.value
# ---- 统一编译期常量表达式求值(值 + 位宽) ----
# 所有 define/常量折叠入口都走这里,避免各写一套(此前 extract_cdefine_int_value /
# _eval_const_int_expr / _cif_eval_node 三套互相独立,导致位宽信息在 define 上丢失)。
def extract_cdefine_int_value(self, val_node: ast.AST*) -> int64:
"""仅取值(丢弃位宽)的薄封装;需要位宽请用 extract_cdefine_int_value_bits。"""
b_legacy: int = 0
return self.extract_cdefine_int_value_bits(val_node, None, &b_legacy)
def extract_cdefine_int_value_bits(self, val_node: ast.AST*, pool: memhub.MemBuddy*, bits_out: int*) -> int64:
"""编译期常量表达式求值:返回值,并把**位宽**写入 bits_out[0](0=未知)。
位宽规则(供 define 物化用):
- 显式强转(Call 的 callee 是整型/typedef,如 WORD / int / t.CUInt16T)→ 该位宽;
- Name 引用其它 define → 沿用其位宽;
- BinOp:操作数的**声明宽度**保持最小(不提升),基准取较宽者;`+ - * <<` 的结果若装不进
基准宽度,就**按结果值加宽**(向上取到 8/16/32/64)—— 静态计算不允许回绕;
基准未知(如纯字面量算式)→ 保持未知,仍由物化端按值推断;
- 字面量/一元 → 未知(物化时按值推断)。
pool 为 None 时无法解析 typedef(仅旧调用方走此路),强转按"取实参值、位宽未知"处理。
"""
if bits_out is not None: bits_out[0] = 0
if val_node is None: return 0
k: int = val_node.kind()
# Case 1: Constant(INT) - e.g. 0x0002
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(val_node)
# True/False boolean constant -> 1/0 (bool treated as int in CDefine context)
if cn.const_kind == ast.CONST_BOOL: return 1 if cn.int_val != 0 else 0
if cn.const_kind != ast.CONST_INT:
# Non-INT constant: fail fast
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError")
return cn.int_val
# Case 2: BinOp - e.g. FOREGROUND_RED | FOREGROUND_GREEN, (1 << N)
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(val_node)
lb: int = 0
rb: int = 0
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)
# 操作数位宽(各自的**声明宽度**,保持最小、不提升):两侧不等取较宽,一侧未知则沿用另一侧
wbase: int = 0
if lb != 0 and rb != 0: wbase = lb if lb > rb else rb
elif lb != 0: wbase = lb
elif rb != 0: wbase = rb
res: int64 = 0
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/未定义),
# 与 require_shift_count_in_range 同一口径直接 fail-fast,不产出垃圾常量。
elif bop.op == ast.OpKind.LShift or bop.op == ast.OpKind.RShift:
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")
if bop.op == ast.OpKind.LShift: res = left_val << right_val
else: res = left_val >> right_val
elif bop.op == ast.OpKind.Add: res = left_val + right_val
elif bop.op == ast.OpKind.Sub: res = left_val - right_val
elif bop.op == ast.OpKind.Mult: res = left_val * right_val
# 编译期除零:绝不把除法交给宿主(会直接把编译器打挂),fail-fast 给出可读诊断。
elif bop.op == ast.OpKind.Div or bop.op == ast.OpKind.FloorDiv or bop.op == ast.OpKind.Mod:
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")
if bop.op == ast.OpKind.Mod: res = left_val % right_val
else: res = left_val // right_val
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")
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 # 符号位
# 固定 64 次上界(i64 最多 64 位):不用 `while t != 0` —— 某些代码生成下
# i64 的移位/比较不收敛,会死循环。
bi: int = 0
while bi < 64 and t > 0:
vw += 1
t = t >> 1
bi += 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
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(val_node)
if nm.id is not None:
# True/False literal -> 1/0 (bool treated as int in CDefine context)
if string.strcmp(nm.id, "True") == 0: return 1
if string.strcmp(nm.id, "False") == 0: return 0
looked_up: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(nm.id)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
# 沿用被引用 define 的位宽(宽度随宏传播,不再丢)
if bits_out is not None: bits_out[0] = self.Gen.TypeGenCDef.lookup_cdefine_bits(nm.id)
return looked_up
# Unregistered reference: fail fast
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError")
# Case 4: Call - 整型/typedef 强转(如 WORD(0x0080) / t.CUnsignedLong(-11)):
# 取实参值,并按**目标类型的位宽**规范化(这赋予常量"已知宽度")。
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(val_node)
t_bits: int = 0
if pool is not None: t_bits = self._annot_int_bits(pool, cl.func)
# Bind the property to a list[...]* local before calling len() (else GEP base breaks)
cl_args_list: list[ast.AST*]* = cl.args
if cl_args_list is not None and len(cl_args_list) > 0:
first_arg: ast.AST* = cl_args_list.get(0)
ib: int = 0
arg_val: int64 = self.extract_cdefine_int_value_bits(first_arg, pool, &ib)
if t_bits != 0:
if bits_out is not None: bits_out[0] = t_bits
return self._normalize_cdefine_bits(arg_val, t_bits)
if bits_out is not None: bits_out[0] = ib
return arg_val
# Call with no args: fail fast
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError")
# Case 5: UnaryOp - e.g. -11
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(val_node)
ob: int = 0
operand_val: int64 = self.extract_cdefine_int_value_bits(uop.operand, pool, &ob)
if bits_out is not None: bits_out[0] = ob
if uop.op == ast.OpKind.USub: return -operand_val
if uop.op == ast.OpKind.UAdd: return operand_val
if uop.op == ast.OpKind.Invert: return ~operand_val
# Unknown UnaryOp operator: fail fast
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError")
# Unknown node kind: fail fast
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError")
# After fatal_error add an explicit return so the block is properly terminated
return 0
def _normalize_cdefine_bits(self, value: int64, bits: int) -> int64:
"""把值按位宽处理:**只有在当前位宽的两种解释下都装不下时才截断**,否则原样保留
(与 ExprGenCdef._cdefine_bits_value 同口径)。
「左侧只会看到截断了的数据」:超宽的值先按 bits 取低位(如 WORD(0x80000000) → 0)。
但**不**把"无符号可容、有符号装不下"的正值重解释成负数:旧实现无条件
`(value << 56) >> 56`,8 位下的 160 变成 -96,正值静默变负,常量算式随之算错
(现场:Gargantua `GW * GH * 4` = 57600 被折成 -34560)。
实现仍避开大掩码字面量(见下)。
"""
if bits == 8:
if value >= -128 and value <= 127: return value
if value > 127 and (value >> 8) == 0: return value
return (value << 56) >> 56
if bits == 16:
if value >= -32768 and value <= 32767: return value
if value > 32767 and (value >> 16) == 0: return value
return (value << 48) >> 48
if bits == 32:
if value >= -2147483647 - 1 and value <= 2147483647: return value
if value > 2147483647 and (value >> 32) == 0: return value
return (value << 32) >> 32
return value
def cdefine_decl_bits(self, pool: memhub.MemBuddy*, annot: ast.AST*, value_node: ast.AST*) -> int:
"""define 声明的位宽裁决(定义方与跨模块使用方**共用同一套**,避免两边位宽不一致)。
① 有声明注解 T(define X: T = ...)→ 按注解 T 的位宽;
② 无注解但右侧表达式自身已定宽(显式强转,如 WORD(0x0080))→ 用该位宽;
③ 都没有 → 按**值的最小可存宽度**(0x80000000 属 2^31..2^32-1 取 32;小值取 8/16,
窄宽度参与算术时由"算术整数提升"先升到 i32,不会溢出)。
返回 0 表示该 define 不是整型(调用方不应据此注册整型常量)。
"""
annot_bits: int = self._define_annot_bits(pool, annot)
if annot_bits != 0: return annot_bits
if value_node is None: return 0
rhs_bits: int = 0
val: int64 = self.extract_cdefine_int_value_bits(value_node, pool, &rhs_bits)
if rhs_bits != 0: return rhs_bits
return self.define_min_width(val)
# define / t.CDefine integer constant storable and width checks
# Auto-derived => 64-bit pattern; explicit type T => T's width range.
def define_min_width(self, value: int64) -> int:
"""Return the minimal storable width (8/16/32/64) for an integer value."""
if value < 0:
if value >= -128: return 8
if value >= -32768: return 16
# -2^31: use an arithmetic equivalent to avoid an i32-wrapping literal
if value >= -2147483647 - 1: return 32
return 64
if value <= 255: return 8
if value <= 65535: return 16
if value <= 2147483647: return 32
# 2^31..2^32-1:无符号 32 位位型即可容纳 → 32 位。
# 用 (value >> 32) == 0 判断,**不要**写成 uint64(uint32(value)) == value:uint32(...) 之后的
# 加宽方向(zext/sext)由 should_zext_widen 按节点无符号性决定,TPC 与 VPC 自举的判定可能不一致;
# 一旦判成 sext,uint64(0x80000000) 会变成 0xFFFFFFFF80000000 ≠ value → 误判 64 位 →
# `GENERIC_READ | GENERIC_WRITE` 被放大到 i64 → 赋给 32 位目标报隐式窄化。
if (value >> 32) == 0: return 32
return 64
# _is_static_int_expr - 该节点是否属于通用求值器一定能折叠的整型表达式(只判定,不求值)
def _binop_is_foldable(self, op: int) -> int:
"""1=通用求值器支持的整型二元运算(与 extract_cdefine_int_value_bits 的 BinOp 分支一致)"""
if op == ast.OpKind.BitOr or op == ast.OpKind.BitAnd or op == ast.OpKind.BitXor: return 1
if op == ast.OpKind.LShift or op == ast.OpKind.RShift: return 1
if op == ast.OpKind.Add or op == ast.OpKind.Sub or op == ast.OpKind.Mult: return 1
if op == ast.OpKind.Div or op == ast.OpKind.FloorDiv or op == ast.OpKind.Mod: return 1
return 0
def _is_static_int_expr(self, node: ast.AST*) -> int:
"""1=通用求值器(extract_cdefine_int_value_bits)一定能求出整数;0=不确定,**不得调用求值器**。
判定范围与求值器严格对齐(否则求值器会 fail-fast,变成误报):
整型/布尔字面量、True/False、**已注册的** CDefine 名、其 +/-/~、求值器支持的整型二元运算、
以及带实参的强转调用。变量、普通函数调用、求值器不支持的算子一律判 0 —— 即"求不出来就不检查"。
"""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(node)
if cn.const_kind == ast.CONST_INT or cn.const_kind == ast.CONST_BOOL: return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is None: return 0
if string.strcmp(nm.id, "True") == 0 or string.strcmp(nm.id, "False") == 0: return 1
self.Gen.TypeGenCDef.lookup_cdefine_constant(nm.id)
if self.Gen.TypeGenCDef.is_cdefine_found() == 0: return 0
return 1
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if self._binop_is_foldable(bop.op) == 0: return 0
if self._is_static_int_expr(bop.left) == 0: return 0
return self._is_static_int_expr(bop.right)
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(node)
if uop.op != ast.OpKind.USub and uop.op != ast.OpKind.UAdd and uop.op != ast.OpKind.Invert: return 0
return self._is_static_int_expr(uop.operand)
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
cl_args_st: list[ast.AST*]* = cl.args
if cl_args_st is None or len(cl_args_st) == 0: return 0
return self._is_static_int_expr(cl_args_st.get(0))
return 0
# _static_int_of - 静态求值整数节点:复用 define 的**通用求值器**(单一实现,避免两套规则)
def _static_int_of(self, node: ast.AST*, out: int64*) -> int:
"""1=静态可确定(值写入 out[0]),0=无法确定(调用方不做检查,也不报错)。
先由 _is_static_int_expr 判定"求值器一定能求出来",再走同一个求值器
(extract_cdefine_int_value_bits,位宽丢弃),因此支持任意编译期可折叠表达式
(`(SHIFT / 2) + 1`、`WORD(3)`、`A | B` 等),且求不出来时不会触发求值器的 fail-fast。
"""
if node is None or out is None: return 0
if self._is_static_int_expr(node) == 0: return 0
b_na: int = 0
out[0] = self.extract_cdefine_int_value_bits(node, None, &b_na)
return 1
# require_shift_count_in_range - 移位计数的静态范围检查(x << y / x >> y 与 <<= / >>= 共用)
def require_shift_count_in_range(self, pool: memhub.MemBuddy*, trans: HT.Translator*, node: ast.AST*, count_node: ast.AST*, lhs_bits: int) -> None:
"""fail-fast:移位计数可静态确定且为负、或 >= 左操作数位宽时直接报错。
移位计数**豁免**"宽度 <= 目标宽度"与符号一致性规则(计数不是写回目标的值),
改为在编译期把能确定的无意义移位拦下来:x << y / x >> y 与 x <<= y / x >>= y 同一套。
计数无法静态确定时不检查(留给运行期)。lhs_bits 应传**提升后的有效宽度**(C 语义下
u8 << 8 合法,因为 u8 先提升到 i32)。"""
if count_node is None or lhs_bits <= 0: return
v: int64 = 0
if self._static_int_of(count_node, &v) == 0: return
if v < 0:
self.Gen.TypeGenErr.fatal_error(node, f"[VPC] shift count is negative ({v}): shifting by a negative amount is undefined. " "Fix: use a non-negative count.", "ValueError")
if uint64(v) >= uint64(lhs_bits):
self.Gen.TypeGenErr.fatal_error(node, f"[VPC] shift count out of range: the count {v} is >= the left operand width {lhs_bits} bits; " f"the result would be 0 or undefined (not a value the compiler may guess). " f"Fix: use a count < {lhs_bits}, or widen the left operand.", "ValueError")
def _is_cdefine_marker_node(self, node: ast.AST*) -> int:
"""Return 1 if the node is the CDefine marker node."""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None and string.strcmp(nm.id, "CDefine") == 0: return 1
return 0
def _define_explicit_annot(self, annot: ast.AST*) -> ast.AST*:
"""Return the explicit type T of a `T | t.CDefine` annotation, or None."""
if annot is None: return None
if annot.kind() != ast.ASTKind.BinOp: return None
bop: ast.BinOp* = ast.BinOp*(annot)
if bop.left is None: return None
if self._is_cdefine_marker_node(bop.left) != 0: return None
return bop.left
def _annot_int_bits(self, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""Return the bit width of an integer annotation, or 0 if unknown."""
if annot is None: return 0
k: int = annot.kind()
nm: str = None
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annot)
nm = at.attr
elif k == ast.ASTKind.Name:
n2: ast.Name* = ast.Name*(annot)
nm = n2.id
if nm is None: return 0
return self._int_bits_of_type_name(pool, nm, 0)
def _int_bits_of_type_name(self, pool: memhub.MemBuddy*, nm: str, depth: int) -> int:
"""类型名 → 整数位宽(0=未知/非整数)。depth 防 typedef 环。
覆盖:内建名(int/bool/int8../uint64/sizet)、`t.CXxx`(map_builtin_type)、
以及**用户 typedef**(如 stdint 的 `WORD: t.CTypedef = uint16` —— 经 lookup_ctypedef
取其底层类型文本再递归定宽)。此前用户 typedef 一律返回 0,导致
`define X: WORD = ...` 的声明宽度不参与校验/物化。
"""
if nm is None or depth > 10: return 0
# 去掉模块/类限定前缀:`t.CUInt16T` → `CUInt16T`;`stdint.uint16` → `uint16`
dot: str = string.strrchr(nm, 46)
if dot is not None and dot[1] != '\0':
return self._int_bits_of_type_name(pool, dot + 1, depth + 1)
if string.strcmp(nm, "int") == 0: return 32
if string.strcmp(nm, "bool") == 0: return 8
if string.strcmp(nm, "int8") == 0 or string.strcmp(nm, "uint8") == 0: return 8
if string.strcmp(nm, "int16") == 0 or string.strcmp(nm, "uint16") == 0: return 16
if string.strcmp(nm, "int32") == 0 or string.strcmp(nm, "uint32") == 0: return 32
if string.strcmp(nm, "int64") == 0 or string.strcmp(nm, "uint64") == 0: return 64
if string.strcmp(nm, "sizet") == 0 or string.strcmp(nm, "size_t") == 0: return 64
ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.map_builtin_type(pool, nm)
if ty is not None:
match ty:
case mlirlite.LLVMType.Int(bits):
if bits == 1: return 8
if bits == 8 or bits == 16 or bits == 32 or bits == 64: return bits
return 0
case _:
return 0
# 用户/include typedef(如 stdint.vp 的 `WORD: t.CTypedef = uint16`):
# 表内值可能是模块限定 ref(stdint.uint16),先用 _expand_typedef_ref 展开;
# 本地表未命中再走 builtins 源扫描兜底。
underlying: str = self.Gen.TypeGenCType.lookup_ctypedef(nm)
if underlying is None:
underlying = self.Gen.TypeGenResolve._lookup_builtins_typedef(pool, nm)
if underlying is not None:
expanded: str = self.Gen.TypeGenResolve._expand_typedef_ref(pool, underlying)
if expanded is not None: underlying = expanded
if underlying is None: return 0
if string.strcmp(underlying, nm) == 0: return 0
return self._int_bits_of_type_name(pool, underlying, depth + 1)
def _define_annot_bits(self, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""Return the bit width of a CDefine declaration's explicit type T."""
return self._annot_int_bits(pool, self._define_explicit_annot(annot))
# cdefine_decl_float_bits - define 声明的**浮点**宽度裁决(定义方与跨模块使用方共用同一套)
def cdefine_decl_float_bits(self, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""1 = 注解显式声明了 32 位 float(`define PI: float = ...`),0 = double / 未声明 / 非浮点。
与 cdefine_decl_bits(整型位宽)并列:浮点常量的宽度只由注解决定(右侧字面量在
.vpi 里只剩 `= 3.14159...`,已丢失 `float(...)` 包裹),故这里只看注解。
"""
return self._annot_float_kind(pool, self._define_explicit_annot(annot))
def _annot_float_kind(self, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""注解节点 → 1(f32)/ 0(其余);成分与 _annot_int_bits 对齐。"""
if annot is None: return 0
nm: str = None
k: int = annot.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annot)
nm = at.attr
elif k == ast.ASTKind.Name:
n2: ast.Name* = ast.Name*(annot)
nm = n2.id
if nm is None: return 0
return self._float_kind_of_type_name(pool, nm, 0)
def _float_kind_of_type_name(self, pool: memhub.MemBuddy*, nm: str, depth: int) -> int:
"""类型名 → 1 表示它是 32 位 float(f32),0 表示 double 或非浮点/未知。depth 防 typedef 环。
覆盖:内建名 float、`t.CFloat` / `t.CFloat32T`(map_builtin_type)、用户/include typedef(递归展开)。
"""
if nm is None or depth > 10: return 0
# 去掉模块/类限定前缀:`t.CFloat` → `CFloat`;`stdint.float32` → `float32`
dot: str = string.strrchr(nm, 46)
if dot is not None and dot[1] != '\0':
return self._float_kind_of_type_name(pool, dot + 1, depth + 1)
if string.strcmp(nm, "float") == 0: return 1
if string.strcmp(nm, "float32") == 0: return 1
ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.map_builtin_type(pool, nm)
if ty is not None:
match ty:
case mlirlite.LLVMType.Float(bits):
if bits == 32: return 1
return 0
case _:
return 0
# 用户/include typedef(如 `MYF: t.CTypedef = float`):展开后再递归判定
underlying: str = self.Gen.TypeGenCType.lookup_ctypedef(nm)
if underlying is None:
underlying = self.Gen.TypeGenResolve._lookup_builtins_typedef(pool, nm)
if underlying is not None:
expanded: str = self.Gen.TypeGenResolve._expand_typedef_ref(pool, underlying)
if expanded is not None: underlying = expanded
if underlying is None: return 0
if string.strcmp(underlying, nm) == 0: return 0
return self._float_kind_of_type_name(pool, underlying, depth + 1)
def _cdefine_int_literal_text(self, name: str) -> str:
"""Return the raw value text of a CDefine line, or None if not found."""
if name is None: return None
fp: str = self.Gen.TypeGenResolve.get_current_file()
if fp is None: return None
f: fileio.File* = fileio.File(fp, fileio.MODE.R)
if f.closed: return None
BUF_SZ: sizet = 1048576
buf: bytes = stdlib.malloc(BUF_SZ)
if buf is None:
f.close()
return None
nrd: LONG = f.read_all(buf, BUF_SZ)
f.close()
if nrd <= 0:
stdlib.free(buf)
return None
if nrd < BUF_SZ: buf[nrd] = 0
name_len: sizet = string.strlen(name)
total_len: sizet = string.strlen(buf)
pos: sizet = 0
result: str = None
while pos < total_len:
line_start: sizet = pos
while pos < total_len and buf[pos] != '\n': pos += 1
line_len: sizet = pos - line_start
if pos < total_len: pos += 1
if line_len < name_len + 1: continue
ok: int = 1
ki: sizet
for ki in range(int(name_len)):
if buf[line_start + ki] != name[ki]:
ok = 0
break
if ok == 0: continue
sep: sizet = line_start + name_len
while sep < line_start + line_len and buf[sep] == ' ': sep += 1
if sep >= line_start + line_len: continue
if buf[sep] != ':' and buf[sep] != '=': continue
eq: sizet = line_start
while eq < line_start + line_len and buf[eq] != '=': eq += 1
if eq >= line_start + line_len: continue
eq += 1
while eq < line_start + line_len and buf[eq] == ' ': eq += 1
vstart: sizet = eq
while eq < line_start + line_len and buf[eq] != ' ' and buf[eq] != '\t' and buf[eq] != '#' and buf[eq] != '\r': eq += 1
if eq == vstart: continue
txt: str = stdlib.malloc(eq - vstart + 1)
if txt is None: continue
vi: sizet
for vi in range(int(eq - vstart)): txt[vi] = buf[vstart + vi]
txt[eq - vstart] = 0
result = txt
break
stdlib.free(buf)
return result
def _int_text_exceeds_64(self, text: str) -> int:
"""Return 1 if the literal text exceeds the 64-bit range, else 0."""
if text is None: return 0
tl: sizet = string.strlen(text)
if tl == 0: return 0
neg: int = 0
i: sizet = 0
if text[0] == '-':
neg = 1
i = 1
elif text[0] == '+': i = 1
if i >= tl: return 0
if i + 1 < tl and text[i] == '0' and (text[i + 1] == 'x' or text[i + 1] == 'X'):
# Hex: significant digits after removing '_' and leading zeros (>16 => overflow)
sig: sizet = 0
j2: sizet = i + 2
leading: int = 1
while j2 < tl:
c2: char = text[j2]
j2 += 1
if c2 == '_': continue
if not ((c2 >= '0' and c2 <= '9') or (c2 >= 'a' and c2 <= 'f') or (c2 >= 'A' and c2 <= 'F')):
return 0
if leading != 0 and c2 == '0': continue
leading = 0
sig += 1
if sig > 16: return 1
return 0
# Decimal: locate the first significant digit
s: sizet = i
while s < tl and (text[s] == '0' or text[s] == '_'): s += 1
digits2: int = 0
j3: sizet = s
while j3 < tl:
c3: char = text[j3]
j3 += 1
if c3 == '_': continue
if c3 < '0' or c3 > '9': return 0
digits2 += 1
if digits2 == 0: return 0
max_text: str = "18446744073709551615"
mlen: sizet = 20
if neg != 0:
max_text = "9223372036854775808"
mlen = 19
if digits2 > mlen: return 1
if digits2 < mlen: return 0
# Equal length: lexicographic digit compare (same as numeric order)
d: sizet = 0
k2: sizet = s
while k2 < tl and d < mlen:
c4: char = text[k2]
k2 += 1
if c4 == '_': continue
mc: char = max_text[d]
if c4 > mc: return 1
if c4 < mc: return 0
d += 1
return 0
def require_cdefine_storable(self, pool: memhub.MemBuddy*, node: ast.AST*, name: str, value: int64, annot: ast.AST*, rhs_bits: int = 0) -> None:
"""Fail fast if a CDefine value is not storable in its declared type.
rhs_bits: 右侧表达式自身的位宽(0=未定宽)。右侧带显式强转(如 WORD(...))且不宽于声明注解时,
截断是**显式**的(左侧只看到截断后的数据),此时不再按"存不下"报错——这正是
"注解存不下且没有强转截断的需要报错"里被豁免的那一半。
"""
text: str = self._cdefine_int_literal_text(name)
bits: int = self._define_annot_bits(pool, annot)
wmin: int = self.define_min_width(value)
if self._int_text_exceeds_64(text) != 0:
self.Gen.TypeGenErr.fatal_error(node, f"[VPC] define constant value not storable: the initial value {text} of constant '{name}' exceeds 64 bits " f"(the widest integer type in this language is 64 bits; the storable range under both signed and unsigned interpretations is " f"-9223372036854775808..18446744073709551615; no wider integer type exists => widening is required). " f"Silent truncation/downgrade is forbidden; compilation aborted. Available integer types: int8/uint8, " f"int16/uint16, int32/uint32, int64/uint64 " f"(or int8/uint8/.../uint64, char, sizet). " f"Fix: move the value into the 64-bit range, or introduce a larger integer type for this number (widen).", "TypeError")
return
if bits == 0: return
# Explicit truncation exemption: the RHS narrowed itself to rhs_bits (a cast such as WORD(...)),
# so the value the declaration stores is already the truncated one.
if rhs_bits != 0 and rhs_bits <= bits: return
# Build the 32-bit bounds from overflow-safe literals (see define_min_width)
m31: int64 = 2147483647
lo: int64 = -m31 - 1
hi: int64 = m31 * 2 + 1
if bits == 8:
lo = -128
hi = 255
elif bits == 16:
lo = -32768
hi = 65535
elif bits == 64: return
if value < lo or value > hi:
self.Gen.TypeGenErr.fatal_error(node, f"[VPC] define constant value not storable: the initial value {value} of constant '{name}' is not storable. " f"Minimal storable width W_min: {wmin} bits; declared type width {bits} bits, " f"storable range of this declaration: {lo}..{hi}. " f"Silent truncation/downgrade is forbidden; compilation aborted. " f"Fix: widen the explicit type to a wider type (such as int32 / int64 / uint64), " f"or correct the literal.", "TypeError")
def require_cdefine_use_width(self, pool: memhub.MemBuddy*, trans: HT.Translator*, target_annot: ast.AST*, value_node: ast.AST*) -> None:
"""Fail fast when a define constant is wider than its use-site type.
比较基准是 define 的**声明位宽**(注解 → 右侧强转 → 值的最小宽度,见 cdefine_decl_bits),
不是值本身的最小宽度:define 声明成 64 位,写进 uint8 位置就是隐式窄化,必须报错。
"""
if value_node is None or target_annot is None: return
if value_node.kind() != ast.ASTKind.Name: return
rn: ast.Name* = ast.Name*(value_node)
if rn.id is None: return
if trans is not None:
if self.Gen.VarGen.lookup_var(trans.SymTab, rn.id) is not None: return
cdef_val: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(rn.id)
if self.Gen.TypeGenCDef.is_cdefine_found() == 0: return
bits: int = self._annot_int_bits(pool, target_annot)
if bits == 0: return
# 声明位宽优先;未登记位宽(0)时才回退到推断位宽(值的最小可存宽度)
dw: int = self.Gen.TypeGenCDef.lookup_cdefine_bits(rn.id)
if dw == 0: dw = self.define_min_width(cdef_val)
if bits >= dw: return
self.Gen.TypeGenErr.fatal_error(value_node, f"[VPC] define constant use-site width insufficient: constant '{rn.id}' is {dw} bits wide " f"(declared width; actual value {cdef_val}), but the use site (variable declaration annotation) provides only {bits} bits. " f"Silent truncation/downgrade is forbidden; compilation aborted. " f"Fix: widen the variable declaration annotation to at least {dw} bits (such as int{dw}T / CUInt{dw}T), " f"or narrow the constant at its define with an explicit cast.", "TypeError")
# eval_cdefine_float_expr - 编译期**浮点**常量表达式求值(CDefine 值为浮点表达式时用)。
# 支持 Constant(float/int)、UnaryOp(+/-)、Call 强转包裹(float(x)/double(x)/t.CDouble(x))、
# BinOp(+ - * /)。成功写 out[0] 并返回 1;不是纯浮点常量表达式时返回 0(交回整型路径)。
# 与 extract_cdefine_int_value_bits 的分工:后者只服务整型 CDefine,遇到 float 字面量会 fail-fast。
def eval_cdefine_float_expr(self, node: ast.AST*, out: double*, pool: memhub.MemBuddy*) -> int:
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(node)
if cn.const_kind == ast.CONST_FLOAT:
out[0] = cn.float_val
return 1
if cn.const_kind == ast.CONST_INT:
out[0] = double(cn.int_val)
return 1
return 0
if k == ast.ASTKind.UnaryOp:
uu: ast.UnaryOp* = ast.UnaryOp*(node)
ov: double = 0.0
if self.eval_cdefine_float_expr(uu.operand, &ov, pool) == 0: return 0
if uu.op == ast.OpKind.USub:
out[0] = -ov
return 1
if uu.op == ast.OpKind.UAdd:
out[0] = ov
return 1
return 0
if k == ast.ASTKind.Call:
# 强转包裹(float(3.14) / double(x) / t.CDouble(x)):取首实参的值
cl: ast.Call* = ast.Call*(node)
if cl.args is not None and len(cl.args) > 0:
return self.eval_cdefine_float_expr(cl.args.get(0), out, pool)
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
lv: double = 0.0
rv: double = 0.0
if self.eval_cdefine_float_expr(bop.left, &lv, pool) == 0: return 0
if self.eval_cdefine_float_expr(bop.right, &rv, pool) == 0: return 0
if bop.op == ast.OpKind.Add:
out[0] = lv + rv
return 1
if bop.op == ast.OpKind.Sub:
out[0] = lv - rv
return 1
if bop.op == ast.OpKind.Mult:
out[0] = lv * rv
return 1
if bop.op == ast.OpKind.Div:
if rv == 0.0:
self.Gen.TypeGenErr.fatal_error(node, "constant expression division by zero: the right operand of '/' evaluates to 0 at compile time (float CDefine).", "ValueError")
out[0] = lv / rv
return 1
return 0
return 0
# expr_has_float_literal - 表达式里是否**含浮点字面量**(递归)。
# 用于把"浮点 CDefine"与"整型 CDefine"分开:只有含浮点字面量时才允许走浮点求值,
# 否则纯整型表达式会被登记成 double,破坏整型位宽与整型语义。
def expr_has_float_literal(self, node: ast.AST*) -> int:
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(node)
if cn.const_kind == ast.CONST_FLOAT: return 1
return 0
if k == ast.ASTKind.UnaryOp:
uu: ast.UnaryOp* = ast.UnaryOp*(node)
return self.expr_has_float_literal(uu.operand)
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if self.expr_has_float_literal(bop.left) != 0: return 1
return self.expr_has_float_literal(bop.right)
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
if cl.args is not None and len(cl.args) > 0:
return self.expr_has_float_literal(cl.args.get(0))
return 0
return 0
# _register_cdefine_from_value - register a CDefine constant (float or int)
def _register_cdefine_from_value(self, pool: memhub.MemBuddy*, name: str, val_node: ast.AST*, annot: ast.AST* = None) -> None:
"""Register a CDefine constant (double for floats, else integer)."""
if pool is None or name is None: return
# 浮点宽度种类(f32 与否)必须**在拆掉右侧包裹前**判定:
# ① 注解有显式类型(`define PI: float = ...` / `PI: float | t.CDefine = ...`)→ 注解说了算
# (`define X: double = float(1.5)` 按 double 走);
# ② 注解没有显式类型(`PI: t.CDefine = float(3.14)`)→ 看右侧强转包裹 float(...) / double(...)。
f32_kind: int = 0
if self._define_explicit_annot(annot) is not None:
f32_kind = self.cdefine_decl_float_bits(pool, annot)
elif val_node is not None and val_node.kind() == ast.ASTKind.Call:
cl_w: ast.Call* = ast.Call*(val_node)
if cl_w.func is not None:
fk_w: int = cl_w.func.kind()
wname_w: str = None
if fk_w == ast.ASTKind.Name:
fn_w: ast.Name* = ast.Name*(cl_w.func)
wname_w = fn_w.id
elif fk_w == ast.ASTKind.Attribute:
fa_w: ast.Attribute* = ast.Attribute*(cl_w.func)
wname_w = fa_w.attr
if wname_w is not None and string.strcmp(wname_w, "float") == 0: f32_kind = 1
# Call-wrapped constant (e.g. float(3.14)): use its first argument as the value
if val_node is not None and val_node.kind() == ast.ASTKind.Call:
cl_cd: ast.Call* = ast.Call*(val_node)
if cl_cd is not None and cl_cd.args is not None:
cl_args_cd: list[ast.AST*]* = cl_cd.args
if len(cl_args_cd) > 0:
first_arg_cd: ast.AST* = cl_args_cd.get(0)
if first_arg_cd is not None and first_arg_cd.kind() == ast.ASTKind.Constant: val_node = first_arg_cd
# Negative float literal: unwrap UnaryOp(USub, Constant(float)); value recovered from source
if val_node is not None and val_node.kind() == ast.ASTKind.UnaryOp:
uu_cd: ast.UnaryOp* = ast.UnaryOp*(val_node)
if uu_cd is not None and uu_cd.op == ast.OpKind.USub and uu_cd.operand is not None and uu_cd.operand.kind() == ast.ASTKind.Constant:
ic_cd: ast.Constant* = ast.Constant*(uu_cd.operand)
if ic_cd is not None and ic_cd.const_kind == ast.CONST_FLOAT: val_node = uu_cd.operand
if val_node is not None and val_node.kind() == ast.ASTKind.Constant:
cn_fp: ast.Constant* = ast.Constant*(val_node)
if cn_fp.const_kind == ast.CONST_STR:
# String CDefine: register as a string constant (reader returns a global string)
self.Gen.TypeGenCDef.register_cdefine_string(pool, name, cn_fp.str_val)
return
if cn_fp.const_kind == ast.CONST_FLOAT:
# Recover the float value from the source file (AST float_val may be overwritten)
use_val: double = cn_fp.float_val
rec_d: double = 0.0
if self._recover_cdefine_float(name, &rec_d) != 0: use_val = rec_d
else:
# Source recovery failed: fail fast to avoid registering a corrupted float
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine float value source-file recovery failed", "InternalCompilerError")
return
self.Gen.TypeGenCDef.register_cdefine_double(pool, name, use_val, f32_kind)
return
if val_node is None:
# CDefine declaration without a value node: fail fast
self.Gen.TypeGenErr.fatal_error(None, f"CDefine value extraction failed: '{name}' is missing an initial value", "TypeError")
return
# 浮点常量表达式(如 `define GW_F = 160.0 * 4`):整型提取器只认整型,遇到其中的 float
# 字面量会 fail-fast("cannot parse a compile-time constant")。这里先按浮点求值;
# 仅当表达式**含浮点字面量**时才走此路,纯整型表达式仍走下面的整型路径(保住位宽/整型语义)。
if self.expr_has_float_literal(val_node) != 0:
fv_try: double = 0.0
if self.eval_cdefine_float_expr(val_node, &fv_try, pool) != 0:
self.Gen.TypeGenCDef.register_cdefine_double(pool, name, fv_try, f32_kind)
return
# 含浮点字面量但不是编译期常量(含变量/函数调用)⇒ fail-fast,不静默降级
self.Gen.TypeGenErr.fatal_error(val_node, f"CDefine float expression is not a compile-time constant: '{name}'", "TypeError")
return
# 位宽裁决:与跨模块使用方共用 cdefine_decl_bits(注解 → 右侧显式强转 → 值的最小可存宽度)。
# 有注解而存不下时:右侧带显式强转截断则不报错(左侧只看到截断值),否则由
# require_cdefine_storable 报"存不下"。
rhs_bits: int = 0
val_fp: int64 = self.extract_cdefine_int_value_bits(val_node, pool, &rhs_bits)
vb: int = self.cdefine_decl_bits(pool, annot, val_node)
# Check storability before registering (source text >64-bit or explicit T too narrow => fail)
self.require_cdefine_storable(pool, val_node, name, val_fp, annot, rhs_bits)
self.Gen.TypeGenCDef.register_cdefine_constant(pool, name, val_fp, vb)
# Recover a CDefine float value by re-reading its source line with atof
def _recover_found(self) -> int:
"""Return whether the last _recover_cdefine_float succeeded."""
return self.RecoverFound
def _recover_float_literal(self, name: str, out: double*, require_cdefine: int) -> int:
"""Recover a float literal from the module source into out[0]; 1 on success."""
self.RecoverFound = 0
fp: str = self.Gen.TypeGenResolve.get_current_file()
if fp is None or name is None or out is None: return 0
f: fileio.File* = fileio.File(fp, fileio.MODE.R)
if f.closed: return 0
BUF_SZ: sizet = 1048576
buf: bytes = stdlib.malloc(BUF_SZ)
if buf is None:
f.close()
return 0
nrd: LONG = f.read_all(buf, BUF_SZ)
f.close()
if nrd <= 0:
stdlib.free(buf)
return 0
if nrd < BUF_SZ: buf[nrd] = 0
name_len: sizet = string.strlen(name)
total_len: sizet = string.strlen(buf)
pos: sizet = 0
while pos < total_len:
line_start: sizet = pos
while pos < total_len and buf[pos] != '\n': pos += 1
line_len: sizet = pos - line_start
if pos < total_len: pos += 1
# define NAME: T = V form: the name is preceded by the define keyword
ns: sizet = line_start
is_define_line: int = 0
if line_len >= 8:
if buf[ns] == 'd' and buf[ns + 1] == 'e' and buf[ns + 2] == 'f' and buf[ns + 3] == 'i' and buf[ns + 4] == 'n' and buf[ns + 5] == 'e':
nb: sizet = ns + 6
while nb < line_start + line_len and (buf[nb] == ' ' or buf[nb] == '\t'): nb += 1
# Keyword only when followed by whitespace (avoid matching defineX)
if nb > ns + 6:
is_define_line = 1
ns = nb
# Match the name at line start (or after define); it must be followed by ':' or '='
if ns + name_len + 1 > line_start + line_len: continue
match_ok: int = 1
ki: sizet
for ki in range(int(name_len)):
if buf[ns + ki] != name[ki]:
match_ok = 0
break
if match_ok == 0: continue
sep: sizet = ns + name_len
while sep < line_start + line_len and buf[sep] == ' ': sep += 1
if sep >= line_start + line_len: continue
if buf[sep] != ':' and buf[sep] != '=': continue
# CDefine path requires t.CDefine in the line, or the define keyword form
if require_cdefine != 0 and is_define_line == 0:
has_cd: int = 0
ci: sizet = line_start + name_len + 1
while ci + 8 <= line_start + line_len:
if (buf[ci] == 't' and buf[ci + 1] == '.' and buf[ci + 2] == 'C' and buf[ci + 3] == 'D' and buf[ci + 4] == 'e' and buf[ci + 5] == 'f' and buf[ci + 6] == 'i' and buf[ci + 7] == 'n' and buf[ci + 8] == 'e'):
has_cd = 1
break
ci += 1
if has_cd == 0: continue
# Find '='
eq: sizet = line_start
while eq < line_start + line_len and buf[eq] != '=': eq += 1
if eq >= line_start + line_len: continue
eq += 1
while eq < line_start + line_len and buf[eq] == ' ': eq += 1
# Call-wrapped float constant: skip the float( prefix and start at the first numeric char
if eq < line_start + line_len:
c0_rf: char = buf[eq]
if not ((c0_rf >= '0' and c0_rf <= '9') or c0_rf == '-' or c0_rf == '+' or c0_rf == '.'):
pfx_rf: sizet = eq
while pfx_rf < line_start + line_len:
cp_rf: char = buf[pfx_rf]
if (cp_rf >= '0' and cp_rf <= '9') or cp_rf == '.' or cp_rf == '-' or cp_rf == '+':
eq = pfx_rf
break
pfx_rf += 1
val_start: sizet = eq
while eq < line_start + line_len and buf[eq] != ' ' and buf[eq] != '\t' and buf[eq] != '#' and buf[eq] != '\r' and buf[eq] != '\n': eq += 1
if eq == val_start: continue
val_txt: str = stdlib.malloc(eq - val_start + 1)
if val_txt is None:
stdlib.free(buf)
return 0
vi: sizet
for vi in range(int(eq - val_start)): val_txt[vi] = buf[val_start + vi]
val_txt[eq - val_start] = 0
stdlib.free(buf)
# Use atof_out (an out param) to avoid the cross-module double return bug
if string.atof_out(val_txt, out) != 0:
self.RecoverFound = 1
return 1
return 0
stdlib.free(buf)
return 0
def _recover_cdefine_float(self, name: str, out: double*) -> int:
"""Recover a CDefine float value (requiring a t.CDefine line)."""
return self._recover_float_literal(name, out, 1)
# _annotation_contains_name - recursively check whether an annotation contains a name
def _annotation_contains_name(self, annot: ast.AST*, name: str) -> int:
"""Return 1 if the annotation recursively contains the given name."""
if annot is None or name is None: return 0
k: int = annot.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annot)
if at.attr is not None and string.strcmp(at.attr, name) == 0: return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(annot)
if nm.id is not None and string.strcmp(nm.id, name) == 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if self._annotation_contains_name(bop.left, name) != 0: return 1
if self._annotation_contains_name(bop.right, name) != 0: return 1
return 0
def _annotation_has_cptr_marker(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation union carries a CPtr marker (the star form `<base> | rawptr`)."""
if annot is None: return 0
k: int = annot.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annot)
if at.attr is not None and string.strcmp(at.attr, "CPtr") == 0: return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(annot)
if nm.id is not None and string.strcmp(nm.id, "CPtr") == 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if self._annotation_has_cptr_marker(bop.left) != 0: return 1
if self._annotation_has_cptr_marker(bop.right) != 0: return 1
return 0
# _is_ptr_element_annotation - check whether an annotation is bytes* or str*
# bytes*/str* => 8-byte element stride (i8**); char*/int8* => 1-byte stride.
def _is_ptr_element_annotation(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation is a bytes* or str* form."""
if annot is None: return 0
k: int = annot.kind()
if k != ast.ASTKind.BinOp: return 0
bop: ast.BinOp* = ast.BinOp*(annot)
if bop.op != ast.OpKind.BitOr: return 0
# str/bytes are already i8*; only the union WITH a pointer marker (str*/bytes*) is i8** and
# needs 8-byte element slots. A union that merely carries them (e.g. `const str`, which is
# t.CConst | str and stays i8*) must keep the 1-byte stride.
if self._annotation_has_cptr_marker(annot) != 0:
if self._annotation_contains_name(annot, "str") != 0: return 1
if self._annotation_contains_name(annot, "bytes") != 0: return 1
# Generic type param / nested union | t.CPtr: scalars use their own size, pointers use 8-byte slots
if self._is_generic_tp_or_nested(bop.left) != 0 or self._is_generic_tp_or_nested(bop.right) != 0:
gp_nm: str = None
if bop.left is not None and bop.left.kind() == ast.ASTKind.Name:
gp_left_nm: ast.Name* = ast.Name*(bop.left)
gp_nm = gp_left_nm.id
elif bop.right is not None and bop.right.kind() == ast.ASTKind.Name:
gp_right_nm: ast.Name* = ast.Name*(bop.right)
gp_nm = gp_right_nm.id
if gp_nm is not None:
# Resolve the generic param via the global gen (direct field access breaks static binding)
gp_resolved: str = self.Gen.TypeGenBase.resolve_generic_type_name(gp_nm)
if gp_resolved is not None and string.strcmp(gp_resolved, gp_nm) != 0:
# Generic param resolved to a scalar => use scalar size, not an 8-byte pointer slot
if self._is_scalar_ty_name(gp_resolved) != 0: return 0
return 1
# 64-bit scalar | CPtr: element is 8 bytes, so use an 8-byte stride (else GEP truncation)
if self._is_64bit_scalar_ty(bop.left) != 0 or self._is_64bit_scalar_ty(bop.right) != 0: return 1
return 0
def _stg_ty_equal(self, pool: memhub.MemBuddy*, ta: mlirlite.LLVMType*, tb: mlirlite.LLVMType*) -> int:
"""Return 1 if the two types are equal by printed content."""
if ta is None or tb is None: return 0
# Fast path: identical instance is equal
if uint64(ta) == uint64(tb): return 1
buf_a: str = pool.alloc_tagged(4096, memhub.DETSITE_ASSIGNGEN_578)
buf_b: str = pool.alloc_tagged(4096, memhub.DETSITE_ASSIGNGEN_579)
if buf_a is None or buf_b is None: return 0
mlirlite.TypePrint(buf_a, 4096, ta, pool)
mlirlite.TypePrint(buf_b, 4096, tb, pool)
if buf_a[0] == 0 or buf_b[0] == 0: return 0
return 1 if string.strcmp(buf_a, buf_b) == 0 else 0
# ============================================================
# 生命期(LifeTag)判定 / 传播 / 汇点检查(架构见 Debugger/ownership-and-new.md §11)
# 标签常量见 GenTypes define LIFE_*;有序,越小越"短命",汇合取 min。
# ============================================================
# ── A2(§14)`@life(ret(...))`:调用点解析被调者的返回值生命期契约 ──
# _RecvClassName - 从方法的接收者表达式取它静态类型指向的类名(去 sha1 前缀);判不定返回 None。
# 只认「名字变量」接收者:变量表里查不到(如 `m.f()` 里的模块名 `m`)返回 None —— 调用方据此
# 把"模块限定函数调用"与"方法调用"分开(二者在 AST 里都是 Attribute)。
def _RecvClassName(self, trans: HT.Translator*, recv: ast.AST*) -> str:
"""接收者静态类型 → 类名(无 sha1 前缀);判不定返回 None"""
if trans is None or recv is None: return None
if recv.kind() != ast.ASTKind.Name: return None
rn: ast.Name* = ast.Name*(recv)
if rn.id is None: return None
v: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, rn.id)
if v is None: return None
ty: mlirlite.LLVMType* = v.Ty # 变量槽类型 = <变量类型>*
if ty is None or self.Gen.ExprGenBase.is_ptr_type(ty) == 0: return None
p1: mlirlite.LLVMType* = ty.Pointee # 变量类型本身
if p1 is None: return None
# 类变量通常是指针(`S*`);值语义的类变量(`s: S`,罕见)直接取本类型。
elem: mlirlite.LLVMType* = p1.Pointee
if self.Gen.ExprGenBase.is_ptr_type(p1) == 0: elem = p1
sname: str = mlirlite.get_struct_name(elem)
if sname is None: return None
pl: int = GenApi.sha1_prefix_len(sname)
if pl > 0: sname = sname + pl
return sname
# _CallArgNodeForParam - `@life(ret(borrows=<形参 pi>))` 在调用点对应的实参节点。
# **方法**调用的形参表含 self(pi=0 即接收者),故 is_method=1 时偏移一格;
# **模块限定函数**(`m.f(...)`:callee 也是 Attribute,但不是方法)形参 0 就是第一个实参,
# 不能偏移 —— 否则 pi=0 会被解析成"模块名"这个非实参节点(现场:本函数把
# `retdep2.IdentBorrow(l)` 的形参 0 取成了 `retdep2` → life=STATIC → 跨模块 ret 契约静默失效)。
def _CallArgNodeForParam(self, cl: ast.Call*, pi: int, is_method: int) -> ast.AST*:
"""第 pi 号形参对应的实参表达式;越界/判不定返回 None"""
if cl is None or pi < 0: return None
cargs: list[ast.AST*]* = cl.args
if is_method != 0:
if pi == 0:
at: ast.Attribute* = ast.Attribute*(cl.func)
return at.value
if cargs is None or sizet(pi - 1) >= len(cargs): return None
return cargs.get(sizet(pi - 1))
if cargs is None or sizet(pi) >= len(cargs): return None
return cargs.get(sizet(pi))
# _CallIsMethod - 该调用是否"接收者是对象变量"的方法调用(决定形参表是否含 self)。
def _CallIsMethod(self, trans: HT.Translator*, cl: ast.Call*) -> int:
"""1 = 方法调用(形参表含 self);0 = 模块限定函数 / 裸函数"""
if cl is None or cl.func is None: return 0
if cl.func.kind() != ast.ASTKind.Attribute: return 0
at: ast.Attribute* = ast.Attribute*(cl.func)
if self._RecvClassName(trans, at.value) is None: return 0
return 1
# _CallRetKind - 解析被调者的 `@life(ret(...))` 契约(本文件表 → 跨模块 .vpi);-1 = 无声明/判不定。
def _CallRetKind(self, trans: HT.Translator*, cl: ast.Call*) -> int:
"""被调者 ret_kind 编码(GenApi.RET_KIND_*);-1 = 无声明/判不定"""
if cl is None or cl.func is None: return -1
fnm: str = self.Gen.ExprGenBase.get_func_name(cl.func)
if fnm is None: return -1
pool: memhub.MemBuddy* = None
if trans is not None: pool = trans.Pool
if pool is None: return -1
# 方法调用:接收者是本帧变量 → 取它静态类型指向的类名;接收者是模块名 → 不当方法。
if cl.func.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(cl.func)
cls: str = self._RecvClassName(trans, at.value)
if cls is not None:
rk_m: int = GenApi.get_ret_kind_method(pool, cls, fnm)
if rk_m != 0: return rk_m
sha_m: str = self.Gen.StructGen.get_struct_sha1(cls)
if sha_m is not None:
rk_m = self.Gen.CallGenBase.lookup_ret_kind_via_vpi(sha_m, cls, fnm)
if rk_m != 0: return rk_m
return -1
# 模块级函数(裸名,或模块限定 `m.f()`):本文件表优先,再跨模块 .vpi 回退。
rk: int = GenApi.get_ret_kind(fnm)
if rk != 0: return rk
mod_sha: str = None
if cl.func.kind() == ast.ASTKind.Attribute:
at2: ast.Attribute* = ast.Attribute*(cl.func)
# 只认裸模块名接收者(`m.f()`);`self.h.f()` 之类链式接收者判不定 → 不猜。
if at2.value is None or at2.value.kind() != ast.ASTKind.Name: return -1
mn2: ast.Name* = ast.Name*(at2.value)
mod_p: str = None
if mn2.id is not None: mod_p = mn2.id
if mod_p is not None:
fi2: str = None
if trans is not None: fi2 = trans._from_imports
mod_sha = self.Gen.StructGen._resolve_module_sha1(mod_p, fi2)
# 接收者既不是模块(解析不出 sha1)也不是变量(否则上面早按方法走了)→ 判不定。
if mod_sha is None: return -1
if mod_sha is None and trans is not None and trans._from_imports is not None:
mod_sha = self.Gen.CallGenBase._lookup_mod_sha1(pool, trans._from_imports, fnm, 0)
if mod_sha is not None:
rk = self.Gen.CallGenBase.lookup_ret_kind_via_vpi(mod_sha, None, fnm)
if rk != 0: return rk
return -1
# _is_module_scope_var - 该名字是否定义在**模块根 scope**(= LLVM 全局存储,静态生命期)。
# 逃逸检查据此把模块级变量(含其数组名退让)判为 LIFE_STATIC,而不是「本帧局部」。
# 注意与 is_global_name 区分:后者只认**函数内** `global X` 声明,认不出模块级定义。
def _is_module_scope_var(self, trans: HT.Translator*, name: str) -> int:
"""1 = name is defined in the module root scope (static storage); else 0."""
if trans is None or name is None or trans.SymTab is None: return 0
sc_m: GenTypes.Scope* = trans.SymTab.Current
while sc_m is not None:
if self.Gen.VarGen.find_var(sc_m.Vars, sc_m.VarCount, name) is not None:
if sc_m.Parent is None: return 1
return 0
sc_m = sc_m.Parent
return 0
# life_of_expr - 求表达式当前值的生命期标签。
# target_ty 参与判定:裸本帧数组名在**指针**目标位置 = 数组退让(交出本帧栈地址),
# 在同型聚合目标位置 = 按值拷贝(LIFE_VALUE)。
# [TEMP-DIAG] life_of_expr 的 Name 分支判定输入快照(定位完删除)。
# hit 语义:1=已登记 HEAP/STATIC 直接返回 / 2=裸数组名+指针目标(FRAME) / 3=裸数组名+值目标(VALUE)
# 4=值类型变量+值目标(VALUE,本次新增分支) / 5=指针或异型落到登记值 / 6=变量非指针落到登记值
def _life_diag_name(self, trans: HT.Translator*, nm: str, rg: int, v: mlirlite.Value*, target_ty: mlirlite.LLVMType*, hit: int) -> int:
vptr: int = -1
iptr: int = -1
ikind: int = -1
tptr: int = -1
if v is not None and v.Ty is not None:
vptr = self.Gen.ExprGenBase.is_ptr_type(v.Ty)
if vptr != 0 and v.Ty.Pointee is not None:
iptr = self.Gen.ExprGenBase.is_ptr_type(v.Ty.Pointee)
ikind = int(v.Ty.Pointee.kind())
if target_ty is not None: tptr = self.Gen.ExprGenBase.is_ptr_type(target_ty)
fn_s: str = None
if trans._cur_func is not None: fn_s = mlirlite.function_get_name(trans._cur_func)
if fn_s is None: fn_s = "?"
nm_s: str = nm
if nm_s is None: nm_s = "?"
stdio.printf("[LIFE-DIAG] fn=%s name=%s rg=%d hit=%d vptr=%d iptr=%d ikind=%d tptr=%d\n",
fn_s, nm_s, rg, hit, vptr, iptr, ikind, tptr)
return 0
def life_of_expr(self, trans: HT.Translator*, node: ast.AST*, target_ty: mlirlite.LLVMType*) -> int:
"""表达式的 LifeTag;无法判定时 LIFE_UNKNOWN(保守放行)"""
if node is None or trans is None: return GenTypes.LIFE_VALUE
k: int = node.kind()
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(node)
if cn is None: return GenTypes.LIFE_VALUE
if cn.const_kind == ast.CONST_STR: return GenTypes.LIFE_STATIC
if cn.const_kind == ast.CONST_NONE: return GenTypes.LIFE_UNKNOWN
return GenTypes.LIFE_VALUE
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is None: return GenTypes.LIFE_UNKNOWN
if GenApi.is_global_name(trans, nm.id) != 0: return GenTypes.LIFE_STATIC
# 模块级(根 scope)变量 = LLVM 全局存储(静态生命期):裸数组名的「退让」拿到的正是
# 静态地址,返回/交出它都不会悬垂。此前只认函数内 `global X` 声明,于是模块级数组
# (如 pyzlib_error_msg)被当成「本帧局部数组」误判成 LIFE_FRAME。
if self._is_module_scope_var(trans, nm.id) != 0: return GenTypes.LIFE_STATIC
if GenApi.is_nonlocal_name(trans, nm.id) != 0: return GenTypes.LIFE_UNKNOWN
v: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if v is None: return GenTypes.LIFE_STATIC
# 变量已登记为堆/静态生命期(如 `o: T* = new heap T()` 的指针变量):以登记为准,
# 下面按"值类型"放宽只针对未登记的帧内局部变量,避免把堆/静态对象误放宽成值拷贝。
rg: int = self.Gen.VarGen.get_var_life(trans.SymTab, nm.id)
res: int = rg
hit: int = 6
if rg == GenTypes.LIFE_HEAP or rg == GenTypes.LIFE_STATIC:
hit = 1
elif v.Ty is not None and self.Gen.ExprGenBase.is_ptr_type(v.Ty) != 0:
inner: mlirlite.LLVMType* = v.Ty.Pointee
if inner is not None and inner.kind() == mlirlite.LLVMType.Array:
# 本帧局部数组名:退让到指针目标交出栈地址;同型聚合目标是值拷贝
if target_ty is not None and self.Gen.ExprGenBase.is_ptr_type(target_ty) != 0:
res = GenTypes.LIFE_FRAME
hit = 2
else:
res = GenTypes.LIFE_VALUE
hit = 3
# 值类型(聚合/标量)局部变量:符号表登记的是入口块 alloca 指针(VarGenMixin.lookup_var),
# 但读取这种变量就是**整块按值拷贝**,交出的是拷贝而非栈地址;当目标位置也是值类型
# (函数按值返回、赋值给值类型槽)时判 LIFE_VALUE。与上方裸数组名、下方 Call 分支的
# 裸构造 `T(...)` 同口径。
# 此前只覆盖「裸数组名」,于是 `col: V3 = V3(...)` 后 `return col` 落到 get_var_life
# 读回本帧 LIFE_FRAME,被 check_return_frame_decl 误报 LifetimeError。
# 只排除"变量值本身或目标位置是地址"的情形:`p: V3*` 的 `return p` 仍判帧生命期。
# 目标位置是**值类型**(函数按值返回、赋值给值类型槽)时,读出的值会整块拷贝到目标
# ⇒ LIFE_VALUE。这里**不要求变量本身也非指针**:class 的局部变量内部是"指针槽"
# (诊断实证:stars 的 col 为 iptr=1,而 clmp 的形参 a 为 iptr=0,两者表示不同),
# 但返回类型是聚合值(rvkind=4)时会做值拷贝,交出的仍是拷贝而非栈地址。
# 反例仍拦:`p: V3*` 的 `return p`(target 是指针,tptr=1)不进入本分支,仍判帧生命期。
elif target_ty is not None and self.Gen.ExprGenBase.is_ptr_type(target_ty) == 0:
res = GenTypes.LIFE_VALUE
hit = 4
else:
hit = 5
# [TEMP-DIAG] 只打"结果不是值拷贝"或"新增分支命中"的情况,避免刷屏(定位完删除)
if res != GenTypes.LIFE_VALUE or hit == 4:
self._life_diag_name(trans, nm.id, rg, v, target_ty, hit)
return res
if k == ast.ASTKind.UnaryOp:
uo: ast.UnaryOp* = ast.UnaryOp*(node)
if uo.op == ast.OpKind.AddrOf and uo.operand is not None and uo.operand.kind() == ast.ASTKind.Name:
onm: ast.Name* = ast.Name*(uo.operand)
if onm.id is not None:
if GenApi.is_global_name(trans, onm.id) != 0: return GenTypes.LIFE_STATIC
# 模块级(根 scope)变量:静态存储,同 Name 分支判据(`&<模块级数组>` 不悬垂)
if self._is_module_scope_var(trans, onm.id) != 0: return GenTypes.LIFE_STATIC
if GenApi.is_nonlocal_name(trans, onm.id) != 0: return GenTypes.LIFE_UNKNOWN
if self.Gen.VarGen.lookup_var(trans.SymTab, onm.id) is not None: return GenTypes.LIFE_FRAME
return GenTypes.LIFE_STATIC
return GenTypes.LIFE_UNKNOWN
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
# §14 A2:`@life(ret(...))` 声明**优先于任何推断**(无条件)。
rk: int = self._CallRetKind(trans, cl)
if rk >= 0:
if rk == GenApi.RET_KIND_FRAME: return GenTypes.LIFE_FRAME
if rk == GenApi.RET_KIND_HEAP: return GenTypes.LIFE_HEAP
if rk == GenApi.RET_KIND_STATIC: return GenTypes.LIFE_STATIC
if rk == GenApi.RET_KIND_NONFRAME: return GenTypes.LIFE_UNKNOWN
if rk >= GenApi.RET_KIND_BORROW_BASE:
pi: int = rk - GenApi.RET_KIND_BORROW_BASE
# 形参位 → 实参位:**方法**调用的形参表含 self(pi=0 = 接收者);
# 模块限定函数(`m.f(...)`)不含,pi 直接就是实参下标。
bn: ast.AST* = self._CallArgNodeForParam(cl, pi, self._CallIsMethod(trans, cl))
if bn is None: return GenTypes.LIFE_UNKNOWN
return self.life_of_expr(trans, bn, None)
# 分配类调用 → 堆;其余调用 → 未知(函数摘要落地前保守放行)
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(cl.func)
if at.attr is not None:
if (string.strcmp(at.attr, "alloc") == 0 or string.strcmp(at.attr, "alloc_buf") == 0 or string.strcmp(at.attr, "calloc") == 0 or string.strcmp(at.attr, "realloc") == 0): return GenTypes.LIFE_HEAP
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
fnm: ast.Name* = ast.Name*(cl.func)
if fnm.id is not None and string.strcmp(fnm.id, "malloc") == 0: return GenTypes.LIFE_HEAP
# 裸 `T(...)` 构造:**没走 `new`、且该类没有 `__new__`** 时,存储就是入口块的 alloca
# (CallGenCtor._translate_struct_ctor:`tmp = _alloca_at_entry(...)`,只有 __new__ 才会
# 改写存储指针)→ 帧生命期。这是"写进本帧对象字段不算逃逸"(调用点同命判定)成立的前提。
# 有 `__new__` 的类仍返回未知(存储由 __new__ 决定,可能是池/堆)。
ctor_nm: str = self.Gen.ExprGenBase.get_func_name(cl.func)
if ctor_nm is not None:
ct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(ctor_nm)
if ct_ty is not None:
ct_en: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(ct_ty)
if ct_en is None or ct_en.HasNew == 0:
# 裸构造 `T(...)` 的存储是本帧入口 alloca,但只要**目标位置是值类型**
# (函数按值返回 / 值类型槽 / 全局值存储),写入就是整块聚合拷贝,与源帧无关
# ⇒ LIFE_VALUE(与上方 Name 分支同口径)。
# 此前还额外要求 `_stg_ty_equal(ct_ty, target_ty)`,而它按类型打印文本比较
# (见本类 _stg_ty_equal),`%V3` 带模块 sha1 前缀时经常不相等 ⇒ 于是
# `CAM = V3(...)`(模块级全局赋值)被误判成"把本帧地址交给模块级全局"。
# 目标位置是地址(`&T(...)`、指针目标、指针槽)才交出本帧地址 ⇒ LIFE_FRAME。
if target_ty is not None and self.Gen.ExprGenBase.is_ptr_type(target_ty) == 0:
return GenTypes.LIFE_VALUE
# [TEMP-DIAG] 打印构造类型与目标类型的**真实 LLVM 文本**(定位完删除):
# ct_ty 是 `T(...)` 自身的类型(`%"..T"` = 值本体 / `ptr` = 已被解析成指针)
# tgt 是目标位置(模块级全局/值槽)的类型文本
ctb: str = "?"
bt1: str = trans.Pool.alloc_tagged(512, memhub.DETSITE_ASSIGNGEN_578)
if bt1 is not None:
mlirlite.TypePrint(bt1, 512, ct_ty, trans.Pool)
ctb = bt1
ttb: str = "(none)"
if target_ty is not None:
bt2: str = trans.Pool.alloc_tagged(512, memhub.DETSITE_ASSIGNGEN_579)
if bt2 is not None:
mlirlite.TypePrint(bt2, 512, target_ty, trans.Pool)
ttb = bt2
hv: int = -1
if ct_en is not None: hv = ct_en.HasNew
fn_c: str = None
if trans._cur_func is not None: fn_c = mlirlite.function_get_name(trans._cur_func)
if fn_c is None: fn_c = "?"
stdio.printf("[CTOR-DIAG] fn=%s ctor=%s hasnew=%d ct_ty=%s tgt=%s\n",
fn_c, ctor_nm, hv, ctb, ttb)
return GenTypes.LIFE_FRAME
return GenTypes.LIFE_UNKNOWN
if k == ast.ASTKind.New:
# M2.4 `new`:生命期由描述符决定(§2.3 组合矩阵)
# stack → slife:帧生命期(等价既有 `&T(...)`)
# heap/memgr slife → 同样按帧生命期:作用域结束即释放(RAII),逃逸出去必然悬垂
# hlife(含 heap/memgr 的缺省)→ 堆/池生命期,可自由传递
nn: ast.New* = ast.New*(node)
if nn is None: return GenTypes.LIFE_UNKNOWN
if nn.Life == ast.NEW_LIFE_SLIFE: return GenTypes.LIFE_FRAME
if nn.Life == ast.NEW_LIFE_DEFAULT and nn.Source == ast.NEW_SRC_STACK: return GenTypes.LIFE_FRAME
return GenTypes.LIFE_HEAP
return GenTypes.LIFE_UNKNOWN
# life_of_lvalue - 求赋值目标(左值)所在存储的生命期。
# P3 用它做"左值比右值长命"的判定:Name 目标看变量/全局;Attribute/Subscript 递归其基对象
# (`obj.f` / `arr[i]` 的存储生命期 = obj / arr 的生命期)。
def life_of_lvalue(self, trans: HT.Translator*, target_node: ast.AST*) -> int:
"""赋值目标的 LifeTag;无法判定时 LIFE_UNKNOWN(保守放行)"""
if trans is None or target_node is None: return GenTypes.LIFE_UNKNOWN
k: int = target_node.kind()
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(target_node)
if nm.id is None: return GenTypes.LIFE_UNKNOWN
if GenApi.is_global_name(trans, nm.id) != 0: return GenTypes.LIFE_STATIC
if GenApi.is_nonlocal_name(trans, nm.id) != 0: return GenTypes.LIFE_UNKNOWN
v: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
# 局部变量槽本身的生命期就是本帧(LIFE_FRAME)。注意**不能**用 get_var_life:
# 那是"变量当前值所指向的存储"的生命期(供传播/借用判定用),语义不同——
# 混用会把 `p = &local`(p 是本帧局部指针)误判成长命左值。
if v is not None: return GenTypes.LIFE_FRAME
return GenTypes.LIFE_STATIC
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(target_node)
if at.value is None: return GenTypes.LIFE_UNKNOWN
return self.life_of_expr(trans, at.value, None)
if k == ast.ASTKind.Subscript:
sb: ast.Subscript* = ast.Subscript*(target_node)
if sb.value is None: return GenTypes.LIFE_UNKNOWN
return self.life_of_expr(trans, sb.value, None)
return GenTypes.LIFE_UNKNOWN
# _fail_if_lvalue_outlives_rhs - P3 的一般规则:左值存储比右值长命时,右值不得交出更短的存储。
# 只在"明确长命"(HEAP/STATIC)的左值上触发;RHS 判定复用 fail_if_life_escapes(FRAME → 报)。
# 例:`obj.f = &local`(obj 指向堆对象,堆活过本帧)→ LifetimeError;
# `local_struct.f = &local2`(左值也在本帧,FRAME)→ 放行(两者同命)。
def _fail_if_lvalue_outlives_rhs(self, trans: HT.Translator*, target_node: ast.AST*, rhs_node: ast.AST*, target_ty: mlirlite.LLVMType*) -> int:
if trans is None or target_node is None or rhs_node is None: return 0
lv: int = self.life_of_lvalue(trans, target_node)
if lv != GenTypes.LIFE_HEAP and lv != GenTypes.LIFE_STATIC: return 0
GenApi.fail_if_stack_addr_escape(rhs_node, "a longer-lived field/element", trans, target_ty)
return 0
# propagate_life - 把 RHS 的生命期写到赋值目标(仅 Name 目标;全局目标走汇点检查)。
def propagate_life(self, trans: HT.Translator*, target_node: ast.AST*, rhs_node: ast.AST*, target_ty: mlirlite.LLVMType*) -> int:
"""Propagate the RHS life tag onto a Name target; 0=done / 1=skipped"""
if trans is None or target_node is None: return 1
if target_node.kind() != ast.ASTKind.Name:
# P3:非 Name 目标(属性/下标)——左值存储比右值长命时,右值不得是本帧栈地址
self._fail_if_lvalue_outlives_rhs(trans, target_node, rhs_node, target_ty)
return 1
tnm: ast.Name* = ast.Name*(target_node)
if tnm.id is None: return 1
if GenApi.is_global_name(trans, tnm.id) != 0: return 1
if GenApi.is_nonlocal_name(trans, tnm.id) != 0: return 1
life: int = self.life_of_expr(trans, rhs_node, target_ty)
# §13 NLL:默认生命期的 `new heap T()` 若证明在本函数内封闭(不外泄)→ 按帧生命期处理。
# 与 `new heap slife` 同口径(生命期 FRAME + 作用域出口释放,见 _slife_note_assign)。
if life == GenTypes.LIFE_HEAP:
if self.Gen.CallGenCtor._new_is_nll_demotable(trans, rhs_node, tnm.id) != 0:
life = GenTypes.LIFE_FRAME
self.Gen.VarGen.set_var_life(trans.SymTab, tnm.id, life)
return 0
# _CurFuncIsEntryMain - 当前正在翻译的函数是否为**进程入口**(1=是 / 0=否)。
# 进程入口有两类:`main`(普通可执行)与 `_start`(ViperOS 应用/库的导出入口,
# 见 `@__attribute__(section(".text.startup"), ...)`)。两者的帧生命期都等于进程生命期:
# 本帧返回即进程结束,栈对象其后不可能再被使用 —— 故同样不做栈地址逃逸检查。
# 嵌套函数不算:它们的帧不是进程生命期,不能享受豁免。
def _CurFuncIsEntryMain(self, trans: HT.Translator*) -> int:
"""当前函数是否为进程入口(main / _start;与 FuncGenDef 对入口不改名同口径)"""
if trans is None or trans._cur_func is None: return 0
nm: str = mlirlite.function_get_name(trans._cur_func)
if nm is None: return 0
if string.strcmp(nm, "main") == 0: return 1
if string.strcmp(nm, "_start") == 0: return 1
return 0
# fail_if_life_escapes - 汇点检查:被写出表达式的生命期为 LIFE_FRAME → LifetimeError。
# 这是 GenApi.fail_if_stack_addr_escape 的唯一实现(语法判据已并入 life_of_expr)。
# 例外(入口 main 全豁免):main 的帧生命期 = 进程生命期,其栈对象在本帧返回(= 进程退出)
# 后不可能再被使用,故 main 内不做栈地址逃逸检查(如 `argparse._mbuddy = mb`:mb 是帧内
# 结构体)。**嵌套函数不豁免**(它们的帧不是进程生命期)。
def fail_if_life_escapes(self, node: ast.AST*, where: str, trans: HT.Translator*, target_ty: mlirlite.LLVMType*, leak_func: str = None, leak_param: int = -1) -> int:
"""Fail fast (LifetimeError) when the written-out value is frame-lifetime storage"""
if node is None or trans is None: return 0
if self._CurFuncIsEntryMain(trans) != 0: return 0
if self.life_of_expr(trans, node, target_ty) != GenTypes.LIFE_FRAME: return 0
# 报文里给出名字(&x / x),便于定位;前缀单独存放,避免 str 相加(str 是 i8*,无 '+' 语义)
nm_txt: str = "expression"
nm_pre: str = ""
if node.kind() == ast.ASTKind.Name:
n2: ast.Name* = ast.Name*(node)
if n2.id is not None: nm_txt = n2.id
elif node.kind() == ast.ASTKind.UnaryOp:
u2: ast.UnaryOp* = ast.UnaryOp*(node)
if u2.operand is not None and u2.operand.kind() == ast.ASTKind.Name:
n3: ast.Name* = ast.Name*(u2.operand)
if n3.id is not None:
nm_txt = n3.id
nm_pre = "&"
# 泄漏点(可选,只有"确实报错"才去算):被调者那边**造成逃逸的写入点**在同一源文件里,
# 追加一帧 "File lifetime leak ..."。"先过 life 判定再采集"是刻意的:escape_leak_line
# 会重扫被调者函数体并动 EscConstEnv/EscIndCount,不能对"其实不报错"的实参白白跑一遍。
leak_f: str = None
leak_l: int = 0
if leak_func is not None and leak_param >= 0:
leak_l = self.Gen.StructGen.escape_leak_line(leak_func, leak_param)
if leak_l > 0: leak_f = self.Gen.TypeGenResolve.get_current_file()
self.Gen.TypeGenErr.fatal_error(node, f"stack address escapes: `{nm_pre}{nm_txt}` is frame-local; using it at {where} hands out a dangling pointer once this frame returns. Copy the value, or allocate on the heap (`new heap hlife ...`).", "LifetimeError", 0, 0, leak_f, leak_l)
return 0
# check_return_frame_decl - **定义端** `return <本帧存储>` 的**声明感知**检查(A2 / §14.3 第 6 条)。
# A2 移除了"一律拦"的旧判据:定义端分不清"交出本帧"与"借出调用方的存储"(后者是
# `return <借用形参>`,一律拦会误报)。但也不能一律放 —— 无声明的函数返回本帧存储会让调用点
# 永远只拿到"未知"(悬垂指针一路静默)。判据收敛到被调者自己的 `@life(ret(...))`:
# · `ret(frame)` → 放行(去向由调用点负责);
# · `ret(borrows=<该形参>)` → 放行(借出的正是那个形参);
# · 无声明 / `ret(heap|static|borrows=none)` → `LifetimeError`(提示补声明 / 改声明)。
# 拿不到当前函数定义(非三条函数体翻译路径)时**不判**:宁可少报,也不误报。
def check_return_frame_decl(self, trans: HT.Translator*, value: ast.AST*) -> int:
"""返回值是本帧存储且当前函数的 ret 声明不允许 → LifetimeError;0=放行"""
if trans is None or value is None: return 0
fd: ast.AST* = self.Gen.StructGen.NllCurFunc
if fd is None: return 0
rv_ty: mlirlite.LLVMType* = None
if trans._cur_func is not None: rv_ty = mlirlite.function_get_ret_ty(trans._cur_func)
# target_ty 必须传返回类型:裸本帧数组名在**指针**返回类型下是数组退让(交出栈地址),
# 在同型聚合返回类型下是按值拷贝(LIFE_VALUE,不在此列)。
life_r: int = self.life_of_expr(trans, value, rv_ty)
if life_r != GenTypes.LIFE_FRAME: return 0
rk: int = GenApi.get_ret_kind_by_func(fd)
if rk == GenApi.RET_KIND_FRAME: return 0
# [TEMP-DIAG] 定义端拦截点输入(只在"判为帧生命期且未被 @life(ret(frame)) 放行"时打,定位完删除)
fn_d: str = None
if trans._cur_func is not None: fn_d = mlirlite.function_get_name(trans._cur_func)
if fn_d is None: fn_d = "?"
rvp: int = -1
rvk: int = -1
if rv_ty is not None:
rvp = self.Gen.ExprGenBase.is_ptr_type(rv_ty)
rvk = int(rv_ty.kind())
stdio.printf("[RT-DIAG] fn=%s life=%d rk=%d rvptr=%d rvkind=%d\n", fn_d, life_r, rk, rvp, rvk)
is_borrow_kind: int = 0
pi: int = -1
if rk >= GenApi.RET_KIND_BORROW_BASE:
is_borrow_kind = 1
pi = rk - GenApi.RET_KIND_BORROW_BASE
# 只有"返回的正是被声明的那个形参"才算借出;返回别的本帧存储仍是悬垂
if self.Gen.StructGen._ParamIndex(ast.FunctionDef*(fd), value) == pi: return 0
# 报文里给出名字(&x / x),便于定位
nm_txt: str = "expression"
nm_pre: str = ""
if value.kind() == ast.ASTKind.Name:
n2: ast.Name* = ast.Name*(value)
if n2.id is not None: nm_txt = n2.id
elif value.kind() == ast.ASTKind.UnaryOp:
u2: ast.UnaryOp* = ast.UnaryOp*(value)
if u2.operand is not None and u2.operand.kind() == ast.ASTKind.Name:
n3: ast.Name* = ast.Name*(u2.operand)
if n3.id is not None:
nm_txt = n3.id
nm_pre = "&"
if rk == 0:
self.Gen.TypeGenErr.fatal_error(value, f"stack address escapes: `{nm_pre}{nm_txt}` is frame-local and this function has no `@life(ret(...))` declaration, so callers cannot know the result's lifetime. Declare `@life(ret(frame))` (the result may point into this frame; callers must keep it in-frame) or `@life(ret(borrows=<param>))` (the result borrows a parameter), or return a value copy / a heap allocation (`new heap hlife ...`).", "LifetimeError")
return 0
if is_borrow_kind != 0:
self.Gen.TypeGenErr.fatal_error(value, f"stack address escapes: `{nm_pre}{nm_txt}` is frame-local but this function declares `@life(ret(borrows=<param>))` (parameter #{pi}), which only permits returning that parameter. Return that parameter, or declare `@life(ret(frame))`.", "LifetimeError")
return 0
self.Gen.TypeGenErr.fatal_error(value, f"stack address escapes: `{nm_pre}{nm_txt}` is frame-local but this function is declared as non-frame (`@life(ret(heap))` / `ret(static)` / `ret(borrows=none)`); the declaration contradicts the returned value. Fix the declaration, or return a non-frame value.", "LifetimeError")
return 0
# _SlifeDropOwned - 该槽已被登记为 slife 绑定时交还所有权:释放它当前拥有的对象、影子清 0。
# 用于所有"槽里不再是编译器拥有的对象"的赋值(覆盖赋值 / 别名拷贝 / 外来指针 / None)。
# 未登记 → no-op(该槽从未被 `new heap slife` 绑定过,编译器不负责它)。
def _SlifeDropOwned(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, target_slot: mlirlite.Value*) -> int:
"""释放该槽当前拥有的对象并置"不拥有"(先按 §3.6 调度 `__del__`);1=处理过 / 0=未登记"""
if target_slot is None: return 0
sh: mlirlite.Value* = self.Gen.VarGen._SlifeShadow(target_slot)
if sh is None: return 0
self.Gen.VarGen._SlifeEmitGuardedFree(pool, builder, mod, trans, target_slot, sh)
self.Gen.VarGen._SlifeNoteStore(pool, builder, sh, 0)
return 1
# _slife_note_assign - M3 赋值处的所有权记账(§4.2 RAII / §4.3 编译器独占释放)。
# 只在**本地 Name 目标**的赋值落地处调用;目标槽 target_slot 即该变量的 alloca。
# ① RHS = `new heap slife T(...)`:登记该槽(首次建影子标志)→ 覆盖释放旧拥有对象 → 影子置 1;
# ② 其它 RHS(`new stack` / `new … hlife` / 裸名别名 `q = p` / 外来指针 / None):
# 槽已登记时交还所有权(释放旧拥有对象 + 影子置 0)——绝不把外来指针当自己的对象释放。
# **必须在值写入之前调用**:覆盖释放要读到槽里的旧值,晚于写入就变成"释放刚写进去的对象"。
def _slife_note_assign(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, target_slot: mlirlite.Value*, rhs_node: ast.AST*, target_name: str = None) -> int:
"""M3:赋值处的 slife 所有权记账;返回 1=该槽此刻是编译器拥有的 slife 绑定"""
if target_slot is None or rhs_node is None or trans is None: return 0
if rhs_node.kind() == ast.ASTKind.New:
nn: ast.New* = ast.New*(rhs_node)
owned: int = self.Gen.CallGenCtor._new_is_owned_heap(nn)
if owned == 0:
# §13 NLL:判据成立时该 `new heap` 也按"编译器托管的堆对象"处理 ——
# 生命期已是 FRAME(propagate_life 同一谓词),这里补上**出口释放**这一半。
owned = self.Gen.CallGenCtor._new_is_nll_demotable(trans, rhs_node, target_name)
if owned != 0:
sh: mlirlite.Value* = self.Gen.VarGen._SlifeEnsureOwned(pool, builder, target_slot)
if sh is None: return 0
self._SlifeDropOwned(pool, builder, mod, trans, target_slot)
self.Gen.VarGen._SlifeNoteStore(pool, builder, sh, 1)
return 1
# 自赋值(p = p):保持原状;否则会"先释放再放弃所有权",把对象漏掉
if rhs_node.kind() == ast.ASTKind.Name:
rn: ast.Name* = ast.Name*(rhs_node)
if rn.id is not None:
src_slot: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, rn.id)
if src_slot is not None and uint64(src_slot) == uint64(target_slot): return 0
# P4 所有权转移:`q = p` 后 **q 接管生命周期** —— 影子标志从 p 移到 q
# (q 置 1、p 置 0)。此后对 p 重新赋值不会释放对象(p 已不拥有该对象),
# 出口只释放 q 一次。不转移的话,`p = None` 会把 q 指向的对象提前释放 → q 悬垂。
if src_slot is not None:
if self.Gen.VarGen._SlifeOwned(src_slot) != 0:
tsh: mlirlite.Value* = self.Gen.VarGen._SlifeEnsureOwned(pool, builder, target_slot)
if tsh is not None:
# 覆盖释放 target 原有对象:若它与源是**同一个对象**(自别名 `q = p; q = p`)
# 则跳过,否则会把两个槽共持的对象提前释放。
self.Gen.VarGen._SlifeDropOwnedUnlessSame(pool, builder, mod, trans, target_slot, src_slot)
self.Gen.VarGen._SlifeNoteStore(pool, builder, tsh, 1)
ssh: mlirlite.Value* = self.Gen.VarGen._SlifeShadow(src_slot)
if ssh is not None: self.Gen.VarGen._SlifeNoteStore(pool, builder, ssh, 0)
return 1
self._SlifeDropOwned(pool, builder, mod, trans, target_slot)
return 0
def _is_addrlike_call(self, node: ast.AST*) -> int:
"""Return 1 if the rhs is an explicit pointer expression (`&x` / `*p`).
"""
if node is None: return 0
# `&x` and `*p` parse as UnaryOp(AddrOf) / UnaryOp(Deref). A pointer-valued RHS must not
# take the module-global "object with static storage -> copy the object" branch.
if node.kind() == ast.ASTKind.UnaryOp:
uo_al: ast.UnaryOp* = ast.UnaryOp*(node)
if uo_al.op == ast.OpKind.AddrOf: return 1
if uo_al.op == ast.OpKind.Deref: return 1
return 0
return 0
def _is_addr_of_expr(self, node: ast.AST*) -> int:
"""Return 1 only for an explicit address-of expression (`&x`).
Distinct from _is_addrlike_call, which also counts deref forms: assigning a *deref*
result (`*p`) to a value-typed variable is a legitimate value copy, so only `&x` indicates
a missing `*` in the annotation.
"""
if node is None: return 0
if node.kind() == ast.ASTKind.UnaryOp:
uo_ao: ast.UnaryOp* = ast.UnaryOp*(node)
if uo_ao.op == ast.OpKind.AddrOf: return 1
return 0
return 0
def _DerefPunBits(self, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*) -> int:
"""位型 pun(`*a = *b`)的标量位宽:浮点取浮点位宽、整数取整型位宽、指针按 64、其余返回 0。
只对**两侧等宽**的标量做位拷贝(double↔u64 / float↔u32 / half↔u16 / ptr↔u64 …);
返回 0 表示该侧不是标量(聚合等)或位宽未知 ⇒ 调用方不做位拷贝,交回 coerce 路径。
"""
if ty is None: return 0
fb: int = self.Gen.ExprGenBase.get_llvm_float_bits(ty)
if fb != 0: return fb
ib: int = self.Gen.ExprGenBase.get_llvm_type_bits(ty)
if ib != 0: return ib
if self.Gen.ExprGenBase.is_ptr_type(ty) != 0: return 64
return 0
def _is_64bit_scalar_ty(self, node: ast.AST*) -> int:
"""Return 1 for a 64-bit scalar type (CUInt64T/CInt64T/CSizeT)."""
if node is None: return 0
ty_nm: str = None
nk: int = node.kind()
if nk == ast.ASTKind.Name:
nm64: ast.Name* = ast.Name*(node)
ty_nm = nm64.id
elif nk == ast.ASTKind.Attribute:
at64: ast.Attribute* = ast.Attribute*(node)
ty_nm = at64.attr
if ty_nm is None: return 0
if string.strcmp(ty_nm, "CUInt64T") == 0: return 1
if string.strcmp(ty_nm, "CInt64T") == 0: return 1
if string.strcmp(ty_nm, "CSizeT") == 0: return 1
# builtin 64-bit short names
if string.strcmp(ty_nm, "sizet") == 0: return 1
if string.strcmp(ty_nm, "size_t") == 0: return 1
if string.strcmp(ty_nm, "int64") == 0: return 1
if string.strcmp(ty_nm, "uint64") == 0: return 1
if string.strcmp(ty_nm, "intfast64") == 0: return 1
if string.strcmp(ty_nm, "uintfast64") == 0: return 1
if string.strcmp(ty_nm, "intleast64") == 0: return 1
if string.strcmp(ty_nm, "uintleast64") == 0: return 1
if string.strcmp(ty_nm, "longlong") == 0: return 1
if string.strcmp(ty_nm, "ulonglong") == 0: return 1
if string.strcmp(ty_nm, "ptrdifft") == 0: return 1
return 0
def _is_scalar_ty_name(self, ty_name: str) -> int:
"""Return 1 if the type name is a known pure scalar (whitelist)."""
if ty_name is None: return 0
if string.strchr(ty_name, '|') is not None: return 0
if string.strchr(ty_name, '*') is not None: return 0
if string.strcmp(ty_name, "str") == 0: return 0
if string.strcmp(ty_name, "bytes") == 0: return 0
if string.strcmp(ty_name, "CPtr") == 0: return 0
if string.strstr(ty_name, "Ptr") is not None: return 0
# builtin short type names
if string.strcmp(ty_name, "sizet") == 0: return 1
if string.strcmp(ty_name, "size_t") == 0: return 1
if string.strcmp(ty_name, "int8") == 0: return 1
if string.strcmp(ty_name, "int16") == 0: return 1
if string.strcmp(ty_name, "int32") == 0: return 1
if string.strcmp(ty_name, "int64") == 0: return 1
if string.strcmp(ty_name, "uint8") == 0: return 1
if string.strcmp(ty_name, "uint16") == 0: return 1
if string.strcmp(ty_name, "uint32") == 0: return 1
if string.strcmp(ty_name, "uint64") == 0: return 1
if string.strcmp(ty_name, "intfast8") == 0: return 1
if string.strcmp(ty_name, "intfast16") == 0: return 1
if string.strcmp(ty_name, "intfast32") == 0: return 1
if string.strcmp(ty_name, "intfast64") == 0: return 1
if string.strcmp(ty_name, "uintfast8") == 0: return 1
if string.strcmp(ty_name, "uintfast16") == 0: return 1
if string.strcmp(ty_name, "uintfast32") == 0: return 1
if string.strcmp(ty_name, "uintfast64") == 0: return 1
if string.strcmp(ty_name, "intleast8") == 0: return 1
if string.strcmp(ty_name, "intleast16") == 0: return 1
if string.strcmp(ty_name, "intleast32") == 0: return 1
if string.strcmp(ty_name, "intleast64") == 0: return 1
if string.strcmp(ty_name, "uintleast8") == 0: return 1
if string.strcmp(ty_name, "uintleast16") == 0: return 1
if string.strcmp(ty_name, "uintleast32") == 0: return 1
if string.strcmp(ty_name, "uintleast64") == 0: return 1
if string.strcmp(ty_name, "uchar") == 0: return 1
if string.strcmp(ty_name, "short") == 0: return 1
if string.strcmp(ty_name, "ushort") == 0: return 1
if string.strcmp(ty_name, "uint") == 0: return 1
if string.strcmp(ty_name, "long") == 0: return 1
if string.strcmp(ty_name, "ulong") == 0: return 1
if string.strcmp(ty_name, "longlong") == 0: return 1
if string.strcmp(ty_name, "ulonglong") == 0: return 1
if string.strcmp(ty_name, "ptrdifft") == 0: return 1
# Whitelist: known pure scalars (base names + stdint uppercase aliases)
if string.strcmp(ty_name, "int") == 0: return 1
if string.strcmp(ty_name, "char") == 0: return 1
# Python builtin scalar names (float/double/bool) pass through as scalar types
if string.strcmp(ty_name, "float") == 0: return 1
if string.strcmp(ty_name, "double") == 0: return 1
if string.strcmp(ty_name, "bool") == 0: return 1
if string.strcmp(ty_name, "CInt") == 0: return 1
if string.strcmp(ty_name, "CUnsignedInt") == 0: return 1
if string.strcmp(ty_name, "CUnsigned") == 0: return 1
if string.strcmp(ty_name, "CChar") == 0: return 1
if string.strcmp(ty_name, "CUnsignedChar") == 0: return 1
if string.strcmp(ty_name, "CSignedChar") == 0: return 1
if string.strcmp(ty_name, "CShort") == 0: return 1
if string.strcmp(ty_name, "CUnsignedShort") == 0: return 1
if string.strcmp(ty_name, "CLong") == 0: return 1
if string.strcmp(ty_name, "CUnsignedLong") == 0: return 1
if string.strcmp(ty_name, "CLongLong") == 0: return 1
if string.strcmp(ty_name, "CUnsignedLongLong") == 0: return 1
if string.strcmp(ty_name, "CSizeT") == 0: return 1
if string.strcmp(ty_name, "CBool") == 0: return 1
# Character width family: widths 8/16/32 are not 8-byte pointer slots
if string.strcmp(ty_name, "CWCharT") == 0: return 1
if string.strcmp(ty_name, "CChar8T") == 0: return 1
if string.strcmp(ty_name, "CChar16T") == 0: return 1
if string.strcmp(ty_name, "CChar32T") == 0: return 1
if string.strcmp(ty_name, "BOOL") == 0: return 1
if string.strcmp(ty_name, "BYTE") == 0: return 1
if string.strcmp(ty_name, "UCHAR") == 0: return 1
if string.strcmp(ty_name, "CHAR") == 0: return 1
if string.strcmp(ty_name, "SHORT") == 0: return 1
if string.strcmp(ty_name, "USHORT") == 0: return 1
if string.strcmp(ty_name, "LONG") == 0: return 1
if string.strcmp(ty_name, "ULONG") == 0: return 1
if string.strcmp(ty_name, "LONGLONG") == 0: return 1
if string.strcmp(ty_name, "ULONGLONG") == 0: return 1
if string.strcmp(ty_name, "INT") == 0: return 1
if string.strcmp(ty_name, "UINT") == 0: return 1
if string.strcmp(ty_name, "WORD") == 0: return 1
if string.strcmp(ty_name, "DWORD") == 0: return 1
if string.strcmp(ty_name, "QWORD") == 0: return 1
# Lowercase bare C type aliases from builtins (same table)
if string.strcmp(ty_name, "uchar") == 0: return 1
if string.strcmp(ty_name, "short") == 0: return 1
if string.strcmp(ty_name, "ushort") == 0: return 1
if string.strcmp(ty_name, "uint") == 0: return 1
if string.strcmp(ty_name, "long") == 0: return 1
if string.strcmp(ty_name, "ulong") == 0: return 1
if string.strcmp(ty_name, "longlong") == 0: return 1
if string.strcmp(ty_name, "ulonglong") == 0: return 1
# Fixed-width int/float (C*T forms and bare aliases, prefix-matched)
if string.strstr(ty_name, "Int8") is not None: return 1
if string.strstr(ty_name, "Int16") is not None: return 1
if string.strstr(ty_name, "Int32") is not None: return 1
if string.strstr(ty_name, "Int64") is not None: return 1
if string.strstr(ty_name, "UInt8") is not None: return 1
if string.strstr(ty_name, "UInt16") is not None: return 1
if string.strstr(ty_name, "UInt32") is not None: return 1
if string.strstr(ty_name, "UInt64") is not None: return 1
if string.strstr(ty_name, "Float") is not None: return 1
if string.strstr(ty_name, "FLOAT") is not None: return 1
if string.strstr(ty_name, "Double") is not None: return 1
if string.strstr(ty_name, "DOUBLE") is not None: return 1
# Unknown class name => not a scalar, use an 8-byte pointer slot
return 0
def _is_generic_tp_or_nested(self, node: ast.AST*) -> int:
"""Return 1 for a generic type-param name or nested union (BinOp)."""
if node is None: return 0
nk: int = node.kind()
if nk == ast.ASTKind.BinOp: return 1
if nk == ast.ASTKind.Name:
nm_tp: ast.Name* = ast.Name*(node)
if nm_tp.id is None: return 0
tp_len: sizet = string.strlen(nm_tp.id)
if tp_len == 1:
if nm_tp.id[0] >= 65 and nm_tp.id[0] <= 90: return 1
return 0
# _init_global_array_from_list - initialize a global array from a list literal
def _init_global_array_from_list(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, array_alloca: mlirlite.Value*, list_node: ast.AST*, trans: HT.Translator*, owner_ctx: str = None, byte_order: int = 0) -> int:
"""Initialize a global array from a list literal via per-element GEP+store.
byte_order (0=native / 1=BigEndian / 2=LittleEndian): when the array variable is big-endian
every element is stored byte-swapped, so a later element read (which un-swaps) sees the same
value; left at 0 for module globals / struct-field defaults (not registered)."""
if builder is None or array_alloca is None or list_node is None: return 1
# Get the array type (pointee of [N x elem_ty]*)
arr_ty: mlirlite.LLVMType* = None
if array_alloca.Ty is not None: arr_ty = array_alloca.Ty.Pointee
if arr_ty is None: return 1
# Match the array type to get the element type and declared length
elem_ty: mlirlite.LLVMType* = None
arr_count: int = 0
match arr_ty:
case mlirlite.LLVMType.Array(et, cnt):
elem_ty = et
arr_count = cnt
if elem_ty is None: return 1
# Get the list elements
lst: ast.List* = ast.List*(list_node)
if lst is None or lst.elts is None: return 1
elts: list[ast.AST*]* = lst.elts
elts_count: sizet = len(elts)
# Special case: a single string literal initializes a char array byte by byte
if self.Gen.ExprGenBase.get_llvm_type_bits(elem_ty) == 8 and elts_count == 1 and arr_count >= 1:
e0_s: ast.AST* = elts.get(0)
if e0_s is not None and e0_s.kind() == ast.ASTKind.Constant:
c0_s: ast.Constant* = ast.Constant*(e0_s)
if c0_s.const_kind == ast.CONST_STR:
sv_s: str = ast.constant_get_str_val(c0_s)
if sv_s is not None:
n_s: sizet = string.strlen(sv_s)
if sizet(arr_count) < n_s:
self.Gen.TypeGenErr.fatal_error(list_node, f"string literal length {n_s} exceeds the declared char array length {arr_count}; out-of-bounds writes are forbidden", "OverflowError")
for bi_s in range(int(n_s)):
bidx_s: mlirlite.Value* = mlirlite.const_int32(pool, bi_s)
bp_s: mlirlite.Value* = mlirlite.build_gep_array(builder, arr_ty, elem_ty, array_alloca, bidx_s)
if bp_s is None: break
bvv: mlirlite.Value* = mlirlite.const_int32(pool, sv_s[bi_s])
bsv: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, bvv, elem_ty, e0_s)
mlirlite.build_store(builder, bsv, bp_s)
return 0
# Element count exceeds declared length: fail fast (avoid out-of-range GEP writes)
if arr_count > 0 and sizet(arr_count) < elts_count:
self.Gen.TypeGenErr.fatal_error(list_node, f"array initializer element count {elts_count} exceeds the declared array length {arr_count}; out-of-bounds writes are forbidden", "OverflowError")
# Emit GEP + store for each element
for i in range(int(elts_count)):
elem_node: ast.AST* = elts.get(i)
# Explicit null check (repo convention: is None); an empty slot means an element count mismatch.
# When the slot is null, fail fast with class/field/index context.
if elem_node is None:
ctx_dbg: str = ""
if owner_ctx is not None: ctx_dbg = owner_ctx
src_line: int = 0
src_col: int = 0
if list_node is not None:
src_line = list_node.lineno
src_col = list_node.col_offset
self.Gen.TypeGenErr.fatal_error(list_node, f"global array initialization hit a null AST element: {ctx_dbg}element {i} is a bare null pointer (0), " f"but the list element count is {elts_count} (element and count mismatch). " f"somewhere upstream produced a null AST element (slot {i} of list-literal node {src_line}:{src_col} was not filled), " f"silently skipping/padding with zero is refused; compilation aborted.", "ValueError")
return 1
# Object validity check (nonzero vptr): a zero vptr makes any virtual call read address 0
elem_vptr: uint64 = *((uint64*)(elem_node))
if elem_vptr == 0:
ctx_vt: str = ""
if owner_ctx is not None: ctx_vt = owner_ctx
vt_line: int = 0
vt_col: int = 0
if list_node is not None:
vt_line = list_node.lineno
vt_col = list_node.col_offset
self.Gen.TypeGenErr.fatal_error(list_node, f"global array initialization hit an invalid-object AST element (vptr is 0): {ctx_vt}" f"element {i} has a nonzero pointer (address {uint64(elem_node)}) but its object header (vtable pointer) is 0, " f"and any virtual call on it (kind()/dump()/...) reads address 0 at `load [0+slot]`. " f"list-literal node position {vt_line}:{vt_col}, element count {elts_count}. " f"somewhere upstream produced an AST node with an uninitialized vptr (typical source: a deep/block copy that moved only fields, not the vptr), " f"refusing to continue; compilation aborted.", "ValueError", vt_col)
return 1
idx_val: mlirlite.Value* = mlirlite.const_int32(pool, i)
elem_ptr: mlirlite.Value* = mlirlite.build_gep_array(builder, arr_ty, elem_ty, array_alloca, idx_val)
if elem_ptr is None: continue
# Nested array: element type is an array and the node is a list literal => recurse
inner_arr: int = 0
if self.Gen.ExprGenBase.is_array_type(elem_ty) != 0:
# Null check for nested element: use is not None (not integer 0)
if elem_node is not None and elem_node.kind() == ast.ASTKind.List: inner_arr = 1
if inner_arr != 0:
self._init_global_array_from_list(builder, pool, mod, elem_ptr, elem_node, trans, owner_ctx, byte_order)
continue
elem_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, elem_node, None, 0, trans)
if elem_val is None:
# Element translation failed: fail fast to expose the upstream cause
self.Gen.TypeGenErr.fatal_error(elem_node, "global array initializer element translation failed", "TypeError")
# Type coercion (e.g. i32 -> i8); Pointee is statically i64 so compare with != 0
store_val: mlirlite.Value* = elem_val
if elem_ptr.Ty is not None and elem_ptr.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, elem_val, elem_ptr.Ty.Pointee, elem_node)
# BigEndian array: store each element byte-swapped (pairs the un-swap on element read).
store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, byte_order)
mlirlite.build_store(builder, store_val, elem_ptr)
return 0
# _apply_aug_op - apply the binary op of an augmented assignment (float, pointer, integer)
def _apply_aug_op(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, op: int, lhs: mlirlite.Value*, rhs: mlirlite.Value*, node: ast.AST* = None, trans: HT.Translator* = None, lhs_node: ast.AST* = None) -> mlirlite.Value*:
"""Apply the binary op of an augmented assignment (float/pointer/int)."""
# Power aug-assign: base types use llvm.pow via ExprGenOps.emit_pow
if op == ast.OpKind.Pow:
if trans is None: return None
return self.Gen.ExprGenOps.emit_pow(pool, builder, trans.Module, lhs, rhs, node, trans)
lhs_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(lhs.Ty)
rhs_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(rhs.Ty)
lhs_fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(lhs.Ty)
rhs_fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(rhs.Ty)
# Float op when either operand is float (check before pointer arithmetic)
if lhs_fbits != 0 or rhs_fbits != 0:
# Pick the target float type (the wider one)
target_fbits: int = lhs_fbits
if rhs_fbits > target_fbits: target_fbits = rhs_fbits
target_float_ty: mlirlite.LLVMType* = mlirlite.Double(pool)
if target_fbits == 32: target_float_ty = mlirlite.Float(pool)
# Coerce both operands to the target float type
lhs = self.Gen.ExprGenBase.coerce_to_type(builder, lhs, target_float_ty, node)
rhs = self.Gen.ExprGenBase.coerce_to_type(builder, rhs, target_float_ty, node)
if op == ast.OpKind.Add: return mlirlite.build_fadd(builder, lhs, rhs)
if op == ast.OpKind.Sub: return mlirlite.build_fsub(builder, lhs, rhs)
if op == ast.OpKind.Mult: return mlirlite.build_fmul(builder, lhs, rhs)
if op == ast.OpKind.Div: return mlirlite.build_fdiv(builder, lhs, rhs)
if op == ast.OpKind.Mod: return mlirlite.build_frem(builder, lhs, rhs)
return None
# Pointer arithmetic: ptr += int / ptr -= int -> getelementptr (scaled by element size)
if lhs_bits == 0 and rhs_bits != 0:
if op == ast.OpKind.Add or op == ast.OpKind.Sub: return self.Gen.ExprGenBase.translate_ptr_arith(pool, builder, lhs, rhs, op)
return None
# Double-pointer arithmetic: delegate the check to ExprGenOps.fail_if_double_ptr_binop
self.Gen.ExprGenOps.fail_if_double_ptr_binop(pool, op, lhs, rhs, node, 1)
# x op= y 的操作数宽度约束(fail-fast,先于运算与提升):
# y 的有效宽度必须 <= x 的宽度。变量按其 LLVM 类型宽度;常量按其值的**最小可存宽度**
# (所以 `u8 += 1` 合法、`u8 += 300` 报错)。y 更宽 = 隐式收窄,必须先显式收窄再写。
# 移位(<<= / >>=)**豁免**:计数不是写回目标的值,改用静态范围检查(见下方)。
#
# 说明:右值有符号而左值无符号**不再单独拦截**——拓宽方向已由 sext/zext 规则统一决定
# (i 类 sext、u 类 zext,见 wiki/Implicit.md §1.1),且二元形态 `x = x op y` 本就允许
# (如 `sizet += int` 里 int 先拓宽到 u64 再相加)。两条路径口径必须一致,否则同一语义
# 换个写法就报错。
y_node_aa: ast.AST* = None
if node is not None and node.kind() == ast.ASTKind.AugAssign: y_node_aa = ast.AugAssign*(node).value
if lhs_bits != 0 and rhs_bits != 0 and op != ast.OpKind.LShift and op != ast.OpKind.RShift:
y_bits_eff: int = rhs_bits
if y_node_aa is not None and y_node_aa.kind() == ast.ASTKind.Constant:
cn_aa: ast.Constant* = ast.Constant*(y_node_aa)
if cn_aa.const_kind == ast.CONST_INT: y_bits_eff = self.define_min_width(cn_aa.int_val)
# 与 translate_binop 同口径:右值是能**无损落进左值位宽**的编译期常量时一律放行(复用同一处
# 判定 _const_fits_int_type),交由下方等宽化的常量适配处理。覆盖 `-1` / `~x` / `True` 等
# **折叠出的常量**(它们的 AST 不是 Constant,旧检查会误按 i32 宽度拦下,而二元形态
# `u16 = u16 - (-1)` 却合法)——两条路径必须一致。
if rhs.IsConst == 1 and self.Gen.ExprGenBase._const_fits_int_type(rhs, lhs.Ty) != 0: y_bits_eff = 0
if y_bits_eff > lhs_bits:
self.Gen.TypeGenErr.fatal_error(node, f"[VPC] augmented assignment operand too wide: the right-hand side needs {y_bits_eff} bits, " f"but the target provides only {lhs_bits} bits; an implicit narrowing is forbidden. " f"Fix: narrow the right-hand side explicitly (e.g. uint{lhs_bits}(...) / int{lhs_bits}(...)) before the operation.", "CastError")
# 整型等宽化:与 translate_binop 的**非移位**口径逐项对齐,使 `x op= y` ≡ `x = x op y`。
# 旧行为只做「谁宽就抬窄的一侧」,于是 `page.free_count(u16) -= 1` 会把 u16 抬到常量 1 的 i32
# 宽度、再在收尾 coerce 回 i16 时撞「隐式窄化」;而二元形态 `x = x - 1` 因走 translate_binop 的
# 常量适配(_const_operand_demotes)并不报错。这里复用**同一处**判定:左值类型即上下文目标位宽
# (tgt_bits = lhs_bits),更宽的一侧若是能**无损落进另一侧**的编译期常量(`1` 默认 i32,但能
# 无损落进 u16),就把常量降到左值位宽,而不是把左值抬到常量位宽。
is_shift_aa: int = 0
if op == ast.OpKind.LShift or op == ast.OpKind.RShift: is_shift_aa = 1
if is_shift_aa != 0:
# 移位:结果位宽 = 左操作数位宽(计数不代表结果类型,不参与位宽比较)。计数一律适配到左
# 操作数类型:能静态确定且无损的常量按 coerce,运行时更宽的值按 C 语义 trunc——与
# `x = x << y` 的 translate_binop 移位分支一致。
if lhs_bits != 0 and rhs_bits != 0 and rhs.Ty != lhs.Ty:
if rhs_bits < lhs_bits or self.Gen.ExprGenBase._const_fits_int_type(rhs, lhs.Ty) != 0:
rhs = self.Gen.ExprGenBase.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
else:
rhs = mlirlite.build_trunc(builder, rhs, lhs.Ty)
rhs_bits = lhs_bits
else:
is_bitwise_aa: int = 0
if op == ast.OpKind.BitAnd or op == ast.OpKind.BitOr or op == ast.OpKind.BitXor: is_bitwise_aa = 1
tgt_bits_aa: int = lhs_bits
if lhs_bits > rhs_bits and rhs_bits > 0:
# 更宽的一侧是左值(load,必非常量):能无损适配的右值常量降回右值类型,否则按 §1.1
# 的统一方向(zext/sext)把更窄的右值拓宽到左值类型。
if self.Gen.ExprGenOps._const_operand_demotes(is_bitwise_aa, lhs, lhs_bits, rhs, rhs.Ty, tgt_bits_aa) != 0:
lhs = self.Gen.ExprGenBase.coerce_to_type(builder, lhs, rhs.Ty, node, trans)
elif self.Gen.ExprGenOps.should_zext_widen(pool, rhs, lhs, y_node_aa, trans) != 0:
rhs = mlirlite.build_zext(builder, rhs, lhs.Ty)
else:
rhs = self.Gen.ExprGenBase.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
elif rhs_bits > lhs_bits and lhs_bits > 0:
# 更宽的一侧是右值:能无损落进左值位宽的常量降回左值类型(`u16 -= 1`),否则如实拓宽
# 左值——左值被抬宽后会在收尾 coerce 处按「隐式窄化」fail-fast,与 `x = x op y` 一致。
if self.Gen.ExprGenOps._const_operand_demotes(is_bitwise_aa, rhs, rhs_bits, lhs, lhs.Ty, tgt_bits_aa) != 0:
rhs = self.Gen.ExprGenBase.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
elif self.Gen.ExprGenOps.should_zext_widen(pool, lhs, rhs, lhs_node, trans) != 0:
lhs = mlirlite.build_zext(builder, lhs, rhs.Ty)
else:
lhs = self.Gen.ExprGenBase.coerce_to_type(builder, lhs, rhs.Ty, node, trans)
# 移位:计数本身豁免宽度/符号规则,改按左操作数位宽(= 结果位宽)做静态范围检查
if is_shift_aa != 0:
self.require_shift_count_in_range(pool, trans, node, y_node_aa, self.Gen.ExprGenBase.get_llvm_type_bits(lhs.Ty))
# Unsigned operands use udiv/urem; signed use sdiv/srem
aug_unsigned: int = 0
if lhs_node is not None:
if self.Gen.ExprGenOps._node_type_unsigned(pool, lhs_node, trans) != 0: aug_unsigned = 1
if op == ast.OpKind.Add: return mlirlite.build_add(builder, lhs, rhs)
elif op == ast.OpKind.Sub: return mlirlite.build_sub(builder, lhs, rhs)
elif op == ast.OpKind.Mult: return mlirlite.build_mul(builder, lhs, rhs)
elif op == ast.OpKind.Div:
if aug_unsigned != 0: return mlirlite.build_udiv(builder, lhs, rhs)
return mlirlite.build_sdiv(builder, lhs, rhs)
elif op == ast.OpKind.FloorDiv:
if aug_unsigned != 0: return mlirlite.build_udiv(builder, lhs, rhs)
return mlirlite.build_sdiv(builder, lhs, rhs)
elif op == ast.OpKind.Mod:
if aug_unsigned != 0: return mlirlite.build_urem(builder, lhs, rhs)
return mlirlite.build_srem(builder, lhs, rhs)
elif op == ast.OpKind.BitAnd: return mlirlite.build_and(builder, lhs, rhs)
elif op == ast.OpKind.BitOr: return mlirlite.build_or(builder, lhs, rhs)
elif op == ast.OpKind.BitXor: return mlirlite.build_xor(builder, lhs, rhs)
elif op == ast.OpKind.LShift: return mlirlite.build_shl(builder, lhs, rhs)
elif op == ast.OpKind.RShift:
# Unsigned >> uses lshr, signed >> uses ashr (i8/i16 default unsigned)
if lhs_bits == 8 or lhs_bits == 16: return mlirlite.build_lshr(builder, lhs, rhs)
if lhs_node is not None and trans is not None:
if self.Gen.ExprGenOps._node_type_unsigned(pool, lhs_node, trans) != 0: return mlirlite.build_lshr(builder, lhs, rhs)
return mlirlite.build_ashr(builder, lhs, rhs)
return None
# translate_augassign - translate augmented assignment statements
def const_assign_msg(self, kind: str, name: str, src_name: str) -> str:
"""Build the const-assignment error message used by const_assign_fail."""
decl: str = "(annotation contains t.CConst; the source name at the declaration was not recorded)"
if src_name is not None: decl = f"CConst | {src_name}"
return GenApi.escape_fstring_copy(f"cannot assign to const variable '{name}' ({kind}); " f"declaration type (including const): {decl}" f"; fix: remove the const descriptor / t.CConst from the declaration, or assign once at the declaration " f"and afterwards use only non-const copies/temporaries; a const object forbids assignment, " f"augmented assignment and element assignment within its scope, and this never degrades to a warning")
def const_assign_fail(self, node: ast.AST*, kind: str, name: str, src_name: str) -> int:
"""Fail fast when a const target is written."""
self.Gen.TypeGenErr.fatal_error(node, self.const_assign_msg(kind, name, src_name), "TypeError")
return 0
def translate_augassign(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate an augmented assignment statement (x += 1)."""
if node is None: return 0
aug: ast.AugAssign* = ast.AugAssign*(node)
if aug is None: return 0
target: ast.AST* = aug.target
if target is None: return 0
tk: int = target.kind()
# Attribute target: self.field += 1 (get_attribute_ptr -> load -> binop -> store)
if tk == ast.ASTKind.Attribute:
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# const field: fail fast on the field write
at_ca: ast.Attribute* = ast.Attribute*(target)
if at_ca is not None and at_ca.attr is not None:
if self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, target) != 0:
self.const_assign_fail(target, "field assignment", at_ca.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, target))
# 1. Get the field pointer
field_ptr: mlirlite.Value* = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, target, trans)
if field_ptr is None:
# fail-fast: attribute pointer resolution failed
self.Gen.TypeGenErr.fatal_error(target, "attribute ptr is None", "TypeError")
# 2. Determine the field type and load the current value
target_ty: mlirlite.LLVMType* = None
if field_ptr.Ty is not None and field_ptr.Ty.Pointee != 0: target_ty = field_ptr.Ty.Pointee
if target_ty is None:
# fail-fast: field type missing, no i32 fallback
self.Gen.TypeGenErr.fatal_error(target, "field type information missing", "TypeError")
# t.CVolatile field: both load and store carry the volatile qualifier
vol_fa: int = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target)
# BigEndian field: the memory slot holds the byte-swapped pattern, so the current value must
# be un-swapped before it takes part in the arithmetic, and the result re-swapped on store.
bo_fa: int = self.Gen.ExprGenAttr.attr_field_byte_order(pool, trans, target)
# rawptr[t.CVolatile|T] base: field load/store also carry volatile
at_aug: ast.Attribute* = ast.Attribute*(target)
if at_aug is not None:
if self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, at_aug.value) != 0: vol_fa = 1
cur_val: mlirlite.Value* = mlirlite.build_load(builder, target_ty, field_ptr, vol_fa)
if cur_val is None:
# fail-fast: attribute current value load failed
self.Gen.TypeGenErr.fatal_error(target, "cannot load the current value of the attribute", "TypeError")
cur_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, cur_val, bo_fa)
# 3. Translate the RHS value
# 与 `x = x op y` 同口径:RHS 在左值类型(target_ty)上下文中翻译,嵌套 binop / CDefine 的
# 目标感知与常量适配据此生效(旧行为 target_ty=None 使 RHS 口径与二元形态不一致)。
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans, target_ty)
if rhs_val is None:
# fail-fast: RHS translation failed
self.Gen.TypeGenErr.fatal_error(target, "rhs translation failed", "TypeError")
# 4. Apply the binary op
result: mlirlite.Value* = self._apply_aug_op(pool, builder, aug.op, cur_val, rhs_val, node, trans, target)
if result is None:
# fail-fast: attribute binary op failed
self.Gen.TypeGenErr.fatal_error(target, f"attribute binary op failed op={aug.op}", "TypeError")
# 5. Coerce the type and store
result = self.Gen.ExprGenBase.coerce_to_type(builder, result, target_ty, node)
if result is None:
# fail-fast: type coercion failed
self.Gen.TypeGenErr.fatal_error(target, "type coercion failed", "TypeError")
# BigEndian field: re-swap the result so the slot keeps the byte-swapped pattern
# (pairs the un-swap of cur_val above, keeping memory big-endian).
result = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, result, bo_fa)
mlirlite.build_store(builder, result, field_ptr, vol_fa)
return 0
# Subscript target: self.state[i] += x (get_subscript_ptr -> load -> binop -> store)
if tk == ast.ASTKind.Subscript:
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# const element target: fail fast when the base variable or field is const
sub_ca: ast.Subscript* = ast.Subscript*(target)
if sub_ca is not None and sub_ca.value is not None:
if sub_ca.value.kind() == ast.ASTKind.Name:
nm_ca: ast.Name* = ast.Name*(sub_ca.value)
if nm_ca.id is not None:
ve_ca: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_ca.id)
if self.Gen.VarGen.var_is_const(ve_ca) != 0:
self.const_assign_fail(target, "element assignment", nm_ca.id, self.Gen.VarGen.var_annot_class_name(ve_ca))
elif sub_ca.value.kind() == ast.ASTKind.Attribute:
at_ca2: ast.Attribute* = ast.Attribute*(sub_ca.value)
if at_ca2.attr is not None:
if self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, sub_ca.value) != 0:
self.const_assign_fail(target, "element assignment", at_ca2.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, sub_ca.value))
# 1. Get the element pointer
elem_ptr: mlirlite.Value* = self.Gen.ExprGenName.get_subscript_ptr(builder, pool, mod, target, trans)
if elem_ptr is None:
# fail-fast: subscript pointer resolution failed
self.Gen.TypeGenErr.fatal_error(target, "subscript ptr is None", "TypeError")
# 2. Determine the element type and load the current value
target_ty: mlirlite.LLVMType* = None
if elem_ptr.Ty is not None and elem_ptr.Ty.Pointee != 0: target_ty = elem_ptr.Ty.Pointee
if target_ty is None:
# fail-fast: element type missing, no i32 fallback
self.Gen.TypeGenErr.fatal_error(target, "element type information missing", "TypeError")
# rawptr[t.CVolatile|T] base: p[i] load/store carry volatile when the base is resolvable
vol_sa: int = 0
sub_aug: ast.Subscript* = ast.Subscript*(target)
if sub_aug is not None: vol_sa = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, sub_aug.value)
# BigEndian array variable (base is a Name with a registered byte order): element load/store
# bswap (TPC HandlesAssign._StoreArrElemWithCoerceBswap). Non-Name bases yield 0 -> no swap.
bo_sa: int = 0
if sub_aug is not None and sub_aug.value is not None and sub_aug.value.kind() == ast.ASTKind.Name:
sub_nm_aug: ast.Name* = ast.Name*(sub_aug.value)
if sub_nm_aug.id is not None:
bo_sa = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, sub_nm_aug.id))
cur_val: mlirlite.Value* = mlirlite.build_load(builder, target_ty, elem_ptr, vol_sa)
if cur_val is None:
# fail-fast: element current value load failed
self.Gen.TypeGenErr.fatal_error(target, "cannot load the current value of the subscript element", "TypeError")
cur_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, cur_val, bo_sa)
# 3. Translate the RHS value
# 与 `x = x op y` 同口径:RHS 在左值类型(target_ty)上下文中翻译,嵌套 binop / CDefine 的
# 目标感知与常量适配据此生效(旧行为 target_ty=None 使 RHS 口径与二元形态不一致)。
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans, target_ty)
if rhs_val is None:
# fail-fast: RHS translation failed
self.Gen.TypeGenErr.fatal_error(target, "rhs translation failed", "TypeError")
# 4. Apply the binary op
result: mlirlite.Value* = self._apply_aug_op(pool, builder, aug.op, cur_val, rhs_val, node, trans, target)
if result is None:
# fail-fast: subscript binary op failed
self.Gen.TypeGenErr.fatal_error(target, f"subscript binary op failed op={aug.op}", "TypeError")
# 5. Coerce the type and store
result = self.Gen.ExprGenBase.coerce_to_type(builder, result, target_ty, node)
if result is None:
# fail-fast: type coercion failed
self.Gen.TypeGenErr.fatal_error(target, "type coercion failed", "TypeError")
# BigEndian array element: re-swap the result so the slot stays byte-swapped (pairs cur_val above).
result = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, result, bo_sa)
mlirlite.build_store(builder, result, elem_ptr, vol_sa)
return 0
# UnaryOp target: *p += v (deref augmented assignment: load, op, store back)
if tk == ast.ASTKind.UnaryOp:
uo_ag: ast.UnaryOp* = ast.UnaryOp*(target)
if uo_ag is None or uo_ag.operand is None: self.Gen.TypeGenErr.fatal_error(target, "augmented-assignment target unary op is missing an operand", "TypeError")
if uo_ag.op != ast.OpKind.Deref:
self.Gen.TypeGenErr.fatal_error(target, "augmented-assignment target supports only the unary '*' dereference (&x / -x / ~x are not valid lvalues)", "SyntaxError")
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
ptr_ag: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, uo_ag.operand, None, 0, trans)
if ptr_ag is None or ptr_ag.Ty is None or self.Gen.ExprGenBase.is_ptr_type(ptr_ag.Ty) == 0:
self.Gen.TypeGenErr.fatal_error(target, "the operand of augmented-assignment '*' must be a pointer type (type inference degraded)", "TypeError")
ty_ag: mlirlite.LLVMType* = ptr_ag.Ty.Pointee
if ty_ag is None:
self.Gen.TypeGenErr.fatal_error(target, "deref augmented-assignment target type missing (pointee is empty)", "TypeError")
old_ag: mlirlite.Value* = mlirlite.build_load(builder, ty_ag, ptr_ag, 0)
if old_ag is None: self.Gen.TypeGenErr.fatal_error(target, "cannot load the current dereferenced value", "TypeError")
# 与 `x = x op y` 同口径:RHS 在解引用目标类型(ty_ag)上下文中翻译
rhs_ag: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans, ty_ag)
if rhs_ag is None: self.Gen.TypeGenErr.fatal_error(target, "rhs translation failed", "TypeError")
res_ag: mlirlite.Value* = self._apply_aug_op(pool, builder, aug.op, old_ag, rhs_ag, node, trans, target)
if res_ag is None: self.Gen.TypeGenErr.fatal_error(target, f"deref binary op failed op={aug.op}", "TypeError")
res_ag = self.Gen.ExprGenBase.coerce_to_type(builder, res_ag, ty_ag, node, trans)
if res_ag is None: self.Gen.TypeGenErr.fatal_error(target, "type coercion failed", "TypeError")
mlirlite.build_store(builder, res_ag, ptr_ag, 0)
return 0
if tk != ast.ASTKind.Name:
# fail-fast: unsupported AugAssign target kind
self.Gen.TypeGenErr.fatal_error(target, "AugAssign target type unsupported; only Name/Attribute/Subscript/*dereference are supported", "TypeError")
nm: ast.Name* = ast.Name*(target)
if nm is None or nm.id is None: return 0
# const variable: fail fast immediately
ve_aug: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if self.Gen.VarGen.var_is_const(ve_aug) != 0:
self.const_assign_fail(target, "augmented assignment", nm.id, self.Gen.VarGen.var_annot_class_name(ve_aug))
# REPL session variable aug-assign: read slot, apply op, write slot and result area
if trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0:
if self.Gen.VarGen.repl_try_augassign(trans, nm.id, aug.op, aug.value, node) != 0: return 0
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# 1. Determine scope: 0=local, 1=global, 2=nonlocal
scope_type: int = 0
if GenApi.is_global_name(trans, nm.id) != 0: scope_type = 1
elif GenApi.is_nonlocal_name(trans, nm.id) != 0: scope_type = 2
# t.CVolatile variable: both load and store carry the volatile qualifier
vol_aa: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
# BigEndian local variable: un-swap on load / re-swap on store so the arithmetic sees the native
# value (local scope only; global/nonlocal are not registered in the byte-order table, like TPC).
bo_aa: int = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
# 2. Load the current value
cur_val: mlirlite.Value* = None
target_alloca: mlirlite.Value* = None
target_ty: mlirlite.LLVMType* = None
if scope_type == 1:
# global variable
target_alloca = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
if target_alloca is not None:
if target_alloca.Ty is None:
# fail-fast: variable type missing, no i32 fallback
self.Gen.TypeGenErr.fatal_error(target, "variable type information missing", "TypeError")
target_ty = target_alloca.Ty.Pointee
cur_val = mlirlite.build_load(builder, target_ty, target_alloca, vol_aa)
elif scope_type == 2:
# nonlocal variable (via closure env)
cur_val = self.Gen.StmtGenDecl.load_nonlocal_var(trans, nm.id)
if cur_val is not None: target_ty = cur_val.Ty
else:
# Ordinary local variable
target_alloca = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if target_alloca is not None:
if target_alloca.Ty is None:
# fail-fast: variable type missing, no i32 fallback
self.Gen.TypeGenErr.fatal_error(target, "variable type information missing", "TypeError")
target_ty = target_alloca.Ty.Pointee
cur_val = mlirlite.build_load(builder, target_ty, target_alloca, vol_aa)
# BigEndian local: un-swap the loaded slot value before the arithmetic (paired with the store below).
cur_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, cur_val, bo_aa)
if cur_val is None:
# fail-fast: target variable current value load failed
self.Gen.TypeGenErr.fatal_error(target, f"cannot load the current value of target variable '{nm.id}'", "TypeError")
# 3. Translate the RHS value
# 与 `x = x op y` 同口径:RHS 在左值变量类型(target_ty)上下文中翻译
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans, target_ty)
if rhs_val is None:
# fail-fast: RHS translation failed
self.Gen.TypeGenErr.fatal_error(target, "rhs translation failed", "TypeError")
# 3b. Operator overload: object += -> __iadd__, fall back to __add__ (locals only)
if scope_type == 0 and target_alloca is not None:
aug_dup: str = self._aug_op_to_dunder(aug.op)
if aug_dup is not None:
ovl_self_aug: mlirlite.Value* = target_alloca
# Pointer storage slot (Ptr(Ptr(Struct))): load the object pointer as self
if target_alloca.Ty is not None and target_alloca.Ty.Pointee != 0 and self.Gen.ExprGenBase.is_ptr_type(target_alloca.Ty.Pointee) != 0:
load_self_aug: mlirlite.Value* = mlirlite.build_load(builder, target_alloca.Ty.Pointee, target_alloca)
if load_self_aug is not None: ovl_self_aug = load_self_aug
# Try the aug dunder only when the target is a struct (class instance)
if self.Gen.StructGen.get_struct_type_from_value(ovl_self_aug) is not None:
aug_extra: sizet* = pool.alloc(8)
if aug_extra is not None:
aug_extra[0] = sizet(rhs_val)
aug_ovl: mlirlite.Value* = self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, ovl_self_aug, aug_dup, trans, aug_extra, 1)
if aug_ovl is None:
aug_fb: str = self._op_to_dunder(aug.op)
if aug_fb is not None: aug_ovl = self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, ovl_self_aug, aug_fb, trans, aug_extra, 1)
if aug_ovl is not None:
aug_res: mlirlite.Value* = aug_ovl
if target_ty is not None and aug_res.Ty is not None and self.Gen.ExprGenBase._same_agg_ty(pool, aug_res.Ty, target_ty) == 0:
# Target is a struct value but the result is a pointer -> load the struct value
if self.Gen.ExprGenBase.is_ptr_type(target_ty) == 0 and self.Gen.ExprGenBase.is_ptr_type(aug_res.Ty) != 0 and aug_res.Ty.Pointee != 0:
aug_res = mlirlite.build_load(builder, aug_res.Ty.Pointee, aug_res)
else:
coerced_aug: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, aug_res, target_ty, node, trans)
if coerced_aug is not None: aug_res = coerced_aug
if aug_res is None or target_ty is None or self.Gen.ExprGenBase._same_agg_ty(pool, aug_res.Ty, target_ty) == 0:
# fail-fast: overload return type mismatch, do not corrupt memory silently
self.Gen.TypeGenErr.fatal_error(node, "augmented-assignment operator overload return type does not match the target variable", "TypeError")
mlirlite.build_store(builder, aug_res, target_alloca, vol_aa)
return 0
# 4. Coerce the type and apply the binary op
result: mlirlite.Value* = self._apply_aug_op(pool, builder, aug.op, cur_val, rhs_val, node, trans, target)
if result is None:
# fail-fast: binary op failed
self.Gen.TypeGenErr.fatal_error(target, f"applying binary op to variable '{nm.id}' failed op={aug.op}", "TypeError")
# 5. Store the result
result = self.Gen.ExprGenBase.coerce_to_type(builder, result, target_ty, node)
if result is None:
# fail-fast: type coercion failed
self.Gen.TypeGenErr.fatal_error(target, "type coercion failed", "TypeError")
if scope_type == 1:
# global variable
if target_alloca is not None: mlirlite.build_store(builder, result, target_alloca, vol_aa)
elif scope_type == 2:
# nonlocal variable
nl_ptr: mlirlite.Value* = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(trans, nm.id)
if nl_ptr is not None: mlirlite.build_store(builder, result, nl_ptr)
else:
# Ordinary local variable
if target_alloca is not None:
# BigEndian local: re-swap the result so the slot keeps the byte-swapped pattern (pairs the load above).
result = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, result, bo_aa)
mlirlite.build_store(builder, result, target_alloca, vol_aa)
return 0
# is_ctypedef_annotation - check whether an annotation is t.CTypedef
def is_ctypedef_annotation(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation is t.CTypedef, else 0."""
return self.Gen.TypeGenCType._is_ctypedef_annotation(annot)
# _is_single_param_carray_annot - check for single-param t.CArray[elem_ty]
def _is_single_param_carray_annot(self, annot: ast.AST*) -> int:
"""Return 1 for a single-param or None-length t.CArray annotation."""
if annot is None or annot.kind() != ast.ASTKind.Subscript: return 0
sub: ast.Subscript* = ast.Subscript*(annot)
if sub.value is None or sub.slice is None: return 0
if sub.value.kind() == ast.ASTKind.Name:
# Bare CArray / builtin rawarray / builtin array are all equivalent.
bn: ast.Name* = ast.Name*(sub.value)
if bn.id is None or (string.strcmp(bn.id, "CArray") != 0 and string.strcmp(bn.id, "rawarray") != 0 and string.strcmp(bn.id, "array") != 0): return 0
else: return 0
# Single-param form: slice is not a Tuple (e.g. CArray[u64])
if sub.slice.kind() != ast.ASTKind.Tuple: return 1
# Two-param form with count=None is equivalent to single-param
tup: ast.Tuple* = ast.Tuple*(sub.slice)
if tup.elts is None or len(tup.elts) < 2: return 0
cnt_node: ast.AST* = tup.elts.get(1)
if cnt_node is not None and cnt_node.kind() == ast.ASTKind.Constant:
cn0: ast.Constant* = ast.Constant*(cnt_node)
if cn0.const_kind == ast.CONST_NONE: return 1
return 0
# _infer_static_array_ty_from_literal - single-param CArray + list literal -> [len x elem_ty]
def _infer_static_array_ty_from_literal(self, pool: memhub.MemBuddy*, annot: ast.AST*, value: ast.AST*, resolved_ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*:
"""Return Array(elem_ty, len) for a single-param CArray with a list literal."""
if value is not None and value.kind() == ast.ASTKind.List and resolved_ty is not None:
if self._is_single_param_carray_annot(annot) != 0:
elem_ty_arr: mlirlite.LLVMType* = resolved_ty.Pointee
lst_arr: ast.List* = ast.List*(value)
cnt_arr: int = 0
if lst_arr.elts is not None: cnt_arr = int(len(lst_arr.elts))
# Special case: a single string literal means length strlen+1 (include the NUL)
if elem_ty_arr is not None and cnt_arr == 1 and lst_arr.elts is not None:
e0_arr: ast.AST* = lst_arr.elts.get(0)
if e0_arr is not None and e0_arr.kind() == ast.ASTKind.Constant:
c0_arr: ast.Constant* = ast.Constant*(e0_arr)
if c0_arr.const_kind == ast.CONST_STR:
sv0_arr: str = ast.constant_get_str_val(c0_arr)
if sv0_arr is not None and self.Gen.ExprGenBase.get_llvm_type_bits(elem_ty_arr) == 8: return mlirlite.Array(pool, elem_ty_arr, int(string.strlen(sv0_arr)) + 1)
if elem_ty_arr is not None and cnt_arr > 0: return mlirlite.Array(pool, elem_ty_arr, cnt_arr)
return resolved_ty
# _build_ann_fail_msg - build a detailed annotation-resolution-failed error
def _build_ann_fail_msg(self, pool: memhub.MemBuddy*, annot: ast.AST*, nm_id: str) -> str:
"""Build a detailed annotation-resolution-failure message (heap buffer)."""
ann_txt_x: str = None
if annot is not None: ann_txt_x = self.Gen.TypeGenResolve._get_type_name_from_annotation(pool, annot)
if ann_txt_x is None: ann_txt_x = "?"
btd_nm_x: str = self.Gen.TypeGenCType.LastBuiltinsTypedefName
btd_rs_x: str = self.Gen.TypeGenCType.LastBuiltinsTypedefResult
msg_len: sizet = 640
msg_buf: str = stdlib.malloc(msg_len)
if msg_buf is None: return ann_txt_x
if btd_nm_x is not None and btd_rs_x is not None: viperlib.snprintf(msg_buf, msg_len, "variable type annotation resolution failed, variable name: %s, annotation: %s. last builtins match: %s expanded to %s, still unresolvable", nm_id, ann_txt_x, btd_nm_x, btd_rs_x)
elif btd_nm_x is not None: viperlib.snprintf(msg_buf, msg_len, "variable type annotation resolution failed, variable name: %s, annotation: %s. last builtins match: %s missed (builtins.vp has no such typedef, or the scan did not run)", nm_id, ann_txt_x, btd_nm_x)
else: viperlib.snprintf(msg_buf, msg_len, "variable type annotation resolution failed, variable name: %s, annotation: %s. last builtins match: not triggered", nm_id, ann_txt_x)
return msg_buf
# translate_annassign_prescan - pre-create alloca for annotated vars
def translate_annassign_prescan(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Pre-create allocas for AnnAssign variables at the entry block."""
if node is None: return 0
k: int = node.kind()
if k != ast.ASTKind.AnnAssign: return 0
aa: ast.AnnAssign* = ast.AnnAssign*(node)
if aa is None or aa.target is None: return 0
target: ast.AST* = aa.target
if target.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(target)
if nm.id is None: return 0
# CDefine annotation: register the constant instead of creating an alloca
if self.is_cdefine_annotation(aa.annotation) != 0:
pool_ps: memhub.MemBuddy* = trans.Pool
self._register_cdefine_from_value(pool_ps, nm.id, aa.value)
return 0
# CTypedef annotation: register the alias only, no storage (avoids duplicate symbols)
if self.is_ctypedef_annotation(aa.annotation) != 0: return 0
# REPL fragment entry: AnnAssign goes to the session slot; skip pre-creating a local alloca
if trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0: return 0
# global/nonlocal variables need no local alloca
if GenApi.is_global_name(trans, nm.id) != 0: return 0
if GenApi.is_nonlocal_name(trans, nm.id) != 0: return 0
# Module-level globals are already registered; only skip the local alloca at module scope
if self.Gen.VarGen.is_module_scope(trans.SymTab) != 0:
if self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id) is not None: return 0
# Check whether it already exists
existing: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, nm.id)
if existing is not None: return 0
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
# Infer the type from the annotation (no hard-coded i32)
var_ty: mlirlite.LLVMType* = None
annot: ast.AST* = aa.annotation
if annot is not None:
# Value-reference markers are parameter-only: a local variable `x: &T` fails fast.
self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "a local variable declaration")
# Specialize generics before resolving so list[X] does not degrade to opaque list*
self._prespecialize_generic_annotation(trans, pool, annot)
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0)
if resolved is not None:
var_ty = resolved
# Single-param CArray + list literal -> static array; keep PreScan and Handle types in sync
var_ty = self._infer_static_array_ty_from_literal(pool, annot, aa.value, var_ty)
# [D31 fail-fast] generic annotation degraded to a bare base class
self._fail_on_degenerated_generic_annotation(aa, nm.id, annot, var_ty)
else:
# fail-fast: annotation resolution failed (no silent i32 fallback)
self.Gen.TypeGenErr.fatal_error(aa, self._build_ann_fail_msg(pool, annot, nm.id), "TypeError")
else:
# fail-fast: AnnAssign must have an annotation
self.Gen.TypeGenErr.fatal_error(aa, f"AnnAssign is missing a type annotation, variable name: {nm.id}", "SyntaxError")
# Ensure the full cross-module struct definition is available (for alloca)
self.Gen.StructGen.ensure_struct_def_in_module(pool, trans.Module, var_ty)
alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, var_ty)
# [TEMP-DIAG] 变量声明的槽类型 + **alloca 地址**(定位完删除):
# `npos = pos + Δ` 之后 pos.y 变化 ⇒ 若 pos/npos 的 slot 地址相同,说明两个变量
# 共用了同一块存储(槽复用/RVO 回填错槽),值语义被破坏。
vtb: str = "?"
bt0: str = pool.alloc_tagged(512, memhub.DETSITE_ASSIGNGEN_578)
if bt0 is not None:
mlirlite.TypePrint(bt0, 512, var_ty, pool)
vtb = bt0
vptr0: int = -1
if var_ty is not None: vptr0 = self.Gen.ExprGenBase.is_ptr_type(var_ty)
nm0: str = nm.id
if nm0 is None: nm0 = "?"
fn0: str = None
if trans._cur_func is not None: fn0 = mlirlite.function_get_name(trans._cur_func)
if fn0 is None: fn0 = "?"
stdio.printf("[VAR-DIAG] fn=%s name=%s vptr=%d slot=0x%x var_ty=%s\n",
fn0, nm0, vptr0, uint64(alloca) if alloca is not None else uint64(0), vtb)
if alloca is not None:
if self.Gen.VarGen.define_var(trans.SymTab, nm.id, alloca) == 0:
# Store the annotation's class name; pass pool so generic Subscript yields the mangled name
if annot is not None:
cls_nm_ps: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(annot, trans._imported_modules, pool, trans._from_imports)
if cls_nm_ps is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, nm.id, cls_nm_ps)
# Check for bytes* or str* (i8** semantics, 8-byte stride)
if self._is_ptr_element_annotation(annot) != 0: self.Gen.VarGen.set_var_ptr_element(trans.SymTab, nm.id)
# const object (annotation has t.CConst and the type is not a pointer): no writes in scope
if self.Gen.TypeGenErr.is_const_object_marker(annot, var_ty) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, nm.id)
# t.CVolatile: mark the variable at declaration; its load/store carry volatile
if self.Gen.TypeGenErr.has_decorator_marker(annot, "CVolatile", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id)
# rawptr[t.CVolatile | T]: dereference load/store carry volatile
if self.Gen.TypeGenErr.has_pointee_volatile_marker(annot) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, nm.id)
return 0
return 0
# _prespecialize_generic_annotation - register the generic specialization before resolving
def _prespecialize_generic_annotation(self, trans: HT.Translator*, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""Pre-specialize a generic annotation; return 1 if attempted."""
if annot is None or trans is None: return 0
# Take the generic Subscript side: the Subscript itself or the Subscript of A*
spec_annot: ast.Subscript* = None
if annot.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ps: ast.BinOp* = ast.BinOp*(annot)
if bop_ps.op == ast.OpKind.BitOr:
if bop_ps.left is not None and bop_ps.left.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ps.left)
elif bop_ps.right is not None and bop_ps.right.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ps.right)
if spec_annot is None: return 0
sv_ps: ast.AST* = spec_annot.value
if sv_ps is None or sv_ps.kind() != ast.ASTKind.Name: return 0
sub_nm: ast.Name* = ast.Name*(sv_ps)
if sub_nm.id is None: return 0
template_cd: ast.ClassDef* = self.Gen.ClassGenGeneric._find_generic_template(sub_nm.id)
if template_cd is None: return 0
type_args_ps: list[str]* = self.Gen.CallGenCtor._extract_type_args_from_slice(pool, spec_annot.slice, trans._from_imports)
if type_args_ps is None or len(type_args_ps) == 0: return 0
self.Gen.ClassGenGeneric._specialize_generic_class(trans, sub_nm.id, type_args_ps)
return 1
# translate_annassign - translate an AnnAssign statement; returns new variable count
def _fail_on_degenerated_generic_annotation(self, aa: ast.AnnAssign*, var_name: str, annot: ast.AST*, var_ty: mlirlite.LLVMType*) -> int:
"""Fail fast when a generic annotation degrades to a bare base pointer."""
if aa is None or annot is None or var_ty is None: return 0
# Take the generic Subscript side: the Subscript itself or the Subscript of A*
ann_sub: ast.Subscript* = None
if annot.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ff: ast.BinOp* = ast.BinOp*(annot)
if bop_ff.op == ast.OpKind.BitOr:
if bop_ff.left is not None and bop_ff.left.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(bop_ff.left)
elif bop_ff.right is not None and bop_ff.right.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(bop_ff.right)
if ann_sub is None or ann_sub.value is None or ann_sub.value.kind() != ast.ASTKind.Name: return 0
base_nm_ff: ast.Name* = ast.Name*(ann_sub.value)
if base_nm_ff.id is None: return 0
# Only treat it as degraded when the base is a real generic template
if self.Gen.ClassGenGeneric._find_generic_template(base_nm_ff.id) is None: return 0
if var_ty.Pointee == 0: return 0
vt_nm_ff: str = mlirlite.get_struct_name(var_ty.Pointee)
if vt_nm_ff is None: return 0
vt_pf_ff: int = GenApi.sha1_prefix_len(vt_nm_ff)
vt_short_ff: str = vt_nm_ff + vt_pf_ff if vt_pf_ff != 0 else vt_nm_ff
if string.strchr(vt_short_ff, '[') is not None:
return 0 # With type args: not degraded
self.Gen.TypeGenErr.fatal_error(aa, f"the generic annotation '{base_nm_ff.id}[...]' of variable '{var_name}' resolved to a bare base class " f"'{vt_nm_ff}*' (type arguments lost); subsequent .get()/.set() will specialize by the wrong element " f"type (such as list[int].get returning i32) -> inttoptr truncates the pointer and crashes. " f"Silent continuation is forbidden -- check the degradation point of this annotation in resolve_annotation_type.", "TypeError")
return 0
def translate_annassign(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate an AnnAssign statement; return the new variable count."""
aa: ast.AnnAssign* = ast.AnnAssign*(node)
if aa is None: return 0
target: ast.AST* = aa.target
if target is None: return 0
# 属性/下标目标(`obj.f: T = v` / `arr[i]: T = v`):走独立路径——不建局部变量,而是按注解
# 类型对既有存储做一次"带 P3 长命左值检查"的写出。原先这里直接 return 0,整条语句被**静默丢弃**。
if target.kind() != ast.ASTKind.Name: return self._translate_lvalue_annassign(trans, aa, target)
nm: ast.Name* = ast.Name*(target)
if nm.id is None: return 0
# CDefine annotation: compile-time constant, no runtime code; register it (idempotent)
if self.is_cdefine_annotation(aa.annotation) != 0:
pool_cd: memhub.MemBuddy* = trans.Pool
self._register_cdefine_from_value(pool_cd, nm.id, aa.value, aa.annotation)
return 0
# CTypedef annotation: register the alias on demand; no storage (avoids duplicate symbols)
if self.is_ctypedef_annotation(aa.annotation) != 0:
pool_ct: memhub.MemBuddy* = trans.Pool
ctd_str: str = self.Gen.TypeGenResolve._get_type_name_from_annotation(pool_ct, aa.value)
if ctd_str is not None: self.Gen.TypeGenCType.register_ctypedef(pool_ct, nm.id, ctd_str)
return 0
# Use-site width check for define/CDefine constants (a: T = C): T must be >= W_min
self.require_cdefine_use_width(trans.Pool, trans, aa.annotation, aa.value)
# REPL session variable: annotated declaration at fragment entry goes to the session slot
if trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0:
rp_e: GenTypes.ReplVarEntry* = self.Gen.VarGen.repl_ensure_entry(trans, nm.id, aa)
if rp_e is not None:
self.Gen.VarGen.repl_try_annassign(trans, nm.id, aa.annotation, aa.value, aa)
return 0
# Duplicate-declaration check (fail fast): re-annotating a module-level name inside a function.
# Legal alternatives: a global declaration plus unannotated assignment, or a differently named local.
has_mod_decl_add: int = 0
if nm.id is not None and trans is not None and self.Gen.VarGen.is_module_scope(trans.SymTab) == 0:
if self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id) is not None: has_mod_decl_add = 1
# 双保险:本文件顶层变量名集合(SymTab.Root.Vars 可能在部分时序看不到)
if has_mod_decl_add == 0 and GenApi.is_file_var(nm.id) != 0: has_mod_decl_add = 1
if has_mod_decl_add != 0:
dup_decl: int = 0
if GenApi.is_global_name(trans, nm.id) != 0 or GenApi.is_nonlocal_name(trans, nm.id) != 0: dup_decl = 1
if dup_decl != 0:
self.Gen.TypeGenErr.fatal_error(aa, f"duplicate declaration: '{nm.id}' is already a module-level variable; after declaring global/nonlocal here " f"it is re-declared with a type annotation. Remove the type annotation on this line; a plain assignment modifies the global", "TypeError")
else:
self.Gen.TypeGenErr.fatal_error(aa, f"duplicate declaration: '{nm.id}' is already a module-level variable; without a global declaration in a function, a type annotation must not re-declare " f"the same name (it would shadow the global with a local). For an independent local, use another name; " f"to modify the global, add a global declaration in the function and remove this annotation", "TypeError")
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# Determine the type (inferred from the annotation)
var_ty: mlirlite.LLVMType* = None
annot: ast.AST* = aa.annotation
if annot is not None:
# Value-reference markers are parameter-only: a local variable `x: &T` fails fast.
self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "a local variable declaration")
# Same as PreScan: specialize generics first so list[X] does not degrade to bare list*
self._prespecialize_generic_annotation(trans, pool, annot)
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0)
if resolved is not None: var_ty = resolved
else:
# fail-fast: annotation resolution failed (no silent i32 fallback)
self.Gen.TypeGenErr.fatal_error(aa, self._build_ann_fail_msg(pool, annot, nm.id), "TypeError")
else:
# fail-fast: AnnAssign must have an annotation
self.Gen.TypeGenErr.fatal_error(aa, f"AnnAssign is missing a type annotation, variable name: {nm.id}", "SyntaxError")
# void cannot be a variable type (only void* and opaque-struct typedef)
if var_ty is not None and self.Gen.StmtGenDecl._is_void_type(var_ty) != 0:
self.Gen.TypeGenErr.fatal_error(aa, f"cannot declare a variable of void type '{nm.id}' (void may only be used for void* and typedef aliases)", "TypeError")
# 注解漏 `*` 的 typo:注解解析成"类(值类型)",右值却是取地址表达式(`&x`)。
# 这在类"引用表示"的宽容规则下会被静默接受,随后把栈/元素地址塞进值语义的槽位
# (典型症状:vptr/父指针指向栈区、对象被就地覆写)。明确报错并提示补 `*`。
if var_ty is not None and aa.value is not None and self._is_addr_of_expr(aa.value) == 1:
if var_ty.kind() == mlirlite.LLVMType.Struct:
self.Gen.TypeGenErr.fatal_error(aa, f"type annotation of '{nm.id}' is a class value type but the initializer is an address ('&...'): did you forget '*'? Write the pointer type ('T*') for a pointer target, or initialize with a value (e.g. 'T(...)')", "TypeError")
# t.Callable function pointer: register the return-type signature
callable_ret: mlirlite.LLVMType* = self.Gen.TypeGenErr.try_extract_callable_ret(pool, annot, trans._imported_modules, trans._from_imports, trans)
if callable_ret is not None:
# Nonzero return already reported inside register_func_ptr_sig; this branch is defensive
if self.Gen.CallGenLeafKit.register_func_ptr_sig(pool, nm.id, callable_ret, aa) != 0: return 0
elif aa.value is not None and aa.value.kind() == ast.ASTKind.Name:
# Function-pointer alias: `f2: rawptr = f` inherits f's already-registered return
# type, so a later indirect call `f2()` can rebuild the callee type. The `rawptr`
# annotation carries no callable information of its own, and without this the indirect
# call would fail fast (see _translate_func_ptr_call's "not registered" error).
src_nm: ast.Name* = ast.Name*(aa.value)
if src_nm.id is not None:
src_ret: mlirlite.LLVMType* = self.Gen.CallGenLeafKit.lookup_func_ptr_ret(src_nm.id)
if src_ret is not None:
if self.Gen.CallGenLeafKit.register_func_ptr_sig(pool, nm.id, src_ret, aa) != 0: return 0
# Value-position volatile call prefix: mark the result slot volatile
if aa.value is not None and aa.value.kind() == ast.ASTKind.Call and (ast.Call*(aa.value)).IsVolatile != 0 and nm.id is not None:
if self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id) is not None: self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id)
# global variable: write to the module-scope global
if GenApi.is_global_name(trans, nm.id) != 0:
mod_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
# 栈地址逃逸:`global G; G: T = &local`,或 `G: T* = arr`(数组退让)——与普通
# 赋值同口径,禁止把本帧栈地址写出;目标为同型聚合时按值拷贝,不在此列。
if aa.value is not None:
tgt_aa: mlirlite.LLVMType* = None
if mod_alloca is not None and mod_alloca.Ty is not None: tgt_aa = mod_alloca.Ty.Pointee
GenApi.fail_if_stack_addr_escape(aa.value, "a module-level global", trans, tgt_aa)
if mod_alloca is not None and aa.value is not None:
# P1-A context width propagation: pass the target type so integer literals materialize at target width
aa_glob_ty: mlirlite.LLVMType* = None
if mod_alloca.Ty is not None: aa_glob_ty = mod_alloca.Ty.Pointee
# Set CurAssignAnnot/CurAssignNode before translating the RHS (kept in sync across all four branches)
trans.CurAssignAnnot = aa.annotation
trans.CurAssignNode = aa.value
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, aa_glob_ty, "module-level global")
if rhs_val is not None:
# Object global with static storage: bind storage and copy the object
stg_ref_aa: mlirlite.Value* = None
if nm.id is not None:
stg_key_aa: str = pool.alloc(sizet(string.strlen(nm.id)) + 8)
if stg_key_aa is not None:
viperlib.snprintf(stg_key_aa, sizet(string.strlen(nm.id)) + 8, "%s.__stg", nm.id)
stg_ref_aa = self.Gen.VarGen.lookup_module_var(trans.SymTab, stg_key_aa)
if self._is_addrlike_call(aa.value) == 0 and stg_ref_aa is not None and rhs_val.Ty is not None and stg_ref_aa.Ty is not None and stg_ref_aa.Ty.Pointee != 0:
stg_ty_aa: mlirlite.LLVMType* = stg_ref_aa.Ty.Pointee
rhs_inner_aa: mlirlite.LLVMType* = None
match rhs_val.Ty:
case mlirlite.LLVMType.Ptr(pointee_aa):
rhs_inner_aa = pointee_aa
case _:
pass
if rhs_inner_aa is not None and rhs_inner_aa.kind() == mlirlite.LLVMType.Struct and self._stg_ty_equal(pool, stg_ty_aa, rhs_inner_aa) != 0:
mlirlite.build_store(builder, stg_ref_aa, mod_alloca)
stg_obj_val_aa: mlirlite.Value* = mlirlite.build_load(builder, stg_ty_aa, rhs_val)
if stg_obj_val_aa is not None: mlirlite.build_store(builder, stg_obj_val_aa, stg_ref_aa)
# Clear the assignment context before returning (avoid leaking into later statements)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
target_ty: mlirlite.LLVMType* = None
if mod_alloca.Ty is not None: target_ty = mod_alloca.Ty.Pointee
if target_ty is not None:
# str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node)
rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans)
# t.CVolatile global: the initializing store carries the volatile qualifier
vol_gaa: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
mlirlite.build_store(builder, rhs_val, mod_alloca, vol_gaa)
# Clear the assignment context (all four branches must stay in sync)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
# nonlocal variable: write through the closure env
if GenApi.is_nonlocal_name(trans, nm.id) != 0:
if aa.value is not None:
# P1-A: fetch the nonlocal slot pointer first so its type can be the target type
nl_ptr: mlirlite.Value* = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(trans, nm.id)
# Infer the nonlocal type from nl_ptr instead of forcing i32
nl_target_ty: mlirlite.LLVMType* = None
if nl_ptr is not None and nl_ptr.Ty is not None: nl_target_ty = nl_ptr.Ty.Pointee
# Set CurAssignAnnot/CurAssignNode before translating the RHS (sync across all four branches)
trans.CurAssignAnnot = aa.annotation
trans.CurAssignNode = aa.value
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, nl_target_ty, "closure variable (nonlocal)")
if rhs_val is not None:
if nl_ptr is not None:
if nl_target_ty is None:
# fail-fast: nl_ptr type lost, cannot store safely
VLogger.error(f"nonlocal variable type information lost, cannot write: {nm.id}", "HandlesAnnAssign")
# Clear the assignment context before returning (avoid leaking into later statements)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node)
rhs_coerced: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, nl_target_ty, node)
mlirlite.build_store(builder, rhs_coerced, nl_ptr)
# Clear the assignment context (all four branches must stay in sync)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
# Module-level global: only module code modifies it; in a function without global, create a local
if self.Gen.VarGen.is_module_scope(trans.SymTab) != 0: mod_glob: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
else: mod_glob = None
if mod_glob is not None:
if aa.value is not None:
# List literal: initialize the array with per-element GEP + store
if aa.value.kind() == ast.ASTKind.List: self._init_global_array_from_list(builder, pool, mod, mod_glob, aa.value, trans)
else:
# P1-A: use the module-level global type as the target type
mg_val_ty: mlirlite.LLVMType* = None
if mod_glob.Ty is not None: mg_val_ty = mod_glob.Ty.Pointee
# Set CurAssignAnnot/CurAssignNode before translating the RHS (sync across all four branches)
trans.CurAssignAnnot = aa.annotation
trans.CurAssignNode = aa.value
rhs_val_mg: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, mg_val_ty, "module-level global")
if rhs_val_mg is not None:
# Object global with static storage: bind storage and copy the object
stg_ref_mg: mlirlite.Value* = None
if nm.id is not None:
stg_key_mg: str = pool.alloc(sizet(string.strlen(nm.id)) + 8)
if stg_key_mg is not None:
viperlib.snprintf(stg_key_mg, sizet(string.strlen(nm.id)) + 8, "%s.__stg", nm.id)
stg_ref_mg = self.Gen.VarGen.lookup_module_var(trans.SymTab, stg_key_mg)
if self._is_addrlike_call(aa.value) == 0 and stg_ref_mg is not None and rhs_val_mg.Ty is not None and stg_ref_mg.Ty is not None and stg_ref_mg.Ty.Pointee != 0:
stg_ty_mg: mlirlite.LLVMType* = stg_ref_mg.Ty.Pointee
rhs_inner_mg: mlirlite.LLVMType* = None
match rhs_val_mg.Ty:
case mlirlite.LLVMType.Ptr(pointee_mg):
rhs_inner_mg = pointee_mg
case _:
pass
if rhs_inner_mg is not None and rhs_inner_mg.kind() == mlirlite.LLVMType.Struct and self._stg_ty_equal(pool, stg_ty_mg, rhs_inner_mg) != 0:
mlirlite.build_store(builder, stg_ref_mg, mod_glob)
stg_obj_val_mg: mlirlite.Value* = mlirlite.build_load(builder, stg_ty_mg, rhs_val_mg)
if stg_obj_val_mg is not None: mlirlite.build_store(builder, stg_obj_val_mg, stg_ref_mg)
# Clear the assignment context before returning (avoid leaking into later statements)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
target_ty_mg: mlirlite.LLVMType* = None
if mod_glob.Ty is not None: target_ty_mg = mod_glob.Ty.Pointee
if target_ty_mg is not None:
# str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val_mg, aa.annotation, node)
rhs_val_mg = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val_mg, target_ty_mg, node)
# t.CVolatile module-level global: the initializing store carries volatile
vol_mga: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
mlirlite.build_store(builder, rhs_val_mg, mod_glob, vol_mga)
# Clear the assignment context (all four branches must stay in sync)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
# Single-param CArray + list literal -> static array; match PreScan so the alloca is reused
var_ty = self._infer_static_array_ty_from_literal(pool, annot, aa.value, var_ty)
# [D31 fail-fast] generic annotation degraded to a bare base class
self._fail_on_degenerated_generic_annotation(aa, nm.id, annot, var_ty)
# Ordinary local variable: create the alloca
alloca: mlirlite.Value* = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, nm.id, var_ty)
if alloca is None:
# fail-fast: storage creation failed
self.Gen.TypeGenErr.fatal_error(aa, f"cannot create storage for variable {nm.id}", "TypeError")
# A reused alloca type must match the resolved annotation type (compare struct names)
if alloca is not None and alloca.Ty is not None and var_ty is not None:
av_pt_d: mlirlite.LLVMType* = alloca.Ty.Pointee
if av_pt_d is not None and self.Gen.ExprGenBase.is_ptr_type(av_pt_d) != 0 and self.Gen.ExprGenBase.is_ptr_type(var_ty) != 0:
av_in: mlirlite.LLVMType* = av_pt_d.Pointee
vt_in: mlirlite.LLVMType* = var_ty.Pointee
if av_in is not None and vt_in is not None:
av_nm_d: str = mlirlite.get_struct_name(av_in)
vt_nm_d: str = mlirlite.get_struct_name(vt_in)
if av_nm_d is not None and vt_nm_d is not None and string.strcmp(av_nm_d, vt_nm_d) != 0:
# Fail fast only when the reused pointee is a bare generic base (lost type args)
av_pf: int = GenApi.sha1_prefix_len(av_nm_d)
av_short: str = av_nm_d + av_pf if av_pf != 0 else av_nm_d
if string.strchr(av_short, '[') is None:
av_tpl: ast.ClassDef* = self.Gen.ClassGenGeneric._find_generic_template(av_short)
if av_tpl is not None:
self.Gen.TypeGenErr.fatal_error(aa, f"the reused storage type of variable '{nm.id}' is the bare generic base class '{av_nm_d}*'" f" (type arguments lost), inconsistent with the annotation-resolved type '{vt_nm_d}*'; " f"silent mixing is forbidden -- method calls would specialize by the wrong element type and truncate the pointer", "TypeError")
# FakeDuck: a str variable gets a shadow struct {var}__meta__ for per-instance fields
fd_str_ty: mlirlite.LLVMType* = None
fd_meta_alloca: mlirlite.Value* = None
fd_meta_name: str = None
if annot is not None and annot.kind() == ast.ASTKind.Name:
fd_annot: ast.Name* = ast.Name*(annot)
if fd_annot is not None and fd_annot.id is not None:
if string.strcmp(fd_annot.id, "str") == 0: fd_str_ty = self.Gen.StructGen.get_struct_type("_str")
if fd_str_ty is not None:
# Ensure the full _str definition is available (opaque types cannot be alloca'd)
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, fd_str_ty)
# Shadow variable name must use a pool buffer (f-string stack buffers get overwritten)
fd_meta_name = pool.alloc(256)
if fd_meta_name is not None: viperlib.snprintf(fd_meta_name, 256, "%s__meta__", nm.id)
fd_meta_alloca = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, fd_meta_name, fd_str_ty)
if fd_meta_alloca is None:
# fail-fast: shadow storage creation failed
self.Gen.TypeGenErr.fatal_error(aa, f"cannot create storage for the str shadow variable {fd_meta_name}", "TypeError")
# Store the annotation's class name; pass pool so generic Subscript yields the mangled name
if annot is not None:
cls_nm_hd: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(annot, trans._imported_modules, pool, trans._from_imports)
if cls_nm_hd is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, nm.id, cls_nm_hd)
# Check for bytes* or str* (i8** semantics, 8-byte stride)
if self._is_ptr_element_annotation(annot) != 0: self.Gen.VarGen.set_var_ptr_element(trans.SymTab, nm.id)
# Byte-order marker (`T | t.BigEndian` / `T | t.LittleEndian`): register the variable's byte
# order so the local load/store paths bswap (mirrors ClassGenDef's field-level detection).
# A bare `t.BigEndian` annotation resolves to i32 upstream, so registration is unconditional.
if self.Gen.TypeGenErr.has_decorator_marker(annot, "BigEndian", 0) != 0: self.Gen.VarGen.set_var_byte_order(trans.SymTab, nm.id, 1)
elif self.Gen.TypeGenErr.has_decorator_marker(annot, "LittleEndian", 0) != 0: self.Gen.VarGen.set_var_byte_order(trans.SymTab, nm.id, 2)
new_vars: int = 0
existing: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, nm.id)
if existing is None: new_vars = 1
# If there is an initial value, store it
if aa.value is not None:
# Coroutine: record the variable name assigned from an async call (for frame-type lookup)
if aa.value.kind() == ast.ASTKind.Call:
val_cl: ast.Call* = ast.Call*(aa.value)
if val_cl.func is not None and val_cl.func.kind() == ast.ASTKind.Name:
val_nm: ast.Name* = ast.Name*(val_cl.func)
if val_nm.id is not None:
if self.Gen.AsyncGen.is_async_func(trans, val_nm.id) != 0: self.Gen.AsyncGen.register_coro_var(trans, nm.id, val_nm.id)
# c.Addr(async_call): interpret the tt: Coroutine = c.Addr(task()) form in place
elif val_cl.func is not None and val_cl.func.kind() == ast.ASTKind.Attribute:
val_at: ast.Attribute* = ast.Attribute*(val_cl.func)
if val_at.attr is not None and string.strcmp(val_at.attr, "Addr") == 0 and val_cl.args is not None and len(val_cl.args) >= 1:
icall: ast.AST* = val_cl.args.get(0)
if icall is not None and icall.kind() == ast.ASTKind.Call:
icl2: ast.Call* = ast.Call*(icall)
if icl2.func is not None and icl2.func.kind() == ast.ASTKind.Name:
inm2: ast.Name* = ast.Name*(icl2.func)
if inm2.id is not None:
if self.Gen.AsyncGen.is_async_func(trans, inm2.id) != 0: self.Gen.AsyncGen.register_coro_var(trans, nm.id, inm2.id)
# Local array from a list literal: zero it first, then per-element GEP + store (no list[T])
local_arr_ty: mlirlite.LLVMType* = None
if aa.value.kind() == ast.ASTKind.List and alloca.Ty is not None:
pt_l: mlirlite.LLVMType* = alloca.Ty.Pointee
if self.Gen.ExprGenBase.is_array_type(pt_l) != 0: local_arr_ty = pt_l
if local_arr_ty is not None:
zero_arr: mlirlite.Value* = mlirlite.ConstZero(pool, local_arr_ty)
if zero_arr is not None: mlirlite.build_store(builder, zero_arr, alloca)
# BigEndian array variable: pass its byte order so the literal elements are stored
# byte-swapped (pairs the un-swap on element read).
arr_list_bo: int = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
self._init_global_array_from_list(builder, pool, mod, alloca, aa.value, trans, None, arr_list_bo)
else:
# Set CurAssignAnnot/CurAssignNode for bare generic construction (sync across all four branches)
trans.CurAssignAnnot = aa.annotation
trans.CurAssignNode = aa.value
# P1-A: use the local alloca pointee as the target type so integer literals materialize at target width
lv_target_ty: mlirlite.LLVMType* = None
if alloca.Ty is not None: lv_target_ty = alloca.Ty.Pointee
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, lv_target_ty, "local variable annotation")
if rhs_val is None:
# fail-fast: 右值翻译失败时绝不能静默跳过整条声明(此前直接落到 `if rhs_val is
# not None` 之外,变量永远保持未初始化,运行期读到栈垃圾后断言崩溃)。
self.Gen.TypeGenErr.fatal_error(aa, f"the right-hand side of the declaration of '{nm.id}' could not be translated (type inference failed; see the preceding diagnostic)", "TypeError")
if rhs_val is not None:
# Fix degraded annotation: rebuild the alloca with the precise RHS struct type
precise_rhs_ty: mlirlite.LLVMType* = None
if rhs_val.Ty is not None:
match rhs_val.Ty:
case mlirlite.LLVMType.Ptr(inner_struct):
match inner_struct:
case mlirlite.LLVMType.Struct(_, _, _):
precise_rhs_ty = rhs_val.Ty
case _:
pass
case _:
pass
if precise_rhs_ty is not None and alloca.Ty is not None:
alloca_pt: mlirlite.LLVMType* = alloca.Ty.Pointee
# Forbid downgrade rebuild: do not rebuild when the RHS pointee is a bare generic base
ann_gen_base: str = None
if annot is not None:
ag_side: ast.AST* = annot
if ag_side.kind() == ast.ASTKind.BinOp:
ag_bop: ast.BinOp* = ast.BinOp*(ag_side)
if ag_bop.op == ast.OpKind.BitOr:
if ag_bop.left is not None and ag_bop.left.kind() == ast.ASTKind.Subscript: ag_side = ag_bop.left
elif ag_bop.right is not None and ag_bop.right.kind() == ast.ASTKind.Subscript: ag_side = ag_bop.right
if ag_side.kind() == ast.ASTKind.Subscript:
ag_sub: ast.Subscript* = ast.Subscript*(ag_side)
if ag_sub.value is not None and ag_sub.value.kind() == ast.ASTKind.Name:
ag_nm: ast.Name* = ast.Name*(ag_sub.value)
if ag_nm.id is not None: ann_gen_base = ag_nm.id
rb_is_bare_generic: int = 0
if alloca_pt is not None and uint64(alloca_pt) != uint64(precise_rhs_ty):
rhs_pt_rb: mlirlite.LLVMType* = precise_rhs_ty.Pointee
if rhs_pt_rb is not None:
rb_nm: str = mlirlite.get_struct_name(rhs_pt_rb)
if rb_nm is not None:
rb_pf: int = GenApi.sha1_prefix_len(rb_nm)
rb_short: str = rb_nm + rb_pf if rb_pf != 0 else rb_nm
if string.strchr(rb_short, '[') is None:
# (1) The bare name hits the generic template registry (list/GSList/...)
if self.Gen.ClassGenGeneric._find_generic_template(rb_short) is not None: rb_is_bare_generic = 1
# (2) Same base name as the annotation => rebuild would downgrade; forbid it
elif ann_gen_base is not None and string.strcmp(rb_short, ann_gen_base) == 0: rb_is_bare_generic = 1
# Forbid narrowing an upcast: annotated base pointer with an RHS derived pointer must not be narrowed
rb_is_upcast: int = 0
if alloca_pt is not None and uint64(alloca_pt) != uint64(precise_rhs_ty) and self.Gen.ExprGenBase.is_ptr_type(alloca_pt) != 0:
ann_pt_rb: mlirlite.LLVMType* = alloca_pt.Pointee
rhs_pt_up: mlirlite.LLVMType* = precise_rhs_ty.Pointee
if ann_pt_rb is not None and rhs_pt_up is not None and uint64(ann_pt_rb) != uint64(rhs_pt_up):
if self.Gen.ExprGenBase._is_inheritance_upcast(pool, rhs_pt_up, ann_pt_rb) != 0: rb_is_upcast = 1
# 注解解析为**无 vtable 的聚合值**(纯数据 struct / array)时,绝不把槽重建为指针:
# `x: V3 = V3(...)` 的 RHS 是构造器返回的 Ptr(V3)(只是 ABI 细节,`_translate_struct_ctor`
# 一律返回指针),若据此重建,x 的槽会变成 Ptr(V3),于是 `x = y` 编译成 `store ptr &y, x_slot`
# —— x 成为 y 的**别名**,y 的后续写入会污染 x(现场:Gargantua 的 pos = npos 之后
# `pos.y * npos.y < 0` 恒不成立,吸积盘永不绘制)。值语义槽必须保持聚合类型,
# 由下方的 coerce_to_type 把 Ptr(T) 做 load 得到一份独立的值拷贝。
# 边界:**有 vtable 的多态类必须继续用指针槽** —— 虚调用/闭包捕获等路径依赖
# 「槽里就是对象指针」(对聚合值 bitcast 到 ptr 是非法 IR),故只对无 vtable 的类放宽。
ann_agg_value: int = 0
if alloca_pt is not None and self.Gen.ExprGenBase.is_ptr_type(alloca_pt) == 0:
ak_av: int = alloca_pt.kind()
if ak_av == mlirlite.LLVMType.Struct:
av_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(alloca_pt)
if av_entry is None or av_entry.HasVTable == 0: ann_agg_value = 1
elif ak_av == mlirlite.LLVMType.Array: ann_agg_value = 1
if alloca_pt is not None and uint64(alloca_pt) != uint64(precise_rhs_ty) and rb_is_bare_generic == 0 and rb_is_upcast == 0 and ann_agg_value == 0:
new_alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, precise_rhs_ty)
if new_alloca is not None:
v_entry_rb: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if v_entry_rb is not None:
v_entry_rb.Alloca = new_alloca
alloca = new_alloca
# Type coercion: convert rhs_val to the alloca pointee (integer literals may need trunc/sext)
target_ty: mlirlite.LLVMType* = None
if alloca.Ty is not None: target_ty = alloca.Ty.Pointee
if target_ty is not None:
# str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node)
rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans)
# t.CVolatile local variable: mark the VarEntry and store with volatile
vol_laa: int = 0
if aa.annotation is not None and nm.id is not None:
# const object local variable: mark VarEntry.IsConst (idempotent with prescan)
if self.Gen.TypeGenErr.is_const_object_marker(aa.annotation, target_ty) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, nm.id)
if self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "CVolatile", 0) != 0:
self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id)
vol_laa = 1
# rawptr[t.CVolatile | T]: mark PointeeVolatile (this store writes the pointer itself)
if self.Gen.TypeGenErr.has_pointee_volatile_marker(aa.annotation) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, nm.id)
# Value-position volatile call prefix: the result slot is accessed with volatile
if aa.value is not None and aa.value.kind() == ast.ASTKind.Call and (ast.Call*(aa.value)).IsVolatile != 0:
self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id)
vol_laa = 1
# M3 所有权记账:必须**先**记账再写值——覆盖释放要读到槽里的旧值
# (`p: T* = new heap slife …` → 释放旧对象 + 影子置 1;`q: T* = p` → 交还所有权)
self._slife_note_assign(pool, builder, mod, trans, alloca, aa.value, nm.id)
# BigEndian local: store the byte-swapped value so the slot is big-endian in memory
# (pairs the un-swap done by ExprGenName.translate_name_value on the read path).
decl_bo: int = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
rhs_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, rhs_val, decl_bo)
mlirlite.build_store(builder, rhs_val, alloca, vol_laa)
# P2 willFree:带注解声明/赋值后清除失效态(新变量本就为 0;复用槽兜底)
self.Gen.VarGen.clear_var_freed(trans.SymTab, nm.id)
# FakeDuck: initialize the shadow struct __data__ field with the initial string pointer
if fd_str_ty is not None and fd_meta_alloca is not None:
fd_data_fe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_str_ty, "__data__")
if fd_data_fe is not None:
fd_data_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, fd_str_ty, fd_data_fe.Ty, fd_meta_alloca, fd_data_fe.Index)
if fd_data_ptr is not None: mlirlite.build_store(builder, rhs_val, fd_data_ptr)
# Clear the assignment context after the RHS translation (sync across all four branches)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
# FakeDuck: initialize the __mbuddy__ field to NULL
if fd_str_ty is not None and fd_meta_alloca is not None:
fd_buddy_fe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_str_ty, "__mbuddy__")
if fd_buddy_fe is not None and fd_buddy_fe.Ty is not None:
fd_null: mlirlite.Value* = mlirlite.ConstNull(pool, fd_buddy_fe.Ty, "null")
fd_buddy_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, fd_str_ty, fd_buddy_fe.Ty, fd_meta_alloca, fd_buddy_fe.Index)
if fd_buddy_ptr is not None and fd_null is not None: mlirlite.build_store(builder, fd_null, fd_buddy_ptr)
# FakeDuck: auto-inject requires fields (e.g. __mbuddy__) for the with context
self.Gen.CallGenFd._inject_requires_fields(trans, pool, builder, mod, fd_meta_alloca, "_str")
# 生命期传播(本地变量):`s: str = l` → Life(s) = Life(l)(数组退让即借用)
if aa.value is not None and nm.id is not None and alloca is not None:
tgt_life_ty: mlirlite.LLVMType* = None
if alloca.Ty is not None: tgt_life_ty = alloca.Ty.Pointee
self.propagate_life(trans, target, aa.value, tgt_life_ty)
return 0
# _translate_lvalue_annassign - 注解赋值的**属性/下标目标**:`obj.f: T = v` / `arr[i]: T = v`。
# 与 Name 目标同口径:注解是源码级权威类型(RHS 以它为靶型求值);并且必须过 P3
# "长命左值不得接收本帧栈地址"的检查(= propagate_life 的非 Name 分支)。
# 存储目标复用既有左值指针求解:Attribute → get_attribute_ptr,Subscript → get_subscript_ptr。
def _translate_lvalue_annassign(self, trans: HT.Translator*, aa: ast.AnnAssign*, target: ast.AST*) -> int:
"""注解赋值的属性/下标目标;0=完成"""
if trans is None or aa is None or target is None: return 0
tk: int = target.kind()
if tk != ast.ASTKind.Attribute and tk != ast.ASTKind.Subscript:
self.Gen.TypeGenErr.fatal_error(target, "the target of an annotated assignment must be a variable, an attribute or a subscript", "SyntaxError")
return 0
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
annot: ast.AST* = aa.annotation
if annot is None:
self.Gen.TypeGenErr.fatal_error(aa, "AnnAssign is missing a type annotation", "SyntaxError")
return 0
# 与 Name 路径同口径解析注解:先特化泛型(避免 list[X] 退化成裸 list*),解析失败即 fail-fast。
self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "an annotated assignment to an attribute/subscript")
self._prespecialize_generic_annotation(trans, pool, annot)
ann_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0)
if ann_ty is None:
self.Gen.TypeGenErr.fatal_error(aa, "cannot resolve the annotation type of an annotated assignment to an attribute/subscript", "TypeError")
return 0
if self.Gen.StmtGenDecl._is_void_type(ann_ty) != 0:
self.Gen.TypeGenErr.fatal_error(aa, "cannot assign to an attribute/subscript of void type (void may only be used for void* and typedef aliases)", "TypeError")
return 0
# 左值存储指针:属性/下标各走既有求解器。失败一律 fail-fast——绝不静默丢掉整条语句。
is_attr: int = 0
ptr: mlirlite.Value* = None
if tk == ast.ASTKind.Attribute:
is_attr = 1
at_lv: ast.Attribute* = ast.Attribute*(target)
if at_lv.attr is not None and self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, target) != 0:
self.const_assign_fail(target, "field assignment", at_lv.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, target))
ptr = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, target, trans)
else:
ptr = self.Gen.ExprGenName.get_subscript_ptr(builder, pool, mod, target, trans)
if ptr is None or ptr.Ty is None or ptr.Ty.Pointee == 0:
self.Gen.TypeGenErr.fatal_error(target, "cannot resolve the storage of the annotated assignment target (the attribute/subscript pointer or its element type is missing)", "TypeError")
return 0
target_ty: mlirlite.LLVMType* = ptr.Ty.Pointee
# P3:左值存储比右值长命时,右值不得交出本帧栈地址(如 `h.p: T* = &local`)。
self._fail_if_lvalue_outlives_rhs(trans, target, aa.value, target_ty)
# 与普通 Assign 同口径:f-string 结果是函数内临时量,写出即悬垂。
if aa.value is not None:
if is_attr != 0: GenApi.fail_if_fstring_escape(aa.value, "field assignment")
else: GenApi.fail_if_fstring_escape(aa.value, "element assignment")
# RHS 以注解靶型求值(CurAssignAnnot/CurAssignNode 供裸泛型构造推断;求值完即复位,不跨语句泄漏)。
trans.CurAssignAnnot = annot
trans.CurAssignNode = aa.value
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, ann_ty, "annotated assignment to an attribute/subscript")
trans.CurAssignAnnot = None
trans.CurAssignNode = None
if rhs_val is None:
self.Gen.TypeGenErr.fatal_error(aa, "the right-hand side of the annotated assignment to an attribute/subscript could not be translated", "TypeError")
return 0
store_val: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, aa, trans)
if store_val is None:
self.Gen.TypeGenErr.fatal_error(aa, "type coercion failed for the annotated assignment to an attribute/subscript", "TypeError")
return 0
# BigEndian field / array element: byte-swap before the store (pairs the read paths).
bo_lv: int = 0
if is_attr != 0:
bo_lv = self.Gen.ExprGenAttr.attr_field_byte_order(pool, trans, target)
else:
sub_lv_b: ast.Subscript* = ast.Subscript*(target)
if sub_lv_b.value is not None and sub_lv_b.value.kind() == ast.ASTKind.Name:
sub_nm_lv: ast.Name* = ast.Name*(sub_lv_b.value)
if sub_nm_lv.id is not None: bo_lv = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, sub_nm_lv.id))
store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, bo_lv)
# t.CVolatile 字段 / rawptr[t.CVolatile|T] 基址:写出同样带 volatile。
vol_lv: int = 0
if is_attr != 0:
vol_lv = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target)
else:
sub_lv: ast.Subscript* = ast.Subscript*(target)
if sub_lv.value is not None: vol_lv = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, sub_lv.value)
mlirlite.build_store(builder, store_val, ptr, vol_lv)
return 0
# translate_assign - handle an Assign statement; returns the number of new variables
def translate_assign(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate an Assign statement; return the number of new variables."""
asgn: ast.Assign* = ast.Assign*(node)
if asgn is None:
VLogger.error("cast failed", "ASGN")
return 0
targets: list[ast.AST*]* = asgn.targets
if targets is None:
VLogger.error("targets is None", "ASGN")
return 0
# Read shared state from trans (replaces 11 parameters)
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# const target: fail fast before assignment (Name/Attribute/Subscript)
for ti_c in range(int(len(targets))):
tgt_c: ast.AST* = targets.get(ti_c)
if tgt_c is None: continue
if tgt_c.kind() == ast.ASTKind.Name:
nm_c: ast.Name* = ast.Name*(tgt_c)
if nm_c.id is None: continue
ve_c: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_c.id)
if self.Gen.VarGen.var_is_const(ve_c) != 0:
self.const_assign_fail(tgt_c, "assignment", nm_c.id, self.Gen.VarGen.var_annot_class_name(ve_c))
elif tgt_c.kind() == ast.ASTKind.Attribute:
at_c: ast.Attribute* = ast.Attribute*(tgt_c)
if at_c.attr is None: continue
if self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, tgt_c) != 0:
self.const_assign_fail(tgt_c, "field assignment", at_c.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, tgt_c))
elif tgt_c.kind() == ast.ASTKind.Subscript:
sub_c: ast.Subscript* = ast.Subscript*(tgt_c)
if sub_c.value is None: continue
if sub_c.value.kind() == ast.ASTKind.Name:
nm_c2: ast.Name* = ast.Name*(sub_c.value)
if nm_c2.id is None: continue
ve_c2: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_c2.id)
if self.Gen.VarGen.var_is_const(ve_c2) != 0:
self.const_assign_fail(tgt_c, "element assignment", nm_c2.id, self.Gen.VarGen.var_annot_class_name(ve_c2))
elif sub_c.value.kind() == ast.ASTKind.Attribute:
at_c2: ast.Attribute* = ast.Attribute*(sub_c.value)
if at_c2.attr is None: continue
if self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, sub_c.value) != 0:
self.const_assign_fail(tgt_c, "element assignment", at_c2.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, sub_c.value))
# f-string escape: results are function-local temporaries; writing them out is dangling
for ti_f in range(int(len(targets))):
tgt_f: ast.AST* = targets.get(ti_f)
if tgt_f is None: continue
tk_f: int = tgt_f.kind()
if tk_f == ast.ASTKind.Attribute: GenApi.fail_if_fstring_escape(asgn.value, "field assignment")
elif tk_f == ast.ASTKind.Subscript: GenApi.fail_if_fstring_escape(asgn.value, "element assignment")
elif tk_f == ast.ASTKind.Name:
nm_f: ast.Name* = ast.Name*(tgt_f)
if nm_f.id is not None and GenApi.is_global_name(trans, nm_f.id) != 0:
GenApi.fail_if_fstring_escape(asgn.value, "global variable assignment")
# 栈地址逃逸:`G = &local` 与 `G = arr`(G 为指针时的数组退让)都把本帧栈地址
# 写进模块级全局,函数返回后悬垂;`G = arr` 且 G 为同型聚合时是值拷贝,不在此列。
gv_sa: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm_f.id)
tgt_sa: mlirlite.LLVMType* = None
if gv_sa is not None and gv_sa.Ty is not None: tgt_sa = gv_sa.Ty.Pointee
GenApi.fail_if_stack_addr_escape(asgn.value, "a module-level global", trans, tgt_sa)
# Translate the RHS; clear CurAssignAnnot/CurAssignNode (no target type to infer here)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
# P1-A: for a single Name target with a known storage type, pass it as the target type
asg_target_ty: mlirlite.LLVMType* = None
if len(targets) == 1:
asg_t0: ast.AST* = targets.get(0)
if asg_t0 is not None and asg_t0.kind() == ast.ASTKind.Name:
asg_nm0: ast.Name* = ast.Name*(asg_t0)
if asg_nm0.id is not None:
asg_slot: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, asg_nm0.id)
if asg_slot is None: asg_slot = self.Gen.VarGen.lookup_module_var(trans.SymTab, asg_nm0.id)
if asg_slot is not None and asg_slot.Ty is not None: asg_target_ty = asg_slot.Ty.Pointee
elif trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0:
# REPL: a typed session variable also uses its slot type as the target type
asg_re: GenTypes.ReplVarEntry* = self.Gen.VarGen.repl_find_entry(trans.ReplCtx, asg_nm0.id)
if asg_re is not None and asg_re.Kind != GenTypes.REPL_KIND_UNTYPED: asg_target_ty = self.Gen.VarGen.repl_slot_ty(pool, asg_re)
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, asgn.value, None, 0, trans, asg_target_ty)
if rhs_val is None:
# RHS is None usually means allocation failure; dump both pool statistics
mb_diag: memhub.MemBuddy* = CompilerFactory.factory.Pool
pp_diag: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if mb_diag is not None: mb_diag.dump_stats("Main Pool(Pool)")
if pp_diag is not None: pp_diag.dump_stats("Permanent Pool(PermPool)")
stdio.fflush(None)
# Enhanced error message: file name + lineno + AST node details
if asgn.value is not None:
sha1: str = trans.ModuleSha1
val_kind: int = asgn.value.kind()
val_line: int = asgn.value.lineno
# Look up the file name from sha1 (human readable)
rel_path: str = None
if sha1 is not None: rel_path = StubMergerStore.LookupSha1RelPath(sha1)
# Extract details by AST node kind
if val_kind == ast.ASTKind.Attribute:
at_nv: ast.Attribute* = ast.Attribute*(asgn.value)
attr_nm: str = at_nv.attr
target_nm: str = "?"
if at_nv.value is not None:
if at_nv.value.kind() == ast.ASTKind.Name:
tn_nm: ast.Name* = ast.Name*(at_nv.value)
target_nm = tn_nm.id
if rel_path is not None: VLogger.error(f"assignment rhs is None: {rel_path}:{val_line} attribute access of '{target_nm}.{attr_nm}' failed", "ASGN")
else: VLogger.error(f"assignment rhs is None: sha1={sha1}:{val_line} attribute access of '{target_nm}.{attr_nm}' failed", "ASGN")
elif val_kind == ast.ASTKind.Name:
nm_nv: ast.Name* = ast.Name*(asgn.value)
if rel_path is not None: VLogger.error(f"assignment rhs is None: {rel_path}:{val_line} variable '{nm_nv.id}' not found", "ASGN")
else: VLogger.error(f"assignment rhs is None: sha1={sha1}:{val_line} variable '{nm_nv.id}' not found", "ASGN")
elif val_kind == ast.ASTKind.Call:
cl_nv: ast.Call* = ast.Call*(asgn.value)
func_nm: str = "?"
if cl_nv.func is not None:
if cl_nv.func.kind() == ast.ASTKind.Name:
fn_nm: ast.Name* = ast.Name*(cl_nv.func)
func_nm = fn_nm.id
if rel_path is not None: VLogger.error(f"assignment rhs is None: {rel_path}:{val_line} call '{func_nm}(...)' returned None", "ASGN")
else: VLogger.error(f"assignment rhs is None: sha1={sha1}:{val_line} call '{func_nm}(...)' returned None", "ASGN")
else:
# 必须与相邻分支一致显式传 category:跨模块模块级函数调用目前不会补默认值,
# 漏传会生成操作数不足的 llvm.call(MLIR 校验直接失败)。
if rel_path is not None: VLogger.error(f"assignment rhs is None: {rel_path}:{val_line} [kind={val_kind}]", "ASGN")
else: VLogger.error(f"assignment rhs is None: sha1={sha1}:{val_line} [kind={val_kind}]", "ASGN")
else: VLogger.error("assignment rhs is None (asgn.value is empty)", "ASGN")
return 0
# 生命期传播(单 Name 目标):`s = l` → Life(s) = Life(l)
if len(targets) == 1:
t0_sl: ast.AST* = targets.get(0)
self.propagate_life(trans, t0_sl, asgn.value, asg_target_ty)
new_vars: int = 0
tn: sizet = len(targets)
for ti in range(int(tn)):
target: ast.AST* = targets.get(ti)
if target is None: continue
tk: int = target.kind()
# Tuple/List assignment: unpack a struct return value into Name targets
if tk == ast.ASTKind.Tuple or tk == ast.ASTKind.List:
tgt_tup: ast.Tuple* = ast.Tuple*(target)
tgt_elts: list[ast.AST*]* = tgt_tup.elts
# Guard: skip this target when rhs is not a struct (tuple)
if tgt_elts is None or rhs_val is None or rhs_val.Ty is None: continue
match rhs_val.Ty:
case mlirlite.LLVMType.Struct(_, _, _):
tgt_n: sizet = len(tgt_elts)
tmp_s: mlirlite.Value* = mlirlite.build_alloca(builder, rhs_val.Ty)
if tmp_s is not None:
mlirlite.build_store(builder, rhs_val, tmp_s)
fchain: mlirlite.ParamNode* = mlirlite.get_struct_fields(rhs_val.Ty)
fcur: mlirlite.ParamNode* = fchain
fti: sizet
for fti in range(int(tgt_n)):
tgt_elt: ast.AST* = tgt_elts.get(fti)
if tgt_elt is None or fcur is None: break
fty: mlirlite.LLVMType* = mlirlite.paramnode_get_ty(fcur)
fcur = mlirlite.paramnode_get_next(fcur)
if fty is None:
# fail-fast: field type missing
self.Gen.TypeGenErr.fatal_error(target, "tuple-unpack field type missing", "TypeError")
# Guard: skip non-Name elements (cannot unpack)
if tgt_elt.kind() != ast.ASTKind.Name: continue
tgt_nm2: ast.Name* = ast.Name*(tgt_elt)
if tgt_nm2.id is None: continue
fp2: mlirlite.Value* = mlirlite.build_gep_struct(builder, rhs_val.Ty, fty, tmp_s, fti)
if fp2 is None: continue
fv2: mlirlite.Value* = mlirlite.build_load(builder, fty, fp2)
tgt_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, tgt_nm2.id)
if tgt_alloca is None: tgt_alloca = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, tgt_nm2.id, fty)
if tgt_alloca is None: continue
sval: mlirlite.Value* = fv2
if tgt_alloca.Ty is not None and tgt_alloca.Ty.Pointee != 0: sval = self.Gen.ExprGenBase.coerce_to_type(builder, fv2, tgt_alloca.Ty.Pointee, node, trans)
mlirlite.build_store(builder, sval, tgt_alloca)
case _:
pass
continue
# Subscript assignment: arr[i] = val / ptr[i] = val / list[i] = val
if tk == ast.ASTKind.Subscript:
# Check for a list[T] Subscript; list subscript assignment uses the inline __setitem__ path
list_obj: mlirlite.Value* = self.Gen.ExprGenName.is_list_subscript(target, trans)
if list_obj is not None:
# list[T]: generate the __setitem__ logic inline
sub_node: ast.Subscript* = ast.Subscript*(target)
if sub_node is not None and sub_node.slice is not None:
list_idx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_node.slice, None, 0, trans)
if list_idx_val is None:
# fail-fast: list index translation failed
self.Gen.TypeGenErr.fatal_error(target, "list index translation failed", "TypeError")
self.Gen.ExprGenName.list_setitem_inline(builder, pool, list_obj, list_idx_val, rhs_val, target)
continue
# Ordinary Subscript assignment
elem_ptr: mlirlite.Value* = self.Gen.ExprGenName.get_subscript_ptr(builder, pool, mod, target, trans)
if elem_ptr is not None:
store_val: mlirlite.Value* = rhs_val
# Fail fast when a rawptr[T] element is an object but RHS is a T pointer (whole-object copy)
self._reject_whole_object_copy_into_ptr_slot(pool, trans, target, elem_ptr, rhs_val)
# Guard: coerce only when the element type is complete
if elem_ptr.Ty is not None and elem_ptr.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, elem_ptr.Ty.Pointee, node, trans)
# BigEndian array variable (base is a Name with a registered byte order): store the
# byte-swapped element so the slot is big-endian in memory (pairs the element read path).
bo_sw: int = 0
sub_wr: ast.Subscript* = ast.Subscript*(target)
if sub_wr is not None and sub_wr.value is not None and sub_wr.value.kind() == ast.ASTKind.Name:
sub_nm_sw: ast.Name* = ast.Name*(sub_wr.value)
if sub_nm_sw.id is not None: bo_sw = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, sub_nm_sw.id))
store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, bo_sw)
# rawptr[t.CVolatile|T] base: p[i] = v stores with volatile when the base is resolvable
vol_sw: int = 0
if sub_wr is not None: vol_sw = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, sub_wr.value)
mlirlite.build_store(builder, store_val, elem_ptr, vol_sw)
continue
# get_subscript_ptr returned None: try the __setitem__ overload for custom classes
sub_asgn: ast.Subscript* = ast.Subscript*(target)
setitem_done: int = 0
if sub_asgn is not None and sub_asgn.value is not None:
if sub_asgn.value.kind() == ast.ASTKind.Name:
sub_nm: ast.Name* = ast.Name*(sub_asgn.value)
if sub_nm.id is not None:
sub_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, sub_nm.id)
if sub_alloca is not None and sub_alloca.Ty is not None and sub_alloca.Ty.Pointee != 0 and self.Gen.ExprGenBase.is_ptr_type(sub_alloca.Ty) != 0:
sub_pointee: mlirlite.LLVMType* = sub_alloca.Ty.Pointee
cls_nm_set: str = self.Gen.StructGen.get_class_name_by_type(pool, sub_pointee)
obj_val_set: mlirlite.Value* = None
# 值类型对象槽(`d: dict = dict(bd)`):槽地址就是 self 指针。
# 此前无条件 load,值类型会传出整个 struct 值当 self(非法),
# 指针类型才需要先 load 出对象指针。
if cls_nm_set is not None: obj_val_set = sub_alloca
else:
obj_val_set = mlirlite.build_load(builder, sub_pointee, sub_alloca)
# Pointer variable (X*): sub_pointee is Ptr(Struct); load it to inspect the inner struct
if obj_val_set is not None and self.Gen.ExprGenBase.is_ptr_type(sub_pointee) != 0 and sub_pointee.Pointee != 0:
cls_nm_set = self.Gen.StructGen.get_class_name_by_type(pool, sub_pointee.Pointee)
if cls_nm_set is not None and obj_val_set is not None:
key_val_set: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_asgn.slice, None, 0, trans)
if key_val_set is not None:
arg_vals_set: sizet* = pool.alloc(16)
if arg_vals_set is not None:
arg_vals_set[0] = sizet(key_val_set)
arg_vals_set[1] = sizet(rhs_val)
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_set, "__setitem__", obj_val_set, arg_vals_set, 2, trans)
setitem_done = 1
elif sub_asgn.value.kind() == ast.ASTKind.Attribute:
# self._ht[key] = val -> get the field pointer then call __setitem__
field_ptr_set: mlirlite.Value* = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, sub_asgn.value, trans)
if field_ptr_set is not None and field_ptr_set.Ty is not None and field_ptr_set.Ty.Pointee != 0 and self.Gen.ExprGenBase.is_ptr_type(field_ptr_set.Ty) != 0:
field_pointee_set: mlirlite.LLVMType* = field_ptr_set.Ty.Pointee
cls_nm_attr: str = self.Gen.StructGen.get_class_name_by_type(pool, field_pointee_set)
obj_val_attr: mlirlite.Value* = field_ptr_set
# field_ptr is Ptr(Ptr(Struct)) for an X* field: load to get Ptr(Struct)
if cls_nm_attr is None and self.Gen.ExprGenBase.is_ptr_type(field_pointee_set) != 0 and field_pointee_set.Pointee != 0:
cls_nm_attr = self.Gen.StructGen.get_class_name_by_type(pool, field_pointee_set.Pointee)
if cls_nm_attr is not None: obj_val_attr = mlirlite.build_load(builder, field_pointee_set, field_ptr_set)
if cls_nm_attr is not None and obj_val_attr is not None:
key_val_attr: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_asgn.slice, None, 0, trans)
if key_val_attr is not None:
arg_vals_attr: sizet* = pool.alloc(16)
if arg_vals_attr is not None:
arg_vals_attr[0] = sizet(key_val_attr)
arg_vals_attr[1] = sizet(rhs_val)
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_attr, "__setitem__", obj_val_attr, arg_vals_attr, 2, trans)
setitem_done = 1
if setitem_done == 0:
# Generic fallback: translate sub.value and try __setitem__ or a direct GEP+store
if sub_asgn is not None and sub_asgn.value is not None:
obj_val_gen: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_asgn.value, None, 0, trans)
key_val_gen: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_asgn.slice, None, 0, trans)
if obj_val_gen is not None and key_val_gen is not None and obj_val_gen.Ty is not None:
# Inline the _get_custom_struct_cls_nm logic (avoid a new dependency)
cls_nm_gen: str = self.Gen.StructGen.get_class_name_by_type(pool, obj_val_gen.Ty)
if cls_nm_gen is None and self.Gen.ExprGenBase.is_ptr_type(obj_val_gen.Ty) != 0 and obj_val_gen.Ty.Pointee != 0:
cls_nm_gen = self.Gen.StructGen.get_class_name_by_type(pool, obj_val_gen.Ty.Pointee)
if cls_nm_gen is not None:
# Custom struct: call __setitem__
arg_vals_gen: sizet* = pool.alloc(16)
if arg_vals_gen is not None:
arg_vals_gen[0] = sizet(key_val_gen)
arg_vals_gen[1] = sizet(rhs_val)
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_gen, "__setitem__", obj_val_gen, arg_vals_gen, 2, trans)
setitem_done = 1
elif self.Gen.ExprGenBase.is_ptr_type(obj_val_gen.Ty) != 0 and obj_val_gen.Ty.Pointee != 0:
# Non-custom struct: direct GEP + store (handles the None from get_subscript_ptr)
elem_gen: mlirlite.LLVMType* = obj_val_gen.Ty.Pointee
store_val_gen: mlirlite.Value* = rhs_val
elem_ptr_gen: mlirlite.Value* = None
match elem_gen:
case mlirlite.LLVMType.Array(arr_elem_gen, _):
elem_ptr_gen = mlirlite.build_gep_array(builder, elem_gen, arr_elem_gen, obj_val_gen, key_val_gen)
case _:
elem_ptr_gen = mlirlite.build_gep(builder, elem_gen, obj_val_gen, key_val_gen)
if elem_ptr_gen is not None and elem_ptr_gen.Ty is not None:
if elem_ptr_gen.Ty.Pointee != 0: store_val_gen = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, elem_ptr_gen.Ty.Pointee, node)
mlirlite.build_store(builder, store_val_gen, elem_ptr_gen)
setitem_done = 1
if setitem_done == 0: self.Gen.TypeGenErr.fatal_error(target, "subscript ptr is None")
continue
# UnaryOp target: *p = val (deref write); only Deref is a valid lvalue, others fail fast
if tk == ast.ASTKind.UnaryOp:
uo_w: ast.UnaryOp* = ast.UnaryOp*(target)
if uo_w is None or uo_w.operand is None: self.Gen.TypeGenErr.fatal_error(target, "assignment target unary op is missing an operand", "TypeError")
if uo_w.op != ast.OpKind.Deref:
self.Gen.TypeGenErr.fatal_error(target, "assignment target supports only the unary '*' dereference (&x / -x / ~x are not valid lvalues)", "SyntaxError")
ptr_w: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, uo_w.operand, None, 0, trans)
if ptr_w is None or ptr_w.Ty is None or self.Gen.ExprGenBase.is_ptr_type(ptr_w.Ty) == 0:
self.Gen.TypeGenErr.fatal_error(target, "the operand of unary '*' assignment must be a pointer type (type inference degraded)", "TypeError")
pointee_w: mlirlite.LLVMType* = ptr_w.Ty.Pointee
if pointee_w is None:
self.Gen.TypeGenErr.fatal_error(target, "deref write target type missing (pointee is empty)", "TypeError")
# `*a = *b` 是 c.Load 语义的**位拷贝**(type-pun):把源内存按**源自身类型**读出来,
# 再按位重解释(bitcast)成目的类型存回去 —— **绝不做数值转换**。缺了它
# `*&u = *&xv`(double → uint64)会退化成 fptosi(现场 `cvttsd2si %xmm0, %rax`)
# ⇒ IEEE 位型丢失(isnan/isinf、sin/cos 的 ef/neg 全错)。
# 判据/口径与 TPC HandlesSpecialCall.EmitRawCopyLlvm 一致(位宽必须匹配目的)。
# 注意**不能**写成"按目的类型重解释的 load"(`load i64, ptr %xv`):文本层 T2
# 「store→load 转发」是按**名字**替换的,会把 `store double %x, ptr %xv` 的 `%x`
# 顶进这条 i64 行 ⇒ `use of value '%x' expects different type ... 'i64' vs 'f64'`。
# 按源自身类型 load(类型与槽的存储类型一致)就不会踩这个问题。
if asgn.value is not None and asgn.value.kind() == ast.ASTKind.UnaryOp:
uo_r: ast.UnaryOp* = ast.UnaryOp*(asgn.value)
# 源操作数限定为非调用形状(`&x` / `p` / `a.b` / `arr[i]` …):调用形状的指针源
# 已被前面的 rhs_val 翻译求值过一次,这里再译会重复求值(位拷贝语义下无意义)。
if uo_r.op == ast.OpKind.Deref and uo_r.operand is not None and uo_r.operand.kind() != ast.ASTKind.Call:
src_ptr_w: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, uo_r.operand, None, 0, trans)
if src_ptr_w is None or src_ptr_w.Ty is None or self.Gen.ExprGenBase.is_ptr_type(src_ptr_w.Ty) == 0:
self.Gen.TypeGenErr.fatal_error(asgn.value, "the operand of unary '*' on the right-hand side of a deref bit-copy must be a pointer type (type inference degraded)", "TypeError")
src_pointee_w: mlirlite.LLVMType* = src_ptr_w.Ty.Pointee
if src_pointee_w is None:
self.Gen.TypeGenErr.fatal_error(asgn.value, "deref bit-copy source pointee type missing", "TypeError")
dst_bits_w: int = self._DerefPunBits(pool, pointee_w)
src_bits_w: int = self._DerefPunBits(pool, src_pointee_w)
if dst_bits_w != 0 and dst_bits_w == src_bits_w:
native_w: mlirlite.Value* = mlirlite.build_load(builder, src_pointee_w, src_ptr_w)
if native_w is None:
self.Gen.TypeGenErr.fatal_error(asgn.value, "deref bit-copy failed to load from the source pointer", "InternalCompilerError")
bits_w: mlirlite.Value* = mlirlite.build_bitcast(builder, native_w, pointee_w)
if bits_w is None:
self.Gen.TypeGenErr.fatal_error(asgn.value, "deref bit-copy failed to reinterpret the loaded value", "InternalCompilerError")
mlirlite.build_store(builder, bits_w, ptr_w)
continue
# 两侧位宽不等(或含聚合):这里不做位拷贝,交回下面的 coerce 路径
# (fail-safe,即等价于改动前行为;聚合位拷贝尚未支持)。
sv_w: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, pointee_w, node, trans)
mlirlite.build_store(builder, sv_w, ptr_w)
continue
# Attribute assignment: obj.field = val
if tk == ast.ASTKind.Attribute:
# Narrow target to ast.Attribute* explicitly (required under the strict pointer rules)
at_tg: ast.Attribute* = ast.Attribute*(target)
# property setter: when attr is a setter, emit the setter call and skip the field store
# (asgn.value gives the setter value param's AST for value-reference auto-address).
if self.Gen.CallGenProp._try_property_setter(pool, builder, mod, at_tg, rhs_val, trans, asgn.value) == 1: continue
# FakeDuck: write per-instance str fields through the {var}__meta__ shadow struct
fd_an: ast.Attribute* = at_tg
if fd_an is not None and fd_an.value is not None and fd_an.attr is not None:
if fd_an.value.kind() == ast.ASTKind.Name:
fd_obj_nm: ast.Name* = ast.Name*(fd_an.value)
if fd_obj_nm.id is not None:
# Pool buffer (f-string stack buffers die when the function returns)
fd_meta_w: str = pool.alloc(256)
if fd_meta_w is not None: viperlib.snprintf(fd_meta_w, 256, "%s__meta__", fd_obj_nm.id)
fd_meta_vw: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, fd_meta_w)
if fd_meta_vw is not None:
fd_sty_w: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type("_str")
if fd_sty_w is not None:
fd_fe_w: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_sty_w, fd_an.attr)
if fd_fe_w is not None and fd_fe_w.Ty is not None:
fd_fp_w: mlirlite.Value* = mlirlite.build_gep_struct(builder, fd_sty_w, fd_fe_w.Ty, fd_meta_vw, fd_fe_w.Index)
if fd_fp_w is not None:
sv_fd: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, fd_fe_w.Ty, node)
mlirlite.build_store(builder, sv_fd, fd_fp_w)
continue
# VTABLE write: obj.method = func writes the vtable slot instead of a field
if self.Gen.CallGenVt._write_vtable_attr(builder, pool, mod, target, rhs_val, trans) == 1: continue
field_ptr: mlirlite.Value* = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, target, trans)
if field_ptr is not None:
# Get the field type and coerce rhs_val (e.g. i32 -> i64)
store_val: mlirlite.Value* = rhs_val
if field_ptr.Ty is not None and field_ptr.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, field_ptr.Ty.Pointee, node, trans)
# BigEndian field: store the byte-swapped value so the slot is big-endian in memory
# (pairs the un-swap done by ExprGenAttr.translate_attribute on the load path).
bo_fw: int = self.Gen.ExprGenAttr.attr_field_byte_order(pool, trans, target)
store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, bo_fw)
# t.CVolatile field: store with the volatile qualifier
vol_fw: int = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target)
# rawptr[t.CVolatile|T] base: p.field = v also stores with volatile
at_wr: ast.Attribute* = ast.Attribute*(target)
if at_wr is not None:
if self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, at_wr.value) != 0: vol_fw = 1
mlirlite.build_store(builder, store_val, field_ptr, vol_fw)
continue
# Guard: field_ptr is None -> build a detailed error message
attr_node: ast.Attribute* = ast.Attribute*(target)
attr_name: str = "(unknown)"
obj_name: str = "(unknown)"
if attr_node is not None:
attr_name = attr_node.attr
if attr_node.value is not None and attr_node.value.kind() == ast.ASTKind.Name:
obj_nm: ast.Name* = ast.Name*(attr_node.value)
obj_name = obj_nm.id
err_buf: str = pool.alloc(256)
if err_buf is not None:
viperlib.snprintf(err_buf, 256, "attribute ptr is None: %s.%s", obj_name, attr_name)
self.Gen.TypeGenErr.fatal_error(target, err_buf)
else: self.Gen.TypeGenErr.fatal_error(target, "attribute ptr is None")
continue
# Name assignment: var = val
if tk == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(target)
# Guard: skip when the target name is missing
if nm.id is None: continue
# global variable: write to the module-scope global
if GenApi.is_global_name(trans, nm.id) != 0:
mod_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
if mod_alloca is None:
# fail-fast: global variable not registered
self.Gen.TypeGenErr.fatal_error(target, f"global variable '{nm.id}' is not registered; cannot assign", "NameError")
# Object global with static storage: bind to storage and copy the object (else dangling/NULL)
stg_ref_as: mlirlite.Value* = None
if nm.id is not None:
stg_key_as: str = pool.alloc(sizet(string.strlen(nm.id)) + 8)
if stg_key_as is not None:
viperlib.snprintf(stg_key_as, sizet(string.strlen(nm.id)) + 8, "%s.__stg", nm.id)
stg_ref_as = self.Gen.VarGen.lookup_module_var(trans.SymTab, stg_key_as)
if self._is_addrlike_call(asgn.value) == 0 and stg_ref_as is not None and rhs_val is not None and rhs_val.Ty is not None and stg_ref_as.Ty is not None and stg_ref_as.Ty.Pointee != 0:
stg_ty_as: mlirlite.LLVMType* = stg_ref_as.Ty.Pointee
rhs_inner_as: mlirlite.LLVMType* = None
match rhs_val.Ty:
case mlirlite.LLVMType.Ptr(pointee_as):
rhs_inner_as = pointee_as
case _:
pass
if rhs_inner_as is not None and rhs_inner_as.kind() == mlirlite.LLVMType.Struct and self._stg_ty_equal(pool, stg_ty_as, rhs_inner_as) != 0:
# 1) Bind the global reference to storage (drop the temporary alloca)
mlirlite.build_store(builder, stg_ref_as, mod_alloca)
# 2) Copy the constructed temporary into storage
stg_obj_val_as: mlirlite.Value* = mlirlite.build_load(builder, stg_ty_as, rhs_val)
if stg_obj_val_as is not None: mlirlite.build_store(builder, stg_obj_val_as, stg_ref_as)
continue
target_ty: mlirlite.LLVMType* = None
if mod_alloca.Ty is not None: target_ty = mod_alloca.Ty.Pointee
if target_ty is not None: rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans)
# t.CVolatile global: store with the volatile qualifier
vol_gas: int = 0
if nm.id is not None: vol_gas = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
mlirlite.build_store(builder, rhs_val, mod_alloca, vol_gas)
continue
# nonlocal variable: write through the closure env
if GenApi.is_nonlocal_name(trans, nm.id) != 0:
nl_ptr: mlirlite.Value* = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(trans, nm.id)
if nl_ptr is None:
# fail-fast: nonlocal variable pointer resolution failed
self.Gen.TypeGenErr.fatal_error(target, f"nonlocal variable '{nm.id}' pointer resolution failed", "NameError")
# Infer the nonlocal type from nl_ptr instead of forcing i32
nl_target_ty: mlirlite.LLVMType* = None
if nl_ptr.Ty is not None: nl_target_ty = nl_ptr.Ty.Pointee
if nl_target_ty is None:
# fail-fast: nl_ptr type lost, cannot store safely
self.Gen.TypeGenErr.fatal_error(target, f"nonlocal variable '{nm.id}' type information lost; cannot write", "TypeError")
rhs_coerced: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, nl_target_ty, node, trans)
mlirlite.build_store(builder, rhs_coerced, nl_ptr)
continue
# REPL session variable: a Name target at fragment entry defines/assigns the session slot
if trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0:
rp_entry: GenTypes.ReplVarEntry* = self.Gen.VarGen.repl_ensure_entry(trans, nm.id, target)
if rp_entry is not None:
self.Gen.VarGen.repl_try_assign(trans, nm.id, rhs_val, asgn.value, node)
continue
# Block assignment to a module-level global inside a function without a global declaration
if self.Gen.VarGen.is_module_scope(trans.SymTab) == 0:
mod_gv: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
mod_decl_g: int = 0
if mod_gv is not None: mod_decl_g = 1
# 双保险:SymTab.Root.Vars 在部分时序/阶段可能看不到本文件已声明的模块全局
# (现场:某函数对模块全局赋值漏写 global → 静默建局部影子 → 全局仍 NULL →
# 下游 ASan 写 0x208 崩)。按规则此处必须 fail-fast,不得静默建局部。
if mod_decl_g == 0 and GenApi.is_file_var(nm.id) != 0: mod_decl_g = 1
if mod_decl_g != 0: self.Gen.TypeGenErr.fatal_error(target, f"variable '{nm.id}' is a module-level global; assignment inside a function requires a global declaration (otherwise a local variable shadows the global, leaving it null and crashing)", "SyntaxError")
# Ordinary local variable
alloca: mlirlite.Value* = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, nm.id, rhs_val.Ty)
if alloca is not None:
# Unannotated assignment: infer the class name from the RHS expression
cls_inf: str = self._infer_rhs_annot(pool, mod, asgn.value, trans, rhs_val)
if cls_inf is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, nm.id, cls_inf)
# Coerce the value to the alloca type (e.g. double -> float)
store_val: mlirlite.Value* = rhs_val
if alloca.Ty is not None and alloca.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, alloca.Ty.Pointee, node, trans)
# BigEndian local variable: store the byte-swapped value so the slot is big-endian in
# memory (pairs the un-swap done by ExprGenName.translate_name_value on the read path).
bo_las: int = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, bo_las)
# t.CVolatile local variable: store with the volatile qualifier
vol_las: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
# M3 所有权记账:必须先记账再写值(覆盖释放要读到旧值);
# `p = new heap slife …` → 释放旧对象 + 影子置 1;`q = p` / 外来指针 → 交还所有权
self._slife_note_assign(pool, builder, mod, trans, alloca, asgn.value, nm.id)
mlirlite.build_store(builder, store_val, alloca, vol_las)
# P2 willFree:对同名变量重新赋值即重新获得有效值,清除失效态
# (新变量由 add_var 置 0;已存在变量走这里,见 clear_var_freed 注释)
self.Gen.VarGen.clear_var_freed(trans.SymTab, nm.id)
# A lambda assigned to a variable must be registered as a closure name
if asgn.value is not None and asgn.value.kind() == ast.ASTKind.Lambda: self.Gen.CallGenLeafKit.register_closure_name(nm.id)
existing: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, nm.id)
if existing is None: new_vars += 1
else:
# fail-fast: variable storage creation failed
self.Gen.TypeGenErr.fatal_error(target, f"variable '{nm.id}' storage creation failed", "TypeError")
return new_vars
# _reject_whole_object_copy_into_ptr_slot - fail fast when a rawptr[T] subscript gets a T pointer RHS
# (the whole object would be copied into an 8-byte pointer slot)
def _reject_whole_object_copy_into_ptr_slot(self, pool: memhub.MemBuddy*, trans: HT.Translator*, target: ast.AST*, elem_ptr: mlirlite.Value*, rhs_val: mlirlite.Value*) -> None:
"""Fail fast when a rawptr[T] subscript gets a T pointer RHS."""
if elem_ptr is None or elem_ptr.Ty is None: return
if rhs_val is None or rhs_val.Ty is None: return
elem_ty: mlirlite.LLVMType* = None
match elem_ptr.Ty:
case mlirlite.LLVMType.Ptr(pointee_wo):
elem_ty = pointee_wo
case _:
return
if elem_ty is None: return
# Only object (struct) elements are checked; scalar and pointer elements return early
if elem_ty.kind() != mlirlite.LLVMType.Struct: return
rhs_inner_wo: mlirlite.LLVMType* = None
match rhs_val.Ty:
case mlirlite.LLVMType.Ptr(pointee_rhs_wo):
rhs_inner_wo = pointee_rhs_wo
case _:
return
if rhs_inner_wo is None: return
if rhs_inner_wo.kind() != mlirlite.LLVMType.Struct: return
# The pointee type must match the element type
if self._stg_ty_equal(pool, elem_ty, rhs_inner_wo) == 0: return
# Array slots (t.CArray[T,N]) are legal; only block when the base is a pointer store
if self.Gen.ExprGenName.subscript_base_store_kind(pool, trans, target) != 0: return
elem_nm_wo: str = mlirlite.get_struct_name(elem_ty)
if elem_nm_wo is None: elem_nm_wo = "<anonymous struct>"
rhs_nm_wo: str = mlirlite.get_struct_name(rhs_inner_wo)
if rhs_nm_wo is None: rhs_nm_wo = elem_nm_wo
elem_sz_wo: int = self.Gen.ClassGenEnum._get_type_size(elem_ty)
ptr_sz_wo: int = 8
over_wo: int = elem_sz_wo - ptr_sz_wo
if over_wo < 0: over_wo = 0
col_s_wo: int = 0
col_e_wo: int = 0
if target is not None:
col_s_wo = target.col_offset + 1
col_e_wo = target.end_col_offset + 1
self.Gen.TypeGenErr.fatal_error(target, f"the lvalue element of the subscript assignment is object '{elem_nm_wo}' ({elem_sz_wo} bytes), " f"but the rhs is a pointer to '{rhs_nm_wo}': the compiler would silently emit a whole-object copy " f"(load {elem_nm_wo} + store {elem_nm_wo}, writing {elem_sz_wo} bytes), " f"instead of writing {ptr_sz_wo} bytes as a pointer. If that address actually stores pointers in 8-byte slots " f"(such as a rawptr[T] buffer/free list), every element written overflows by {over_wo} bytes, " f"writing through adjacent blocks. Fix: declare the base type of the pointer slot in pointer-element form " f"(`rawptr[T]*` or `rawptr[rawptr[T]]`) before assigning; " f"or explicitly cast the base to t.CPtr (untyped pointer) and write with 8-byte pointer semantics.", "TypeError", col_s_wo, col_e_wo)
# _infer_rhs_annot - infer a variable's annotation class name from the RHS expression
def _infer_rhs_annot(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator*, rhs_val: mlirlite.Value* = None) -> str:
"""Infer a variable's annotation class name from the RHS, or None."""
if node is None or trans is None: return None
k: int = node.kind()
# Call: class instantiation or method call
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
if cl is None or cl.func is None: return None
if cl.func.kind() == ast.ASTKind.Name:
# Class instantiation: only accept a registered struct name
cand: str = (ast.Name*(cl.func)).id
if cand is None: return None
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(cand)
if se is not None: return cand
return None
if cl.func.kind() == ast.ASTKind.Attribute:
# Method call: resolve the exact return class from the vpi annotation cache
ca: ast.Attribute* = ast.Attribute*(cl.func)
if ca is None or ca.attr is None or ca.value is None: return None
obj_cls: str = self._infer_rhs_annot(pool, mod, ca.value, trans)
if obj_cls is None:
# Module-level function call: resolve the return class from the module's vpi cache
if ca.value.kind() == ast.ASTKind.Name:
mod_nm_rhs: ast.Name* = ast.Name*(ca.value)
if mod_nm_rhs.id is not None:
mod_sha1_rhs: str = self.Gen.CallGenBase._lookup_module_sha1(mod_nm_rhs.id)
if mod_sha1_rhs is not None:
temp_dir_rhs: str = None
if self.Gen.CallGenBase.TempDir is not None: temp_dir_rhs = self.Gen.CallGenBase.TempDir
if temp_dir_rhs is not None: VpiReader._ensure_vpi_ret_annot_loaded(mod_sha1_rhs, temp_dir_rhs)
annot_rhs: str = VpiReader._lookup_vpi_ret_annot(mod_sha1_rhs, None, ca.attr)
if annot_rhs is not None:
ret_cls_rhs: str = self._extract_annot_class_name(pool, annot_rhs)
if ret_cls_rhs is not None: return ret_cls_rhs
return None
if self._cls_has_dunder(pool, mod, obj_cls, ca.attr) == 0: return None
# SHA1 of the module defining the method (struct entry, else current module)
cls_sha1: str = self.Gen.StructGen.get_struct_sha1(obj_cls)
if cls_sha1 is None and trans.ModuleSha1 is not None: cls_sha1 = trans.ModuleSha1
ret_cls: str = None
if cls_sha1 is not None:
temp_dir: str = None
if self.Gen.CallGenBase.TempDir is not None: temp_dir = self.Gen.CallGenBase.TempDir
if temp_dir is not None: VpiReader._ensure_vpi_ret_annot_loaded(cls_sha1, temp_dir)
annot_ret: str = VpiReader._lookup_vpi_ret_annot(cls_sha1, obj_cls, ca.attr)
if annot_ret is not None: ret_cls = self._extract_annot_class_name(pool, annot_ret)
return ret_cls
return None
# Name: the source variable annotation
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm is None or nm.id is None: return None
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if ve is None: return None
return self.Gen.VarGen.var_annot_class_name(ve)
# BinOp/UnaryOp: operator overload returns an object of the same class
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop is None or bop.left is None: return None
dunder: str = self._op_to_dunder(bop.op)
if dunder is None: return None
lhs_cls: str = self._infer_rhs_annot(pool, mod, bop.left, trans)
if lhs_cls is None or self._cls_has_dunder(pool, mod, lhs_cls, dunder) == 0: return None
return lhs_cls
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(node)
if uop is None or uop.operand is None: return None
dunder: str = self._op_to_dunder(uop.op)
if dunder is None: return None
opr_cls: str = self._infer_rhs_annot(pool, mod, uop.operand, trans)
if opr_cls is None or self._cls_has_dunder(pool, mod, opr_cls, dunder) == 0: return None
return opr_cls
# Attribute: field annotation class name (recursive attribute chain)
if k == ast.ASTKind.Attribute: return self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, node)
return None
# _extract_annot_class_name - extract a registered struct class name from a return annotation text
def _extract_annot_class_name(self, pool: memhub.MemBuddy*, annot: str) -> str:
"""Extract a registered struct class name from a return annotation."""
if pool is None or annot is None: return None
alen: sizet = string.strlen(annot)
if alen == 0: return None
seg: str = pool.alloc(256)
if seg is None: return None
start: sizet = 0
i: sizet
for i in range(int(alen)):
if annot[i] == '|':
seglen: sizet = i - start
# Segment [start, i): trim spaces/quotes then look up the registry
s: sizet = start
e: sizet = i
while s < e and (annot[s] == ' ' or annot[s] == '\'' or annot[s] == '"'): s += 1
# Also strip a trailing '*'; otherwise find_struct misses and chained calls degrade
while e > s and (annot[e - 1] == ' ' or annot[e - 1] == '\'' or annot[e - 1] == '"' or annot[e - 1] == '*'): e -= 1
seglen = e - s
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(seg)
if se is not None: return seg
start = i + 1
# Last segment (no '|' suffix)
s: sizet = start
e: sizet = alen
while s < e and (annot[s] == ' ' or annot[s] == '\'' or annot[s] == '"'): s += 1
# Also strip a trailing '*'; otherwise find_struct misses and chained calls degrade
while e > s and (annot[e - 1] == ' ' or annot[e - 1] == '\'' or annot[e - 1] == '"' or annot[e - 1] == '*'): e -= 1
seglen: sizet = e - s
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(seg)
if se is not None: return seg
return None
# _op_to_dunder - operator -> dunder method name (local copy to avoid cross-mixin calls)
def _op_to_dunder(self, op: int) -> str:
"""Map a BinOp/UnaryOp operator to its dunder name, or None."""
if op == ast.OpKind.Add: return "__add__"
if op == ast.OpKind.Sub: return "__sub__"
if op == ast.OpKind.Mult: return "__mul__"
if op == ast.OpKind.Div: return "__truediv__"
if op == ast.OpKind.FloorDiv: return "__floordiv__"
if op == ast.OpKind.Mod: return "__mod__"
if op == ast.OpKind.Pow: return "__pow__"
if op == ast.OpKind.BitAnd: return "__and__"
if op == ast.OpKind.BitOr: return "__or__"
if op == ast.OpKind.BitXor: return "__xor__"
if op == ast.OpKind.LShift: return "__lshift__"
if op == ast.OpKind.RShift: return "__rshift__"
if op == ast.OpKind.USub: return "__neg__"
if op == ast.OpKind.UAdd: return "__pos__"
if op == ast.OpKind.Invert: return "__invert__"
return None
# _aug_op_to_dunder - augmented operator -> aug dunder (__iadd__ etc.); None falls back to _op_to_dunder
def _aug_op_to_dunder(self, op: int) -> str:
"""Map an AugAssign operator to its in-place dunder name, or None."""
if op == ast.OpKind.Add: return "__iadd__"
if op == ast.OpKind.Sub: return "__isub__"
if op == ast.OpKind.Mult: return "__imul__"
if op == ast.OpKind.Div: return "__itruediv__"
if op == ast.OpKind.FloorDiv: return "__ifloordiv__"
if op == ast.OpKind.Mod: return "__imod__"
if op == ast.OpKind.Pow: return "__ipow__"
if op == ast.OpKind.BitAnd: return "__iand__"
if op == ast.OpKind.BitOr: return "__ior__"
if op == ast.OpKind.BitXor: return "__ixor__"
if op == ast.OpKind.LShift: return "__ilshift__"
if op == ast.OpKind.RShift: return "__irshift__"
return None
# _cls_has_dunder - check whether a class (including its MRO) has a dunder method
def _cls_has_dunder(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, class_name: str, dunder: str) -> int:
"""Return 1 if the class or its MRO chain has the dunder method."""
if class_name is None or dunder is None or mod is None: return 0
lookup_name: str = pool.alloc(128)
if lookup_name is None: return 0
viperlib.snprintf(lookup_name, 128, "%s.%s", class_name, dunder)
if self.Gen.CallGenBase.find_func_in_module(mod, lookup_name) is not None: return 1
# Search the inheritance chain ancestors
mro_ptr: sizet* = self.Gen.StructGen.get_mro_chain(pool, class_name)
mi: int = 1
while mro_ptr is not None and mro_ptr[mi] != 0:
anc: str = rawptr(mro_ptr[mi])
if anc is not None:
anc_lookup: str = pool.alloc(128)
if anc_lookup is not None:
viperlib.snprintf(anc_lookup, 128, "%s.%s", anc, dunder)
if self.Gen.CallGenBase.find_func_in_module(mod, anc_lookup) is not None: return 1
mi += 1
# Cross-module: a class with a SHA1 (method declared in the stub) counts as having it
if self.Gen.StructGen.get_struct_sha1(class_name) is not None: return 1
return 0
def InitAssignGen(self) -> int:
"""Initialize AssignGen fields (called by Generator at construction)."""
if self is None: return 1
self.RecoverFound = 0
return 0