R14:rawptr[X] -> X*(56 文件 / 834 行改动);保留 6 处 rawptr[t.CPtr](存纯裸指针的规范例外)。 修复:class X(t.REnum) 的变体字段写成自引用 X* 时,字段解析在变体扫描期经 ensure_forward_class 建了一个 opaque 前向占位;末尾 register_struct 按 Ty 指针去重会再建第二个同名 entry,而 find_struct 取首个(占位,0 字段)→ X.__sizeof__() == 0,报「已注册但大小为 0(字段链表未填充)」。改为注册前若命中同模块前向占位则就地填充(与 ensure_forward_class 文档口径一致)。TPC 无此问题,属 TPV 侧缺口。 验证:自举三重重编 SUCCESS;全量 77/77 顺序回归通过(修复前卡在 LLvmLiteTest)。
2418 lines
157 KiB
Plaintext
2418 lines
157 KiB
Plaintext
import t, c
|
||
from stdint import *
|
||
import ast
|
||
import mlirlite
|
||
import memhub
|
||
import string
|
||
import stdio
|
||
import stdlib
|
||
import viperlib
|
||
import w32.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
|
||
def extract_cdefine_int_value(self, val_node: ast.AST*) -> int64:
|
||
"""Extract an integer constant from an AnnAssign value node."""
|
||
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 值提取失败:无法从表达式解析编译期常量", "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)
|
||
left_val: int64 = self.extract_cdefine_int_value(bop.left)
|
||
right_val: int64 = self.extract_cdefine_int_value(bop.right)
|
||
if bop.op == ast.OpKind.BitOr: return left_val | right_val
|
||
if bop.op == ast.OpKind.BitAnd: return left_val & right_val
|
||
if bop.op == ast.OpKind.BitXor: return left_val ^ right_val
|
||
if bop.op == ast.OpKind.LShift: return left_val << right_val
|
||
if bop.op == ast.OpKind.RShift: return left_val >> right_val
|
||
if bop.op == ast.OpKind.Add: return left_val + right_val
|
||
if bop.op == ast.OpKind.Sub: return left_val - right_val
|
||
if bop.op == ast.OpKind.Mult: return left_val * right_val
|
||
if bop.op == ast.OpKind.Div: return left_val // right_val
|
||
if bop.op == ast.OpKind.FloorDiv: return left_val // right_val
|
||
if bop.op == ast.OpKind.Mod: return left_val % right_val
|
||
# Unknown BinOp operator: fail fast
|
||
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine 值提取失败:无法从表达式解析编译期常量", "TypeError")
|
||
|
||
# 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: return looked_up
|
||
# Unregistered reference: fail fast
|
||
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine 值提取失败:无法从表达式解析编译期常量", "TypeError")
|
||
|
||
# Case 4: Call - e.g. t.CUnsignedLong(-11)
|
||
if k == ast.ASTKind.Call:
|
||
cl: ast.Call* = ast.Call*(val_node)
|
||
# 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)
|
||
arg_val: int64 = self.extract_cdefine_int_value(first_arg)
|
||
return arg_val
|
||
# Call with no args: fail fast
|
||
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine 值提取失败:无法从表达式解析编译期常量", "TypeError")
|
||
|
||
# Case 5: UnaryOp - e.g. -11
|
||
if k == ast.ASTKind.UnaryOp:
|
||
uop: ast.UnaryOp* = ast.UnaryOp*(val_node)
|
||
operand_val: int64 = self.extract_cdefine_int_value(uop.operand)
|
||
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 值提取失败:无法从表达式解析编译期常量", "TypeError")
|
||
|
||
# Unknown node kind: fail fast
|
||
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine 值提取失败:无法从表达式解析编译期常量", "TypeError")
|
||
# After fatal_error add an explicit return so the block is properly terminated
|
||
return 0
|
||
|
||
# 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: fits an unsigned 32-bit bit pattern
|
||
if uint64(uint32(value)) == value: return 32
|
||
return 64
|
||
|
||
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
|
||
if string.strcmp(nm, "int") == 0: return 32
|
||
if string.strcmp(nm, "bool") == 0: return 8
|
||
# Fixed-width lowercase aliases from stdint/builtins (not t module names)
|
||
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 None: return 0
|
||
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
|
||
|
||
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))
|
||
|
||
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(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(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*) -> None:
|
||
"""Fail fast if a CDefine value is not storable in its declared type."""
|
||
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"[TPV] define 常量数值不可存储: 常量 '{name}' 的初始值 {text} 超出 64 位" f"(本语言最宽的整数类型为 64 位,按有符号/无符号两种解释的可存储范围为 " f"-9223372036854775808..18446744073709551615,不存在更宽的整数类型 ⇒ 需要扩容)。" f"禁止静默截断/降级,已终止编译。可用整数类型: int8/uint8、" f"int16/uint16、int32/uint32、int64/uint64" f"(或 int8/uint8/…/uint64、char、sizet)。" f"修法: 把值改到 64 位范围内,或为该数字引入更大的整数类型(扩容)。", "TypeError")
|
||
return
|
||
if bits == 0: 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"[TPV] define 常量数值不可存储: 常量 '{name}' 的初始值 {value} 不可存储。" f"最小可容纳宽度 W_min: {wmin} 位;声明类型位宽 {bits} 位," f"该声明可存储范围: {lo}..{hi}。" f"禁止静默截断/降级,已终止编译。" f"修法: 把显式类型改为更宽的类型(如 int32 / int64 / uint64)," f"或修正该字面量。", "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."""
|
||
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
|
||
wmin: int = self.define_min_width(cdef_val)
|
||
if bits >= wmin: return
|
||
self.Gen.TypeGenErr.fatal_error(value_node, f"[TPV] define 常量使用处宽度不足: 常量 '{rn.id}' 的实际值 {cdef_val} " f"需要至少 {wmin} 位(最小可容纳宽度 W_min)," f"但使用处(变量声明注解)只提供 {bits} 位。" f"禁止静默截断/降级,已终止编译。" f"修法: 把变量声明注解加宽到至少 {wmin} 位(如 int{wmin}T / CUInt{wmin}T)," f"或改用更小的常量值。", "TypeError")
|
||
|
||
# _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
|
||
# 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, t.CPtr(&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 浮点值源文件恢复失败", "InternalCompilerError")
|
||
return
|
||
self.Gen.TypeGenCDef.register_cdefine_double(pool, name, use_val)
|
||
return
|
||
if val_node is None:
|
||
# CDefine declaration without a value node: fail fast
|
||
self.Gen.TypeGenErr.fatal_error(None, f"CDefine 值提取失败:'{name}' 缺少初始值", "TypeError")
|
||
return
|
||
val_fp: int64 = self.extract_cdefine_int_value(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)
|
||
self.Gen.TypeGenCDef.register_cdefine_constant(pool, name, val_fp)
|
||
|
||
# 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(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(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
|
||
|
||
# _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
|
||
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(512)
|
||
buf_b: str = pool.alloc(512)
|
||
if buf_a is None or buf_b is None: return 0
|
||
mlirlite.TypePrint(buf_a, 512, ta, pool)
|
||
mlirlite.TypePrint(buf_b, 512, 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
|
||
|
||
def _is_addrlike_call(self, node: ast.AST*) -> int:
|
||
"""Return 1 if the rhs is an explicit c.Addr/c.Deref-style pointer call."""
|
||
if node is None or 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_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) -> int:
|
||
"""Initialize a global array from a list literal via per-element GEP+store."""
|
||
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"字符串字面量长度 {n_s} 超过字符数组声明长度 {arr_count},不允许越界写入", "OverflowError")
|
||
for bi_s in range(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"数组初始化元素数 {elts_count} 超过数组声明长度 {arr_count},不允许越界写入", "OverflowError")
|
||
|
||
# Emit GEP + store for each element
|
||
for i in range(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"全局数组初始化遇到空 AST 元素:{ctx_dbg}第 {i} 个元素为裸空指针(0)," f"但列表元素计数为 {elts_count}(元素与计数不一致)。" f"上层某处产生了空 AST 元素(列表字面量节点 {src_line}:{src_col} 的第 {i} 槽未被填充)," f"拒绝静默跳过/补零,已终止编译。", "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"全局数组初始化遇到【对象无效】的 AST 元素(vptr 为 0):{ctx_vt}" f"第 {i} 个元素指针非 0(地址 {uint64(elem_node)})但其对象首字(vtable 指针)为 0," f"对它的任何虚调用(kind()/dump()/…) 都会在 `load [0+slot]` 处读地址 0。" f"列表字面量节点位置 {vt_line}:{vt_col},元素计数 {elts_count}。" f"上层某处产生了「未初始化 vptr 的 AST 节点」(典型源:深拷贝/整块拷贝只搬字段未搬 vptr)," f"拒绝继续,已终止编译。", "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)
|
||
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, "全局数组初始化元素翻译失败", "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)
|
||
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)
|
||
|
||
# Integer op: type promotion
|
||
if lhs_bits > rhs_bits and rhs_bits > 0: rhs = self.Gen.ExprGenBase.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
|
||
elif rhs_bits > lhs_bits and lhs_bits > 0: lhs = self.Gen.ExprGenBase.coerce_to_type(builder, lhs, rhs.Ty, node, trans)
|
||
|
||
# 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 = "(注解含 t.CConst,声明处来源名未记录)"
|
||
if src_name is not None: decl = f"t.CConst | {src_name}"
|
||
return GenApi.escape_fstring_copy(f"不能对 const 变量 '{name}' {kind}" f"(声明处类型(含 const):{decl}" f";修法:删掉该声明里的 const 描述符 / t.CConst,或改为声明时一次赋值、" f"之后只用非 const 的副本/中间量;const 对象在其作用域内禁止赋值、" f"增量赋值与元素赋值,且不会降级为警告)")
|
||
|
||
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, "字段赋值", 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, "字段类型信息缺失", "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)
|
||
# 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, "无法加载 attribute 当前值", "TypeError")
|
||
|
||
# 3. Translate the RHS value
|
||
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans)
|
||
if rhs_val is None:
|
||
# fail-fast: RHS translation failed
|
||
self.Gen.TypeGenErr.fatal_error(target, "rhs 翻译失败", "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 二元运算失败 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, "类型对齐失败", "TypeError")
|
||
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, "的元素赋值", 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, "的元素赋值", 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, "元素类型信息缺失", "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)
|
||
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, "无法加载 subscript 元素当前值", "TypeError")
|
||
|
||
# 3. Translate the RHS value
|
||
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans)
|
||
if rhs_val is None:
|
||
# fail-fast: RHS translation failed
|
||
self.Gen.TypeGenErr.fatal_error(target, "rhs 翻译失败", "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 二元运算失败 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, "类型对齐失败", "TypeError")
|
||
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, "增量赋值目标单目运算缺少操作数", "TypeError")
|
||
if uo_ag.op != ast.OpKind.Deref:
|
||
self.Gen.TypeGenErr.fatal_error(target, "增量赋值目标只支持单目 '*' 解引用(&x / -x / ~x 不是合法左值)", "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, "增量赋值 '*' 的操作数必须是指针类型(类型推断退化)", "TypeError")
|
||
ty_ag: mlirlite.LLVMType* = ptr_ag.Ty.Pointee
|
||
if ty_ag is None:
|
||
self.Gen.TypeGenErr.fatal_error(target, "解引用增量赋值的目标类型缺失(pointee 为空)", "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, "无法加载解引用当前值", "TypeError")
|
||
rhs_ag: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans)
|
||
if rhs_ag is None: self.Gen.TypeGenErr.fatal_error(target, "rhs 翻译失败", "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"解引用二元运算失败 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, "类型对齐失败", "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 目标类型不支持,仅支持 Name/Attribute/Subscript/*解引用", "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, "增量赋值", 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))
|
||
|
||
# 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, "变量类型信息缺失", "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, "变量类型信息缺失", "TypeError")
|
||
target_ty = target_alloca.Ty.Pointee
|
||
cur_val = mlirlite.build_load(builder, target_ty, target_alloca, vol_aa)
|
||
|
||
if cur_val is None:
|
||
# fail-fast: target variable current value load failed
|
||
self.Gen.TypeGenErr.fatal_error(target, f"无法加载目标变量 '{nm.id}' 的当前值", "TypeError")
|
||
|
||
# 3. Translate the RHS value
|
||
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans)
|
||
if rhs_val is None:
|
||
# fail-fast: RHS translation failed
|
||
self.Gen.TypeGenErr.fatal_error(target, "rhs 翻译失败", "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, "增量赋值运算符重载返回值类型与目标变量不匹配", "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"对变量 '{nm.id}' 应用二元运算失败 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, "类型对齐失败", "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: 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 = 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, "变量类型注解解析失败,变量名: %s,注解: %s。builtins 最后匹配: %s 展开为 %s,仍无法解析", nm_id, ann_txt_x, btd_nm_x, btd_rs_x)
|
||
elif btd_nm_x is not None: viperlib.snprintf(msg_buf, msg_len, "变量类型注解解析失败,变量名: %s,注解: %s。builtins 最后匹配: %s 未命中(builtins.vp 无该 typedef 或扫描未运行)", nm_id, ann_txt_x, btd_nm_x)
|
||
else: viperlib.snprintf(msg_buf, msg_len, "变量类型注解解析失败,变量名: %s,注解: %s。builtins 最后匹配: 未触发", 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:
|
||
# 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 缺少类型注解,变量名: {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)
|
||
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"变量 '{var_name}' 的泛型注解 '{base_nm_ff.id}[...]' 解析成了裸基类 " f"'{vt_nm_ff}*'(丢失类型实参);后续 .get()/.set() 会按错误元素类型" f"特化(如 list[int].get 返回 i32)→ inttoptr 截断指针崩溃。" f"禁止静默继续——请检查该注解在 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 or target.kind() != ast.ASTKind.Name: return 0
|
||
|
||
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.
|
||
if nm.id is not None and trans is not None and self.Gen.VarGen.is_module_scope(trans.SymTab) == 0 and self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id) is not None:
|
||
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"重复声明:'{nm.id}' 已是模块级变量,此处声明 global/nonlocal 之后" f"又用类型注解重新声明。请删除本行的类型注解,直接赋值即可修改全局", "TypeError")
|
||
else:
|
||
self.Gen.TypeGenErr.fatal_error(aa, f"重复声明:'{nm.id}' 已是模块级变量,函数内未声明 global 时不得再用" f"类型注解声明同名变量(会把全局遮蔽为局部)。如需独立局部变量请换名;" f"如需修改全局请在函数内加 global 声明并删除本行注解", "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:
|
||
# 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 缺少类型注解,变量名: {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"不能声明 void 类型的变量 '{nm.id}'(void 只能用于 void* 与 typedef 别名)", "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
|
||
|
||
# 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)
|
||
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, "模块级全局")
|
||
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, "闭包变量(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 变量类型信息丢失,无法写入: {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, "模块级全局")
|
||
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"无法为变量 {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"变量 '{nm.id}' 复用到的存储类型为裸泛型基类 '{av_nm_d}*'" f"(丢了类型实参),与注解解析类型 '{vt_nm_d}*' 不一致;" f"禁止静默混用——方法调用会按错误元素类型特化并截断指针", "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"无法为 str 影子变量 {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)
|
||
|
||
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)
|
||
self._init_global_array_from_list(builder, pool, mod, alloca, aa.value, trans)
|
||
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, "局部变量注解")
|
||
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
|
||
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:
|
||
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
|
||
mlirlite.build_store(builder, rhs_val, alloca, vol_laa)
|
||
# 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")
|
||
|
||
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(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, "赋值", 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, "字段赋值", 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, "的元素赋值", 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, "的元素赋值", 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(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, "字段赋值")
|
||
elif tk_f == ast.ASTKind.Subscript: GenApi.fail_if_fstring_escape(asgn.value, "元素赋值")
|
||
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, "全局变量赋值")
|
||
|
||
# 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("主池(Pool)")
|
||
if pp_diag is not None: pp_diag.dump_stats("常驻池(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"赋值右侧为 None: {rel_path}:{val_line} '{target_nm}.{attr_nm}' 属性访问失败", "ASGN")
|
||
else: VLogger.error(f"赋值右侧为 None: sha1={sha1}:{val_line} '{target_nm}.{attr_nm}' 属性访问失败", "ASGN")
|
||
elif val_kind == ast.ASTKind.Name:
|
||
nm_nv: ast.Name* = ast.Name*(asgn.value)
|
||
if rel_path is not None: VLogger.error(f"赋值右侧为 None: {rel_path}:{val_line} 变量 '{nm_nv.id}' 未找到", "ASGN")
|
||
else: VLogger.error(f"赋值右侧为 None: sha1={sha1}:{val_line} 变量 '{nm_nv.id}' 未找到", "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"赋值右侧为 None: {rel_path}:{val_line} 调用 '{func_nm}(...)' 返回 None", "ASGN")
|
||
else: VLogger.error(f"赋值右侧为 None: sha1={sha1}:{val_line} 调用 '{func_nm}(...)' 返回 None", "ASGN")
|
||
else:
|
||
if rel_path is not None: VLogger.error(f"赋值右侧为 None: {rel_path}:{val_line} [kind={val_kind}]")
|
||
else: VLogger.error(f"赋值右侧为 None: sha1={sha1}:{val_line} [kind={val_kind}]")
|
||
else: VLogger.error("赋值右侧为 None (asgn.value 为空)", "ASGN")
|
||
return 0
|
||
|
||
new_vars: int = 0
|
||
tn: sizet = len(targets)
|
||
for ti in range(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(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, "元组解包字段类型缺失", "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 下标翻译失败", "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)
|
||
# rawptr[t.CVolatile|T] base: p[i] = v stores with volatile when the base is resolvable
|
||
vol_sw: int = 0
|
||
sub_wr: ast.Subscript* = ast.Subscript*(target)
|
||
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* = mlirlite.build_load(builder, sub_pointee, sub_alloca)
|
||
# Pointer variable (X*): sub_pointee is Ptr(Struct); load it to inspect the inner struct
|
||
if cls_nm_set is None and 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, "赋值目标单目运算缺少操作数", "TypeError")
|
||
if uo_w.op != ast.OpKind.Deref:
|
||
self.Gen.TypeGenErr.fatal_error(target, "赋值目标只支持单目 '*' 解引用(&x / -x / ~x 不是合法左值)", "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, "单目 '*' 赋值的操作数必须是指针类型(类型推断退化)", "TypeError")
|
||
pointee_w: mlirlite.LLVMType* = ptr_w.Ty.Pointee
|
||
if pointee_w is None:
|
||
self.Gen.TypeGenErr.fatal_error(target, "解引用写入的目标类型缺失(pointee 为空)", "TypeError")
|
||
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
|
||
if self.Gen.CallGenProp._try_property_setter(pool, builder, mod, at_tg, rhs_val, trans) == 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)
|
||
# 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 变量 '{nm.id}' 未注册,无法赋值", "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 变量 '{nm.id}' 指针解析失败", "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 变量 '{nm.id}' 类型信息丢失,无法写入", "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)
|
||
if mod_gv is not None: self.Gen.TypeGenErr.fatal_error(target, f"变量 '{nm.id}' 是模块级全局变量,函数内赋值需用 global 声明(否则创建局部变量遮蔽全局,导致全局保持 null 崩溃)", "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)
|
||
# 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))
|
||
mlirlite.build_store(builder, store_val, alloca, vol_las)
|
||
# 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"变量 '{nm.id}' 存储创建失败", "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 = "<匿名结构体>"
|
||
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"下标赋值的左值元素是对象 '{elem_nm_wo}'({elem_sz_wo} 字节)," f"右值却是指向 '{rhs_nm_wo}' 的指针:编译器会静默生成整对象拷贝" f"(load {elem_nm_wo} + store {elem_nm_wo},写 {elem_sz_wo} 字节)," f"而不是按指针写 {ptr_sz_wo} 字节。若该地址实际按 8 字节槽存放指针" f"(如 rawptr[T] 缓冲/空闲链表),每写一个元素就越界 {over_wo} 字节," f"直接写穿相邻块。修法:把指针槽的基座类型写成指针元素形态" f"(`rawptr[T]*` 或 `rawptr[rawptr[T]]`)后再赋值;" f"或把基座显式转为 t.CPtr(无类型指针)后按 8 字节指针语义写入。", "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(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
|