3762 lines
252 KiB
Plaintext
3762 lines
252 KiB
Plaintext
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 t.CDefine (module-level function)
|
||
def is_cdefine_annotation(self, annot: ast.AST*) -> int:
|
||
"""Return 1 if the annotation is t.CDefine, else 0."""
|
||
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, "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 a t.CDefine marker node."""
|
||
if node is None: return 0
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(node)
|
||
if at.attr is not None and string.strcmp(at.attr, "CDefine") == 0: return 1
|
||
return 0
|
||
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_t_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_t_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_t_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_t_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 (c.Addr/c.Deref/c.Cast/c.Ptr call, or new-syntax `&x` / `*p`)."""
|
||
if node is None: return 0
|
||
# New syntax (R3/R4): `&x` and `*p` parse as UnaryOp(AddrOf) / UnaryOp(Deref) and replace the
|
||
# c.Addr / c.Deref call forms. A pointer-valued RHS must not take the module-global
|
||
# "object with static storage -> copy the object" branch, so both forms count as addrlike.
|
||
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
|
||
if node.kind() != ast.ASTKind.Call: return 0
|
||
cl: ast.Call* = ast.Call*(node)
|
||
if cl.func is None or cl.func.kind() != ast.ASTKind.Attribute: return 0
|
||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if at.value is None or at.value.kind() != ast.ASTKind.Name: return 0
|
||
vn: ast.Name* = ast.Name*(at.value)
|
||
if vn.id is None or string.strcmp(vn.id, "c") != 0: return 0
|
||
if at.attr is None: return 0
|
||
if string.strcmp(at.attr, "Addr") == 0: return 1
|
||
if string.strcmp(at.attr, "Deref") == 0: return 1
|
||
if string.strcmp(at.attr, "Cast") == 0: return 1
|
||
if string.strcmp(at.attr, "Ptr") == 0: return 1
|
||
return 0
|
||
|
||
def _is_addr_of_expr(self, node: ast.AST*) -> int:
|
||
"""Return 1 only for an explicit address-of expression (`&x` / `c.Addr(x)`).
|
||
|
||
Distinct from _is_addrlike_call, which also counts deref/cast 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
|
||
if node.kind() != ast.ASTKind.Call: return 0
|
||
cl_ao: ast.Call* = ast.Call*(node)
|
||
if cl_ao.func is None or cl_ao.func.kind() != ast.ASTKind.Attribute: return 0
|
||
at_ao: ast.Attribute* = ast.Attribute*(cl_ao.func)
|
||
if at_ao.value is None or at_ao.value.kind() != ast.ASTKind.Name: return 0
|
||
vn_ao: ast.Name* = ast.Name*(at_ao.value)
|
||
if vn_ao.id is None or string.strcmp(vn_ao.id, "c") != 0: return 0
|
||
if at_ao.attr is not None and string.strcmp(at_ao.attr, "Addr") == 0: return 1
|
||
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
|
||
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
|
||
# Strip the t./c. namespace prefix so int and bare aliases are judged the same
|
||
if ty_name[0] == 't' and ty_name[1] == '.': ty_name += 2
|
||
elif ty_name[0] == 'c' and ty_name[1] == '.': ty_name += 2
|
||
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
|
||
# 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"t.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.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(sub.value)
|
||
if at.attr is None or string.strcmp(at.attr, "CArray") != 0: return 0
|
||
if at.value is None or at.value.kind() != ast.ASTKind.Name: return 0
|
||
mv: ast.Name* = ast.Name*(at.value)
|
||
if mv.id is None or string.strcmp(mv.id, "t") != 0: return 0
|
||
elif sub.value.kind() == ast.ASTKind.Name:
|
||
# Bare CArray / builtin rawarray / builtin array are all equivalent to t.CArray
|
||
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. t.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
|