Files
ViperC/App/lib/core/Gen/AssignGenMixin.vp
T
TermiNexus 9e55e909a8 feat(gen): LifeTag 接到 new 描述符;vpi 文本缓存收敛为单一入口
life_of_expr 新增 ASTKind.New 分支(§2.3 矩阵):stack→LIFE_FRAME;heap/memgr 的 slife 同样按帧生命期(作用域末 RAII 释放,逃逸必悬垂);hlife→LIFE_HEAP。于是 new stack slife Foo() 的结果被赋给变量/返回时会被既有汇点判据 fail_if_life_escapes 拦住(该判据本来就是生命期驱动,无需再加语法特例)。

VpiReader 新增 _cache_vpi_annots_text 单一入口(ret/param/逃逸摘要一次喂齐),两个 vpi 加载点都改调它:从结构上消除"某个加载点漏喂某类缓存"这类静默退化(本次 EscapeXmodTest 漏检的根因类别)。
2026-10-08 18:19:15 +08:00

2592 lines
170 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 value extraction failed: cannot parse a compile-time constant from the expression", "TypeError")
return cn.int_val
# Case 2: BinOp - e.g. FOREGROUND_RED | FOREGROUND_GREEN, (1 << N)
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(val_node)
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 value extraction failed: cannot parse a compile-time constant from the expression", "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 value extraction failed: cannot parse a compile-time constant from the expression", "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 value extraction failed: cannot parse a compile-time constant from the expression", "TypeError")
# Case 5: UnaryOp - e.g. -11
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(val_node)
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 value extraction failed: cannot parse a compile-time constant from the expression", "TypeError")
# Unknown node kind: fail fast
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError")
# After fatal_error add an explicit return so the block is properly terminated
return 0
# 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 constant value not storable: the initial value {text} of constant '{name}' exceeds 64 bits " f"(the widest integer type in this language is 64 bits; the storable range under both signed and unsigned interpretations is " f"-9223372036854775808..18446744073709551615; no wider integer type exists => widening is required). " f"Silent truncation/downgrade is forbidden; compilation aborted. Available integer types: int8/uint8, " f"int16/uint16, int32/uint32, int64/uint64 " f"(or int8/uint8/.../uint64, char, sizet). " f"Fix: move the value into the 64-bit range, or introduce a larger integer type for this number (widen).", "TypeError")
return
if bits == 0: return
# 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 constant value not storable: the initial value {value} of constant '{name}' is not storable. " f"Minimal storable width W_min: {wmin} bits; declared type width {bits} bits, " f"storable range of this declaration: {lo}..{hi}. " f"Silent truncation/downgrade is forbidden; compilation aborted. " f"Fix: widen the explicit type to a wider type (such as int32 / int64 / uint64), " f"or correct the literal.", "TypeError")
def require_cdefine_use_width(self, pool: memhub.MemBuddy*, trans: HT.Translator*, target_annot: ast.AST*, value_node: ast.AST*) -> None:
"""Fail fast when a define constant is wider than its use-site type."""
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 constant use-site width insufficient: the actual value {cdef_val} of constant '{rn.id}' " f"needs at least {wmin} bits (minimal storable width W_min), " f"but the use site (variable declaration annotation) provides only {bits} bits. " f"Silent truncation/downgrade is forbidden; compilation aborted. " f"Fix: widen the variable declaration annotation to at least {wmin} bits (such as int{wmin}T / CUInt{wmin}T), " f"or use a smaller constant value.", "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, &rec_d) != 0: use_val = rec_d
else:
# Source recovery failed: fail fast to avoid registering a corrupted float
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine float value source-file recovery failed", "InternalCompilerError")
return
self.Gen.TypeGenCDef.register_cdefine_double(pool, name, use_val)
return
if val_node is None:
# CDefine declaration without a value node: fail fast
self.Gen.TypeGenErr.fatal_error(None, f"CDefine value extraction failed: '{name}' is missing an initial value", "TypeError")
return
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
def _annotation_has_cptr_marker(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation union carries a CPtr marker (the star form `<base> | t.CPtr`)."""
if annot is None: return 0
k: int = annot.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annot)
if at.attr is not None and string.strcmp(at.attr, "CPtr") == 0: return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(annot)
if nm.id is not None and string.strcmp(nm.id, "CPtr") == 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if self._annotation_has_cptr_marker(bop.left) != 0: return 1
if self._annotation_has_cptr_marker(bop.right) != 0: return 1
return 0
# _is_ptr_element_annotation - check whether an annotation is bytes* or str*
# bytes*/str* => 8-byte element stride (i8**); char*/int8* => 1-byte stride.
def _is_ptr_element_annotation(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation is a bytes* or str* form."""
if annot is None: return 0
k: int = annot.kind()
if k != ast.ASTKind.BinOp: return 0
bop: ast.BinOp* = ast.BinOp*(annot)
if bop.op != ast.OpKind.BitOr: return 0
# str/bytes are already i8*; only the union WITH a pointer marker (str*/bytes*) is i8** and
# needs 8-byte element slots. A union that merely carries them (e.g. `const str`, which is
# t.CConst | str and stays i8*) must keep the 1-byte stride.
if self._annotation_has_cptr_marker(annot) != 0:
if self._annotation_contains_name(annot, "str") != 0: return 1
if self._annotation_contains_name(annot, "bytes") != 0: return 1
# Generic type param / nested union | t.CPtr: scalars use their own size, pointers use 8-byte slots
if self._is_generic_tp_or_nested(bop.left) != 0 or self._is_generic_tp_or_nested(bop.right) != 0:
gp_nm: str = None
if bop.left is not None and bop.left.kind() == ast.ASTKind.Name:
gp_left_nm: ast.Name* = ast.Name*(bop.left)
gp_nm = gp_left_nm.id
elif bop.right is not None and bop.right.kind() == ast.ASTKind.Name:
gp_right_nm: ast.Name* = ast.Name*(bop.right)
gp_nm = gp_right_nm.id
if gp_nm is not None:
# Resolve the generic param via the global gen (direct field access breaks static binding)
gp_resolved: str = self.Gen.TypeGenBase.resolve_generic_type_name(gp_nm)
if gp_resolved is not None and string.strcmp(gp_resolved, gp_nm) != 0:
# Generic param resolved to a scalar => use scalar size, not an 8-byte pointer slot
if self._is_scalar_ty_name(gp_resolved) != 0: return 0
return 1
# 64-bit scalar | CPtr: element is 8 bytes, so use an 8-byte stride (else GEP truncation)
if self._is_64bit_scalar_ty(bop.left) != 0 or self._is_64bit_scalar_ty(bop.right) != 0: return 1
return 0
def _stg_ty_equal(self, pool: memhub.MemBuddy*, ta: mlirlite.LLVMType*, tb: mlirlite.LLVMType*) -> int:
"""Return 1 if the two types are equal by printed content."""
if ta is None or tb is None: return 0
# Fast path: identical instance is equal
if uint64(ta) == uint64(tb): return 1
buf_a: str = pool.alloc_tagged(4096, memhub.DETSITE_ASSIGNGEN_578)
buf_b: str = pool.alloc_tagged(4096, memhub.DETSITE_ASSIGNGEN_579)
if buf_a is None or buf_b is None: return 0
mlirlite.TypePrint(buf_a, 4096, ta, pool)
mlirlite.TypePrint(buf_b, 4096, tb, pool)
if buf_a[0] == 0 or buf_b[0] == 0: return 0
return 1 if string.strcmp(buf_a, buf_b) == 0 else 0
# ============================================================
# 生命期(LifeTag)判定 / 传播 / 汇点检查(架构见 Debugger/ownership-and-new.md §11)
# 标签常量见 GenTypes define LIFE_*;有序,越小越"短命",汇合取 min。
# ============================================================
# life_of_expr - 求表达式当前值的生命期标签。
# target_ty 参与判定:裸本帧数组名在**指针**目标位置 = 数组退让(交出本帧栈地址),
# 在同型聚合目标位置 = 按值拷贝(LIFE_VALUE)。
def life_of_expr(self, trans: HT.Translator*, node: ast.AST*, target_ty: mlirlite.LLVMType*) -> int:
"""表达式的 LifeTag;无法判定时 LIFE_UNKNOWN(保守放行)"""
if node is None or trans is None: return GenTypes.LIFE_VALUE
k: int = node.kind()
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(node)
if cn is None: return GenTypes.LIFE_VALUE
if cn.const_kind == ast.CONST_STR: return GenTypes.LIFE_STATIC
if cn.const_kind == ast.CONST_NONE: return GenTypes.LIFE_UNKNOWN
return GenTypes.LIFE_VALUE
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is None: return GenTypes.LIFE_UNKNOWN
if GenApi.is_global_name(trans, nm.id) != 0: return GenTypes.LIFE_STATIC
if GenApi.is_nonlocal_name(trans, nm.id) != 0: return GenTypes.LIFE_UNKNOWN
v: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if v is None: return GenTypes.LIFE_STATIC
if v.Ty is not None and self.Gen.ExprGenBase.is_ptr_type(v.Ty) != 0:
inner: mlirlite.LLVMType* = v.Ty.Pointee
if inner is not None and inner.kind() == mlirlite.LLVMType.Array:
# 本帧局部数组名:退让到指针目标交出栈地址;同型聚合目标是值拷贝
if target_ty is not None and self.Gen.ExprGenBase.is_ptr_type(target_ty) != 0: return GenTypes.LIFE_FRAME
return GenTypes.LIFE_VALUE
# 指针/标量变量:读登记的生命期(默认 UNKNOWN)
return self.Gen.VarGen.get_var_life(trans.SymTab, nm.id)
if k == ast.ASTKind.UnaryOp:
uo: ast.UnaryOp* = ast.UnaryOp*(node)
if uo.op == ast.OpKind.AddrOf and uo.operand is not None and uo.operand.kind() == ast.ASTKind.Name:
onm: ast.Name* = ast.Name*(uo.operand)
if onm.id is not None:
if GenApi.is_global_name(trans, onm.id) != 0: return GenTypes.LIFE_STATIC
if GenApi.is_nonlocal_name(trans, onm.id) != 0: return GenTypes.LIFE_UNKNOWN
if self.Gen.VarGen.lookup_var(trans.SymTab, onm.id) is not None: return GenTypes.LIFE_FRAME
return GenTypes.LIFE_STATIC
return GenTypes.LIFE_UNKNOWN
if k == ast.ASTKind.Call:
# 分配类调用 → 堆;其余调用 → 未知(函数摘要落地前保守放行)
cl: ast.Call* = ast.Call*(node)
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(cl.func)
if at.attr is not None:
if (string.strcmp(at.attr, "alloc") == 0 or string.strcmp(at.attr, "alloc_buf") == 0 or string.strcmp(at.attr, "calloc") == 0 or string.strcmp(at.attr, "realloc") == 0): return GenTypes.LIFE_HEAP
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
fnm: ast.Name* = ast.Name*(cl.func)
if fnm.id is not None and string.strcmp(fnm.id, "malloc") == 0: return GenTypes.LIFE_HEAP
return GenTypes.LIFE_UNKNOWN
if k == ast.ASTKind.New:
# M2.4 `new`:生命期由描述符决定(§2.3 组合矩阵)
# stack → slife:帧生命期(等价既有 `&T(...)`)
# heap/memgr slife → 同样按帧生命期:作用域结束即释放(RAII),逃逸出去必然悬垂
# hlife(含 heap/memgr 的缺省)→ 堆/池生命期,可自由传递
nn: ast.New* = ast.New*(node)
if nn is None: return GenTypes.LIFE_UNKNOWN
if nn.Life == ast.NEW_LIFE_SLIFE: return GenTypes.LIFE_FRAME
if nn.Life == ast.NEW_LIFE_DEFAULT and nn.Source == ast.NEW_SRC_STACK: return GenTypes.LIFE_FRAME
return GenTypes.LIFE_HEAP
return GenTypes.LIFE_UNKNOWN
# propagate_life - 把 RHS 的生命期写到赋值目标(仅 Name 目标;全局目标走汇点检查)。
def propagate_life(self, trans: HT.Translator*, target_node: ast.AST*, rhs_node: ast.AST*, target_ty: mlirlite.LLVMType*) -> int:
"""Propagate the RHS life tag onto a Name target; 0=done / 1=skipped"""
if trans is None or target_node is None: return 1
if target_node.kind() != ast.ASTKind.Name: return 1
tnm: ast.Name* = ast.Name*(target_node)
if tnm.id is None: return 1
if GenApi.is_global_name(trans, tnm.id) != 0: return 1
if GenApi.is_nonlocal_name(trans, tnm.id) != 0: return 1
life: int = self.life_of_expr(trans, rhs_node, target_ty)
self.Gen.VarGen.set_var_life(trans.SymTab, tnm.id, life)
return 0
# fail_if_life_escapes - 汇点检查:被写出表达式的生命期为 LIFE_FRAME → LifetimeError。
# 这是 GenApi.fail_if_stack_addr_escape 的唯一实现(语法判据已并入 life_of_expr)。
def fail_if_life_escapes(self, node: ast.AST*, where: str, trans: HT.Translator*, target_ty: mlirlite.LLVMType*) -> int:
"""Fail fast (LifetimeError) when the written-out value is frame-lifetime storage"""
if node is None or trans is None: return 0
if self.life_of_expr(trans, node, target_ty) != GenTypes.LIFE_FRAME: return 0
# 报文里给出名字(&x / x),便于定位;前缀单独存放,避免 str 相加(str 是 i8*,无 '+' 语义)
nm_txt: str = "expression"
nm_pre: str = ""
if node.kind() == ast.ASTKind.Name:
n2: ast.Name* = ast.Name*(node)
if n2.id is not None: nm_txt = n2.id
elif node.kind() == ast.ASTKind.UnaryOp:
u2: ast.UnaryOp* = ast.UnaryOp*(node)
if u2.operand is not None and u2.operand.kind() == ast.ASTKind.Name:
n3: ast.Name* = ast.Name*(u2.operand)
if n3.id is not None:
nm_txt = n3.id
nm_pre = "&"
self.Gen.TypeGenErr.fatal_error(node, f"stack address escapes: `{nm_pre}{nm_txt}` refers to storage of the current frame (local variable/array or parameter); using it at {where} hands out a stack address, which becomes invalid once the function returns (dangling pointer). Return the value itself (a value copy) or allocate on the heap (`new heap hlife ...`).", "LifetimeError")
return 0
def _is_addrlike_call(self, node: ast.AST*) -> int:
"""Return 1 if the rhs is an explicit pointer expression (c.Addr/c.Deref/c.Cast/c.Ptr call, or new-syntax `&x` / `*p`)."""
if node is None: return 0
# New syntax (R3/R4): `&x` and `*p` parse as UnaryOp(AddrOf) / UnaryOp(Deref) and replace the
# c.Addr / c.Deref call forms. A pointer-valued RHS must not take the module-global
# "object with static storage -> copy the object" branch, so both forms count as addrlike.
if node.kind() == ast.ASTKind.UnaryOp:
uo_al: ast.UnaryOp* = ast.UnaryOp*(node)
if uo_al.op == ast.OpKind.AddrOf: return 1
if uo_al.op == ast.OpKind.Deref: return 1
return 0
if node.kind() != ast.ASTKind.Call: return 0
cl: ast.Call* = ast.Call*(node)
if cl.func is None or cl.func.kind() != ast.ASTKind.Attribute: return 0
at: ast.Attribute* = ast.Attribute*(cl.func)
if at.value is None or at.value.kind() != ast.ASTKind.Name: return 0
vn: ast.Name* = ast.Name*(at.value)
if vn.id is None or string.strcmp(vn.id, "c") != 0: return 0
if at.attr is None: return 0
if string.strcmp(at.attr, "Addr") == 0: return 1
if string.strcmp(at.attr, "Deref") == 0: return 1
if string.strcmp(at.attr, "Cast") == 0: return 1
if string.strcmp(at.attr, "Ptr") == 0: return 1
return 0
def _is_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"string literal length {n_s} exceeds the declared char array length {arr_count}; out-of-bounds writes are forbidden", "OverflowError")
for bi_s in range(n_s):
bidx_s: mlirlite.Value* = mlirlite.const_int32(pool, bi_s)
bp_s: mlirlite.Value* = mlirlite.build_gep_array(builder, arr_ty, elem_ty, array_alloca, bidx_s)
if bp_s is None: break
bvv: mlirlite.Value* = mlirlite.const_int32(pool, sv_s[bi_s])
bsv: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, bvv, elem_ty, e0_s)
mlirlite.build_store(builder, bsv, bp_s)
return 0
# Element count exceeds declared length: fail fast (avoid out-of-range GEP writes)
if arr_count > 0 and sizet(arr_count) < elts_count:
self.Gen.TypeGenErr.fatal_error(list_node, f"array initializer element count {elts_count} exceeds the declared array length {arr_count}; out-of-bounds writes are forbidden", "OverflowError")
# Emit GEP + store for each element
for i in range(elts_count):
elem_node: ast.AST* = elts.get(i)
# Explicit null check (repo convention: is None); an empty slot means an element count mismatch.
# When the slot is null, fail fast with class/field/index context.
if elem_node is None:
ctx_dbg: str = ""
if owner_ctx is not None: ctx_dbg = owner_ctx
src_line: int = 0
src_col: int = 0
if list_node is not None:
src_line = list_node.lineno
src_col = list_node.col_offset
self.Gen.TypeGenErr.fatal_error(list_node, f"global array initialization hit a null AST element: {ctx_dbg}element {i} is a bare null pointer (0), " f"but the list element count is {elts_count} (element and count mismatch). " f"somewhere upstream produced a null AST element (slot {i} of list-literal node {src_line}:{src_col} was not filled), " f"silently skipping/padding with zero is refused; compilation aborted.", "ValueError")
return 1
# Object validity check (nonzero vptr): a zero vptr makes any virtual call read address 0
elem_vptr: uint64 = *((uint64*)(elem_node))
if elem_vptr == 0:
ctx_vt: str = ""
if owner_ctx is not None: ctx_vt = owner_ctx
vt_line: int = 0
vt_col: int = 0
if list_node is not None:
vt_line = list_node.lineno
vt_col = list_node.col_offset
self.Gen.TypeGenErr.fatal_error(list_node, f"global array initialization hit an invalid-object AST element (vptr is 0): {ctx_vt}" f"element {i} has a nonzero pointer (address {uint64(elem_node)}) but its object header (vtable pointer) is 0, " f"and any virtual call on it (kind()/dump()/...) reads address 0 at `load [0+slot]`. " f"list-literal node position {vt_line}:{vt_col}, element count {elts_count}. " f"somewhere upstream produced an AST node with an uninitialized vptr (typical source: a deep/block copy that moved only fields, not the vptr), " f"refusing to continue; compilation aborted.", "ValueError", vt_col)
return 1
idx_val: mlirlite.Value* = mlirlite.const_int32(pool, i)
elem_ptr: mlirlite.Value* = mlirlite.build_gep_array(builder, arr_ty, elem_ty, array_alloca, idx_val)
if elem_ptr is None: continue
# Nested array: element type is an array and the node is a list literal => recurse
inner_arr: int = 0
if self.Gen.ExprGenBase.is_array_type(elem_ty) != 0:
# Null check for nested element: use is not None (not integer 0)
if elem_node is not None and elem_node.kind() == ast.ASTKind.List: inner_arr = 1
if inner_arr != 0:
self._init_global_array_from_list(builder, pool, mod, elem_ptr, elem_node, trans, owner_ctx)
continue
elem_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, elem_node, None, 0, trans)
if elem_val is None:
# Element translation failed: fail fast to expose the upstream cause
self.Gen.TypeGenErr.fatal_error(elem_node, "global array initializer element translation failed", "TypeError")
# Type coercion (e.g. i32 -> i8); Pointee is statically i64 so compare with != 0
store_val: mlirlite.Value* = elem_val
if elem_ptr.Ty is not None and elem_ptr.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, elem_val, elem_ptr.Ty.Pointee, elem_node)
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 = "(annotation contains t.CConst; the source name at the declaration was not recorded)"
if src_name is not None: decl = f"t.CConst | {src_name}"
return GenApi.escape_fstring_copy(f"cannot assign to const variable '{name}' ({kind}); " f"declaration type (including const): {decl}" f"; fix: remove the const descriptor / t.CConst from the declaration, or assign once at the declaration " f"and afterwards use only non-const copies/temporaries; a const object forbids assignment, " f"augmented assignment and element assignment within its scope, and this never degrades to a warning")
def const_assign_fail(self, node: ast.AST*, kind: str, name: str, src_name: str) -> int:
"""Fail fast when a const target is written."""
self.Gen.TypeGenErr.fatal_error(node, self.const_assign_msg(kind, name, src_name), "TypeError")
return 0
def translate_augassign(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate an augmented assignment statement (x += 1)."""
if node is None: return 0
aug: ast.AugAssign* = ast.AugAssign*(node)
if aug is None: return 0
target: ast.AST* = aug.target
if target is None: return 0
tk: int = target.kind()
# Attribute target: self.field += 1 (get_attribute_ptr -> load -> binop -> store)
if tk == ast.ASTKind.Attribute:
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# const field: fail fast on the field write
at_ca: ast.Attribute* = ast.Attribute*(target)
if at_ca is not None and at_ca.attr is not None:
if self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, target) != 0:
self.const_assign_fail(target, "field assignment", at_ca.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, target))
# 1. Get the field pointer
field_ptr: mlirlite.Value* = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, target, trans)
if field_ptr is None:
# fail-fast: attribute pointer resolution failed
self.Gen.TypeGenErr.fatal_error(target, "attribute ptr is None", "TypeError")
# 2. Determine the field type and load the current value
target_ty: mlirlite.LLVMType* = None
if field_ptr.Ty is not None and field_ptr.Ty.Pointee != 0: target_ty = field_ptr.Ty.Pointee
if target_ty is None:
# fail-fast: field type missing, no i32 fallback
self.Gen.TypeGenErr.fatal_error(target, "field type information missing", "TypeError")
# t.CVolatile field: both load and store carry the volatile qualifier
vol_fa: int = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target)
# rawptr[t.CVolatile|T] base: field load/store also carry volatile
at_aug: ast.Attribute* = ast.Attribute*(target)
if at_aug is not None:
if self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, at_aug.value) != 0: vol_fa = 1
cur_val: mlirlite.Value* = mlirlite.build_load(builder, target_ty, field_ptr, vol_fa)
if cur_val is None:
# fail-fast: attribute current value load failed
self.Gen.TypeGenErr.fatal_error(target, "cannot load the current value of the attribute", "TypeError")
# 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 translation failed", "TypeError")
# 4. Apply the binary op
result: mlirlite.Value* = self._apply_aug_op(pool, builder, aug.op, cur_val, rhs_val, node, trans, target)
if result is None:
# fail-fast: attribute binary op failed
self.Gen.TypeGenErr.fatal_error(target, f"attribute binary op failed op={aug.op}", "TypeError")
# 5. Coerce the type and store
result = self.Gen.ExprGenBase.coerce_to_type(builder, result, target_ty, node)
if result is None:
# fail-fast: type coercion failed
self.Gen.TypeGenErr.fatal_error(target, "type coercion failed", "TypeError")
mlirlite.build_store(builder, result, field_ptr, vol_fa)
return 0
# Subscript target: self.state[i] += x (get_subscript_ptr -> load -> binop -> store)
if tk == ast.ASTKind.Subscript:
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# const element target: fail fast when the base variable or field is const
sub_ca: ast.Subscript* = ast.Subscript*(target)
if sub_ca is not None and sub_ca.value is not None:
if sub_ca.value.kind() == ast.ASTKind.Name:
nm_ca: ast.Name* = ast.Name*(sub_ca.value)
if nm_ca.id is not None:
ve_ca: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_ca.id)
if self.Gen.VarGen.var_is_const(ve_ca) != 0:
self.const_assign_fail(target, "element assignment", nm_ca.id, self.Gen.VarGen.var_annot_class_name(ve_ca))
elif sub_ca.value.kind() == ast.ASTKind.Attribute:
at_ca2: ast.Attribute* = ast.Attribute*(sub_ca.value)
if at_ca2.attr is not None:
if self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, sub_ca.value) != 0:
self.const_assign_fail(target, "element assignment", at_ca2.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, sub_ca.value))
# 1. Get the element pointer
elem_ptr: mlirlite.Value* = self.Gen.ExprGenName.get_subscript_ptr(builder, pool, mod, target, trans)
if elem_ptr is None:
# fail-fast: subscript pointer resolution failed
self.Gen.TypeGenErr.fatal_error(target, "subscript ptr is None", "TypeError")
# 2. Determine the element type and load the current value
target_ty: mlirlite.LLVMType* = None
if elem_ptr.Ty is not None and elem_ptr.Ty.Pointee != 0: target_ty = elem_ptr.Ty.Pointee
if target_ty is None:
# fail-fast: element type missing, no i32 fallback
self.Gen.TypeGenErr.fatal_error(target, "element type information missing", "TypeError")
# rawptr[t.CVolatile|T] base: p[i] load/store carry volatile when the base is resolvable
vol_sa: int = 0
sub_aug: ast.Subscript* = ast.Subscript*(target)
if sub_aug is not None: vol_sa = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, sub_aug.value)
cur_val: mlirlite.Value* = mlirlite.build_load(builder, target_ty, elem_ptr, vol_sa)
if cur_val is None:
# fail-fast: element current value load failed
self.Gen.TypeGenErr.fatal_error(target, "cannot load the current value of the subscript element", "TypeError")
# 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 translation failed", "TypeError")
# 4. Apply the binary op
result: mlirlite.Value* = self._apply_aug_op(pool, builder, aug.op, cur_val, rhs_val, node, trans, target)
if result is None:
# fail-fast: subscript binary op failed
self.Gen.TypeGenErr.fatal_error(target, f"subscript binary op failed op={aug.op}", "TypeError")
# 5. Coerce the type and store
result = self.Gen.ExprGenBase.coerce_to_type(builder, result, target_ty, node)
if result is None:
# fail-fast: type coercion failed
self.Gen.TypeGenErr.fatal_error(target, "type coercion failed", "TypeError")
mlirlite.build_store(builder, result, elem_ptr, vol_sa)
return 0
# UnaryOp target: *p += v (deref augmented assignment: load, op, store back)
if tk == ast.ASTKind.UnaryOp:
uo_ag: ast.UnaryOp* = ast.UnaryOp*(target)
if uo_ag is None or uo_ag.operand is None: self.Gen.TypeGenErr.fatal_error(target, "augmented-assignment target unary op is missing an operand", "TypeError")
if uo_ag.op != ast.OpKind.Deref:
self.Gen.TypeGenErr.fatal_error(target, "augmented-assignment target supports only the unary '*' dereference (&x / -x / ~x are not valid lvalues)", "SyntaxError")
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
ptr_ag: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, uo_ag.operand, None, 0, trans)
if ptr_ag is None or ptr_ag.Ty is None or self.Gen.ExprGenBase.is_ptr_type(ptr_ag.Ty) == 0:
self.Gen.TypeGenErr.fatal_error(target, "the operand of augmented-assignment '*' must be a pointer type (type inference degraded)", "TypeError")
ty_ag: mlirlite.LLVMType* = ptr_ag.Ty.Pointee
if ty_ag is None:
self.Gen.TypeGenErr.fatal_error(target, "deref augmented-assignment target type missing (pointee is empty)", "TypeError")
old_ag: mlirlite.Value* = mlirlite.build_load(builder, ty_ag, ptr_ag, 0)
if old_ag is None: self.Gen.TypeGenErr.fatal_error(target, "cannot load the current dereferenced value", "TypeError")
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 translation failed", "TypeError")
res_ag: mlirlite.Value* = self._apply_aug_op(pool, builder, aug.op, old_ag, rhs_ag, node, trans, target)
if res_ag is None: self.Gen.TypeGenErr.fatal_error(target, f"deref binary op failed op={aug.op}", "TypeError")
res_ag = self.Gen.ExprGenBase.coerce_to_type(builder, res_ag, ty_ag, node, trans)
if res_ag is None: self.Gen.TypeGenErr.fatal_error(target, "type coercion failed", "TypeError")
mlirlite.build_store(builder, res_ag, ptr_ag, 0)
return 0
if tk != ast.ASTKind.Name:
# fail-fast: unsupported AugAssign target kind
self.Gen.TypeGenErr.fatal_error(target, "AugAssign target type unsupported; only Name/Attribute/Subscript/*dereference are supported", "TypeError")
nm: ast.Name* = ast.Name*(target)
if nm is None or nm.id is None: return 0
# const variable: fail fast immediately
ve_aug: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if self.Gen.VarGen.var_is_const(ve_aug) != 0:
self.const_assign_fail(target, "augmented assignment", nm.id, self.Gen.VarGen.var_annot_class_name(ve_aug))
# REPL session variable aug-assign: read slot, apply op, write slot and result area
if trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0:
if self.Gen.VarGen.repl_try_augassign(trans, nm.id, aug.op, aug.value, node) != 0: return 0
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# 1. Determine scope: 0=local, 1=global, 2=nonlocal
scope_type: int = 0
if GenApi.is_global_name(trans, nm.id) != 0: scope_type = 1
elif GenApi.is_nonlocal_name(trans, nm.id) != 0: scope_type = 2
# t.CVolatile variable: both load and store carry the volatile qualifier
vol_aa: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
# 2. Load the current value
cur_val: mlirlite.Value* = None
target_alloca: mlirlite.Value* = None
target_ty: mlirlite.LLVMType* = None
if scope_type == 1:
# global variable
target_alloca = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
if target_alloca is not None:
if target_alloca.Ty is None:
# fail-fast: variable type missing, no i32 fallback
self.Gen.TypeGenErr.fatal_error(target, "variable type information missing", "TypeError")
target_ty = target_alloca.Ty.Pointee
cur_val = mlirlite.build_load(builder, target_ty, target_alloca, vol_aa)
elif scope_type == 2:
# nonlocal variable (via closure env)
cur_val = self.Gen.StmtGenDecl.load_nonlocal_var(trans, nm.id)
if cur_val is not None: target_ty = cur_val.Ty
else:
# Ordinary local variable
target_alloca = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if target_alloca is not None:
if target_alloca.Ty is None:
# fail-fast: variable type missing, no i32 fallback
self.Gen.TypeGenErr.fatal_error(target, "variable type information missing", "TypeError")
target_ty = target_alloca.Ty.Pointee
cur_val = mlirlite.build_load(builder, target_ty, target_alloca, vol_aa)
if cur_val is None:
# fail-fast: target variable current value load failed
self.Gen.TypeGenErr.fatal_error(target, f"cannot load the current value of target variable '{nm.id}'", "TypeError")
# 3. Translate the RHS value
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 translation failed", "TypeError")
# 3b. Operator overload: object += -> __iadd__, fall back to __add__ (locals only)
if scope_type == 0 and target_alloca is not None:
aug_dup: str = self._aug_op_to_dunder(aug.op)
if aug_dup is not None:
ovl_self_aug: mlirlite.Value* = target_alloca
# Pointer storage slot (Ptr(Ptr(Struct))): load the object pointer as self
if target_alloca.Ty is not None and target_alloca.Ty.Pointee != 0 and self.Gen.ExprGenBase.is_ptr_type(target_alloca.Ty.Pointee) != 0:
load_self_aug: mlirlite.Value* = mlirlite.build_load(builder, target_alloca.Ty.Pointee, target_alloca)
if load_self_aug is not None: ovl_self_aug = load_self_aug
# Try the aug dunder only when the target is a struct (class instance)
if self.Gen.StructGen.get_struct_type_from_value(ovl_self_aug) is not None:
aug_extra: sizet* = pool.alloc(8)
if aug_extra is not None:
aug_extra[0] = sizet(rhs_val)
aug_ovl: mlirlite.Value* = self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, ovl_self_aug, aug_dup, trans, aug_extra, 1)
if aug_ovl is None:
aug_fb: str = self._op_to_dunder(aug.op)
if aug_fb is not None: aug_ovl = self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, ovl_self_aug, aug_fb, trans, aug_extra, 1)
if aug_ovl is not None:
aug_res: mlirlite.Value* = aug_ovl
if target_ty is not None and aug_res.Ty is not None and self.Gen.ExprGenBase._same_agg_ty(pool, aug_res.Ty, target_ty) == 0:
# Target is a struct value but the result is a pointer -> load the struct value
if self.Gen.ExprGenBase.is_ptr_type(target_ty) == 0 and self.Gen.ExprGenBase.is_ptr_type(aug_res.Ty) != 0 and aug_res.Ty.Pointee != 0:
aug_res = mlirlite.build_load(builder, aug_res.Ty.Pointee, aug_res)
else:
coerced_aug: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, aug_res, target_ty, node, trans)
if coerced_aug is not None: aug_res = coerced_aug
if aug_res is None or target_ty is None or self.Gen.ExprGenBase._same_agg_ty(pool, aug_res.Ty, target_ty) == 0:
# fail-fast: overload return type mismatch, do not corrupt memory silently
self.Gen.TypeGenErr.fatal_error(node, "augmented-assignment operator overload return type does not match the target variable", "TypeError")
mlirlite.build_store(builder, aug_res, target_alloca, vol_aa)
return 0
# 4. Coerce the type and apply the binary op
result: mlirlite.Value* = self._apply_aug_op(pool, builder, aug.op, cur_val, rhs_val, node, trans, target)
if result is None:
# fail-fast: binary op failed
self.Gen.TypeGenErr.fatal_error(target, f"applying binary op to variable '{nm.id}' failed op={aug.op}", "TypeError")
# 5. Store the result
result = self.Gen.ExprGenBase.coerce_to_type(builder, result, target_ty, node)
if result is None:
# fail-fast: type coercion failed
self.Gen.TypeGenErr.fatal_error(target, "type coercion failed", "TypeError")
if scope_type == 1:
# global variable
if target_alloca is not None: mlirlite.build_store(builder, result, target_alloca, vol_aa)
elif scope_type == 2:
# nonlocal variable
nl_ptr: mlirlite.Value* = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(trans, nm.id)
if nl_ptr is not None: mlirlite.build_store(builder, result, nl_ptr)
else:
# Ordinary local variable
if target_alloca is not None: 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, "variable type annotation resolution failed, variable name: %s, annotation: %s. last builtins match: %s expanded to %s, still unresolvable", nm_id, ann_txt_x, btd_nm_x, btd_rs_x)
elif btd_nm_x is not None: viperlib.snprintf(msg_buf, msg_len, "variable type annotation resolution failed, variable name: %s, annotation: %s. last builtins match: %s missed (builtins.vp has no such typedef, or the scan did not run)", nm_id, ann_txt_x, btd_nm_x)
else: viperlib.snprintf(msg_buf, msg_len, "variable type annotation resolution failed, variable name: %s, annotation: %s. last builtins match: not triggered", nm_id, ann_txt_x)
return msg_buf
# translate_annassign_prescan - pre-create alloca for annotated vars
def translate_annassign_prescan(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Pre-create allocas for AnnAssign variables at the entry block."""
if node is None: return 0
k: int = node.kind()
if k != ast.ASTKind.AnnAssign: return 0
aa: ast.AnnAssign* = ast.AnnAssign*(node)
if aa is None or aa.target is None: return 0
target: ast.AST* = aa.target
if target.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(target)
if nm.id is None: return 0
# CDefine annotation: register the constant instead of creating an alloca
if self.is_cdefine_annotation(aa.annotation) != 0:
pool_ps: memhub.MemBuddy* = trans.Pool
self._register_cdefine_from_value(pool_ps, nm.id, aa.value)
return 0
# CTypedef annotation: register the alias only, no storage (avoids duplicate symbols)
if self.is_ctypedef_annotation(aa.annotation) != 0: return 0
# REPL fragment entry: AnnAssign goes to the session slot; skip pre-creating a local alloca
if trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0: return 0
# global/nonlocal variables need no local alloca
if GenApi.is_global_name(trans, nm.id) != 0: return 0
if GenApi.is_nonlocal_name(trans, nm.id) != 0: return 0
# Module-level globals are already registered; only skip the local alloca at module scope
if self.Gen.VarGen.is_module_scope(trans.SymTab) != 0:
if self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id) is not None: return 0
# Check whether it already exists
existing: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, nm.id)
if existing is not None: return 0
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
# Infer the type from the annotation (no hard-coded i32)
var_ty: mlirlite.LLVMType* = None
annot: ast.AST* = aa.annotation
if annot is not None:
# Value-reference markers are parameter-only: a local variable `x: &T` fails fast.
self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "a local variable declaration")
# Specialize generics before resolving so list[X] does not degrade to opaque list*
self._prespecialize_generic_annotation(trans, pool, annot)
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0)
if resolved is not None:
var_ty = resolved
# Single-param CArray + list literal -> static array; keep PreScan and Handle types in sync
var_ty = self._infer_static_array_ty_from_literal(pool, annot, aa.value, var_ty)
# [D31 fail-fast] generic annotation degraded to a bare base class
self._fail_on_degenerated_generic_annotation(aa, nm.id, annot, var_ty)
else:
# fail-fast: annotation resolution failed (no silent i32 fallback)
self.Gen.TypeGenErr.fatal_error(aa, self._build_ann_fail_msg(pool, annot, nm.id), "TypeError")
else:
# fail-fast: AnnAssign must have an annotation
self.Gen.TypeGenErr.fatal_error(aa, f"AnnAssign is missing a type annotation, variable name: {nm.id}", "SyntaxError")
# Ensure the full cross-module struct definition is available (for alloca)
self.Gen.StructGen.ensure_struct_def_in_module(pool, trans.Module, var_ty)
alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, var_ty)
if alloca is not None:
if self.Gen.VarGen.define_var(trans.SymTab, nm.id, alloca) == 0:
# Store the annotation's class name; pass pool so generic Subscript yields the mangled name
if annot is not None:
cls_nm_ps: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(annot, trans._imported_modules, pool, trans._from_imports)
if cls_nm_ps is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, nm.id, cls_nm_ps)
# Check for bytes* or str* (i8** semantics, 8-byte stride)
if self._is_ptr_element_annotation(annot) != 0: self.Gen.VarGen.set_var_ptr_element(trans.SymTab, nm.id)
# const object (annotation has t.CConst and the type is not a pointer): no writes in scope
if self.Gen.TypeGenErr.is_const_object_marker(annot, var_ty) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, nm.id)
# t.CVolatile: mark the variable at declaration; its load/store carry volatile
if self.Gen.TypeGenErr.has_decorator_marker(annot, "CVolatile", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id)
# rawptr[t.CVolatile | T]: dereference load/store carry volatile
if self.Gen.TypeGenErr.has_pointee_volatile_marker(annot) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, nm.id)
return 0
return 0
# _prespecialize_generic_annotation - register the generic specialization before resolving
def _prespecialize_generic_annotation(self, trans: HT.Translator*, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""Pre-specialize a generic annotation; return 1 if attempted."""
if annot is None or trans is None: return 0
# Take the generic Subscript side: the Subscript itself or the Subscript of A*
spec_annot: ast.Subscript* = None
if annot.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ps: ast.BinOp* = ast.BinOp*(annot)
if bop_ps.op == ast.OpKind.BitOr:
if bop_ps.left is not None and bop_ps.left.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ps.left)
elif bop_ps.right is not None and bop_ps.right.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ps.right)
if spec_annot is None: return 0
sv_ps: ast.AST* = spec_annot.value
if sv_ps is None or sv_ps.kind() != ast.ASTKind.Name: return 0
sub_nm: ast.Name* = ast.Name*(sv_ps)
if sub_nm.id is None: return 0
template_cd: ast.ClassDef* = self.Gen.ClassGenGeneric._find_generic_template(sub_nm.id)
if template_cd is None: return 0
type_args_ps: list[str]* = self.Gen.CallGenCtor._extract_type_args_from_slice(pool, spec_annot.slice, trans._from_imports)
if type_args_ps is None or len(type_args_ps) == 0: return 0
self.Gen.ClassGenGeneric._specialize_generic_class(trans, sub_nm.id, type_args_ps)
return 1
# translate_annassign - translate an AnnAssign statement; returns new variable count
def _fail_on_degenerated_generic_annotation(self, aa: ast.AnnAssign*, var_name: str, annot: ast.AST*, var_ty: mlirlite.LLVMType*) -> int:
"""Fail fast when a generic annotation degrades to a bare base pointer."""
if aa is None or annot is None or var_ty is None: return 0
# Take the generic Subscript side: the Subscript itself or the Subscript of A*
ann_sub: ast.Subscript* = None
if annot.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ff: ast.BinOp* = ast.BinOp*(annot)
if bop_ff.op == ast.OpKind.BitOr:
if bop_ff.left is not None and bop_ff.left.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(bop_ff.left)
elif bop_ff.right is not None and bop_ff.right.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(bop_ff.right)
if ann_sub is None or ann_sub.value is None or ann_sub.value.kind() != ast.ASTKind.Name: return 0
base_nm_ff: ast.Name* = ast.Name*(ann_sub.value)
if base_nm_ff.id is None: return 0
# Only treat it as degraded when the base is a real generic template
if self.Gen.ClassGenGeneric._find_generic_template(base_nm_ff.id) is None: return 0
if var_ty.Pointee == 0: return 0
vt_nm_ff: str = mlirlite.get_struct_name(var_ty.Pointee)
if vt_nm_ff is None: return 0
vt_pf_ff: int = GenApi.sha1_prefix_len(vt_nm_ff)
vt_short_ff: str = vt_nm_ff + vt_pf_ff if vt_pf_ff != 0 else vt_nm_ff
if string.strchr(vt_short_ff, '[') is not None:
return 0 # With type args: not degraded
self.Gen.TypeGenErr.fatal_error(aa, f"the generic annotation '{base_nm_ff.id}[...]' of variable '{var_name}' resolved to a bare base class " f"'{vt_nm_ff}*' (type arguments lost); subsequent .get()/.set() will specialize by the wrong element " f"type (such as list[int].get returning i32) -> inttoptr truncates the pointer and crashes. " f"Silent continuation is forbidden -- check the degradation point of this annotation in resolve_annotation_type.", "TypeError")
return 0
def translate_annassign(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate an AnnAssign statement; return the new variable count."""
aa: ast.AnnAssign* = ast.AnnAssign*(node)
if aa is None: return 0
target: ast.AST* = aa.target
if target is None 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.
has_mod_decl_add: int = 0
if nm.id is not None and trans is not None and self.Gen.VarGen.is_module_scope(trans.SymTab) == 0:
if self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id) is not None: has_mod_decl_add = 1
# 双保险:本文件顶层变量名集合(SymTab.Root.Vars 可能在部分时序看不到)
if has_mod_decl_add == 0 and GenApi.is_file_var(nm.id) != 0: has_mod_decl_add = 1
if has_mod_decl_add != 0:
dup_decl: int = 0
if GenApi.is_global_name(trans, nm.id) != 0 or GenApi.is_nonlocal_name(trans, nm.id) != 0: dup_decl = 1
if dup_decl != 0:
self.Gen.TypeGenErr.fatal_error(aa, f"duplicate declaration: '{nm.id}' is already a module-level variable; after declaring global/nonlocal here " f"it is re-declared with a type annotation. Remove the type annotation on this line; a plain assignment modifies the global", "TypeError")
else:
self.Gen.TypeGenErr.fatal_error(aa, f"duplicate declaration: '{nm.id}' is already a module-level variable; without a global declaration in a function, a type annotation must not re-declare " f"the same name (it would shadow the global with a local). For an independent local, use another name; " f"to modify the global, add a global declaration in the function and remove this annotation", "TypeError")
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# Determine the type (inferred from the annotation)
var_ty: mlirlite.LLVMType* = None
annot: ast.AST* = aa.annotation
if annot is not None:
# Value-reference markers are parameter-only: a local variable `x: &T` fails fast.
self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "a local variable declaration")
# Same as PreScan: specialize generics first so list[X] does not degrade to bare list*
self._prespecialize_generic_annotation(trans, pool, annot)
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0)
if resolved is not None: var_ty = resolved
else:
# fail-fast: annotation resolution failed (no silent i32 fallback)
self.Gen.TypeGenErr.fatal_error(aa, self._build_ann_fail_msg(pool, annot, nm.id), "TypeError")
else:
# fail-fast: AnnAssign must have an annotation
self.Gen.TypeGenErr.fatal_error(aa, f"AnnAssign is missing a type annotation, variable name: {nm.id}", "SyntaxError")
# void cannot be a variable type (only void* and opaque-struct typedef)
if var_ty is not None and self.Gen.StmtGenDecl._is_void_type(var_ty) != 0:
self.Gen.TypeGenErr.fatal_error(aa, f"cannot declare a variable of void type '{nm.id}' (void may only be used for void* and typedef aliases)", "TypeError")
# 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)
# 栈地址逃逸:`global G; G: T = &local`,或 `G: T* = arr`(数组退让)——与普通
# 赋值同口径,禁止把本帧栈地址写出;目标为同型聚合时按值拷贝,不在此列。
if aa.value is not None:
tgt_aa: mlirlite.LLVMType* = None
if mod_alloca is not None and mod_alloca.Ty is not None: tgt_aa = mod_alloca.Ty.Pointee
GenApi.fail_if_stack_addr_escape(aa.value, "a module-level global variable", trans, tgt_aa)
if mod_alloca is not None and aa.value is not None:
# P1-A context width propagation: pass the target type so integer literals materialize at target width
aa_glob_ty: mlirlite.LLVMType* = None
if mod_alloca.Ty is not None: aa_glob_ty = mod_alloca.Ty.Pointee
# Set CurAssignAnnot/CurAssignNode before translating the RHS (kept in sync across all four branches)
trans.CurAssignAnnot = aa.annotation
trans.CurAssignNode = aa.value
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, aa_glob_ty, "module-level global")
if rhs_val is not None:
# Object global with static storage: bind storage and copy the object
stg_ref_aa: mlirlite.Value* = None
if nm.id is not None:
stg_key_aa: str = pool.alloc(sizet(string.strlen(nm.id)) + 8)
if stg_key_aa is not None:
viperlib.snprintf(stg_key_aa, sizet(string.strlen(nm.id)) + 8, "%s.__stg", nm.id)
stg_ref_aa = self.Gen.VarGen.lookup_module_var(trans.SymTab, stg_key_aa)
if self._is_addrlike_call(aa.value) == 0 and stg_ref_aa is not None and rhs_val.Ty is not None and stg_ref_aa.Ty is not None and stg_ref_aa.Ty.Pointee != 0:
stg_ty_aa: mlirlite.LLVMType* = stg_ref_aa.Ty.Pointee
rhs_inner_aa: mlirlite.LLVMType* = None
match rhs_val.Ty:
case mlirlite.LLVMType.Ptr(pointee_aa):
rhs_inner_aa = pointee_aa
case _:
pass
if rhs_inner_aa is not None and rhs_inner_aa.kind() == mlirlite.LLVMType.Struct and self._stg_ty_equal(pool, stg_ty_aa, rhs_inner_aa) != 0:
mlirlite.build_store(builder, stg_ref_aa, mod_alloca)
stg_obj_val_aa: mlirlite.Value* = mlirlite.build_load(builder, stg_ty_aa, rhs_val)
if stg_obj_val_aa is not None: mlirlite.build_store(builder, stg_obj_val_aa, stg_ref_aa)
# Clear the assignment context before returning (avoid leaking into later statements)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
target_ty: mlirlite.LLVMType* = None
if mod_alloca.Ty is not None: target_ty = mod_alloca.Ty.Pointee
if target_ty is not None:
# str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node)
rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans)
# t.CVolatile global: the initializing store carries the volatile qualifier
vol_gaa: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
mlirlite.build_store(builder, rhs_val, mod_alloca, vol_gaa)
# Clear the assignment context (all four branches must stay in sync)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
# nonlocal variable: write through the closure env
if GenApi.is_nonlocal_name(trans, nm.id) != 0:
if aa.value is not None:
# P1-A: fetch the nonlocal slot pointer first so its type can be the target type
nl_ptr: mlirlite.Value* = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(trans, nm.id)
# Infer the nonlocal type from nl_ptr instead of forcing i32
nl_target_ty: mlirlite.LLVMType* = None
if nl_ptr is not None and nl_ptr.Ty is not None: nl_target_ty = nl_ptr.Ty.Pointee
# Set CurAssignAnnot/CurAssignNode before translating the RHS (sync across all four branches)
trans.CurAssignAnnot = aa.annotation
trans.CurAssignNode = aa.value
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, nl_target_ty, "closure variable (nonlocal)")
if rhs_val is not None:
if nl_ptr is not None:
if nl_target_ty is None:
# fail-fast: nl_ptr type lost, cannot store safely
VLogger.error(f"nonlocal variable type information lost, cannot write: {nm.id}", "HandlesAnnAssign")
# Clear the assignment context before returning (avoid leaking into later statements)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node)
rhs_coerced: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, nl_target_ty, node)
mlirlite.build_store(builder, rhs_coerced, nl_ptr)
# Clear the assignment context (all four branches must stay in sync)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
# Module-level global: only module code modifies it; in a function without global, create a local
if self.Gen.VarGen.is_module_scope(trans.SymTab) != 0: mod_glob: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
else: mod_glob = None
if mod_glob is not None:
if aa.value is not None:
# List literal: initialize the array with per-element GEP + store
if aa.value.kind() == ast.ASTKind.List: self._init_global_array_from_list(builder, pool, mod, mod_glob, aa.value, trans)
else:
# P1-A: use the module-level global type as the target type
mg_val_ty: mlirlite.LLVMType* = None
if mod_glob.Ty is not None: mg_val_ty = mod_glob.Ty.Pointee
# Set CurAssignAnnot/CurAssignNode before translating the RHS (sync across all four branches)
trans.CurAssignAnnot = aa.annotation
trans.CurAssignNode = aa.value
rhs_val_mg: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, mg_val_ty, "module-level global")
if rhs_val_mg is not None:
# Object global with static storage: bind storage and copy the object
stg_ref_mg: mlirlite.Value* = None
if nm.id is not None:
stg_key_mg: str = pool.alloc(sizet(string.strlen(nm.id)) + 8)
if stg_key_mg is not None:
viperlib.snprintf(stg_key_mg, sizet(string.strlen(nm.id)) + 8, "%s.__stg", nm.id)
stg_ref_mg = self.Gen.VarGen.lookup_module_var(trans.SymTab, stg_key_mg)
if self._is_addrlike_call(aa.value) == 0 and stg_ref_mg is not None and rhs_val_mg.Ty is not None and stg_ref_mg.Ty is not None and stg_ref_mg.Ty.Pointee != 0:
stg_ty_mg: mlirlite.LLVMType* = stg_ref_mg.Ty.Pointee
rhs_inner_mg: mlirlite.LLVMType* = None
match rhs_val_mg.Ty:
case mlirlite.LLVMType.Ptr(pointee_mg):
rhs_inner_mg = pointee_mg
case _:
pass
if rhs_inner_mg is not None and rhs_inner_mg.kind() == mlirlite.LLVMType.Struct and self._stg_ty_equal(pool, stg_ty_mg, rhs_inner_mg) != 0:
mlirlite.build_store(builder, stg_ref_mg, mod_glob)
stg_obj_val_mg: mlirlite.Value* = mlirlite.build_load(builder, stg_ty_mg, rhs_val_mg)
if stg_obj_val_mg is not None: mlirlite.build_store(builder, stg_obj_val_mg, stg_ref_mg)
# Clear the assignment context before returning (avoid leaking into later statements)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
target_ty_mg: mlirlite.LLVMType* = None
if mod_glob.Ty is not None: target_ty_mg = mod_glob.Ty.Pointee
if target_ty_mg is not None:
# str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val_mg, aa.annotation, node)
rhs_val_mg = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val_mg, target_ty_mg, node)
# t.CVolatile module-level global: the initializing store carries volatile
vol_mga: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
mlirlite.build_store(builder, rhs_val_mg, mod_glob, vol_mga)
# Clear the assignment context (all four branches must stay in sync)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
# Single-param CArray + list literal -> static array; match PreScan so the alloca is reused
var_ty = self._infer_static_array_ty_from_literal(pool, annot, aa.value, var_ty)
# [D31 fail-fast] generic annotation degraded to a bare base class
self._fail_on_degenerated_generic_annotation(aa, nm.id, annot, var_ty)
# Ordinary local variable: create the alloca
alloca: mlirlite.Value* = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, nm.id, var_ty)
if alloca is None:
# fail-fast: storage creation failed
self.Gen.TypeGenErr.fatal_error(aa, f"cannot create storage for variable {nm.id}", "TypeError")
# A reused alloca type must match the resolved annotation type (compare struct names)
if alloca is not None and alloca.Ty is not None and var_ty is not None:
av_pt_d: mlirlite.LLVMType* = alloca.Ty.Pointee
if av_pt_d is not None and self.Gen.ExprGenBase.is_ptr_type(av_pt_d) != 0 and self.Gen.ExprGenBase.is_ptr_type(var_ty) != 0:
av_in: mlirlite.LLVMType* = av_pt_d.Pointee
vt_in: mlirlite.LLVMType* = var_ty.Pointee
if av_in is not None and vt_in is not None:
av_nm_d: str = mlirlite.get_struct_name(av_in)
vt_nm_d: str = mlirlite.get_struct_name(vt_in)
if av_nm_d is not None and vt_nm_d is not None and string.strcmp(av_nm_d, vt_nm_d) != 0:
# Fail fast only when the reused pointee is a bare generic base (lost type args)
av_pf: int = GenApi.sha1_prefix_len(av_nm_d)
av_short: str = av_nm_d + av_pf if av_pf != 0 else av_nm_d
if string.strchr(av_short, '[') is None:
av_tpl: ast.ClassDef* = self.Gen.ClassGenGeneric._find_generic_template(av_short)
if av_tpl is not None:
self.Gen.TypeGenErr.fatal_error(aa, f"the reused storage type of variable '{nm.id}' is the bare generic base class '{av_nm_d}*'" f" (type arguments lost), inconsistent with the annotation-resolved type '{vt_nm_d}*'; " f"silent mixing is forbidden -- method calls would specialize by the wrong element type and truncate the pointer", "TypeError")
# FakeDuck: a str variable gets a shadow struct {var}__meta__ for per-instance fields
fd_str_ty: mlirlite.LLVMType* = None
fd_meta_alloca: mlirlite.Value* = None
fd_meta_name: str = None
if annot is not None and annot.kind() == ast.ASTKind.Name:
fd_annot: ast.Name* = ast.Name*(annot)
if fd_annot is not None and fd_annot.id is not None:
if string.strcmp(fd_annot.id, "str") == 0: fd_str_ty = self.Gen.StructGen.get_struct_type("_str")
if fd_str_ty is not None:
# Ensure the full _str definition is available (opaque types cannot be alloca'd)
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, fd_str_ty)
# Shadow variable name must use a pool buffer (f-string stack buffers get overwritten)
fd_meta_name = pool.alloc(256)
if fd_meta_name is not None: viperlib.snprintf(fd_meta_name, 256, "%s__meta__", nm.id)
fd_meta_alloca = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, fd_meta_name, fd_str_ty)
if fd_meta_alloca is None:
# fail-fast: shadow storage creation failed
self.Gen.TypeGenErr.fatal_error(aa, f"cannot create storage for the str shadow variable {fd_meta_name}", "TypeError")
# Store the annotation's class name; pass pool so generic Subscript yields the mangled name
if annot is not None:
cls_nm_hd: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(annot, trans._imported_modules, pool, trans._from_imports)
if cls_nm_hd is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, nm.id, cls_nm_hd)
# Check for bytes* or str* (i8** semantics, 8-byte stride)
if self._is_ptr_element_annotation(annot) != 0: self.Gen.VarGen.set_var_ptr_element(trans.SymTab, nm.id)
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, "local variable annotation")
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")
# 生命期传播(本地变量):`s: str = l` → Life(s) = Life(l)(数组退让即借用)
if aa.value is not None and nm.id is not None and alloca is not None:
tgt_life_ty: mlirlite.LLVMType* = None
if alloca.Ty is not None: tgt_life_ty = alloca.Ty.Pointee
self.propagate_life(trans, target, aa.value, tgt_life_ty)
return 0
# translate_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, "assignment", nm_c.id, self.Gen.VarGen.var_annot_class_name(ve_c))
elif tgt_c.kind() == ast.ASTKind.Attribute:
at_c: ast.Attribute* = ast.Attribute*(tgt_c)
if at_c.attr is None: continue
if self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, tgt_c) != 0:
self.const_assign_fail(tgt_c, "field assignment", at_c.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, tgt_c))
elif tgt_c.kind() == ast.ASTKind.Subscript:
sub_c: ast.Subscript* = ast.Subscript*(tgt_c)
if sub_c.value is None: continue
if sub_c.value.kind() == ast.ASTKind.Name:
nm_c2: ast.Name* = ast.Name*(sub_c.value)
if nm_c2.id is None: continue
ve_c2: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_c2.id)
if self.Gen.VarGen.var_is_const(ve_c2) != 0:
self.const_assign_fail(tgt_c, "element assignment", nm_c2.id, self.Gen.VarGen.var_annot_class_name(ve_c2))
elif sub_c.value.kind() == ast.ASTKind.Attribute:
at_c2: ast.Attribute* = ast.Attribute*(sub_c.value)
if at_c2.attr is None: continue
if self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, sub_c.value) != 0:
self.const_assign_fail(tgt_c, "element assignment", at_c2.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, sub_c.value))
# f-string escape: results are function-local temporaries; writing them out is dangling
for ti_f in range(len(targets)):
tgt_f: ast.AST* = targets.get(ti_f)
if tgt_f is None: continue
tk_f: int = tgt_f.kind()
if tk_f == ast.ASTKind.Attribute: GenApi.fail_if_fstring_escape(asgn.value, "field assignment")
elif tk_f == ast.ASTKind.Subscript: GenApi.fail_if_fstring_escape(asgn.value, "element assignment")
elif tk_f == ast.ASTKind.Name:
nm_f: ast.Name* = ast.Name*(tgt_f)
if nm_f.id is not None and GenApi.is_global_name(trans, nm_f.id) != 0:
GenApi.fail_if_fstring_escape(asgn.value, "global variable assignment")
# 栈地址逃逸:`G = &local` 与 `G = arr`(G 为指针时的数组退让)都把本帧栈地址
# 写进模块级全局,函数返回后悬垂;`G = arr` 且 G 为同型聚合时是值拷贝,不在此列。
gv_sa: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm_f.id)
tgt_sa: mlirlite.LLVMType* = None
if gv_sa is not None and gv_sa.Ty is not None: tgt_sa = gv_sa.Ty.Pointee
GenApi.fail_if_stack_addr_escape(asgn.value, "a module-level global variable", trans, tgt_sa)
# Translate the RHS; clear CurAssignAnnot/CurAssignNode (no target type to infer here)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
# P1-A: for a single Name target with a known storage type, pass it as the target type
asg_target_ty: mlirlite.LLVMType* = None
if len(targets) == 1:
asg_t0: ast.AST* = targets.get(0)
if asg_t0 is not None and asg_t0.kind() == ast.ASTKind.Name:
asg_nm0: ast.Name* = ast.Name*(asg_t0)
if asg_nm0.id is not None:
asg_slot: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, asg_nm0.id)
if asg_slot is None: asg_slot = self.Gen.VarGen.lookup_module_var(trans.SymTab, asg_nm0.id)
if asg_slot is not None and asg_slot.Ty is not None: asg_target_ty = asg_slot.Ty.Pointee
elif trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0:
# REPL: a typed session variable also uses its slot type as the target type
asg_re: GenTypes.ReplVarEntry* = self.Gen.VarGen.repl_find_entry(trans.ReplCtx, asg_nm0.id)
if asg_re is not None and asg_re.Kind != GenTypes.REPL_KIND_UNTYPED: asg_target_ty = self.Gen.VarGen.repl_slot_ty(pool, asg_re)
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, asgn.value, None, 0, trans, asg_target_ty)
if rhs_val is None:
# RHS is None usually means allocation failure; dump both pool statistics
mb_diag: memhub.MemBuddy* = CompilerFactory.factory.Pool
pp_diag: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if mb_diag is not None: mb_diag.dump_stats("Main Pool(Pool)")
if pp_diag is not None: pp_diag.dump_stats("Permanent Pool(PermPool)")
stdio.fflush(None)
# Enhanced error message: file name + lineno + AST node details
if asgn.value is not None:
sha1: str = trans.ModuleSha1
val_kind: int = asgn.value.kind()
val_line: int = asgn.value.lineno
# Look up the file name from sha1 (human readable)
rel_path: str = None
if sha1 is not None: rel_path = StubMergerStore.LookupSha1RelPath(sha1)
# Extract details by AST node kind
if val_kind == ast.ASTKind.Attribute:
at_nv: ast.Attribute* = ast.Attribute*(asgn.value)
attr_nm: str = at_nv.attr
target_nm: str = "?"
if at_nv.value is not None:
if at_nv.value.kind() == ast.ASTKind.Name:
tn_nm: ast.Name* = ast.Name*(at_nv.value)
target_nm = tn_nm.id
if rel_path is not None: VLogger.error(f"assignment rhs is None: {rel_path}:{val_line} attribute access of '{target_nm}.{attr_nm}' failed", "ASGN")
else: VLogger.error(f"assignment rhs is None: sha1={sha1}:{val_line} attribute access of '{target_nm}.{attr_nm}' failed", "ASGN")
elif val_kind == ast.ASTKind.Name:
nm_nv: ast.Name* = ast.Name*(asgn.value)
if rel_path is not None: VLogger.error(f"assignment rhs is None: {rel_path}:{val_line} variable '{nm_nv.id}' not found", "ASGN")
else: VLogger.error(f"assignment rhs is None: sha1={sha1}:{val_line} variable '{nm_nv.id}' not found", "ASGN")
elif val_kind == ast.ASTKind.Call:
cl_nv: ast.Call* = ast.Call*(asgn.value)
func_nm: str = "?"
if cl_nv.func is not None:
if cl_nv.func.kind() == ast.ASTKind.Name:
fn_nm: ast.Name* = ast.Name*(cl_nv.func)
func_nm = fn_nm.id
if rel_path is not None: VLogger.error(f"assignment rhs is None: {rel_path}:{val_line} call '{func_nm}(...)' returned None", "ASGN")
else: VLogger.error(f"assignment rhs is None: sha1={sha1}:{val_line} call '{func_nm}(...)' returned None", "ASGN")
else:
# 必须与相邻分支一致显式传 category:跨模块模块级函数调用目前不会补默认值,
# 漏传会生成操作数不足的 llvm.call(MLIR 校验直接失败)。
if rel_path is not None: VLogger.error(f"assignment rhs is None: {rel_path}:{val_line} [kind={val_kind}]", "ASGN")
else: VLogger.error(f"assignment rhs is None: sha1={sha1}:{val_line} [kind={val_kind}]", "ASGN")
else: VLogger.error("assignment rhs is None (asgn.value is empty)", "ASGN")
return 0
# 生命期传播(单 Name 目标):`s = l` → Life(s) = Life(l)
if len(targets) == 1:
t0_sl: ast.AST* = targets.get(0)
self.propagate_life(trans, t0_sl, asgn.value, asg_target_ty)
new_vars: int = 0
tn: sizet = len(targets)
for ti in range(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, "tuple-unpack field type missing", "TypeError")
# Guard: skip non-Name elements (cannot unpack)
if tgt_elt.kind() != ast.ASTKind.Name: continue
tgt_nm2: ast.Name* = ast.Name*(tgt_elt)
if tgt_nm2.id is None: continue
fp2: mlirlite.Value* = mlirlite.build_gep_struct(builder, rhs_val.Ty, fty, tmp_s, fti)
if fp2 is None: continue
fv2: mlirlite.Value* = mlirlite.build_load(builder, fty, fp2)
tgt_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, tgt_nm2.id)
if tgt_alloca is None: tgt_alloca = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, tgt_nm2.id, fty)
if tgt_alloca is None: continue
sval: mlirlite.Value* = fv2
if tgt_alloca.Ty is not None and tgt_alloca.Ty.Pointee != 0: sval = self.Gen.ExprGenBase.coerce_to_type(builder, fv2, tgt_alloca.Ty.Pointee, node, trans)
mlirlite.build_store(builder, sval, tgt_alloca)
case _:
pass
continue
# Subscript assignment: arr[i] = val / ptr[i] = val / list[i] = val
if tk == ast.ASTKind.Subscript:
# Check for a list[T] Subscript; list subscript assignment uses the inline __setitem__ path
list_obj: mlirlite.Value* = self.Gen.ExprGenName.is_list_subscript(target, trans)
if list_obj is not None:
# list[T]: generate the __setitem__ logic inline
sub_node: ast.Subscript* = ast.Subscript*(target)
if sub_node is not None and sub_node.slice is not None:
list_idx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_node.slice, None, 0, trans)
if list_idx_val is None:
# fail-fast: list index translation failed
self.Gen.TypeGenErr.fatal_error(target, "list index translation failed", "TypeError")
self.Gen.ExprGenName.list_setitem_inline(builder, pool, list_obj, list_idx_val, rhs_val, target)
continue
# Ordinary Subscript assignment
elem_ptr: mlirlite.Value* = self.Gen.ExprGenName.get_subscript_ptr(builder, pool, mod, target, trans)
if elem_ptr is not None:
store_val: mlirlite.Value* = rhs_val
# Fail fast when a rawptr[T] element is an object but RHS is a T pointer (whole-object copy)
self._reject_whole_object_copy_into_ptr_slot(pool, trans, target, elem_ptr, rhs_val)
# Guard: coerce only when the element type is complete
if elem_ptr.Ty is not None and elem_ptr.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, elem_ptr.Ty.Pointee, node, trans)
# 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, "assignment target unary op is missing an operand", "TypeError")
if uo_w.op != ast.OpKind.Deref:
self.Gen.TypeGenErr.fatal_error(target, "assignment target supports only the unary '*' dereference (&x / -x / ~x are not valid lvalues)", "SyntaxError")
ptr_w: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, uo_w.operand, None, 0, trans)
if ptr_w is None or ptr_w.Ty is None or self.Gen.ExprGenBase.is_ptr_type(ptr_w.Ty) == 0:
self.Gen.TypeGenErr.fatal_error(target, "the operand of unary '*' assignment must be a pointer type (type inference degraded)", "TypeError")
pointee_w: mlirlite.LLVMType* = ptr_w.Ty.Pointee
if pointee_w is None:
self.Gen.TypeGenErr.fatal_error(target, "deref write target type missing (pointee is empty)", "TypeError")
sv_w: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, pointee_w, node, trans)
mlirlite.build_store(builder, sv_w, ptr_w)
continue
# Attribute assignment: obj.field = val
if tk == ast.ASTKind.Attribute:
# Narrow target to ast.Attribute* explicitly (required under the strict pointer rules)
at_tg: ast.Attribute* = ast.Attribute*(target)
# property setter: when attr is a setter, emit the setter call and skip the field store
# (asgn.value gives the setter value param's AST for value-reference auto-address).
if self.Gen.CallGenProp._try_property_setter(pool, builder, mod, at_tg, rhs_val, trans, asgn.value) == 1: continue
# FakeDuck: write per-instance str fields through the {var}__meta__ shadow struct
fd_an: ast.Attribute* = at_tg
if fd_an is not None and fd_an.value is not None and fd_an.attr is not None:
if fd_an.value.kind() == ast.ASTKind.Name:
fd_obj_nm: ast.Name* = ast.Name*(fd_an.value)
if fd_obj_nm.id is not None:
# Pool buffer (f-string stack buffers die when the function returns)
fd_meta_w: str = pool.alloc(256)
if fd_meta_w is not None: viperlib.snprintf(fd_meta_w, 256, "%s__meta__", fd_obj_nm.id)
fd_meta_vw: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, fd_meta_w)
if fd_meta_vw is not None:
fd_sty_w: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type("_str")
if fd_sty_w is not None:
fd_fe_w: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_sty_w, fd_an.attr)
if fd_fe_w is not None and fd_fe_w.Ty is not None:
fd_fp_w: mlirlite.Value* = mlirlite.build_gep_struct(builder, fd_sty_w, fd_fe_w.Ty, fd_meta_vw, fd_fe_w.Index)
if fd_fp_w is not None:
sv_fd: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, fd_fe_w.Ty, node)
mlirlite.build_store(builder, sv_fd, fd_fp_w)
continue
# VTABLE write: obj.method = func writes the vtable slot instead of a field
if self.Gen.CallGenVt._write_vtable_attr(builder, pool, mod, target, rhs_val, trans) == 1: continue
field_ptr: mlirlite.Value* = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, target, trans)
if field_ptr is not None:
# Get the field type and coerce rhs_val (e.g. i32 -> i64)
store_val: mlirlite.Value* = rhs_val
if field_ptr.Ty is not None and field_ptr.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, field_ptr.Ty.Pointee, node, trans)
# t.CVolatile field: store with the volatile qualifier
vol_fw: int = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target)
# rawptr[t.CVolatile|T] base: p.field = v also stores with volatile
at_wr: ast.Attribute* = ast.Attribute*(target)
if at_wr is not None:
if self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, at_wr.value) != 0: vol_fw = 1
mlirlite.build_store(builder, store_val, field_ptr, vol_fw)
continue
# Guard: field_ptr is None -> build a detailed error message
attr_node: ast.Attribute* = ast.Attribute*(target)
attr_name: str = "(unknown)"
obj_name: str = "(unknown)"
if attr_node is not None:
attr_name = attr_node.attr
if attr_node.value is not None and attr_node.value.kind() == ast.ASTKind.Name:
obj_nm: ast.Name* = ast.Name*(attr_node.value)
obj_name = obj_nm.id
err_buf: str = pool.alloc(256)
if err_buf is not None:
viperlib.snprintf(err_buf, 256, "attribute ptr is None: %s.%s", obj_name, attr_name)
self.Gen.TypeGenErr.fatal_error(target, err_buf)
else: self.Gen.TypeGenErr.fatal_error(target, "attribute ptr is None")
continue
# Name assignment: var = val
if tk == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(target)
# Guard: skip when the target name is missing
if nm.id is None: continue
# global variable: write to the module-scope global
if GenApi.is_global_name(trans, nm.id) != 0:
mod_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
if mod_alloca is None:
# fail-fast: global variable not registered
self.Gen.TypeGenErr.fatal_error(target, f"global variable '{nm.id}' is not registered; cannot assign", "NameError")
# Object global with static storage: bind to storage and copy the object (else dangling/NULL)
stg_ref_as: mlirlite.Value* = None
if nm.id is not None:
stg_key_as: str = pool.alloc(sizet(string.strlen(nm.id)) + 8)
if stg_key_as is not None:
viperlib.snprintf(stg_key_as, sizet(string.strlen(nm.id)) + 8, "%s.__stg", nm.id)
stg_ref_as = self.Gen.VarGen.lookup_module_var(trans.SymTab, stg_key_as)
if self._is_addrlike_call(asgn.value) == 0 and stg_ref_as is not None and rhs_val is not None and rhs_val.Ty is not None and stg_ref_as.Ty is not None and stg_ref_as.Ty.Pointee != 0:
stg_ty_as: mlirlite.LLVMType* = stg_ref_as.Ty.Pointee
rhs_inner_as: mlirlite.LLVMType* = None
match rhs_val.Ty:
case mlirlite.LLVMType.Ptr(pointee_as):
rhs_inner_as = pointee_as
case _:
pass
if rhs_inner_as is not None and rhs_inner_as.kind() == mlirlite.LLVMType.Struct and self._stg_ty_equal(pool, stg_ty_as, rhs_inner_as) != 0:
# 1) Bind the global reference to storage (drop the temporary alloca)
mlirlite.build_store(builder, stg_ref_as, mod_alloca)
# 2) Copy the constructed temporary into storage
stg_obj_val_as: mlirlite.Value* = mlirlite.build_load(builder, stg_ty_as, rhs_val)
if stg_obj_val_as is not None: mlirlite.build_store(builder, stg_obj_val_as, stg_ref_as)
continue
target_ty: mlirlite.LLVMType* = None
if mod_alloca.Ty is not None: target_ty = mod_alloca.Ty.Pointee
if target_ty is not None: rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans)
# t.CVolatile global: store with the volatile qualifier
vol_gas: int = 0
if nm.id is not None: vol_gas = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
mlirlite.build_store(builder, rhs_val, mod_alloca, vol_gas)
continue
# nonlocal variable: write through the closure env
if GenApi.is_nonlocal_name(trans, nm.id) != 0:
nl_ptr: mlirlite.Value* = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(trans, nm.id)
if nl_ptr is None:
# fail-fast: nonlocal variable pointer resolution failed
self.Gen.TypeGenErr.fatal_error(target, f"nonlocal variable '{nm.id}' pointer resolution failed", "NameError")
# Infer the nonlocal type from nl_ptr instead of forcing i32
nl_target_ty: mlirlite.LLVMType* = None
if nl_ptr.Ty is not None: nl_target_ty = nl_ptr.Ty.Pointee
if nl_target_ty is None:
# fail-fast: nl_ptr type lost, cannot store safely
self.Gen.TypeGenErr.fatal_error(target, f"nonlocal variable '{nm.id}' type information lost; cannot write", "TypeError")
rhs_coerced: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, nl_target_ty, node, trans)
mlirlite.build_store(builder, rhs_coerced, nl_ptr)
continue
# REPL session variable: a Name target at fragment entry defines/assigns the session slot
if trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0:
rp_entry: GenTypes.ReplVarEntry* = self.Gen.VarGen.repl_ensure_entry(trans, nm.id, target)
if rp_entry is not None:
self.Gen.VarGen.repl_try_assign(trans, nm.id, rhs_val, asgn.value, node)
continue
# Block assignment to a module-level global inside a function without a global declaration
if self.Gen.VarGen.is_module_scope(trans.SymTab) == 0:
mod_gv: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
mod_decl_g: int = 0
if mod_gv is not None: mod_decl_g = 1
# 双保险:SymTab.Root.Vars 在部分时序/阶段可能看不到本文件已声明的模块全局
# (现场:某函数对模块全局赋值漏写 global → 静默建局部影子 → 全局仍 NULL →
# 下游 ASan 写 0x208 崩)。按规则此处必须 fail-fast,不得静默建局部。
if mod_decl_g == 0 and GenApi.is_file_var(nm.id) != 0: mod_decl_g = 1
if mod_decl_g != 0: self.Gen.TypeGenErr.fatal_error(target, f"variable '{nm.id}' is a module-level global; assignment inside a function requires a global declaration (otherwise a local variable shadows the global, leaving it null and crashing)", "SyntaxError")
# Ordinary local variable
alloca: mlirlite.Value* = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, nm.id, rhs_val.Ty)
if alloca is not None:
# Unannotated assignment: infer the class name from the RHS expression
cls_inf: str = self._infer_rhs_annot(pool, mod, asgn.value, trans, rhs_val)
if cls_inf is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, nm.id, cls_inf)
# Coerce the value to the alloca type (e.g. double -> float)
store_val: mlirlite.Value* = rhs_val
if alloca.Ty is not None and alloca.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, alloca.Ty.Pointee, node, trans)
# 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"variable '{nm.id}' storage creation failed", "TypeError")
return new_vars
# _reject_whole_object_copy_into_ptr_slot - fail fast when a rawptr[T] subscript gets a T pointer RHS
# (the whole object would be copied into an 8-byte pointer slot)
def _reject_whole_object_copy_into_ptr_slot(self, pool: memhub.MemBuddy*, trans: HT.Translator*, target: ast.AST*, elem_ptr: mlirlite.Value*, rhs_val: mlirlite.Value*) -> None:
"""Fail fast when a rawptr[T] subscript gets a T pointer RHS."""
if elem_ptr is None or elem_ptr.Ty is None: return
if rhs_val is None or rhs_val.Ty is None: return
elem_ty: mlirlite.LLVMType* = None
match elem_ptr.Ty:
case mlirlite.LLVMType.Ptr(pointee_wo):
elem_ty = pointee_wo
case _:
return
if elem_ty is None: return
# Only object (struct) elements are checked; scalar and pointer elements return early
if elem_ty.kind() != mlirlite.LLVMType.Struct: return
rhs_inner_wo: mlirlite.LLVMType* = None
match rhs_val.Ty:
case mlirlite.LLVMType.Ptr(pointee_rhs_wo):
rhs_inner_wo = pointee_rhs_wo
case _:
return
if rhs_inner_wo is None: return
if rhs_inner_wo.kind() != mlirlite.LLVMType.Struct: return
# The pointee type must match the element type
if self._stg_ty_equal(pool, elem_ty, rhs_inner_wo) == 0: return
# Array slots (t.CArray[T,N]) are legal; only block when the base is a pointer store
if self.Gen.ExprGenName.subscript_base_store_kind(pool, trans, target) != 0: return
elem_nm_wo: str = mlirlite.get_struct_name(elem_ty)
if elem_nm_wo is None: elem_nm_wo = "<anonymous struct>"
rhs_nm_wo: str = mlirlite.get_struct_name(rhs_inner_wo)
if rhs_nm_wo is None: rhs_nm_wo = elem_nm_wo
elem_sz_wo: int = self.Gen.ClassGenEnum._get_type_size(elem_ty)
ptr_sz_wo: int = 8
over_wo: int = elem_sz_wo - ptr_sz_wo
if over_wo < 0: over_wo = 0
col_s_wo: int = 0
col_e_wo: int = 0
if target is not None:
col_s_wo = target.col_offset + 1
col_e_wo = target.end_col_offset + 1
self.Gen.TypeGenErr.fatal_error(target, f"the lvalue element of the subscript assignment is object '{elem_nm_wo}' ({elem_sz_wo} bytes), " f"but the rhs is a pointer to '{rhs_nm_wo}': the compiler would silently emit a whole-object copy " f"(load {elem_nm_wo} + store {elem_nm_wo}, writing {elem_sz_wo} bytes), " f"instead of writing {ptr_sz_wo} bytes as a pointer. If that address actually stores pointers in 8-byte slots " f"(such as a rawptr[T] buffer/free list), every element written overflows by {over_wo} bytes, " f"writing through adjacent blocks. Fix: declare the base type of the pointer slot in pointer-element form " f"(`rawptr[T]*` or `rawptr[rawptr[T]]`) before assigning; " f"or explicitly cast the base to t.CPtr (untyped pointer) and write with 8-byte pointer semantics.", "TypeError", col_s_wo, col_e_wo)
# _infer_rhs_annot - infer a variable's annotation class name from the RHS expression
def _infer_rhs_annot(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator*, rhs_val: mlirlite.Value* = None) -> str:
"""Infer a variable's annotation class name from the RHS, or None."""
if node is None or trans is None: return None
k: int = node.kind()
# Call: class instantiation or method call
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
if cl is None or cl.func is None: return None
if cl.func.kind() == ast.ASTKind.Name:
# Class instantiation: only accept a registered struct name
cand: str = (ast.Name*(cl.func)).id
if cand is None: return None
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(cand)
if se is not None: return cand
return None
if cl.func.kind() == ast.ASTKind.Attribute:
# Method call: resolve the exact return class from the vpi annotation cache
ca: ast.Attribute* = ast.Attribute*(cl.func)
if ca is None or ca.attr is None or ca.value is None: return None
obj_cls: str = self._infer_rhs_annot(pool, mod, ca.value, trans)
if obj_cls is None:
# Module-level function call: resolve the return class from the module's vpi cache
if ca.value.kind() == ast.ASTKind.Name:
mod_nm_rhs: ast.Name* = ast.Name*(ca.value)
if mod_nm_rhs.id is not None:
mod_sha1_rhs: str = self.Gen.CallGenBase._lookup_module_sha1(mod_nm_rhs.id)
if mod_sha1_rhs is not None:
temp_dir_rhs: str = None
if self.Gen.CallGenBase.TempDir is not None: temp_dir_rhs = self.Gen.CallGenBase.TempDir
if temp_dir_rhs is not None: VpiReader._ensure_vpi_ret_annot_loaded(mod_sha1_rhs, temp_dir_rhs)
annot_rhs: str = VpiReader._lookup_vpi_ret_annot(mod_sha1_rhs, None, ca.attr)
if annot_rhs is not None:
ret_cls_rhs: str = self._extract_annot_class_name(pool, annot_rhs)
if ret_cls_rhs is not None: return ret_cls_rhs
return None
if self._cls_has_dunder(pool, mod, obj_cls, ca.attr) == 0: return None
# SHA1 of the module defining the method (struct entry, else current module)
cls_sha1: str = self.Gen.StructGen.get_struct_sha1(obj_cls)
if cls_sha1 is None and trans.ModuleSha1 is not None: cls_sha1 = trans.ModuleSha1
ret_cls: str = None
if cls_sha1 is not None:
temp_dir: str = None
if self.Gen.CallGenBase.TempDir is not None: temp_dir = self.Gen.CallGenBase.TempDir
if temp_dir is not None: VpiReader._ensure_vpi_ret_annot_loaded(cls_sha1, temp_dir)
annot_ret: str = VpiReader._lookup_vpi_ret_annot(cls_sha1, obj_cls, ca.attr)
if annot_ret is not None: ret_cls = self._extract_annot_class_name(pool, annot_ret)
return ret_cls
return None
# Name: the source variable annotation
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm is None or nm.id is None: return None
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if ve is None: return None
return self.Gen.VarGen.var_annot_class_name(ve)
# BinOp/UnaryOp: operator overload returns an object of the same class
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop is None or bop.left is None: return None
dunder: str = self._op_to_dunder(bop.op)
if dunder is None: return None
lhs_cls: str = self._infer_rhs_annot(pool, mod, bop.left, trans)
if lhs_cls is None or self._cls_has_dunder(pool, mod, lhs_cls, dunder) == 0: return None
return lhs_cls
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(node)
if uop is None or uop.operand is None: return None
dunder: str = self._op_to_dunder(uop.op)
if dunder is None: return None
opr_cls: str = self._infer_rhs_annot(pool, mod, uop.operand, trans)
if opr_cls is None or self._cls_has_dunder(pool, mod, opr_cls, dunder) == 0: return None
return opr_cls
# Attribute: field annotation class name (recursive attribute chain)
if k == ast.ASTKind.Attribute: return self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, node)
return None
# _extract_annot_class_name - extract a registered struct class name from a return annotation text
def _extract_annot_class_name(self, pool: memhub.MemBuddy*, annot: str) -> str:
"""Extract a registered struct class name from a return annotation."""
if pool is None or annot is None: return None
alen: sizet = string.strlen(annot)
if alen == 0: return None
seg: str = pool.alloc(256)
if seg is None: return None
start: sizet = 0
i: sizet
for i in range(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