2321 lines
136 KiB
Plaintext
2321 lines
136 KiB
Plaintext
import lib.core.Gen.GenTypes as GenTypes
|
||
import lib.core.Gen.Generator as Generator
|
||
from stdint import *
|
||
import ast
|
||
import mlirlite
|
||
import memhub
|
||
import string
|
||
import stdlib
|
||
import stdio
|
||
import viperlib
|
||
import lib.core.VLogger as VLogger
|
||
import lib.core.Gen.Translator as HT
|
||
import lib.core.Gen.Type.TypeSpec as TSpec
|
||
import lib.core.CompilerFactory as CompilerFactory
|
||
import lib.Projectrans.Config as Config
|
||
import lib.core.Gen.GenApi as GenApi
|
||
|
||
|
||
# ============================================================
|
||
# StmtGenCifMixin - compile-time cif eval + NewXxxHandle factory
|
||
|
||
|
||
# ============================================================
|
||
# StmtGenCifMixin - cif eval + factory (lowest base class)
|
||
novirtual class StmtGenCifMixin:
|
||
"""Compile-time cif evaluation and NewXxxHandle factory (lowest base class)."""
|
||
|
||
# --- compile-time cif eval state (was HandlesIf._g_cif_eval_ok) ---
|
||
# use 8-byte type: Mixin standalone and Generator composite layouts must align
|
||
Gen: Generator.Generator*
|
||
CifEvalOk: sizet
|
||
|
||
# --- enum registry (was HandlesEnum._enum_table/_enum_count) ---
|
||
EnumTable: GenTypes.EnumEntry*
|
||
EnumCount: int
|
||
|
||
# --- OOM stmt-level diag count (translate_stmt rate-limits printed used) ---
|
||
DiagStmtCount: int
|
||
|
||
# ============================================================
|
||
# Cluster B: compile-time cif macro eval (migrated from HandlesIf.vp)
|
||
|
||
def _is_cif_attr(self, attr: str) -> int:
|
||
"""Check the internal cif marker attribute name produced by the parser (c.@CIf family).
|
||
|
||
The '@' prefix is internal-only spelling (a leading '@' cannot be lexed as an identifier), so a
|
||
user-written c.CIf/c.CIfdef/c.CIfndef never matches here and is reported as removed instead.
|
||
"""
|
||
if attr is None: return 0
|
||
if string.strcmp(attr, "@CIf") == 0: return 1
|
||
if string.strcmp(attr, "@CIfdef") == 0: return 1
|
||
if string.strcmp(attr, "@CIfndef") == 0: return 1
|
||
return 0
|
||
|
||
def _is_cif_call(self, node: ast.AST*) -> int:
|
||
"""Check whether test is a cif marker call (c.@CIf family, or a bare @CIf-family name)."""
|
||
if node is None or node.kind() != ast.ASTKind.Call: return 0
|
||
cl: ast.Call* = ast.Call*(node)
|
||
if cl.func is None: return 0
|
||
fk: int = cl.func.kind()
|
||
if fk == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if at.attr is None: return 0
|
||
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
|
||
vn: ast.Name* = ast.Name*(at.value)
|
||
if vn.id is not None and string.strcmp(vn.id, "c") == 0: return self._is_cif_attr(at.attr)
|
||
return 0
|
||
if fk == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(cl.func)
|
||
if nm.id is not None and self._is_cif_attr(nm.id) != 0: return 1
|
||
return 0
|
||
|
||
def _get_platform_macro_value(self, name: str) -> int:
|
||
"""Evaluate a platform built-in macro from the target triple, setting CifEvalOk on hit."""
|
||
self.CifEvalOk = 0
|
||
if name is None: return 0
|
||
triple: str = Config.TargetTriple
|
||
if triple is None: triple = "x86_64-pc-windows-gnu"
|
||
is_win: int = 0
|
||
is_linux: int = 0
|
||
is_x64: int = 0
|
||
if string.strstr(triple, "windows") is not None or string.strstr(triple, "win32") is not None or string.strstr(triple, "mingw") is not None or string.strstr(triple, "msvc") is not None:
|
||
is_win = 1
|
||
if string.strstr(triple, "linux") is not None: is_linux = 1
|
||
if string.strstr(triple, "x86_64") is not None: is_x64 = 1
|
||
# simplification: target is x86_64 Windows (_WIN64/WIN64 always match is_win)
|
||
if string.strcmp(name, "_WIN32") == 0: self.CifEvalOk = is_win; return 1 if is_win else 0
|
||
if string.strcmp(name, "WIN32") == 0: self.CifEvalOk = is_win; return 1 if is_win else 0
|
||
if string.strcmp(name, "_WIN64") == 0: self.CifEvalOk = is_win; return 1 if is_win else 0
|
||
if string.strcmp(name, "WIN64") == 0: self.CifEvalOk = is_win; return 1 if is_win else 0
|
||
if string.strcmp(name, "__linux__") == 0: self.CifEvalOk = is_linux; return 1 if is_linux else 0
|
||
if string.strcmp(name, "__x86_64__") == 0: self.CifEvalOk = is_x64; return 1 if is_x64 else 0
|
||
if string.strcmp(name, "__x86_64") == 0: self.CifEvalOk = is_x64; return 1 if is_x64 else 0
|
||
if string.strcmp(name, "_M_X64") == 0: self.CifEvalOk = is_x64; return 1 if is_x64 else 0
|
||
if string.strcmp(name, "SIZEOF_VOID_P") == 0: self.CifEvalOk = 1; return 8 if is_x64 else 4
|
||
if string.strcmp(name, "__SIZEOF_POINTER__") == 0: self.CifEvalOk = 1; return 8 if is_x64 else 4
|
||
if string.strcmp(name, "NDEBUG") == 0: self.CifEvalOk = 1; return 0
|
||
return 0
|
||
|
||
def _cif_name_value(self, pool: memhub.MemBuddy*, trans: HT.Translator*, name: str) -> int:
|
||
"""Resolve a macro name via local CDefine, cross-module CDefine, then platform macro."""
|
||
self.CifEvalOk = 0
|
||
if name is None or trans is None: return 0
|
||
v: int = int(self.Gen.TypeGenCDef.lookup_cdefine_constant(name))
|
||
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
|
||
self.CifEvalOk = 1
|
||
return v
|
||
v2: int = int(self.Gen.ExprGenCdef._lookup_cross_module_cdefine(pool, name, trans._from_imports, trans._imported_modules))
|
||
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
|
||
self.CifEvalOk = 1
|
||
return v2
|
||
pv: int = self._get_platform_macro_value(name)
|
||
if self.CifEvalOk != 0: return pv
|
||
return 0
|
||
|
||
def _cif_attr_value(self, pool: memhub.MemBuddy*, trans: HT.Translator*, at: ast.Attribute*) -> int:
|
||
"""Resolve an attribute macro via platform macro, local CDefine, then cross-module CDefine."""
|
||
self.CifEvalOk = 0
|
||
if at is None or at.attr is None or at.value is None or trans is None: return 0
|
||
if at.value.kind() != ast.ASTKind.Name: return 0
|
||
vn: ast.Name* = ast.Name*(at.value)
|
||
if vn.id is None: return 0
|
||
v: int = int(self.Gen.TypeGenCDef.lookup_cdefine_constant(at.attr))
|
||
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
|
||
self.CifEvalOk = 1
|
||
return v
|
||
# cross-module: resolve the real module name (expand the from_imports alias)
|
||
real_mod: str = vn.id
|
||
if trans._from_imports is not None:
|
||
resolved: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, vn.id, 0)
|
||
if resolved is not None:
|
||
rm_len: sizet = 0
|
||
while resolved[rm_len] != ' ' and resolved[rm_len] != '\0' and resolved[rm_len] != ':': rm_len += 1
|
||
rm_buf: str = pool.alloc(rm_len + 1)
|
||
if rm_buf is not None:
|
||
for rm_i in range(int(rm_len)): rm_buf[rm_i] = resolved[rm_i]
|
||
rm_buf[rm_len] = '\0'
|
||
real_mod = rm_buf
|
||
fi_len: sizet = string.strlen(at.attr) + string.strlen(real_mod) + 2
|
||
fi: str = pool.alloc(fi_len)
|
||
if fi is None: return 0
|
||
viperlib.snprintf(fi, fi_len, "%s:%s", at.attr, real_mod)
|
||
v2: int = int(self.Gen.ExprGenCdef._lookup_cross_module_cdefine(pool, at.attr, fi))
|
||
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
|
||
self.CifEvalOk = 1
|
||
return v2
|
||
return 0
|
||
|
||
def _cif_eval_node(self, pool: memhub.MemBuddy*, trans: HT.Translator*, node: ast.AST*) -> int:
|
||
"""Recursively evaluate a CIf constant expression, setting CifEvalOk=1 on success."""
|
||
if node is None:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Constant:
|
||
cn: ast.Constant* = ast.Constant*(node)
|
||
# integer and boolean literals are compile-time constants (True/False carry 1/0)
|
||
if cn.const_kind == ast.CONST_INT or cn.const_kind == ast.CONST_BOOL:
|
||
self.CifEvalOk = 1
|
||
# IntVal(i64) → int(i32):显式收窄(本函数返回 int)
|
||
return int(cn.int_val)
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
if k == ast.ASTKind.Call: return self._cif_eval_call(pool, trans, node)
|
||
if k == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(node)
|
||
if nm.id is None:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
return self._cif_name_value(pool, trans, nm.id)
|
||
if k == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(node)
|
||
return self._cif_attr_value(pool, trans, at)
|
||
if k == ast.ASTKind.UnaryOp:
|
||
uop: ast.UnaryOp* = ast.UnaryOp*(node)
|
||
if uop.operand is None:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
ov: int = self._cif_eval_node(pool, trans, uop.operand)
|
||
if self.CifEvalOk == 0: return 0
|
||
if uop.op == ast.OpKind.Not: return 1 if ov == 0 else 0
|
||
if uop.op == ast.OpKind.USub: return -ov
|
||
if uop.op == ast.OpKind.UAdd: return ov
|
||
if uop.op == ast.OpKind.Invert: return ~ov
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
if k == ast.ASTKind.BoolOp:
|
||
bo: ast.BoolOp* = ast.BoolOp*(node)
|
||
vals: list[ast.AST*]* = bo.values
|
||
if vals is None:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
bn: sizet = len(vals)
|
||
if bo.op == ast.OpKind.And:
|
||
for bi in range(int(bn)):
|
||
rv: int = self._cif_eval_node(pool, trans, vals.get(bi))
|
||
if self.CifEvalOk == 0: return 0
|
||
if rv == 0: return 0
|
||
return 1
|
||
if bo.op == ast.OpKind.Or:
|
||
for bi in range(int(bn)):
|
||
rv: int = self._cif_eval_node(pool, trans, vals.get(bi))
|
||
if self.CifEvalOk == 0: return 0
|
||
if rv != 0: return 1
|
||
return 0
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
if k == ast.ASTKind.Compare:
|
||
cmp: ast.Compare* = ast.Compare*(node)
|
||
lv: int = self._cif_eval_node(pool, trans, cmp.left)
|
||
if self.CifEvalOk == 0: return 0
|
||
ops_list: list[int]* = (list[int]*)(cmp.ops)
|
||
comps: list[ast.AST*]* = cmp.comparators
|
||
if ops_list is None or comps is None:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
cc: sizet = len(comps)
|
||
ci: sizet
|
||
for ci in range(int(cc)):
|
||
rv: int = self._cif_eval_node(pool, trans, comps.get(ci))
|
||
if self.CifEvalOk == 0: return 0
|
||
op_id: int = ops_list.get(ci)
|
||
ok_cmp: int = 0
|
||
if op_id == ast.OpKind.Eq: ok_cmp = 1 if lv == rv else 0
|
||
elif op_id == ast.OpKind.Ne: ok_cmp = 1 if lv != rv else 0
|
||
elif op_id == ast.OpKind.Lt: ok_cmp = 1 if lv < rv else 0
|
||
elif op_id == ast.OpKind.Le: ok_cmp = 1 if lv <= rv else 0
|
||
elif op_id == ast.OpKind.Gt: ok_cmp = 1 if lv > rv else 0
|
||
elif op_id == ast.OpKind.Ge: ok_cmp = 1 if lv >= rv else 0
|
||
else:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
if ok_cmp == 0: return 0
|
||
lv = rv
|
||
return 1
|
||
if k == ast.ASTKind.BinOp:
|
||
bop: ast.BinOp* = ast.BinOp*(node)
|
||
lv: int = self._cif_eval_node(pool, trans, bop.left)
|
||
if self.CifEvalOk == 0: return 0
|
||
rv: int = self._cif_eval_node(pool, trans, bop.right)
|
||
if self.CifEvalOk == 0: return 0
|
||
if bop.op == ast.OpKind.Add: return lv + rv
|
||
if bop.op == ast.OpKind.Sub: return lv - rv
|
||
if bop.op == ast.OpKind.Mult: return lv * rv
|
||
if bop.op == ast.OpKind.Div:
|
||
# integer constant division: same as FloorDiv (TPC ConstEvaluator L354-355)
|
||
if rv == 0:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
return lv // rv
|
||
if bop.op == ast.OpKind.FloorDiv:
|
||
if rv == 0:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
return lv // rv
|
||
if bop.op == ast.OpKind.Mod:
|
||
if rv == 0:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
return lv % rv
|
||
if bop.op == ast.OpKind.BitAnd: return lv & rv
|
||
if bop.op == ast.OpKind.BitOr: return lv | rv
|
||
if bop.op == ast.OpKind.BitXor: return lv ^ rv
|
||
if bop.op == ast.OpKind.LShift: return lv << rv
|
||
if bop.op == ast.OpKind.RShift: return lv >> rv
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
|
||
def _eval_ctraits_isptr(self, symtab: GenTypes.SymbolTable*, name: str) -> int:
|
||
"""Evaluate ctraits.isptr(name): 1 if pointer, 0 if not, -1 if undeterminable."""
|
||
if name is None or symtab is None: return -1
|
||
v: mlirlite.Value* = self.Gen.VarGen.lookup_var(symtab, name)
|
||
if v is None or v.Ty is None: return -1
|
||
pointee: mlirlite.LLVMType* = v.Ty.Pointee
|
||
if pointee is not None and self.Gen.ExprGenBase.is_ptr_type(pointee) != 0: return 1
|
||
return 0
|
||
|
||
def _cif_eval_defined(self, pool: memhub.MemBuddy*, trans: HT.Translator*, cl: ast.Call*) -> int:
|
||
"""Evaluate the defined(NAME) compile-time predicate (1 = macro defined, 0 = not).
|
||
|
||
defined() is always evaluable: an unknown name yields 0. A wrong argument shape is fail-fast.
|
||
The macro name resolves through the same CDefine / cross-module / platform-macro paths as a
|
||
bare name in a cif condition.
|
||
"""
|
||
cargs: list[ast.AST*]* = cl.args
|
||
if cargs is None or len(cargs) != 1:
|
||
self.Gen.TypeGenErr.fatal_error(cl, "defined() takes exactly one macro name, e.g. defined(NAME)", "SyntaxError")
|
||
return 0
|
||
arg0: ast.AST* = cargs.get(0)
|
||
# Dotted macro name (module.NAME, e.g. defined(platmacro.IS_WINDOWS)): resolve it the same
|
||
# way a bare macro name resolves; a resolvable value means the macro exists.
|
||
if arg0 is not None and arg0.kind() == ast.ASTKind.Attribute:
|
||
self._cif_eval_node(pool, trans, arg0)
|
||
dotted_defined: int = int(self.CifEvalOk)
|
||
self.CifEvalOk = 1
|
||
return 1 if dotted_defined != 0 else 0
|
||
name_arg: str = None
|
||
if arg0 is not None and arg0.kind() == ast.ASTKind.Name:
|
||
nm0: ast.Name* = ast.Name*(arg0)
|
||
name_arg = nm0.id
|
||
elif arg0 is not None and arg0.kind() == ast.ASTKind.Constant:
|
||
cn0: ast.Constant* = ast.Constant*(arg0)
|
||
if cn0.const_kind == ast.CONST_STR: name_arg = cn0.str_val
|
||
if name_arg is None:
|
||
self.Gen.TypeGenErr.fatal_error(cl, "defined() requires a bare macro name, e.g. defined(NAME)", "SyntaxError")
|
||
return 0
|
||
self._cif_name_value(pool, trans, name_arg)
|
||
defined: int = int(self.CifEvalOk)
|
||
# the defined() test itself is evaluable even when the macro is absent (defined = 0)
|
||
self.CifEvalOk = 1
|
||
return 1 if defined != 0 else 0
|
||
|
||
def _cif_eval_call(self, pool: memhub.MemBuddy*, trans: HT.Translator*, node: ast.AST*) -> int:
|
||
"""Evaluate a compile-time call inside a cif/celif condition: defined(NAME) or ctraits.isptr(x).
|
||
|
||
Unknown / runtime calls return 0 with CifEvalOk=0, so the caller reports "not a compile-time
|
||
constant" instead of emitting a runtime branch.
|
||
"""
|
||
self.CifEvalOk = 0
|
||
if node is None or trans is None: return 0
|
||
cl: ast.Call* = ast.Call*(node)
|
||
if cl is None or cl.func is None: return 0
|
||
fk: int = cl.func.kind()
|
||
# defined(NAME): compile-time macro predicate. Valid only inside a cif/celif condition; any
|
||
# other use is reported by the call generator (bare `defined(...)` name).
|
||
if fk == ast.ASTKind.Name:
|
||
dn: ast.Name* = ast.Name*(cl.func)
|
||
if dn.id is not None and string.strcmp(dn.id, "defined") == 0:
|
||
return self._cif_eval_defined(pool, trans, cl)
|
||
if fk != ast.ASTKind.Attribute: return 0
|
||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if at.value is None or at.attr is None: return 0
|
||
if at.value.kind() != ast.ASTKind.Name: return 0
|
||
vn: ast.Name* = ast.Name*(at.value)
|
||
if vn.id is None: return 0
|
||
if string.strcmp(vn.id, "ctraits") != 0 or string.strcmp(at.attr, "isptr") != 0: return 0
|
||
cargs: list[ast.AST*]* = cl.args
|
||
if cargs is None or len(cargs) < 1:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
arg0: ast.AST* = cargs.get(0)
|
||
if arg0 is None or arg0.kind() != ast.ASTKind.Name:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
nm0: ast.Name* = ast.Name*(arg0)
|
||
if nm0.id is None:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
r: int = self._eval_ctraits_isptr(trans.SymTab, nm0.id)
|
||
if r < 0:
|
||
self.CifEvalOk = 0
|
||
return 0
|
||
self.CifEvalOk = 1
|
||
return r
|
||
|
||
def _evaluate_cif_test(self, pool: memhub.MemBuddy*, trans: HT.Translator*, test: ast.AST*) -> int:
|
||
"""Evaluate an internal cif marker call (c.@CIf family), returning the condition.
|
||
|
||
Sets CifEvalOk=1 when the condition is a compile-time constant, 0 otherwise (the caller then
|
||
fail-fasts: cif/celif conditions must be compile-time constants, no runtime fallback).
|
||
"""
|
||
self.CifEvalOk = 0
|
||
cl: ast.Call* = ast.Call*(test)
|
||
if cl is None or cl.func is None: return 0
|
||
kind: str = None
|
||
fk: int = cl.func.kind()
|
||
if fk == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
kind = at.attr
|
||
elif fk == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(cl.func)
|
||
kind = nm.id
|
||
if kind is None: return 0
|
||
cargs: list[ast.AST*]* = cl.args
|
||
if cargs is None or len(cargs) < 1: return 0
|
||
arg0: ast.AST* = cargs.get(0)
|
||
if string.strcmp(kind, "@CIf") == 0:
|
||
v: int = self._cif_eval_node(pool, trans, arg0)
|
||
if self.CifEvalOk == 0: return 0
|
||
return 1 if v != 0 else 0
|
||
# @CIfdef / @CIfndef: test whether the macro name is defined
|
||
defined: sizet = 0
|
||
if arg0 is not None and arg0.kind() == ast.ASTKind.Name:
|
||
nm0: ast.Name* = ast.Name*(arg0)
|
||
if nm0.id is None: return 0
|
||
self._cif_name_value(pool, trans, nm0.id)
|
||
defined = self.CifEvalOk
|
||
elif arg0 is not None and arg0.kind() == ast.ASTKind.Constant:
|
||
cn0: ast.Constant* = ast.Constant*(arg0)
|
||
if cn0.const_kind == ast.CONST_STR and cn0.str_val is not None:
|
||
self._cif_name_value(pool, trans, cn0.str_val)
|
||
defined = self.CifEvalOk
|
||
else: return 0
|
||
elif arg0 is not None and arg0.kind() == ast.ASTKind.Attribute:
|
||
at0: ast.Attribute* = ast.Attribute*(arg0)
|
||
self._cif_attr_value(pool, trans, at0)
|
||
defined = self.CifEvalOk
|
||
else: return 0
|
||
# the defined-check itself succeeded (an undefined name is not an error; defined=0)
|
||
self.CifEvalOk = 1
|
||
if string.strcmp(kind, "@CIfdef") == 0: return 1 if defined != 0 else 0
|
||
# @CIfndef
|
||
return 0 if defined != 0 else 1
|
||
|
||
def _prune_cif_test(self, pool: memhub.MemBuddy*, trans: HT.Translator*, test: ast.AST*) -> int:
|
||
"""Return the compile-time value (1/0) of a cif/celif marker condition.
|
||
|
||
Fail-fast when the condition is not a compile-time constant: cif/celif never fall back to a
|
||
runtime branch. Shared by translate_if and the coroutine If handler.
|
||
"""
|
||
v: int = self._evaluate_cif_test(pool, trans, test)
|
||
if self.CifEvalOk == 0:
|
||
self.Gen.TypeGenErr.fatal_error(test,
|
||
f"the cif/celif condition is not a compile-time constant (line {test.lineno}):"
|
||
" cif/celif accept only CDefine constants, integer literals, defined(NAME), and their"
|
||
" constant arithmetic/comparison/boolean/bitwise combinations", "TypeError")
|
||
return v
|
||
|
||
# translate_return - translate Return (migrated from HandlesReturn.ReturnHandle): emits ret,
|
||
# a bare return emits the return-type default zero (void→ret void, ptr→null, float→0.0).
|
||
def translate_return(self, trans: HT.Translator*, node: ast.AST*) -> int:
|
||
"""Translate a Return statement."""
|
||
rt: ast.Return* = ast.Return*(node)
|
||
if rt is None: return 0
|
||
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
mod: mlirlite.LLVMModule* = trans.Module
|
||
|
||
val: mlirlite.Value* = None
|
||
if rt.value is not None:
|
||
# f-string escape (return f"..."): its buffer lives on this frame's stack and dies on
|
||
# return; copy it out explicitly (GenApi.escape_fstring_copy) before returning it.
|
||
GenApi.fail_if_fstring_escape(rt.value, "function return value")
|
||
# A2(§14.3 第 6 条,方案 A):定义端对 `return <本帧存储>` 的**声明感知** fail-fast。
|
||
# A2 移除了"一律拦"的旧判据(定义端分不清"交出本帧"与"借出调用方的存储"),
|
||
# 但也不能一律放:判据收敛到被调者自己的 `@life(ret(...))` —— `ret(frame)` 或
|
||
# `ret(borrows=<该形参>)` 放行;无声明 / 声明为非帧 → LifetimeError。
|
||
# 实现见 AssignGenMixin.check_return_frame_decl。
|
||
self.Gen.AssignGen.check_return_frame_decl(trans, rt.value)
|
||
# return a, b → pack an anonymous struct value (multi-return, return type tuple[T1,T2])
|
||
if rt.value.kind() == ast.ASTKind.Tuple:
|
||
rtp: ast.Tuple* = ast.Tuple*(rt.value)
|
||
rtp_elts: list[ast.AST*]* = rtp.elts
|
||
rt_n: sizet = 0
|
||
if rtp_elts is not None: rt_n = len(rtp_elts)
|
||
if rt_n > 0 and rt_n <= 16:
|
||
rt_vals: sizet* = pool.alloc(8 * 16)
|
||
if rt_vals is not None:
|
||
rt_val_ok: int = 1
|
||
rt_head: mlirlite.ParamNode* = None
|
||
rt_tail: mlirlite.ParamNode* = None
|
||
rvi: sizet
|
||
for rvi in range(int(rt_n)):
|
||
ev_i: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, rtp_elts.get(rvi), trans._funcs, trans._func_count, trans)
|
||
if ev_i is None:
|
||
rt_val_ok = 0
|
||
break
|
||
rt_vals[rvi] = sizet(ev_i)
|
||
if rt_val_ok != 0:
|
||
# build the anonymous struct type (field types = element value types)
|
||
rvi2: sizet
|
||
for rvi2 in range(int(rt_n)):
|
||
rv_i: mlirlite.Value* = mlirlite.Value*((rt_vals[rvi2]))
|
||
if rv_i is None or rv_i.Ty is None:
|
||
rt_val_ok = 0
|
||
break
|
||
rn: mlirlite.ParamNode* = mlirlite.new_param_node(pool, rv_i.Ty)
|
||
if rn is None:
|
||
rt_val_ok = 0
|
||
break
|
||
if rt_head is None: rt_head = rn
|
||
else: mlirlite.paramnode_set_next(rt_tail, rn)
|
||
rt_tail = rn
|
||
rt_struct_ty: mlirlite.LLVMType* = None
|
||
if rt_val_ok != 0 and rt_head is not None: rt_struct_ty = mlirlite.Struct(pool, rt_head, rt_n, None)
|
||
rt_tmp: mlirlite.Value* = None
|
||
if rt_struct_ty is not None: rt_tmp = mlirlite.build_alloca(builder, rt_struct_ty)
|
||
if rt_tmp is not None:
|
||
rvi3: sizet
|
||
for rvi3 in range(int(rt_n)):
|
||
rv3: mlirlite.Value* = mlirlite.Value*(rt_vals[rvi3])
|
||
rfp: mlirlite.Value* = mlirlite.build_gep_struct(builder, rt_struct_ty, rv3.Ty, rt_tmp, rvi3)
|
||
if rfp is None:
|
||
rt_val_ok = 0
|
||
break
|
||
mlirlite.build_store(builder, rv3, rfp)
|
||
if rt_val_ok != 0: val = mlirlite.build_load(builder, rt_struct_ty, rt_tmp)
|
||
# return self: return the self pointer (Ptr(StructTy)) itself, not the struct value;
|
||
# lookup_var yields the alloca storing self, so load it to get Ptr(StructTy).
|
||
if rt.value.kind() == ast.ASTKind.Name:
|
||
ret_nm: ast.Name* = ast.Name*(rt.value)
|
||
if ret_nm is not None and ret_nm.id is not None and string.strcmp(ret_nm.id, "self") == 0:
|
||
self_ptr: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, "self")
|
||
if self_ptr is not None:
|
||
# check whether self_ptr is Ptr(Ptr(...)) (alloca storing a pointer)
|
||
self_pointee_ty: mlirlite.LLVMType* = None
|
||
match self_ptr.Ty:
|
||
case mlirlite.LLVMType.Ptr(inner_ty):
|
||
self_pointee_ty = inner_ty
|
||
if self_pointee_ty is not None and self.Gen.ExprGenBase.is_ptr_type(self_pointee_ty) != 0:
|
||
# Ptr(Ptr(StructTy)) → load yields Ptr(StructTy)
|
||
val = mlirlite.build_load(builder, self_pointee_ty, self_ptr)
|
||
else: val = self_ptr
|
||
if val is None:
|
||
# P1-A (context bit-width propagation): pass the function return type as target_ty
|
||
# so `return 12345` (-> uint64) materializes an i64 directly, skipping the zext.
|
||
ret_val_ty: mlirlite.LLVMType* = None
|
||
cur_func_rv: mlirlite.Function* = trans._cur_func
|
||
if cur_func_rv is not None: ret_val_ty = mlirlite.function_get_ret_ty(cur_func_rv)
|
||
# `return <闭包变量>` 且返回注解是 t.Callable:闭包对象是 {fn_ptr, env_ptr},
|
||
# 不是裸函数指针。调用方把结果存进 t.Callable 变量后 `f()` 会按裸函数指针直调,
|
||
# 跳到堆地址崩溃(ClosureCallTest 现场)。这里明确 fail-fast,要求用 rawptr/t.CPtr。
|
||
if rt.value.kind() == ast.ASTKind.Name and trans is not None and trans.CurFuncRetAnnot is not None:
|
||
clo_nm_rv: ast.Name* = ast.Name*(rt.value)
|
||
if clo_nm_rv.id is not None and self.Gen.CallGenLeafKit.is_closure_name(clo_nm_rv.id) != 0:
|
||
clo_ret_rv: mlirlite.LLVMType* = self.Gen.TypeGenErr.try_extract_callable_ret(pool, trans.CurFuncRetAnnot, trans._imported_modules, trans._from_imports, trans)
|
||
if clo_ret_rv is not None:
|
||
self.Gen.TypeGenErr.fatal_error(rt, f"the return annotation is t.Callable (a bare function pointer) but the returned value is the closure variable '{clo_nm_rv.id}'. A closure is a (fn_ptr, env_ptr) object; storing it where a t.Callable is expected makes the caller jump into heap data. Annotate the return as rawptr / t.CPtr instead", "TypeError")
|
||
# [C1] return-annotation context: record this return's direct value node so a bare
|
||
# generic ctor (`return Stack()` in `-> Stack[int]`) specializes via CurFuncRetAnnot.
|
||
trans.CurRetNode = rt.value
|
||
val = self.Gen.ExprGen.translate_value(builder, pool, mod, rt.value, trans._funcs, trans._func_count, trans, ret_val_ty, "return annotation")
|
||
trans.CurRetNode = None
|
||
|
||
# val is None (bare return): emit per the function return type - void→ret void,
|
||
# ptr→null, float→0.0, else zero (missing float handling makes llc reject the type).
|
||
if val is None:
|
||
cur_func: mlirlite.Function* = trans._cur_func
|
||
is_void_ret: int = 0
|
||
is_ptr_ret: int = 0
|
||
is_float_ret: int = 0
|
||
ret_ty: mlirlite.LLVMType* = None
|
||
if cur_func is not None:
|
||
ret_ty = mlirlite.function_get_ret_ty(cur_func)
|
||
if ret_ty is not None:
|
||
match ret_ty:
|
||
case mlirlite.LLVMType.Void():
|
||
is_void_ret = 1
|
||
case mlirlite.LLVMType.Ptr(_pe):
|
||
is_ptr_ret = 1
|
||
case mlirlite.LLVMType.Float(_fb):
|
||
is_float_ret = 1
|
||
if is_void_ret != 0:
|
||
# M3 owned slife(§4.2):每个 return 都是函数出口,先释放本函数登记的 slife
|
||
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
|
||
mlirlite.build_ret_void(builder)
|
||
return 0
|
||
if is_ptr_ret != 0:
|
||
null_val: mlirlite.Value* = mlirlite.ConstNull(pool, ret_ty, "null")
|
||
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
|
||
mlirlite.build_ret(builder, null_val)
|
||
return 0
|
||
if is_float_ret != 0:
|
||
zero_f: mlirlite.Value* = mlirlite.ConstFloat(pool, ret_ty, 0.0)
|
||
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
|
||
mlirlite.build_ret(builder, zero_f)
|
||
return 0
|
||
if ret_ty is None:
|
||
# fail-fast: bare return with unknown return type; no hard-coded i32 fallback
|
||
self.Gen.TypeGenErr.fatal_error(rt, "cannot determine the function return type", "TypeError")
|
||
val = mlirlite.ConstZero(pool, ret_ty)
|
||
if val is None:
|
||
# fail-fast: cannot build a zero value for the return type
|
||
self.Gen.TypeGenErr.fatal_error(rt, "cannot determine the function return type", "TypeError")
|
||
|
||
# coerce val to the function return type: i1→i8 etc. use zext (bool semantics, 1 stays 1);
|
||
# int→ptr must use inttoptr even at equal bit width, else llc rejects the return type.
|
||
cur_func_rt: mlirlite.Function* = trans._cur_func
|
||
ret_ty_rt: mlirlite.LLVMType* = None
|
||
if cur_func_rt is not None and val is not None and val.Ty is not None: ret_ty_rt = mlirlite.function_get_ret_ty(cur_func_rt)
|
||
# void function with `return <expr>`: drop the value and emit ret void (valid Python);
|
||
# returning the value would emit ret i32 and conflict with define void.
|
||
if ret_ty_rt is not None and val is not None:
|
||
rv_void: int = 0
|
||
match ret_ty_rt:
|
||
case mlirlite.LLVMType.Void():
|
||
rv_void = 1
|
||
if rv_void != 0:
|
||
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
|
||
mlirlite.build_ret_void(builder)
|
||
return 0
|
||
if ret_ty_rt is not None:
|
||
# str/bytes is no generic-pointer wildcard: a bare str/bytes return annotation with a
|
||
# pointer-to-aggregate value → fail-fast, before coerce_to_type silently bitcasts ptr→ptr.
|
||
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(val, trans.CurFuncRetAnnot, node)
|
||
val_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(val.Ty)
|
||
ret_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(ret_ty_rt)
|
||
val_is_ptr: int = self.Gen.ExprGenBase.is_ptr_type(val.Ty)
|
||
ret_is_ptr: int = self.Gen.ExprGenBase.is_ptr_type(ret_ty_rt)
|
||
# 按值返回聚合(数组/结构体):聚合变量在 VPC 中以 alloca 指针表示,返回类型是
|
||
# 聚合值时须 load 出整个聚合(与上方多返回值 struct 的 `build_load(... rt_struct_ty ...)`
|
||
# → ret 同形)。仅当被指类型与返回类型是同一聚合时成立;其余 Foo*→Foo 仍由
|
||
# coerce_to_type fail-fast,不放开隐式解引用。
|
||
if val_is_ptr != 0 and ret_is_ptr == 0 and ret_ty_rt is not None:
|
||
ret_kind_rv: int = ret_ty_rt.kind()
|
||
if ret_kind_rv == mlirlite.LLVMType.Struct or ret_kind_rv == mlirlite.LLVMType.Array:
|
||
v_inner_rv: mlirlite.LLVMType* = None
|
||
match val.Ty:
|
||
case mlirlite.LLVMType.Ptr(pi_rv):
|
||
v_inner_rv = pi_rv
|
||
case _:
|
||
pass
|
||
if v_inner_rv is not None and self.Gen.ExprGenBase._same_agg_ty(pool, v_inner_rv, ret_ty_rt) != 0:
|
||
loaded_rv: mlirlite.Value* = mlirlite.build_load(builder, ret_ty_rt, val)
|
||
if loaded_rv is not None:
|
||
val = loaded_rv
|
||
val_is_ptr = 0
|
||
# ptr mismatch: val is an integer but ret is a ptr (or vice versa), must convert
|
||
if val_is_ptr != ret_is_ptr:
|
||
# [FIX 2026-08-30] __new__ returns a class pointer (T*): during pending Phase A the
|
||
# value may be a degenerate i32, so allow inttoptr and stay pending for convergence.
|
||
fn_nm_ret: str = None
|
||
if cur_func_rt is not None: fn_nm_ret = mlirlite.function_get_name(cur_func_rt)
|
||
if fn_nm_ret is not None and string.strstr(fn_nm_ret, "__new__") is not None and val_is_ptr == 0 and ret_is_ptr != 0: val = mlirlite.build_inttoptr(builder, val, ret_ty_rt)
|
||
else:
|
||
# pass trans: with Phase A deps pending, a cross-module call's return type
|
||
# degrades to i32, so int→ptr is a rebuild, not an error - skip fail-fast.
|
||
val = self.Gen.ExprGenBase.coerce_to_type(builder, val, ret_ty_rt, node, trans)
|
||
elif val_is_ptr != 0 and ret_is_ptr != 0:
|
||
# pointer → pointer with different element types (e.g. i8* vs class pointer
|
||
# Circle*) needs a bitcast back, else llc rejects the function result type.
|
||
val_pointee: mlirlite.LLVMType* = None
|
||
ret_pointee: mlirlite.LLVMType* = None
|
||
match val.Ty:
|
||
case mlirlite.LLVMType.Ptr(v_inner):
|
||
val_pointee = v_inner
|
||
match ret_ty_rt:
|
||
case mlirlite.LLVMType.Ptr(r_inner):
|
||
ret_pointee = r_inner
|
||
if val_pointee is not None and ret_pointee is not None and uint64(val_pointee) != uint64(ret_pointee): val = self.Gen.ExprGenBase.coerce_to_type(builder, val, ret_ty_rt, node)
|
||
elif val_bits != 0 and ret_bits != 0 and val_bits != ret_bits:
|
||
if val_bits == 1 and val_bits < ret_bits: val = mlirlite.build_zext(builder, val, ret_ty_rt)
|
||
else:
|
||
# pass trans: unsigned sources (uint16/uint32/CSizeT) must zext; without it
|
||
# _node_type_unsigned cannot see the annotation and wrongly uses sext.
|
||
val = self.Gen.ExprGenBase.coerce_to_type(builder, val, ret_ty_rt, node, trans)
|
||
elif self.Gen.ExprGenBase.get_llvm_float_bits(val.Ty) != 0 or self.Gen.ExprGenBase.get_llvm_float_bits(ret_ty_rt) != 0:
|
||
# Float alignment: SpecTypeBits reports 0 for Float/Double, so coerce here
|
||
# (fpext/fptrunc/si2fp/fptosi) or a `-> float` return emitting double fails llc.
|
||
val = self.Gen.ExprGenBase.coerce_to_type(builder, val, ret_ty_rt, node, trans)
|
||
# M3 owned slife(§4.2):返回值已算完,此处是常规出口——先释放 slife 再 ret
|
||
# (必须在 val 求值之后:`return p.x` 之类若先释放 p 再读就成了 use-after-free)。
|
||
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
|
||
mlirlite.build_ret(builder, val)
|
||
return 0
|
||
|
||
# translate_if - translate if/elif/else (migrated from HandlesIf.IfHandle.Handle):
|
||
# a cif/celif condition (internal c.@CIf marker) branch-prunes at compile time; a condition that
|
||
# is not a compile-time constant is a fail-fast error (no runtime fallback).
|
||
def translate_if(self, trans: HT.Translator*, node: ast.AST*) -> int:
|
||
"""Translate an if/elif/else statement."""
|
||
if node is None: return 0
|
||
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
func: mlirlite.Function* = trans._cur_func
|
||
|
||
if builder is None or func is None: return 0
|
||
|
||
if_node: ast.If* = ast.If*(node)
|
||
|
||
# 0. compile-time condition (cif/celif desugared to If(test=c.@CIf(EXPR))): evaluate the
|
||
# constant expression and compile only the taken branch (celif recurses via orelse). A
|
||
# condition that is not a compile-time constant is fail-fast; there is no runtime fallback.
|
||
if self._is_cif_call(if_node.test) == 1:
|
||
cif_val: int = self._prune_cif_test(pool, trans, if_node.test)
|
||
if cif_val != 0:
|
||
body_cif: list[ast.AST*]* = if_node.children
|
||
if body_cif is not None:
|
||
bc_cif: sizet = len(body_cif)
|
||
bi_cif: sizet
|
||
for bi_cif in range(int(bc_cif)):
|
||
stmt_cif: ast.AST* = body_cif.get(bi_cif)
|
||
if stmt_cif is not None: self.Gen.StmtGen.translate_stmt(trans, stmt_cif)
|
||
else:
|
||
orelse_cif: list[ast.AST*]* = if_node.orelse
|
||
if orelse_cif is not None and len(orelse_cif) == 1:
|
||
sub_cif: ast.AST* = orelse_cif.get(0)
|
||
if sub_cif is not None and sub_cif.kind() == ast.ASTKind.If:
|
||
# celif: recurse (the sub-If test is another c.@CIf marker)
|
||
self.translate_if(trans, sub_cif)
|
||
elif sub_cif is not None: self.Gen.StmtGen.translate_stmt(trans, sub_cif)
|
||
elif orelse_cif is not None:
|
||
oc_cif: sizet = len(orelse_cif)
|
||
oi_cif: sizet
|
||
for oi_cif in range(int(oc_cif)):
|
||
stmt_cif2: ast.AST* = orelse_cif.get(oi_cif)
|
||
if stmt_cif2 is not None: self.Gen.StmtGen.translate_stmt(trans, stmt_cif2)
|
||
return 0
|
||
|
||
# 1. evaluate the condition expression
|
||
cond_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, if_node.test, trans._funcs, trans._func_count, trans)
|
||
|
||
# 2. coerce to i1 (prefer __bool__; Compare/Not already return i1)
|
||
if cond_val is None:
|
||
# fail-fast: if condition expression failed to translate
|
||
self.Gen.TypeGenErr.fatal_error(if_node.test, "failed to translate the if condition expression", "TypeError")
|
||
cond_i1: mlirlite.Value* = self.Gen.ExprGenOps.coerce_cond_to_bool(pool, builder, trans.Module, cond_val, trans)
|
||
|
||
# 3. create blocks (unique labels from trans._label_counter, not shared with SSA names)
|
||
cnt: int = trans._label_counter
|
||
trans._label_counter = cnt + 1
|
||
|
||
name_buf: str = pool.alloc(32)
|
||
viperlib.snprintf(name_buf, 32, "if.then.%d", cnt)
|
||
then_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "if.end.%d", cnt)
|
||
merge_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
# check for an else branch
|
||
has_else: int = 0
|
||
orelse_list: list[ast.AST*]* = if_node.orelse
|
||
if orelse_list is not None:
|
||
if len(orelse_list) > 0: has_else = 1
|
||
|
||
else_bb: mlirlite.BasicBlock* = None
|
||
if has_else == 1:
|
||
viperlib.snprintf(name_buf, 32, "if.else.%d", cnt)
|
||
else_bb = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
# 4. emit the conditional branch
|
||
if has_else == 1: mlirlite.build_cond_br(builder, cond_i1, then_bb, else_bb)
|
||
else: mlirlite.build_cond_br(builder, cond_i1, then_bb, merge_bb)
|
||
|
||
# 生命期汇合:进入分支前快照(else 分支需从该状态重新开始,见 §11 控制流 Meet)
|
||
life_snap0: sizet* = self.Gen.VarGen.life_capture(trans)
|
||
# willFree 失效槽表同点位快照(分支体内的 free 不得泄漏到合流之后)
|
||
freed_snap0: sizet* = self.Gen.VarGen.freed_capture(trans)
|
||
|
||
# 5. translate the then body; reset the merge-reachable flag first (nested control flow
|
||
# overwrites it, so the branch-tail check must not see a stale value from before this If).
|
||
trans._ctrl_merge_reachable = 1
|
||
mlirlite.position_at_end(builder, then_bb)
|
||
body: list[ast.AST*]* = if_node.children
|
||
if body is not None:
|
||
body_count: sizet = len(body)
|
||
for bi in range(int(body_count)):
|
||
stmt: ast.AST* = body.get(bi)
|
||
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
|
||
|
||
# if the then block is not terminated, branch to merge (record whether it did)
|
||
then_branches_merge: int = 0
|
||
if mlirlite.builder_cur_block_is_terminated(builder) == 0:
|
||
if trans._ctrl_merge_reachable == 0:
|
||
# branch tail is nested control flow and merge is structurally unreachable (all
|
||
# branches return): close with unreachable, never br merge, to keep reachability honest.
|
||
mlirlite.build_unreachable(builder)
|
||
else:
|
||
mlirlite.build_br(builder, merge_bb)
|
||
then_branches_merge = 1
|
||
|
||
# then 末状态快照(供两端 Meet:先复位到 if 前状态,再与 then 末状态合并)
|
||
life_snap1: sizet* = self.Gen.VarGen.life_capture(trans)
|
||
freed_snap1: sizet* = self.Gen.VarGen.freed_capture(trans)
|
||
|
||
# 6. translate the else body (if any)
|
||
else_branches_merge: int = 0
|
||
if has_else == 1:
|
||
# 复位到 if 前状态,让 else 分支从同一基线出发(否则会看到 then 分支写入的 Life)
|
||
self.Gen.VarGen.life_restore(life_snap0)
|
||
self.Gen.VarGen.freed_restore(freed_snap0)
|
||
# as in then: reset the flag to avoid stale values from the then branch
|
||
trans._ctrl_merge_reachable = 1
|
||
mlirlite.position_at_end(builder, else_bb)
|
||
else_count: sizet = len(orelse_list)
|
||
for ei in range(int(else_count)):
|
||
stmt: ast.AST* = orelse_list.get(ei)
|
||
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
|
||
if mlirlite.builder_cur_block_is_terminated(builder) == 0:
|
||
if trans._ctrl_merge_reachable == 0:
|
||
# nested control flow with all branches returning: emit unreachable, no br merge
|
||
mlirlite.build_unreachable(builder)
|
||
else:
|
||
mlirlite.build_br(builder, merge_bb)
|
||
else_branches_merge = 1
|
||
|
||
# 生命期汇合:有 else → 与 then 末状态 Meet;无 else → 与 if 前状态 Meet(else 路径=跳过)
|
||
if has_else == 1: self.Gen.VarGen.life_join(life_snap1)
|
||
else: self.Gen.VarGen.life_join(life_snap0)
|
||
|
||
# willFree 失效槽表合流:只有"到达合流的路径"参与(否则会把"分支体内 free 后 return"
|
||
# 的失效态错误地带到合流之后——fileio.vp 的 `if …: free(buf); return None` 即此现场)。
|
||
# 两分支都到达 → 求交(curr=else 末态 ∩ then 末态);
|
||
# 仅 then 到达 → 取 then 末态;
|
||
# 仅 else/无 else → 取 else 末态(无 else 时即 if 前状态)。
|
||
if has_else == 1:
|
||
if then_branches_merge != 0 and else_branches_merge != 0:
|
||
self.Gen.VarGen.freed_join(freed_snap1)
|
||
elif then_branches_merge != 0:
|
||
self.Gen.VarGen.freed_restore(freed_snap1)
|
||
else:
|
||
self.Gen.VarGen.freed_restore(freed_snap0)
|
||
|
||
# 7. merge reachability (used by tail-flow checks): without else cond_br always reaches
|
||
# merge; with else, any branch br reaching merge suffices; only all-return is unreachable.
|
||
trans._ctrl_merge_reachable = 1
|
||
if has_else == 1 and then_branches_merge == 0 and else_branches_merge == 0: trans._ctrl_merge_reachable = 0
|
||
|
||
# 8. position at merge to continue with following code
|
||
mlirlite.position_at_end(builder, merge_bb)
|
||
return 0
|
||
|
||
# loop-else support (for-else / while-else): run else on normal exit (condition false /
|
||
# iterator exhausted), skip on break; the natural-exit edge targets else_bb (else end_bb).
|
||
def _LoopElseCount(self, orelse_list: list[ast.AST*]*) -> int:
|
||
"""Return 1 if the orelse list is non-empty (an else clause exists)."""
|
||
if orelse_list is None: return 0
|
||
if len(orelse_list) > 0: return 1
|
||
return 0
|
||
|
||
def _MakeLoopElseBlock(self, pool: memhub.MemBuddy*, func: mlirlite.Function*, name_buf: str, kind: str, cnt: int, has_else: int) -> mlirlite.BasicBlock*:
|
||
"""Create the loop-else block named kind.else.cnt when has_else is set, else None."""
|
||
if has_else == 0: return None
|
||
if func is None: return None
|
||
viperlib.snprintf(name_buf, 32, "%s.else.%d", kind, cnt)
|
||
return mlirlite.create_block(pool, func, name_buf)
|
||
|
||
def _EmitLoopElse(self, trans: HT.Translator*, orelse_list: list[ast.AST*]*, else_bb: mlirlite.BasicBlock*, end_bb: mlirlite.BasicBlock*) -> int:
|
||
"""Emit the loop-else block into else_bb, translating orelse and branching to end_bb."""
|
||
if else_bb is None: return 0
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
if builder is None: return 0
|
||
# as in translate_if: reset the merge-reachable flag before translating branches
|
||
trans._ctrl_merge_reachable = 1
|
||
mlirlite.position_at_end(builder, else_bb)
|
||
if orelse_list is not None:
|
||
n: sizet = len(orelse_list)
|
||
for i in range(int(n)):
|
||
stmt: ast.AST* = orelse_list.get(i)
|
||
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
|
||
if mlirlite.builder_cur_block_is_terminated(builder) == 0:
|
||
mlirlite.build_br(builder, end_bb)
|
||
return 1
|
||
return 0
|
||
|
||
# translate_while - translate while loops (migrated from HandlesWhile.WhileHandle.Handle)
|
||
# into cond/body/end blocks with a condition branch back to cond.
|
||
def translate_while(self, trans: HT.Translator*, node: ast.AST*) -> int:
|
||
"""Translate a while loop statement."""
|
||
if node is None: return 0
|
||
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
func: mlirlite.Function* = trans._cur_func
|
||
|
||
if builder is None or func is None: return 0
|
||
|
||
while_node: ast.While* = ast.While*(node)
|
||
|
||
# 1. create blocks cond/body/end (trans._label_counter labels, not shared with SSA)
|
||
cnt: int = trans._label_counter
|
||
trans._label_counter = cnt + 1
|
||
|
||
name_buf: str = pool.alloc(32)
|
||
viperlib.snprintf(name_buf, 32, "while.cond.%d", cnt)
|
||
cond_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "while.body.%d", cnt)
|
||
body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "while.end.%d", cnt)
|
||
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
# loop else: the natural-exit (condition false) edge targets else_bb (else end_bb)
|
||
has_else_while: int = self._LoopElseCount(while_node.orelse)
|
||
else_bb_while: mlirlite.BasicBlock* = self._MakeLoopElseBlock(pool, func, name_buf, "while", cnt, has_else_while)
|
||
|
||
# 2. branch to the cond block
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# 3. cond block: evaluate the condition and branch
|
||
mlirlite.position_at_end(builder, cond_bb)
|
||
cond_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, while_node.test, trans._funcs, trans._func_count, trans)
|
||
if cond_val is None:
|
||
# fail-fast: while condition expression failed to translate
|
||
self.Gen.TypeGenErr.fatal_error(while_node.test, "failed to translate the while condition expression", "TypeError")
|
||
|
||
# coerce to i1 (prefer __bool__; Compare/Not already return i1)
|
||
cond_i1: mlirlite.Value* = self.Gen.ExprGenOps.coerce_cond_to_bool(pool, builder, trans.Module, cond_val, trans)
|
||
mlirlite.build_cond_br(builder, cond_i1, body_bb, else_bb_while if else_bb_while is not None else end_bb)
|
||
|
||
# 4. body block: translate the loop body and branch back to cond
|
||
mlirlite.position_at_end(builder, body_bb)
|
||
|
||
# save the old loop context and set break/continue targets
|
||
old_break: mlirlite.BasicBlock* = trans._break_bb
|
||
old_continue: mlirlite.BasicBlock* = trans._continue_bb
|
||
trans._break_bb = end_bb
|
||
trans._continue_bb = cond_bb
|
||
|
||
body: list[ast.AST*]* = while_node.children
|
||
if body is not None:
|
||
body_count: sizet = len(body)
|
||
for bi in range(int(body_count)):
|
||
stmt: ast.AST* = body.get(bi)
|
||
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
|
||
|
||
# restore the old loop context
|
||
trans._break_bb = old_break
|
||
trans._continue_bb = old_continue
|
||
|
||
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, cond_bb)
|
||
|
||
# 5. loop-else tail (run orelse on natural exit), then position at end
|
||
ret_while: int = self._EmitLoopElse(trans, while_node.orelse, else_bb_while, end_bb)
|
||
mlirlite.position_at_end(builder, end_bb)
|
||
# append90: with else there is an endif; without else while's cond_br always references
|
||
# end_bb, so merge is reachable and needs normal tail handling.
|
||
trans._ctrl_merge_reachable = 1
|
||
# infinite loop (constant true) with no local break makes end structurally unreachable:
|
||
# set 0 so the tail emits unreachable; skip that when an else exists (else_bb brs to end).
|
||
if has_else_while == 0 and self._IsConstTrue(while_node.test) != 0 and self._StmtHasBreak(body) == 0: trans._ctrl_merge_reachable = 0
|
||
return 0
|
||
|
||
# infinite-loop helpers (merge reachability, migrated from HandlesWhile module funcs):
|
||
# _IsConstTrue tests a constant-true condition; _NodeHasBreak/_StmtHasBreak scan local break.
|
||
def _IsConstTrue(self, test: ast.AST*) -> int:
|
||
"""Return 1 if the while condition is a constant true (1 / True)."""
|
||
if test is None: return 0
|
||
if test.kind() == ast.ASTKind.Constant:
|
||
cn: ast.Constant* = ast.Constant*(test)
|
||
if cn.const_kind == ast.CONST_INT and cn.int_val != 0: return 1
|
||
if cn.const_kind == ast.CONST_BOOL and cn.int_val != 0: return 1
|
||
return 0
|
||
|
||
def _NodeHasBreak(self, node: ast.AST*) -> int:
|
||
"""Return 1 if the subtree contains a local Break, skipping nested While/For subtrees."""
|
||
if node is None: return 0
|
||
ch: list[ast.AST*]* = node.children
|
||
if ch is not None:
|
||
n: sizet = len(ch)
|
||
for bi in range(int(n)):
|
||
c: ast.AST* = ch.get(bi)
|
||
if c is None: continue
|
||
k: int = c.kind()
|
||
if k == ast.ASTKind.Break: return 1
|
||
if k == ast.ASTKind.While or k == ast.ASTKind.For: continue
|
||
if self._NodeHasBreak(c) != 0: return 1
|
||
return 0
|
||
|
||
def _StmtHasBreak(self, body: list[ast.AST*]*) -> int:
|
||
"""Return 1 if the statement list contains a break belonging to this loop level."""
|
||
if body is None: return 0
|
||
n: sizet = len(body)
|
||
for bi in range(int(n)):
|
||
s: ast.AST* = body.get(bi)
|
||
if s is None: continue
|
||
k: int = s.kind()
|
||
if k == ast.ASTKind.Break: return 1
|
||
if k == ast.ASTKind.While or k == ast.ASTKind.For: continue
|
||
if self._NodeHasBreak(s) != 0: return 1
|
||
return 0
|
||
|
||
# translate_for - translate for loops (migrated from HandlesFor.ForHandle.Handle): supports
|
||
# for i in range(start, stop, step) as cond/body/incr/end blocks with a signed/unsigned test.
|
||
def translate_for(self, trans: HT.Translator*, node: ast.AST*) -> int:
|
||
"""Translate a for i in range(...) loop statement."""
|
||
if node is None: return 0
|
||
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
func: mlirlite.Function* = trans._cur_func
|
||
|
||
if builder is None or func is None: return 0
|
||
|
||
for_node: ast.For* = ast.For*(node)
|
||
|
||
# 1. get the loop variable (single Name / multi-var Tuple)
|
||
target: ast.AST* = for_node.target
|
||
if target is None: return 0
|
||
tk: int = target.kind()
|
||
if tk != ast.ASTKind.Name and tk != ast.ASTKind.Tuple: return 0
|
||
var_name: str = None
|
||
if tk == ast.ASTKind.Name:
|
||
target_nm: ast.Name* = ast.Name*(target)
|
||
var_name = target_nm.id
|
||
if var_name is None: return 0
|
||
|
||
# 2. parse the iterator: supports range() and pointer iteration
|
||
iter_node: ast.AST* = for_node.iter
|
||
if iter_node is None: return 0
|
||
|
||
# check whether it is a range() call
|
||
is_range_iter: int = 0
|
||
if iter_node.kind() == ast.ASTKind.Call:
|
||
call_pre: ast.Call* = ast.Call*(iter_node)
|
||
fn_pre: str = self.Gen.ExprGenBase.get_func_name(call_pre.func)
|
||
if fn_pre is not None:
|
||
if string.strcmp(fn_pre, "range") == 0: is_range_iter = 1
|
||
elif string.strcmp(fn_pre, "enumerate") == 0 or string.strcmp(fn_pre, "zip") == 0:
|
||
pass # multi-var iteration, handled in the non-range branch below
|
||
else:
|
||
err_msg: str = pool.alloc(256)
|
||
if err_msg is not None:
|
||
viperlib.snprintf(err_msg, 256, "only range()/enumerate()/zip() or pointer iteration is supported, got call '%s'", fn_pre)
|
||
self.Gen.TypeGenErr.fatal_error(iter_node, err_msg)
|
||
self.Gen.TypeGenErr.fatal_error(iter_node, "only range()/enumerate()/zip() or pointer iteration is supported")
|
||
|
||
# non-range iteration: pointer / array / list-literal / container / multi-var paths
|
||
if is_range_iter == 0:
|
||
# list literal: iterate in element order (for x in [1, 2, 3])
|
||
if iter_node.kind() == ast.ASTKind.List: return self._handle_list_literal_iter(trans, for_node, var_name, iter_node)
|
||
# enumerate(x): for i, v in enumerate(x) (multi-var iteration)
|
||
if iter_node.kind() == ast.ASTKind.Call:
|
||
call_mi: ast.Call* = ast.Call*(iter_node)
|
||
fn_mi: str = self.Gen.ExprGenBase.get_func_name(call_mi.func)
|
||
if fn_mi is not None and string.strcmp(fn_mi, "enumerate") == 0:
|
||
if tk != ast.ASTKind.Tuple: self.Gen.TypeGenErr.fatal_error(iter_node, "enumerate iteration requires two loop variables (for i, v in enumerate(x))")
|
||
tup_ie: ast.Tuple* = ast.Tuple*(target)
|
||
t1_buf_ie: bytes = pool.alloc(128)
|
||
t2_buf_ie: bytes = pool.alloc(128)
|
||
if t1_buf_ie is None or t2_buf_ie is None: return 0
|
||
t1_buf_ie[0] = '\0'
|
||
t2_buf_ie[0] = '\0'
|
||
if self._extract_tuple_names(tup_ie, t1_buf_ie, t2_buf_ie) == 0: self.Gen.TypeGenErr.fatal_error(iter_node, "enumerate iteration variables must be simple names")
|
||
return self._handle_enumerate_iter(trans, for_node, t1_buf_ie, t2_buf_ie, call_mi)
|
||
if fn_mi is not None and string.strcmp(fn_mi, "zip") == 0:
|
||
if tk != ast.ASTKind.Tuple: self.Gen.TypeGenErr.fatal_error(iter_node, "zip iteration requires two loop variables (for a, b in zip(...))")
|
||
tup_iz: ast.Tuple* = ast.Tuple*(target)
|
||
t1_buf_iz: bytes = pool.alloc(128)
|
||
t2_buf_iz: bytes = pool.alloc(128)
|
||
if t1_buf_iz is None or t2_buf_iz is None: return 0
|
||
t1_buf_iz[0] = '\0'
|
||
t2_buf_iz[0] = '\0'
|
||
if self._extract_tuple_names(tup_iz, t1_buf_iz, t2_buf_iz) == 0: self.Gen.TypeGenErr.fatal_error(iter_node, "zip iteration variables must be simple names")
|
||
return self._handle_zip_iter(trans, for_node, t1_buf_iz, t2_buf_iz, call_mi)
|
||
# list[T] container variable: iterate by dynamic __count__
|
||
if iter_node.kind() == ast.ASTKind.Name and trans is not None:
|
||
nm_lst: ast.Name* = ast.Name*(iter_node)
|
||
if nm_lst.id is not None:
|
||
ve_lst: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_lst.id)
|
||
lst_annot: str = self.Gen.VarGen.var_annot_class_name(ve_lst)
|
||
if lst_annot is not None:
|
||
# dict container: iterate by __count__/__keys__ layout (__iter__ inlined);
|
||
# [S4] exact base-name compare avoids matching any class name containing "dict".
|
||
if TSpec.SpecTypeNameBaseIs(lst_annot, "dict") != 0:
|
||
lst_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, iter_node, trans._funcs, trans._func_count, trans)
|
||
if lst_val is not None and lst_val.Ty is not None and self.Gen.ExprGenBase.is_ptr_type(lst_val.Ty) != 0: return self._handle_dict_iter(trans, for_node, var_name, lst_val)
|
||
# [S4] same: strstr("list") wrongly matched playlist / GSList; non-list base
|
||
# user classes fall through to the OOP __iter__/__next__ path below.
|
||
if TSpec.SpecTypeNameBaseIs(lst_annot, "list") != 0:
|
||
lst_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, iter_node, trans._funcs, trans._func_count, trans)
|
||
if lst_val is not None and lst_val.Ty is not None and self.Gen.ExprGenBase.is_ptr_type(lst_val.Ty) != 0: return self._handle_list_iter(trans, for_node, var_name, lst_val)
|
||
# OOP iter protocol: if the iterator source is a class with __iter__/__next__, call
|
||
# them (TPC HandlesFor.py L142-166); dict/list were already inlined above.
|
||
ov_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, iter_node, trans._funcs, trans._func_count, trans)
|
||
if ov_val is not None and ov_val.Ty is not None and self.Gen.ExprGenBase.is_ptr_type(ov_val.Ty) != 0:
|
||
ov_st: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(ov_val)
|
||
if ov_st is not None:
|
||
ov_cn: str = self.Gen.StructGen.get_class_name_by_type(pool, ov_st)
|
||
if ov_cn is not None:
|
||
oi_lookup: str = pool.alloc(128)
|
||
if oi_lookup is not None:
|
||
viperlib.snprintf(oi_lookup, 128, "%s.__iter__", ov_cn)
|
||
oi_fn: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(trans.Module, oi_lookup)
|
||
if oi_fn is not None:
|
||
on_lookup: str = pool.alloc(128)
|
||
if on_lookup is not None:
|
||
viperlib.snprintf(on_lookup, 128, "%s.__next__", ov_cn)
|
||
on_fn: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(trans.Module, on_lookup)
|
||
if on_fn is not None: return self._handle_oop_iter(trans, for_node, var_name, ov_cn, ov_val)
|
||
# other unsupported literals: fast-fail explicitly to avoid list[T] specialization
|
||
# crashes or ambiguous semantics.
|
||
ik: int = iter_node.kind()
|
||
if ik == ast.ASTKind.Constant:
|
||
cn_ik: ast.Constant* = ast.Constant*(iter_node)
|
||
if cn_ik.const_kind != ast.CONST_STR: self.Gen.TypeGenErr.fatal_error(iter_node, "for iteration does not support this constant (only strings, arrays, list literals, range())")
|
||
elif ik == ast.ASTKind.Dict or ik == ast.ASTKind.Set or ik == ast.ASTKind.Tuple or ik == ast.ASTKind.SetComp or ik == ast.ASTKind.DictComp:
|
||
self.Gen.TypeGenErr.fatal_error(iter_node, "for iteration does not support dict/set/tuple literals; use range(), a t.CArray array, or a list literal")
|
||
return self._handle_ptr_iter(trans, for_node, var_name)
|
||
|
||
call: ast.Call* = ast.Call*(iter_node)
|
||
|
||
# 3. parse range args: range(stop) / range(start, stop) / range(start, stop, step)
|
||
args: list[ast.AST*]* = call.args
|
||
if args is None: return 0
|
||
arg_count: sizet = len(args)
|
||
|
||
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
start_val: mlirlite.Value* = mlirlite.const_int32(pool, 0)
|
||
stop_val: mlirlite.Value* = None
|
||
step_val: mlirlite.Value* = mlirlite.const_int32(pool, 1)
|
||
|
||
if arg_count == 0:
|
||
# fail-fast: range needs at least one argument
|
||
self.Gen.TypeGenErr.fatal_error(call, "range requires at least one argument", "SyntaxError")
|
||
elif arg_count == 1:
|
||
stop_val = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, args.get(0), trans._funcs, trans._func_count, trans)
|
||
if stop_val is None:
|
||
# fail-fast: range argument failed to translate
|
||
self.Gen.TypeGenErr.fatal_error(args.get(0), "failed to translate the range argument", "TypeError")
|
||
elif arg_count >= 2:
|
||
start_val = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, args.get(0), trans._funcs, trans._func_count, trans)
|
||
if start_val is None:
|
||
# fail-fast: range argument failed to translate
|
||
self.Gen.TypeGenErr.fatal_error(args.get(0), "failed to translate the range argument", "TypeError")
|
||
stop_val = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, args.get(1), trans._funcs, trans._func_count, trans)
|
||
if stop_val is None:
|
||
# fail-fast: range argument failed to translate
|
||
self.Gen.TypeGenErr.fatal_error(args.get(1), "failed to translate the range argument", "TypeError")
|
||
if arg_count >= 3:
|
||
step_val = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, args.get(2), trans._funcs, trans._func_count, trans)
|
||
if step_val is None:
|
||
# fail-fast: range argument failed to translate
|
||
self.Gen.TypeGenErr.fatal_error(args.get(2), "failed to translate the range argument", "TypeError")
|
||
# a negative literal step inverts the loop test (TPC HandlesFor.py L86-94): range(a,b,-1)
|
||
# uses i > stop. Only negative literals are detected; variable steps default to positive.
|
||
step_neg_const: int = 0
|
||
if arg_count >= 3:
|
||
step_arg_node: ast.AST* = args.get(2)
|
||
if step_arg_node is not None:
|
||
skn: int = step_arg_node.kind()
|
||
if skn == ast.ASTKind.UnaryOp:
|
||
uop_r: ast.UnaryOp* = ast.UnaryOp*(step_arg_node)
|
||
if uop_r.op == ast.OpKind.USub and uop_r.operand is not None:
|
||
if uop_r.operand.kind() == ast.ASTKind.Constant:
|
||
uoc_r: ast.Constant* = ast.Constant*(uop_r.operand)
|
||
if uoc_r.int_val > 0: step_neg_const = 1
|
||
elif skn == ast.ASTKind.Constant:
|
||
skc_r: ast.Constant* = ast.Constant*(step_arg_node)
|
||
if skc_r.int_val < 0: step_neg_const = 1
|
||
|
||
# 3.4 range 边界统一提升到**至少 i32**(仿 C 整数提升):i8/i16 边界不再以窄位宽参与比较
|
||
# 与自增。拓宽方向由 coerce_to_type → should_zext_widen 决定:非负 define 常量是**无符号
|
||
# 位模式**(Debugger/define-constants.md §7.2),必须 zext(define N = 50000 → 16 位 0xC350
|
||
# → 50000,而非 sext 成 -15536)。u64 边界保持 i64(不动,避免截断)。
|
||
if stop_val is not None and stop_val.Ty is not None:
|
||
sb_p4: int = self.Gen.ExprGenBase.get_llvm_type_bits(stop_val.Ty)
|
||
if sb_p4 != 0 and sb_p4 < 32:
|
||
nd_s4: ast.AST* = args.get(0)
|
||
if arg_count >= 2: nd_s4 = args.get(1)
|
||
stop_val = self.Gen.ExprGenBase.coerce_to_type(builder, stop_val, i32_ty, nd_s4, trans)
|
||
if start_val is not None and start_val.Ty is not None:
|
||
sa_p4: int = self.Gen.ExprGenBase.get_llvm_type_bits(start_val.Ty)
|
||
if sa_p4 != 0 and sa_p4 < 32:
|
||
start_val = self.Gen.ExprGenBase.coerce_to_type(builder, start_val, i32_ty, args.get(0), trans)
|
||
if step_val is not None and step_val.Ty is not None:
|
||
sp_p4: int = self.Gen.ExprGenBase.get_llvm_type_bits(step_val.Ty)
|
||
if sp_p4 != 0 and sp_p4 < 32:
|
||
step_val = self.Gen.ExprGenBase.coerce_to_type(builder, step_val, i32_ty, args.get(2), trans)
|
||
|
||
# 3.5 loop-var storage width: 边界已提升到 ≥i32,所以只有 i64 边界需要 i64 循环变量,
|
||
# 其余一律 i32(不再有 u16→i16 / u8→i8 档:窄位宽循环变量会把边界截断)。
|
||
# Applies only to undeclared loop vars.
|
||
loop_store_ty: mlirlite.LLVMType* = i32_ty
|
||
range_arg_bits: int = 0
|
||
if arg_count == 1:
|
||
if stop_val is not None and stop_val.Ty is not None: range_arg_bits = self.Gen.ExprGenBase.get_llvm_type_bits(stop_val.Ty)
|
||
else:
|
||
start_bits_rt: int = 0
|
||
stop_bits_rt: int = 0
|
||
if start_val is not None and start_val.Ty is not None: start_bits_rt = self.Gen.ExprGenBase.get_llvm_type_bits(start_val.Ty)
|
||
if stop_val is not None and stop_val.Ty is not None: stop_bits_rt = self.Gen.ExprGenBase.get_llvm_type_bits(stop_val.Ty)
|
||
range_arg_bits = start_bits_rt if start_bits_rt > stop_bits_rt else stop_bits_rt
|
||
if range_arg_bits == 64: loop_store_ty = mlirlite.Int64(pool)
|
||
|
||
# 3.6 loop unsignedness: an unsigned loop-var annotation or any unsigned range bound uses
|
||
# icmp ult/ugt instead of slt/sgt (via ExprGenOps._node_type_unsigned).
|
||
loop_unsigned: int = 0
|
||
if tk == ast.ASTKind.Name:
|
||
if self.Gen.ExprGenOps._node_type_unsigned(pool, target, trans) != 0: loop_unsigned = 1
|
||
if loop_unsigned == 0:
|
||
args_u: sizet
|
||
for args_u in range(int(arg_count)):
|
||
if self.Gen.ExprGenOps._node_type_unsigned(pool, args.get(args_u), trans) != 0:
|
||
loop_unsigned = 1
|
||
break
|
||
|
||
# 3.7 a negative literal stop (-1 / -N) forces signed comparison: stop = -1 is UINT_MAX
|
||
# unsigned, making i > stop always false; negative literals belong to the signed domain.
|
||
stop_neg_const: int = 0
|
||
if arg_count >= 2:
|
||
stop_arg_node: ast.AST* = args.get(1)
|
||
if stop_arg_node is not None:
|
||
stn_k: int = stop_arg_node.kind()
|
||
if stn_k == ast.ASTKind.UnaryOp:
|
||
stop_uop: ast.UnaryOp* = ast.UnaryOp*(stop_arg_node)
|
||
if stop_uop.op == ast.OpKind.USub and stop_uop.operand is not None:
|
||
if stop_uop.operand.kind() == ast.ASTKind.Constant:
|
||
stop_uc: ast.Constant* = ast.Constant*(stop_uop.operand)
|
||
if stop_uc.int_val > 0: stop_neg_const = 1
|
||
elif stn_k == ast.ASTKind.Constant:
|
||
stop_c: ast.Constant* = ast.Constant*(stop_arg_node)
|
||
if stop_c.int_val < 0: stop_neg_const = 1
|
||
if stop_neg_const != 0: loop_unsigned = 0
|
||
|
||
# 4. create/find the loop-var alloca using lookup_current (current scope only): lookup_var
|
||
# inside a closure could hit a same-named parent var and corrupt cross-function SSA.
|
||
var_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, var_name)
|
||
new_vars: int = 0
|
||
if var_alloca is None:
|
||
# use _alloca_at_entry: allocas go in the entry block for dominance (an inline
|
||
# build_alloca in a branch block breaks cross-block loop use); width follows range args.
|
||
var_alloca = self.Gen.VarGen._alloca_at_entry(builder, loop_store_ty)
|
||
if self.Gen.VarGen.define_var(trans.SymTab, var_name, var_alloca) == 0: new_vars = 1
|
||
|
||
# get the loop var's actual element type (alloca is a pointer, deref Pointee); use the
|
||
# declared type (e.g. i64) instead of a hard-coded i32 to avoid load/store mismatches.
|
||
loop_var_ty: mlirlite.LLVMType* = i32_ty
|
||
if var_alloca is not None and var_alloca.Ty is not None:
|
||
match var_alloca.Ty:
|
||
case mlirlite.LLVMType.Ptr(pointee_ty):
|
||
if pointee_ty is not None: loop_var_ty = pointee_ty
|
||
loop_var_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(loop_var_ty)
|
||
|
||
# 5. store the initial value (align start_val, e.g. i32/i64, to loop_var_ty)
|
||
init_val: mlirlite.Value* = start_val
|
||
if start_val is not None and start_val.Ty is not None:
|
||
start_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(start_val.Ty)
|
||
if start_bits != 0 and start_bits != loop_var_bits:
|
||
if start_bits < loop_var_bits: init_val = mlirlite.build_sext(builder, start_val, loop_var_ty)
|
||
else: init_val = mlirlite.build_trunc(builder, start_val, loop_var_ty)
|
||
mlirlite.build_store(builder, init_val, var_alloca)
|
||
|
||
# 6. create blocks cond/body/incr/end (trans._label_counter labels, not shared with SSA)
|
||
cnt: int = trans._label_counter
|
||
trans._label_counter = cnt + 1
|
||
|
||
name_buf: str = pool.alloc(32)
|
||
viperlib.snprintf(name_buf, 32, "for.cond.%d", cnt)
|
||
cond_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "for.body.%d", cnt)
|
||
body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "for.incr.%d", cnt)
|
||
incr_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "for.end.%d", cnt)
|
||
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
# loop else: the exhausted-iterator (i out of range) edge targets else_bb (else end_bb)
|
||
has_else_for: int = self._LoopElseCount(for_node.orelse)
|
||
else_bb_for: mlirlite.BasicBlock* = self._MakeLoopElseBlock(pool, func, name_buf, "for", cnt, has_else_for)
|
||
|
||
# 7. branch to the cond block
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# 8. cond block: load i, icmp slt i, stop, cond_br to body/end
|
||
mlirlite.position_at_end(builder, cond_bb)
|
||
cur_i: mlirlite.Value* = mlirlite.build_load(builder, loop_var_ty, var_alloca)
|
||
# align types: stop_val may be i64 (e.g. range(strlen(s))), widen cur_i to its type
|
||
cmp_lhs: mlirlite.Value* = cur_i
|
||
cmp_rhs: mlirlite.Value* = stop_val
|
||
if stop_val is not None and stop_val.Ty is not None:
|
||
stop_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(stop_val.Ty)
|
||
cur_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(cur_i.Ty)
|
||
if stop_bits != 0 and cur_bits != 0 and stop_bits != cur_bits:
|
||
if cur_bits < stop_bits: cmp_lhs = mlirlite.build_sext(builder, cur_i, stop_val.Ty)
|
||
else:
|
||
# loop var wider than stop (e.g. sizet + range(i32)): truncate cur_i to
|
||
# stop_val's type; widening the smaller stop_val emits invalid IR.
|
||
cmp_lhs = mlirlite.build_trunc(builder, cur_i, stop_val.Ty)
|
||
# signed base predicate: negative step inverts to i > stop (SGT); otherwise i < stop (SLT)
|
||
range_cmp_op: int = mlirlite.ICMP_SLT
|
||
if step_neg_const != 0: range_cmp_op = mlirlite.ICMP_SGT
|
||
# unsigned loop: use unsigned predicates (positive step ult / negative step ugt)
|
||
if loop_unsigned != 0:
|
||
if step_neg_const != 0: range_cmp_op = mlirlite.ICMP_UGT
|
||
else: range_cmp_op = mlirlite.ICMP_ULT
|
||
cond_i1: mlirlite.Value* = mlirlite.build_icmp(builder, range_cmp_op, cmp_lhs, cmp_rhs)
|
||
mlirlite.build_cond_br(builder, cond_i1, body_bb, else_bb_for if else_bb_for is not None else end_bb)
|
||
|
||
# 9. body block: translate the loop body and branch to incr
|
||
mlirlite.position_at_end(builder, body_bb)
|
||
|
||
# save the old loop context and set break/continue targets
|
||
old_break: mlirlite.BasicBlock* = trans._break_bb
|
||
old_continue: mlirlite.BasicBlock* = trans._continue_bb
|
||
trans._break_bb = end_bb
|
||
trans._continue_bb = incr_bb
|
||
|
||
body: list[ast.AST*]* = for_node.children
|
||
if body is not None:
|
||
body_count: sizet = len(body)
|
||
for bi in range(int(body_count)):
|
||
stmt: ast.AST* = body.get(bi)
|
||
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
|
||
|
||
# restore the old loop context
|
||
trans._break_bb = old_break
|
||
trans._continue_bb = old_continue
|
||
|
||
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, incr_bb)
|
||
|
||
# 10. incr block: i = i + step, branch back to cond
|
||
mlirlite.position_at_end(builder, incr_bb)
|
||
cur_i2: mlirlite.Value* = mlirlite.build_load(builder, loop_var_ty, var_alloca)
|
||
# align types: step_val may be i32/i64; align it to loop_var_ty to match cur_i2
|
||
incr_step: mlirlite.Value* = step_val
|
||
if step_val is not None and step_val.Ty is not None:
|
||
step_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(step_val.Ty)
|
||
cur2_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(cur_i2.Ty)
|
||
if step_bits != 0 and cur2_bits != 0 and step_bits != cur2_bits:
|
||
if step_bits > cur2_bits: incr_step = mlirlite.build_trunc(builder, step_val, loop_var_ty)
|
||
else: incr_step = mlirlite.build_sext(builder, step_val, loop_var_ty)
|
||
next_i: mlirlite.Value* = mlirlite.build_add(builder, cur_i2, incr_step)
|
||
mlirlite.build_store(builder, next_i, var_alloca)
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# 11. loop-else tail (run orelse when exhausted), then position at end
|
||
ret_for: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_for, end_bb)
|
||
mlirlite.position_at_end(builder, end_bb)
|
||
# append90: for loops branch to end_bb on normal exit → merge always reachable (normal tail)
|
||
trans._ctrl_merge_reachable = 1
|
||
return new_vars
|
||
|
||
# multi-var iteration helpers (enumerate / zip, migrated from ForHandleBase): sources may be
|
||
# range(start, stop, step) / string (const or var) / list[T]; zip stops when either source ends.
|
||
def _extract_tuple_names(self, tup: ast.Tuple*, out1: str, out2: str) -> int:
|
||
"""Extract the first two Name targets of a Tuple into out1/out2, returning 1 on success."""
|
||
if tup is None or tup.elts is None or out1 is None or out2 is None: return 0
|
||
elts: list[ast.AST*]* = tup.elts
|
||
n: sizet = len(elts)
|
||
if n < 2: return 0
|
||
e0: ast.AST* = elts.get(0)
|
||
e1: ast.AST* = elts.get(1)
|
||
if e0 is None or e1 is None: return 0
|
||
if e0.kind() != ast.ASTKind.Name or e1.kind() != ast.ASTKind.Name: return 0
|
||
n0: ast.Name* = ast.Name*(e0)
|
||
n1: ast.Name* = ast.Name*(e1)
|
||
if n0.id is None or n1.id is None: return 0
|
||
string.strcpy(out1, n0.id)
|
||
string.strcpy(out2, n1.id)
|
||
return 1
|
||
|
||
def _seq_kind(self, trans: HT.Translator*, src: ast.AST*) -> int:
|
||
"""Classify the iteration source as range, string, or list; return 0 if unsupported."""
|
||
if src is None: return 0
|
||
k: int = src.kind()
|
||
if k == ast.ASTKind.Call:
|
||
cl_s: ast.Call* = ast.Call*(src)
|
||
fn_s: str = self.Gen.ExprGenBase.get_func_name(cl_s.func)
|
||
if fn_s is not None and string.strcmp(fn_s, "range") == 0: return GenTypes.SEQ_KIND_RANGE
|
||
return 0
|
||
if k == ast.ASTKind.Constant:
|
||
cn_s: ast.Constant* = ast.Constant*(src)
|
||
if cn_s is not None and cn_s.const_kind == ast.CONST_STR: return GenTypes.SEQ_KIND_STR
|
||
return 0
|
||
if k == ast.ASTKind.Name:
|
||
nm_s: ast.Name* = ast.Name*(src)
|
||
if nm_s.id is not None and trans is not None:
|
||
ve_s: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_s.id)
|
||
sq_annot: str = self.Gen.VarGen.var_annot_class_name(ve_s)
|
||
if sq_annot is not None:
|
||
# [S4] exact base-name compare (strstr("list") wrongly matched playlist etc.)
|
||
if TSpec.SpecTypeNameBaseIs(sq_annot, "list") != 0: return GenTypes.SEQ_KIND_LIST
|
||
return GenTypes.SEQ_KIND_STR
|
||
return 0
|
||
|
||
# _range_work_bits - range 工作位宽:取各已求值参数位宽的最大值,下限 i32(32)。
|
||
# 与 `for i in range(...)` 的 loop_store_ty 同口径,避免内部 i64 索引写回 i32 循环变量时误报窄化。
|
||
def _range_work_bits(self, a: mlirlite.Value*, b: mlirlite.Value*, c: mlirlite.Value*) -> int:
|
||
"""Working integer width for a range() source: max of the provided argument widths, at least 32."""
|
||
bits: int = 32
|
||
if a is not None and a.Ty is not None:
|
||
ab: int = self.Gen.ExprGenBase.get_llvm_type_bits(a.Ty)
|
||
if ab > bits: bits = ab
|
||
if b is not None and b.Ty is not None:
|
||
bb: int = self.Gen.ExprGenBase.get_llvm_type_bits(b.Ty)
|
||
if bb > bits: bits = bb
|
||
if c is not None and c.Ty is not None:
|
||
cb: int = self.Gen.ExprGenBase.get_llvm_type_bits(c.Ty)
|
||
if cb > bits: bits = cb
|
||
return bits
|
||
|
||
def _build_seq_loop(self, trans: HT.Translator*, for_node: ast.For*, t1: str, t2: str, src1: ast.AST*, src2: ast.AST*, n_srcs: int) -> int:
|
||
"""Build a sequence iteration loop for enumerate (n_srcs=1) or zip (n_srcs=2)."""
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
func: mlirlite.Function* = trans._cur_func
|
||
|
||
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
||
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
|
||
|
||
# ---- source 1 setup ----
|
||
kind1: int = self._seq_kind(trans, src1)
|
||
if kind1 == 0: self.Gen.TypeGenErr.fatal_error(for_node, "sequence iteration: source 1 type is not supported")
|
||
r1_start: mlirlite.Value* = None
|
||
r1_stop: mlirlite.Value* = None
|
||
r1_step: mlirlite.Value* = None
|
||
s1_ptr: mlirlite.Value* = None
|
||
l1_count: mlirlite.Value* = None
|
||
l1_data: mlirlite.Value* = None
|
||
l1_esize: mlirlite.Value* = None
|
||
elem1_ty: mlirlite.LLVMType* = i8_ptr_ty
|
||
# range 的工作位宽/类型(随参数位宽,仿 `for i in range(...)`);非 range 源不使用。
|
||
r1_wty: mlirlite.LLVMType* = None
|
||
r1_bits: int = 0
|
||
if kind1 == GenTypes.SEQ_KIND_RANGE:
|
||
# 先求值参数,再由参数位宽决定工作类型:i32 字面量就按 i32 算,
|
||
# 避免内部 i64 索引写回 i32 循环变量时触发隐式窄化误报。
|
||
call_src1: ast.Call* = ast.Call*(src1)
|
||
cargs1: list[ast.AST*]* = call_src1.args
|
||
a1: mlirlite.Value* = None
|
||
b1: mlirlite.Value* = None
|
||
c1: mlirlite.Value* = None
|
||
n1: sizet = 0
|
||
if cargs1 is not None:
|
||
n1 = len(cargs1)
|
||
if n1 >= 1:
|
||
a1 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs1.get(0), trans._funcs, trans._func_count, trans)
|
||
if a1 is None:
|
||
# fail-fast: range argument failed to translate
|
||
self.Gen.TypeGenErr.fatal_error(cargs1.get(0), "failed to translate the range argument", "TypeError")
|
||
if n1 >= 2:
|
||
b1 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs1.get(1), trans._funcs, trans._func_count, trans)
|
||
if b1 is None:
|
||
# fail-fast: range argument failed to translate
|
||
self.Gen.TypeGenErr.fatal_error(cargs1.get(1), "failed to translate the range argument", "TypeError")
|
||
if n1 >= 3:
|
||
c1 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs1.get(2), trans._funcs, trans._func_count, trans)
|
||
if c1 is None:
|
||
# fail-fast: range argument failed to translate
|
||
self.Gen.TypeGenErr.fatal_error(cargs1.get(2), "failed to translate the range argument", "TypeError")
|
||
r1_bits = self._range_work_bits(a1, b1, c1)
|
||
r1_wty = self.Gen.ExprGenBase._int_type_of_bits(pool, r1_bits)
|
||
if r1_wty is None: r1_wty = i32_ty
|
||
elem1_ty = r1_wty
|
||
r1_start = mlirlite.const_int(pool, r1_bits, 0)
|
||
r1_step = mlirlite.const_int(pool, r1_bits, 1)
|
||
r1_stop = mlirlite.const_int(pool, r1_bits, 0)
|
||
if a1 is not None: r1_stop = self.Gen.ExprGenBase.coerce_to_type(builder, a1, r1_wty, cargs1.get(0))
|
||
if b1 is not None: r1_start = self.Gen.ExprGenBase.coerce_to_type(builder, b1, r1_wty, cargs1.get(1))
|
||
if c1 is not None: r1_step = self.Gen.ExprGenBase.coerce_to_type(builder, c1, r1_wty, cargs1.get(2))
|
||
elif kind1 == GenTypes.SEQ_KIND_STR:
|
||
elem1_ty = i8_ty
|
||
s1_ptr = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, src1, trans._funcs, trans._func_count, trans)
|
||
if s1_ptr is None: self.Gen.TypeGenErr.fatal_error(for_node, "sequence iteration: failed to translate the string source")
|
||
elif kind1 == GenTypes.SEQ_KIND_LIST:
|
||
elem1_ty = i8_ptr_ty
|
||
l1_obj: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, src1, trans._funcs, trans._func_count, trans)
|
||
if l1_obj is None or l1_obj.Ty is None or self.Gen.ExprGenBase.is_ptr_type(l1_obj.Ty) == 0: self.Gen.TypeGenErr.fatal_error(for_node, "sequence iteration: list source is not a pointer type")
|
||
l1_count = mlirlite.build_load(builder, i64_ty, mlirlite.build_gep(builder, i64_ty, l1_obj, mlirlite.const_int64(pool, 1)))
|
||
l1_data = mlirlite.build_load(builder, i8_ptr_ty, mlirlite.build_gep(builder, i64_ty, l1_obj, mlirlite.const_int64(pool, 0)))
|
||
l1_esize = mlirlite.build_load(builder, i64_ty, mlirlite.build_gep(builder, i64_ty, l1_obj, mlirlite.const_int64(pool, 4)))
|
||
|
||
# ---- source 2 setup (zip) ----
|
||
kind2: int = 0
|
||
elem2_ty: mlirlite.LLVMType* = elem1_ty
|
||
s2_ptr: mlirlite.Value* = None
|
||
l2_count: mlirlite.Value* = None
|
||
l2_data: mlirlite.Value* = None
|
||
l2_esize: mlirlite.Value* = None
|
||
r2_start: mlirlite.Value* = None
|
||
r2_stop: mlirlite.Value* = None
|
||
r2_step: mlirlite.Value* = None
|
||
# range 的工作位宽/类型(source 2 对称处理)。
|
||
r2_wty: mlirlite.LLVMType* = None
|
||
r2_bits: int = 0
|
||
if n_srcs >= 2:
|
||
kind2 = self._seq_kind(trans, src2)
|
||
if kind2 == 0: self.Gen.TypeGenErr.fatal_error(for_node, "sequence iteration: source 2 type is not supported")
|
||
if kind2 == GenTypes.SEQ_KIND_RANGE:
|
||
# 与 source 1 同口径:工作类型随参数位宽,避免 i64 索引写回窄位宽循环变量误报。
|
||
call_src2: ast.Call* = ast.Call*(src2)
|
||
cargs2: list[ast.AST*]* = call_src2.args
|
||
a2: mlirlite.Value* = None
|
||
b2: mlirlite.Value* = None
|
||
c2: mlirlite.Value* = None
|
||
n2: sizet = 0
|
||
if cargs2 is not None:
|
||
n2 = len(cargs2)
|
||
if n2 >= 1:
|
||
a2 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs2.get(0), trans._funcs, trans._func_count, trans)
|
||
if a2 is None:
|
||
# fail-fast: range argument failed to translate
|
||
self.Gen.TypeGenErr.fatal_error(cargs2.get(0), "failed to translate the range argument", "TypeError")
|
||
if n2 >= 2:
|
||
b2 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs2.get(1), trans._funcs, trans._func_count, trans)
|
||
if b2 is None:
|
||
# fail-fast: range argument failed to translate
|
||
self.Gen.TypeGenErr.fatal_error(cargs2.get(1), "failed to translate the range argument", "TypeError")
|
||
if n2 >= 3:
|
||
c2 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, cargs2.get(2), trans._funcs, trans._func_count, trans)
|
||
if c2 is None:
|
||
# fail-fast: range argument failed to translate
|
||
self.Gen.TypeGenErr.fatal_error(cargs2.get(2), "failed to translate the range argument", "TypeError")
|
||
r2_bits = self._range_work_bits(a2, b2, c2)
|
||
r2_wty = self.Gen.ExprGenBase._int_type_of_bits(pool, r2_bits)
|
||
if r2_wty is None: r2_wty = i32_ty
|
||
elem2_ty = r2_wty
|
||
r2_start = mlirlite.const_int(pool, r2_bits, 0)
|
||
r2_step = mlirlite.const_int(pool, r2_bits, 1)
|
||
r2_stop = mlirlite.const_int(pool, r2_bits, 0)
|
||
if a2 is not None: r2_stop = self.Gen.ExprGenBase.coerce_to_type(builder, a2, r2_wty, cargs2.get(0))
|
||
if b2 is not None: r2_start = self.Gen.ExprGenBase.coerce_to_type(builder, b2, r2_wty, cargs2.get(1))
|
||
if c2 is not None: r2_step = self.Gen.ExprGenBase.coerce_to_type(builder, c2, r2_wty, cargs2.get(2))
|
||
elif kind2 == GenTypes.SEQ_KIND_STR:
|
||
elem2_ty = i8_ty
|
||
s2_ptr = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, src2, trans._funcs, trans._func_count, trans)
|
||
if s2_ptr is None: self.Gen.TypeGenErr.fatal_error(for_node, "sequence iteration: failed to translate string source 2")
|
||
elif kind2 == GenTypes.SEQ_KIND_LIST:
|
||
elem2_ty = i8_ptr_ty
|
||
l2_obj: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, src2, trans._funcs, trans._func_count, trans)
|
||
if l2_obj is None or l2_obj.Ty is None or self.Gen.ExprGenBase.is_ptr_type(l2_obj.Ty) == 0: self.Gen.TypeGenErr.fatal_error(for_node, "sequence iteration: list source 2 is not a pointer type")
|
||
l2_count = mlirlite.build_load(builder, i64_ty, mlirlite.build_gep(builder, i64_ty, l2_obj, mlirlite.const_int64(pool, 1)))
|
||
l2_data = mlirlite.build_load(builder, i8_ptr_ty, mlirlite.build_gep(builder, i64_ty, l2_obj, mlirlite.const_int64(pool, 0)))
|
||
l2_esize = mlirlite.build_load(builder, i64_ty, mlirlite.build_gep(builder, i64_ty, l2_obj, mlirlite.const_int64(pool, 4)))
|
||
|
||
# ---- loop-var allocas ----
|
||
# t1: enumerate → index (i32); zip → source-1 element
|
||
t1_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, t1)
|
||
new_vars: int = 0
|
||
if t1_alloca is None:
|
||
if n_srcs == 1: t1_alloca = self.Gen.VarGen._alloca_at_entry(builder, i32_ty)
|
||
else: t1_alloca = self.Gen.VarGen._alloca_at_entry(builder, elem1_ty)
|
||
if t1_alloca is not None:
|
||
if self.Gen.VarGen.define_var(trans.SymTab, t1, t1_alloca) == 0: new_vars += 1
|
||
# t2: source-2 element (for enumerate = source-1 element)
|
||
t2_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, t2)
|
||
if t2_alloca is None:
|
||
t2_alloca = self.Gen.VarGen._alloca_at_entry(builder, elem2_ty)
|
||
if t2_alloca is not None:
|
||
if self.Gen.VarGen.define_var(trans.SymTab, t2, t2_alloca) == 0: new_vars += 1
|
||
|
||
# implicit index variable (i64)
|
||
idx_alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, i64_ty)
|
||
mlirlite.build_store(builder, mlirlite.const_int64(pool, 0), idx_alloca)
|
||
|
||
# basic blocks: cond / body / incr / end
|
||
cnt: int = trans._label_counter
|
||
trans._label_counter = cnt + 1
|
||
|
||
name_buf: str = pool.alloc(32)
|
||
viperlib.snprintf(name_buf, 32, "seq.cond.%d", cnt)
|
||
cond_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "seq.body.%d", cnt)
|
||
body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "seq.incr.%d", cnt)
|
||
incr_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "seq.end.%d", cnt)
|
||
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
# loop else: the exhausted edge (any source out of range/terminator) targets else_bb (else end_bb)
|
||
has_else_seq: int = self._LoopElseCount(for_node.orelse)
|
||
else_bb_seq: mlirlite.BasicBlock* = self._MakeLoopElseBlock(pool, func, name_buf, "seq", cnt, has_else_seq)
|
||
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# cond block: source-1 done check (zip also needs source 2 done)
|
||
mlirlite.position_at_end(builder, cond_bb)
|
||
cur_idx: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, idx_alloca)
|
||
done1: mlirlite.Value* = None
|
||
if kind1 == GenTypes.SEQ_KIND_RANGE:
|
||
# range 的索引按工作位宽截断后再比较(索引是编译器内部量,不涉及用户窄化)
|
||
idx1_cmp: mlirlite.Value* = cur_idx
|
||
if r1_bits != 64: idx1_cmp = mlirlite.build_trunc(builder, cur_idx, r1_wty)
|
||
done1 = mlirlite.build_icmp(builder, mlirlite.ICMP_SLT, idx1_cmp, r1_stop)
|
||
elif kind1 == GenTypes.SEQ_KIND_STR:
|
||
ch_ptr1: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, s1_ptr, cur_idx)
|
||
ch1: mlirlite.Value* = mlirlite.build_load(builder, i8_ty, ch_ptr1)
|
||
zero1: mlirlite.Value* = mlirlite.const_int(pool, 8, 0)
|
||
done1 = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, ch1, zero1)
|
||
elif kind1 == GenTypes.SEQ_KIND_LIST: done1 = mlirlite.build_icmp(builder, mlirlite.ICMP_SLT, cur_idx, l1_count)
|
||
cont: mlirlite.Value* = done1
|
||
if n_srcs >= 2:
|
||
done2: mlirlite.Value* = None
|
||
if kind2 == GenTypes.SEQ_KIND_RANGE:
|
||
idx2_cmp: mlirlite.Value* = cur_idx
|
||
if r2_bits != 64: idx2_cmp = mlirlite.build_trunc(builder, cur_idx, r2_wty)
|
||
done2 = mlirlite.build_icmp(builder, mlirlite.ICMP_SLT, idx2_cmp, r2_stop)
|
||
elif kind2 == GenTypes.SEQ_KIND_STR:
|
||
ch_ptr2: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, s2_ptr, cur_idx)
|
||
ch2: mlirlite.Value* = mlirlite.build_load(builder, i8_ty, ch_ptr2)
|
||
zero2: mlirlite.Value* = mlirlite.const_int(pool, 8, 0)
|
||
done2 = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, ch2, zero2)
|
||
elif kind2 == GenTypes.SEQ_KIND_LIST: done2 = mlirlite.build_icmp(builder, mlirlite.ICMP_SLT, cur_idx, l2_count)
|
||
cont = mlirlite.build_and(builder, done1, done2)
|
||
mlirlite.build_cond_br(builder, cont, body_bb, else_bb_seq if else_bb_seq is not None else end_bb)
|
||
|
||
# body block: fetch element → store → loop body
|
||
mlirlite.position_at_end(builder, body_bb)
|
||
# source-1 element
|
||
v1: mlirlite.Value* = None
|
||
if kind1 == GenTypes.SEQ_KIND_RANGE:
|
||
idx1_b: mlirlite.Value* = cur_idx
|
||
if r1_bits != 64: idx1_b = mlirlite.build_trunc(builder, cur_idx, r1_wty)
|
||
step1: mlirlite.Value* = mlirlite.build_mul(builder, idx1_b, r1_step)
|
||
v1 = mlirlite.build_add(builder, r1_start, step1)
|
||
elif kind1 == GenTypes.SEQ_KIND_STR:
|
||
ch_ptr1b: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, s1_ptr, cur_idx)
|
||
v1 = mlirlite.build_load(builder, i8_ty, ch_ptr1b)
|
||
elif kind1 == GenTypes.SEQ_KIND_LIST:
|
||
off1: mlirlite.Value* = mlirlite.build_mul(builder, cur_idx, l1_esize)
|
||
addr1: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, l1_data, off1)
|
||
v1 = mlirlite.build_load(builder, i8_ptr_ty, addr1)
|
||
# store t1 / t2
|
||
if t1_alloca is not None and t1_alloca.Ty is not None:
|
||
t1_ty: mlirlite.LLVMType* = t1_alloca.Ty.Pointee
|
||
if n_srcs == 1:
|
||
# enumerate: t1 = index (trunc i64 → i32)
|
||
idx32: mlirlite.Value* = mlirlite.build_trunc(builder, cur_idx, i32_ty)
|
||
mlirlite.build_store(builder, idx32, t1_alloca)
|
||
if t2_alloca is not None and t2_alloca.Ty is not None and v1 is not None:
|
||
t2_ty: mlirlite.LLVMType* = t2_alloca.Ty.Pointee
|
||
v1c: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, v1, t2_ty, src1)
|
||
mlirlite.build_store(builder, v1c, t2_alloca)
|
||
else:
|
||
if v1 is not None:
|
||
v1c: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, v1, t1_ty, src1)
|
||
mlirlite.build_store(builder, v1c, t1_alloca)
|
||
# source-2 element → t2
|
||
v2: mlirlite.Value* = None
|
||
if kind2 == GenTypes.SEQ_KIND_RANGE:
|
||
idx2_b: mlirlite.Value* = cur_idx
|
||
if r2_bits != 64: idx2_b = mlirlite.build_trunc(builder, cur_idx, r2_wty)
|
||
step2: mlirlite.Value* = mlirlite.build_mul(builder, idx2_b, r2_step)
|
||
v2 = mlirlite.build_add(builder, r2_start, step2)
|
||
elif kind2 == GenTypes.SEQ_KIND_STR:
|
||
ch_ptr2b: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, s2_ptr, cur_idx)
|
||
v2 = mlirlite.build_load(builder, i8_ty, ch_ptr2b)
|
||
elif kind2 == GenTypes.SEQ_KIND_LIST:
|
||
off2: mlirlite.Value* = mlirlite.build_mul(builder, cur_idx, l2_esize)
|
||
addr2: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, l2_data, off2)
|
||
v2 = mlirlite.build_load(builder, i8_ptr_ty, addr2)
|
||
if t2_alloca is not None and t2_alloca.Ty is not None and v2 is not None:
|
||
t2_ty2: mlirlite.LLVMType* = t2_alloca.Ty.Pointee
|
||
v2c: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, v2, t2_ty2, src2)
|
||
mlirlite.build_store(builder, v2c, t2_alloca)
|
||
|
||
# save the old loop context and set break/continue targets
|
||
old_break: mlirlite.BasicBlock* = trans._break_bb
|
||
old_continue: mlirlite.BasicBlock* = trans._continue_bb
|
||
trans._break_bb = end_bb
|
||
trans._continue_bb = incr_bb
|
||
|
||
body: list[ast.AST*]* = for_node.children
|
||
if body is not None:
|
||
body_count: sizet = len(body)
|
||
for bi in range(int(body_count)):
|
||
stmt: ast.AST* = body.get(bi)
|
||
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
|
||
|
||
trans._break_bb = old_break
|
||
trans._continue_bb = old_continue
|
||
|
||
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, incr_bb)
|
||
|
||
# incr block: index++
|
||
mlirlite.position_at_end(builder, incr_bb)
|
||
one_val: mlirlite.Value* = mlirlite.const_int64(pool, 1)
|
||
next_idx: mlirlite.Value* = mlirlite.build_add(builder, cur_idx, one_val)
|
||
mlirlite.build_store(builder, next_idx, idx_alloca)
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# loop-else tail (run orelse when exhausted), then position at end
|
||
ret_seq: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_seq, end_bb)
|
||
mlirlite.position_at_end(builder, end_bb)
|
||
# append90: for loops branch to end_bb on normal exit → merge always reachable (normal tail)
|
||
trans._ctrl_merge_reachable = 1
|
||
return new_vars
|
||
|
||
# _handle_ptr_iter - pointer iteration `for x in ptr:` (migrated from ForHandleIter): walk the
|
||
# pointer with an implicit index until the dereferenced element is empty (null terminator).
|
||
def _handle_ptr_iter(self, trans: HT.Translator*, for_node: ast.For*, var_name: str) -> int:
|
||
"""Iterate a pointer (for x in ptr) until the dereferenced element is empty."""
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
func: mlirlite.Function* = trans._cur_func
|
||
|
||
# translate the iterator expression to get the pointer value
|
||
ptr_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, for_node.iter, trans._funcs, trans._func_count, trans)
|
||
if ptr_val is None: self.Gen.TypeGenErr.fatal_error(for_node, "pointer iteration: failed to translate the iterator expression")
|
||
|
||
# check the iterator type
|
||
if self.Gen.ExprGenBase.is_ptr_type(ptr_val.Ty) == 0:
|
||
# array iteration: for k in arr (fixed length, elements of any type)
|
||
if self.Gen.ExprGenBase.is_array_type(ptr_val.Ty) != 0: return self._handle_array_iter(trans, for_node, var_name, ptr_val)
|
||
self.Gen.TypeGenErr.fatal_error(for_node, "pointer iteration: iterator is not a pointer type")
|
||
|
||
# get the element type
|
||
elem_ty: mlirlite.LLVMType* = ptr_val.Ty.Pointee
|
||
if elem_ty is None: self.Gen.TypeGenErr.fatal_error(for_node, "pointer iteration: failed to get the element type")
|
||
|
||
# element type is an array (Ptr([N x T]), e.g. a decayed t.CArray): load it and iterate by length (no null terminator)
|
||
if self.Gen.ExprGenBase.is_array_type(elem_ty) != 0:
|
||
arr_v2: mlirlite.Value* = mlirlite.build_load(builder, elem_ty, ptr_val)
|
||
if arr_v2 is not None: return self._handle_array_iter(trans, for_node, var_name, arr_v2)
|
||
|
||
# the element type decides the "empty" test (stop when the dereferenced value is empty):
|
||
# integer → ==0; pointer → ==null; float → ==0.0
|
||
elem_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(elem_ty)
|
||
elem_is_ptr2: int = self.Gen.ExprGenBase.is_ptr_type(elem_ty)
|
||
elem_fbits2: int = self.Gen.ExprGenBase.get_llvm_float_bits(elem_ty)
|
||
if elem_bits == 0 and elem_is_ptr2 == 0 and elem_fbits2 == 0: self.Gen.TypeGenErr.fatal_error(for_node, "pointer iteration: element type is not integer/pointer/float; cannot check the null terminator")
|
||
|
||
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
|
||
# create the loop-var alloca (stores the element value)
|
||
var_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, var_name)
|
||
new_vars: int = 0
|
||
if var_alloca is None:
|
||
var_alloca = self.Gen.VarGen._alloca_at_entry(builder, elem_ty)
|
||
if self.Gen.VarGen.define_var(trans.SymTab, var_name, var_alloca) == 0: new_vars = 1
|
||
|
||
# create the implicit index variable, init 0
|
||
idx_alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, i32_ty)
|
||
mlirlite.build_store(builder, mlirlite.const_int32(pool, 0), idx_alloca)
|
||
|
||
# create blocks cond/body/incr/end
|
||
cnt: int = trans._label_counter
|
||
trans._label_counter = cnt + 1
|
||
|
||
name_buf: str = pool.alloc(32)
|
||
viperlib.snprintf(name_buf, 32, "ptr.cond.%d", cnt)
|
||
cond_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "ptr.body.%d", cnt)
|
||
body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "ptr.incr.%d", cnt)
|
||
incr_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "ptr.end.%d", cnt)
|
||
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
# loop else: the terminating-element (empty deref) edge targets else_bb (else end_bb)
|
||
has_else_ptr: int = self._LoopElseCount(for_node.orelse)
|
||
else_bb_ptr: mlirlite.BasicBlock* = self._MakeLoopElseBlock(pool, func, name_buf, "ptr", cnt, has_else_ptr)
|
||
|
||
# branch to cond
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# cond block: load index, GEP, load elem, empty check (int==0 / ptr==null / float==0.0)
|
||
mlirlite.position_at_end(builder, cond_bb)
|
||
cur_idx: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, idx_alloca)
|
||
elem_ptr: mlirlite.Value* = mlirlite.build_gep(builder, elem_ty, ptr_val, cur_idx)
|
||
cur_elem: mlirlite.Value* = mlirlite.build_load(builder, elem_ty, elem_ptr)
|
||
is_null: mlirlite.Value* = None
|
||
if elem_is_ptr2 != 0:
|
||
null_p: mlirlite.Value* = mlirlite.ConstNull(pool, elem_ty, "null")
|
||
if null_p is not None: is_null = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, cur_elem, null_p)
|
||
elif elem_fbits2 != 0:
|
||
zero_f2: mlirlite.Value* = mlirlite.ConstFloat(pool, elem_ty, 0.0)
|
||
if zero_f2 is not None: is_null = mlirlite.build_fcmp(builder, mlirlite.FCMP_OEQ, cur_elem, zero_f2)
|
||
else:
|
||
null_val: mlirlite.Value* = mlirlite.const_int(pool, elem_bits, 0)
|
||
is_null = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, cur_elem, null_val)
|
||
if is_null is None: is_null = mlirlite.const_int(pool, 1, 0)
|
||
mlirlite.build_cond_br(builder, is_null, else_bb_ptr if else_bb_ptr is not None else end_bb, body_bb)
|
||
|
||
# body block: store elem to var, translate loop body
|
||
mlirlite.position_at_end(builder, body_bb)
|
||
mlirlite.build_store(builder, cur_elem, var_alloca)
|
||
|
||
# save the old loop context and set break/continue targets
|
||
old_break: mlirlite.BasicBlock* = trans._break_bb
|
||
old_continue: mlirlite.BasicBlock* = trans._continue_bb
|
||
trans._break_bb = end_bb
|
||
trans._continue_bb = incr_bb
|
||
|
||
body: list[ast.AST*]* = for_node.children
|
||
if body is not None:
|
||
body_count: sizet = len(body)
|
||
for bi in range(int(body_count)):
|
||
stmt: ast.AST* = body.get(bi)
|
||
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
|
||
|
||
# restore the old loop context
|
||
trans._break_bb = old_break
|
||
trans._continue_bb = old_continue
|
||
|
||
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, incr_bb)
|
||
|
||
# incr block: index++, br cond
|
||
mlirlite.position_at_end(builder, incr_bb)
|
||
one_val: mlirlite.Value* = mlirlite.const_int32(pool, 1)
|
||
next_idx: mlirlite.Value* = mlirlite.build_add(builder, cur_idx, one_val)
|
||
mlirlite.build_store(builder, next_idx, idx_alloca)
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# loop-else tail (run orelse when exhausted), then position at end
|
||
ret_ptr: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_ptr, end_bb)
|
||
mlirlite.position_at_end(builder, end_bb)
|
||
# append90: for loops branch to end_bb on normal exit → merge always reachable (normal tail)
|
||
trans._ctrl_merge_reachable = 1
|
||
return new_vars
|
||
|
||
# _handle_array_iter - array iteration `for x in arr:` (migrated from ForHandleIter): iterate a
|
||
# fixed-length array by length (elements of any type, no null-terminator check).
|
||
def _handle_array_iter(self, trans: HT.Translator*, for_node: ast.For*, var_name: str, array_val: mlirlite.Value*) -> int:
|
||
"""Iterate a fixed-length array (for x in arr) by its length."""
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
func: mlirlite.Function* = trans._cur_func
|
||
|
||
# parse the array type [N x elem_ty]
|
||
arr_ty: mlirlite.LLVMType* = array_val.Ty
|
||
elem_ty: mlirlite.LLVMType* = None
|
||
elem_count: int = 0
|
||
match arr_ty:
|
||
case mlirlite.LLVMType.Array(et, n):
|
||
elem_ty = et
|
||
elem_count = n
|
||
if elem_ty is None or elem_count <= 0: self.Gen.TypeGenErr.fatal_error(for_node, "array iteration: failed to resolve the array type")
|
||
|
||
# array value → temp alloca (arrays are not pointers, copy then GEP-iterate); it must live
|
||
# in the entry block, else LLVM treats it as dynamic alloca and deep stacks can fault.
|
||
arr_tmp: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, arr_ty)
|
||
if arr_tmp is None: self.Gen.TypeGenErr.fatal_error(for_node, "array iteration: failed to allocate temporary storage")
|
||
mlirlite.build_store(builder, array_val, arr_tmp)
|
||
|
||
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
|
||
# loop-var alloca (stores the element value)
|
||
var_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, var_name)
|
||
new_vars: int = 0
|
||
if var_alloca is None:
|
||
var_alloca = self.Gen.VarGen._alloca_at_entry(builder, elem_ty)
|
||
if self.Gen.VarGen.define_var(trans.SymTab, var_name, var_alloca) == 0: new_vars = 1
|
||
|
||
# implicit index variable, init 0
|
||
idx_alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, i32_ty)
|
||
mlirlite.build_store(builder, mlirlite.const_int32(pool, 0), idx_alloca)
|
||
|
||
# basic blocks: cond / body / incr / end
|
||
cnt: int = trans._label_counter
|
||
trans._label_counter = cnt + 1
|
||
|
||
name_buf: str = pool.alloc(32)
|
||
viperlib.snprintf(name_buf, 32, "arr.cond.%d", cnt)
|
||
cond_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "arr.elem.%d", cnt)
|
||
elem_check_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "arr.body.%d", cnt)
|
||
body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "arr.incr.%d", cnt)
|
||
incr_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "arr.end.%d", cnt)
|
||
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
# loop else: out-of-range or terminator (natural end) edge targets else_bb (else end_bb)
|
||
has_else_arr: int = self._LoopElseCount(for_node.orelse)
|
||
else_bb_arr: mlirlite.BasicBlock* = self._MakeLoopElseBlock(pool, func, name_buf, "arr", cnt, has_else_arr)
|
||
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# cond block: bounds check first (idx < count; out of range must not GEP+load to test empty)
|
||
mlirlite.position_at_end(builder, cond_bb)
|
||
cur_idx: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, idx_alloca)
|
||
count_val: mlirlite.Value* = mlirlite.const_int32(pool, elem_count)
|
||
in_bounds: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_SLT, cur_idx, count_val)
|
||
mlirlite.build_cond_br(builder, in_bounds, elem_check_bb, else_bb_arr if else_bb_arr is not None else end_bb)
|
||
|
||
# elem_check block: GEP(0, idx) → load elem → empty check (NULL terminator). Integer/pointer/
|
||
# float elements stop early when empty, bounded by length; aggregate elements use length only.
|
||
mlirlite.position_at_end(builder, elem_check_bb)
|
||
elem_ptr: mlirlite.Value* = mlirlite.build_gep_array(builder, arr_ty, elem_ty, arr_tmp, cur_idx)
|
||
cur_elem: mlirlite.Value* = mlirlite.build_load(builder, elem_ty, elem_ptr)
|
||
elem_bits_a: int = self.Gen.ExprGenBase.get_llvm_type_bits(elem_ty)
|
||
elem_ptr_a: int = self.Gen.ExprGenBase.is_ptr_type(elem_ty)
|
||
elem_fbits_a: int = self.Gen.ExprGenBase.get_llvm_float_bits(elem_ty)
|
||
if elem_bits_a != 0 or elem_ptr_a != 0 or elem_fbits_a != 0:
|
||
is_empty_a: mlirlite.Value* = None
|
||
if elem_ptr_a != 0:
|
||
null_ap: mlirlite.Value* = mlirlite.ConstNull(pool, elem_ty, "null")
|
||
if null_ap is not None: is_empty_a = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, cur_elem, null_ap)
|
||
elif elem_fbits_a != 0:
|
||
zero_af: mlirlite.Value* = mlirlite.ConstFloat(pool, elem_ty, 0.0)
|
||
if zero_af is not None: is_empty_a = mlirlite.build_fcmp(builder, mlirlite.FCMP_OEQ, cur_elem, zero_af)
|
||
else:
|
||
zero_ai: mlirlite.Value* = mlirlite.const_int(pool, elem_bits_a, 0)
|
||
is_empty_a = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, cur_elem, zero_ai)
|
||
if is_empty_a is not None: mlirlite.build_cond_br(builder, is_empty_a, else_bb_arr if else_bb_arr is not None else end_bb, body_bb)
|
||
else: mlirlite.build_br(builder, body_bb)
|
||
else: mlirlite.build_br(builder, body_bb)
|
||
|
||
# body block: store elem to var → loop body
|
||
mlirlite.position_at_end(builder, body_bb)
|
||
mlirlite.build_store(builder, cur_elem, var_alloca)
|
||
|
||
old_break: mlirlite.BasicBlock* = trans._break_bb
|
||
old_continue: mlirlite.BasicBlock* = trans._continue_bb
|
||
trans._break_bb = end_bb
|
||
trans._continue_bb = incr_bb
|
||
|
||
body: list[ast.AST*]* = for_node.children
|
||
if body is not None:
|
||
body_count: sizet = len(body)
|
||
for bi in range(int(body_count)):
|
||
stmt: ast.AST* = body.get(bi)
|
||
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
|
||
|
||
trans._break_bb = old_break
|
||
trans._continue_bb = old_continue
|
||
|
||
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, incr_bb)
|
||
|
||
# incr block: index++
|
||
mlirlite.position_at_end(builder, incr_bb)
|
||
one_val: mlirlite.Value* = mlirlite.const_int32(pool, 1)
|
||
next_idx: mlirlite.Value* = mlirlite.build_add(builder, cur_idx, one_val)
|
||
mlirlite.build_store(builder, next_idx, idx_alloca)
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# loop-else tail (run orelse when exhausted), then position at end
|
||
ret_arr: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_arr, end_bb)
|
||
mlirlite.position_at_end(builder, end_bb)
|
||
# append90: for loops branch to end_bb on normal exit → merge always reachable (normal tail)
|
||
trans._ctrl_merge_reachable = 1
|
||
return new_vars
|
||
|
||
# _handle_list_literal_iter - list-literal iteration `for x in [a, b, c]:` builds a fixed-length
|
||
# array value (element type from the first element) and reuses array iteration.
|
||
def _handle_list_literal_iter(self, trans: HT.Translator*, for_node: ast.For*, var_name: str, list_node: ast.AST*) -> int:
|
||
"""Iterate a list literal (for x in [a, b, c]) in element order."""
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
|
||
lst: ast.List* = ast.List*(list_node)
|
||
if lst is None or lst.elts is None: self.Gen.TypeGenErr.fatal_error(for_node, "list literal iteration: empty list")
|
||
elts: list[ast.AST*]* = lst.elts
|
||
elts_count: sizet = len(elts)
|
||
if elts_count <= 0: self.Gen.TypeGenErr.fatal_error(for_node, "list literal iteration: empty list")
|
||
|
||
# translate the first element to determine the element type
|
||
first_el: ast.AST* = elts.get(0)
|
||
# explicit null check (repo convention: pointer null tests always use `is None`): a list
|
||
# element count above the actual fill means a bare 0 slot, i.e. an empty AST element upstream.
|
||
if first_el is None:
|
||
self.Gen.TypeGenErr.fatal_error(for_node, f"list literal iteration encountered an empty AST element: element 0 is a bare null pointer (0), " f"but the element count is {elts_count} (element and count mismatch); something upstream produced an empty AST element, compilation aborted.", "ValueError")
|
||
# validate the object (vptr non-zero, see AssignGenMixin.vp:1174-1183): for @t.CVTable class
|
||
# objects the first word is the vptr; if 0, any later virtual call reads address 0.
|
||
if *((uint64*)(first_el)) == 0:
|
||
self.Gen.TypeGenErr.fatal_error(for_node, f"list literal iteration encountered an [INVALID OBJECT] AST element (vptr is 0): element 0 pointer is nonzero" f"(address {uint64(first_el)}) but the first word of its object (vtable pointer) is 0, element count {elts_count};" f"something upstream produced an [uninitialized vptr AST node] (typical source: a deep copy that moved fields but not the vptr), compilation aborted.", "ValueError")
|
||
first_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, first_el, trans._funcs, trans._func_count, trans)
|
||
if first_val is None or first_val.Ty is None: self.Gen.TypeGenErr.fatal_error(first_el, "list literal iteration: failed to determine the element type")
|
||
elem_ty: mlirlite.LLVMType* = first_val.Ty
|
||
|
||
# build an [N x elem_ty] array value: write elements into a temp alloca then load it; this
|
||
# too must live in the entry block to avoid a dynamic alloca (see _handle_array_iter).
|
||
arr_ty: mlirlite.LLVMType* = mlirlite.Array(pool, elem_ty, elts_count)
|
||
tmp_alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, arr_ty)
|
||
if tmp_alloca is None: self.Gen.TypeGenErr.fatal_error(for_node, "list literal iteration: failed to allocate the temporary array")
|
||
first_ptr: mlirlite.Value* = mlirlite.build_gep_array(builder, arr_ty, elem_ty, tmp_alloca, mlirlite.const_int32(pool, 0))
|
||
if first_ptr is not None: mlirlite.build_store(builder, first_val, first_ptr)
|
||
# write the remaining elements (coerce to elem_ty)
|
||
i: sizet = 1
|
||
while i < elts_count:
|
||
el_node: ast.AST* = elts.get(i)
|
||
# explicit null check (as above, `is None`): never let the translate_value guard swallow it
|
||
if el_node is None:
|
||
self.Gen.TypeGenErr.fatal_error(for_node, f"list literal iteration encountered an empty AST element: element {i} is a bare null pointer (0), " f"but the element count is {elts_count} (element and count mismatch); something upstream produced an empty AST element, compilation aborted.", "ValueError")
|
||
# object validity check (vptr non-zero), same as element 0: same "take element then use" path
|
||
if *((uint64*)(el_node)) == 0:
|
||
self.Gen.TypeGenErr.fatal_error(for_node, f"list literal iteration encountered an [INVALID OBJECT] AST element (vptr is 0): element {i} pointer is nonzero" f"(address {uint64(el_node)}) but the first word of its object (vtable pointer) is 0, element count {elts_count};" f"something upstream produced an [uninitialized vptr AST node] (typical source: a deep copy that moved fields but not the vptr), compilation aborted.", "ValueError")
|
||
el_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, el_node, trans._funcs, trans._func_count, trans)
|
||
if el_val is not None:
|
||
el_val = self.Gen.ExprGenBase.coerce_to_type(builder, el_val, elem_ty, el_node)
|
||
el_ptr: mlirlite.Value* = mlirlite.build_gep_array(builder, arr_ty, elem_ty, tmp_alloca, mlirlite.const_int32(pool, i))
|
||
if el_ptr is not None: mlirlite.build_store(builder, el_val, el_ptr)
|
||
i += 1
|
||
# load the array value back and reuse array iteration
|
||
arr_val: mlirlite.Value* = mlirlite.build_load(builder, arr_ty, tmp_alloca)
|
||
if arr_val is None: self.Gen.TypeGenErr.fatal_error(for_node, "list literal iteration: failed to load the array")
|
||
return self._handle_array_iter(trans, for_node, var_name, arr_val)
|
||
|
||
# _handle_list_iter - list[T] container iteration `for v in lst:` (migrated from ForHandleIter):
|
||
# iterate by dynamic __count__ (layout __data__ @0, __count__ @8, __elem_size__ @32).
|
||
def _handle_list_iter(self, trans: HT.Translator*, for_node: ast.For*, var_name: str, list_val: mlirlite.Value*) -> int:
|
||
"""Iterate a list[T] container (for v in lst) by its dynamic __count__ length."""
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
func: mlirlite.Function* = trans._cur_func
|
||
|
||
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
||
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
|
||
|
||
# read __count__ (offset 8, GEP index 1)
|
||
cnt_ptr: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, list_val, mlirlite.const_int64(pool, 1))
|
||
if cnt_ptr is None: self.Gen.TypeGenErr.fatal_error(for_node, "list iteration: failed to locate __count__")
|
||
count_val: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, cnt_ptr)
|
||
|
||
# loop-var alloca (element type i8*, matching inline list.get)
|
||
var_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, var_name)
|
||
new_vars: int = 0
|
||
if var_alloca is None:
|
||
var_alloca = self.Gen.VarGen._alloca_at_entry(builder, i8_ptr_ty)
|
||
if self.Gen.VarGen.define_var(trans.SymTab, var_name, var_alloca) == 0: new_vars = 1
|
||
|
||
# implicit index variable (i64, aligned with __count__)
|
||
idx_alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, i64_ty)
|
||
mlirlite.build_store(builder, mlirlite.const_int64(pool, 0), idx_alloca)
|
||
|
||
# basic blocks: cond / body / incr / end
|
||
cnt: int = trans._label_counter
|
||
trans._label_counter = cnt + 1
|
||
|
||
name_buf: str = pool.alloc(32)
|
||
viperlib.snprintf(name_buf, 32, "lst.cond.%d", cnt)
|
||
cond_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "lst.body.%d", cnt)
|
||
body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "lst.incr.%d", cnt)
|
||
incr_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "lst.end.%d", cnt)
|
||
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
# loop else: exhausted (idx out of range) edge targets else_bb (else end_bb)
|
||
has_else_lst: int = self._LoopElseCount(for_node.orelse)
|
||
else_bb_lst: mlirlite.BasicBlock* = self._MakeLoopElseBlock(pool, func, name_buf, "lst", cnt, has_else_lst)
|
||
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# cond block: load index, icmp slt idx, count
|
||
mlirlite.position_at_end(builder, cond_bb)
|
||
cur_idx: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, idx_alloca)
|
||
in_bounds: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_SLT, cur_idx, count_val)
|
||
mlirlite.build_cond_br(builder, in_bounds, body_bb, else_bb_lst if else_bb_lst is not None else end_bb)
|
||
|
||
# body block: __data__ + idx*__elem_size__ → load element → store to var
|
||
mlirlite.position_at_end(builder, body_bb)
|
||
dpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, list_val, mlirlite.const_int64(pool, 0))
|
||
data_ptr: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, dpp)
|
||
epp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, list_val, mlirlite.const_int64(pool, 4))
|
||
esize: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, epp)
|
||
offset: mlirlite.Value* = mlirlite.build_mul(builder, cur_idx, esize)
|
||
elem_addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, data_ptr, offset)
|
||
cur_elem: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, elem_addr)
|
||
mlirlite.build_store(builder, cur_elem, var_alloca)
|
||
|
||
# save the old loop context and set break/continue targets
|
||
old_break: mlirlite.BasicBlock* = trans._break_bb
|
||
old_continue: mlirlite.BasicBlock* = trans._continue_bb
|
||
trans._break_bb = end_bb
|
||
trans._continue_bb = incr_bb
|
||
|
||
body: list[ast.AST*]* = for_node.children
|
||
if body is not None:
|
||
body_count: sizet = len(body)
|
||
for bi in range(int(body_count)):
|
||
stmt: ast.AST* = body.get(bi)
|
||
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
|
||
|
||
trans._break_bb = old_break
|
||
trans._continue_bb = old_continue
|
||
|
||
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, incr_bb)
|
||
|
||
# incr block: index++
|
||
mlirlite.position_at_end(builder, incr_bb)
|
||
one_val: mlirlite.Value* = mlirlite.const_int64(pool, 1)
|
||
next_idx: mlirlite.Value* = mlirlite.build_add(builder, cur_idx, one_val)
|
||
mlirlite.build_store(builder, next_idx, idx_alloca)
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# loop-else tail (run orelse when exhausted), then position at end
|
||
ret_lst: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_lst, end_bb)
|
||
mlirlite.position_at_end(builder, end_bb)
|
||
# append90: for loops branch to end_bb on normal exit → merge always reachable (normal tail)
|
||
trans._ctrl_merge_reachable = 1
|
||
return new_vars
|
||
|
||
def _handle_dict_iter(self, trans: HT.Translator*, for_node: ast.For*, var_name: str, dict_val: mlirlite.Value*) -> int:
|
||
"""Iterate a dict container (for key in d) over its __keys__ up to __count__."""
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
func: mlirlite.Function* = trans._cur_func
|
||
|
||
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
||
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
|
||
|
||
# read __count__ (slot 2, byte offset 16)
|
||
cnt_ptr: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, dict_val, mlirlite.const_int64(pool, 2))
|
||
if cnt_ptr is None: self.Gen.TypeGenErr.fatal_error(for_node, "dict iteration: failed to locate __count__")
|
||
count_val: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, cnt_ptr)
|
||
|
||
# loop-var alloca (element type i8*, matching dict.__next__ returning str)
|
||
var_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, var_name)
|
||
new_vars: int = 0
|
||
if var_alloca is None:
|
||
var_alloca = self.Gen.VarGen._alloca_at_entry(builder, i8_ptr_ty)
|
||
if self.Gen.VarGen.define_var(trans.SymTab, var_name, var_alloca) == 0: new_vars = 1
|
||
|
||
# implicit index variable (i64, aligned with __count__)
|
||
idx_alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, i64_ty)
|
||
mlirlite.build_store(builder, mlirlite.const_int64(pool, 0), idx_alloca)
|
||
|
||
# basic blocks: cond / body / incr / end
|
||
cnt: int = trans._label_counter
|
||
trans._label_counter = cnt + 1
|
||
|
||
name_buf: str = pool.alloc(32)
|
||
viperlib.snprintf(name_buf, 32, "dict.cond.%d", cnt)
|
||
cond_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "dict.body.%d", cnt)
|
||
body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "dict.incr.%d", cnt)
|
||
incr_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
viperlib.snprintf(name_buf, 32, "dict.end.%d", cnt)
|
||
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
# loop else: exhausted (idx out of range) edge targets else_bb (else end_bb)
|
||
has_else_dict: int = self._LoopElseCount(for_node.orelse)
|
||
else_bb_dict: mlirlite.BasicBlock* = self._MakeLoopElseBlock(pool, func, name_buf, "dict", cnt, has_else_dict)
|
||
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# cond block: load index, icmp slt idx, count
|
||
mlirlite.position_at_end(builder, cond_bb)
|
||
cur_idx: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, idx_alloca)
|
||
in_bounds: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_SLT, cur_idx, count_val)
|
||
mlirlite.build_cond_br(builder, in_bounds, body_bb, else_bb_dict if else_bb_dict is not None else end_bb)
|
||
|
||
# body block: __keys__ + idx*8 → load key (i8*) → store to var
|
||
mlirlite.position_at_end(builder, body_bb)
|
||
kpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, dict_val, mlirlite.const_int64(pool, 0))
|
||
keys_ptr: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, kpp)
|
||
eight_val: mlirlite.Value* = mlirlite.const_int64(pool, 8)
|
||
key_offset: mlirlite.Value* = mlirlite.build_mul(builder, cur_idx, eight_val)
|
||
key_addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, keys_ptr, key_offset)
|
||
cur_key: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, key_addr)
|
||
mlirlite.build_store(builder, cur_key, var_alloca)
|
||
|
||
# save the old loop context and set break/continue targets
|
||
old_break: mlirlite.BasicBlock* = trans._break_bb
|
||
old_continue: mlirlite.BasicBlock* = trans._continue_bb
|
||
trans._break_bb = end_bb
|
||
trans._continue_bb = incr_bb
|
||
|
||
body: list[ast.AST*]* = for_node.children
|
||
if body is not None:
|
||
body_count: sizet = len(body)
|
||
for bi in range(int(body_count)):
|
||
stmt: ast.AST* = body.get(bi)
|
||
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
|
||
|
||
trans._break_bb = old_break
|
||
trans._continue_bb = old_continue
|
||
|
||
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, incr_bb)
|
||
|
||
# incr block: index++
|
||
mlirlite.position_at_end(builder, incr_bb)
|
||
one_val: mlirlite.Value* = mlirlite.const_int64(pool, 1)
|
||
next_idx: mlirlite.Value* = mlirlite.build_add(builder, cur_idx, one_val)
|
||
mlirlite.build_store(builder, next_idx, idx_alloca)
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# loop-else tail (run orelse when exhausted), then position at end
|
||
ret_dict: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_dict, end_bb)
|
||
mlirlite.position_at_end(builder, end_bb)
|
||
# append90: for loops branch to end_bb on normal exit → merge always reachable (normal tail)
|
||
trans._ctrl_merge_reachable = 1
|
||
return new_vars
|
||
|
||
# _handle_oop_iter - OOP iteration protocol `for x in obj:` (migrated from ForHandleIter):
|
||
# calls Class.__iter__/__next__; the hidden __stop_iter_flag__ (i1*) is set on StopIteration.
|
||
def _handle_oop_iter(self, trans: HT.Translator*, for_node: ast.For*, var_name: str, class_name: str, obj_val: mlirlite.Value*) -> int:
|
||
"""Iterate via the OOP protocol (for x in obj), calling __iter__/__next__."""
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
func: mlirlite.Function* = trans._cur_func
|
||
mod: mlirlite.LLVMModule* = trans.Module
|
||
|
||
if builder is None or func is None or mod is None: return 0
|
||
if obj_val is None or obj_val.Ty is None: self.Gen.TypeGenErr.fatal_error(for_node, "OOP iteration: the iteration source object type is unknown")
|
||
if self.Gen.ExprGenBase.is_ptr_type(obj_val.Ty) == 0: self.Gen.TypeGenErr.fatal_error(for_node, "OOP iteration: the iteration source object is not a pointer type")
|
||
|
||
# 1. find {Class}.__iter__ and call it to get the iterator
|
||
iter_lookup: str = pool.alloc(128)
|
||
if iter_lookup is None: return 0
|
||
viperlib.snprintf(iter_lookup, 128, "%s.__iter__", class_name)
|
||
iter_fn: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, iter_lookup)
|
||
if iter_fn is None: self.Gen.TypeGenErr.fatal_error(for_node, f"OOP iteration: class {class_name} has no __iter__ method")
|
||
iter_ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(iter_fn)
|
||
iter_fn_name: str = mlirlite.function_get_name(iter_fn)
|
||
if iter_ret_ty is None or iter_fn_name is None: return 0
|
||
# align the self arg to __iter__'s first param type (stub may declare self as i8*)
|
||
iter_self: mlirlite.Value* = obj_val
|
||
iter_first: mlirlite.Param* = mlirlite.function_get_param_head(iter_fn)
|
||
if iter_first is not None:
|
||
iter_self_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(iter_first)
|
||
if iter_self_ty is not None: iter_self = mlirlite.build_bitcast(builder, obj_val, iter_self_ty)
|
||
if iter_self is None: return 0
|
||
iter_self.Next = None
|
||
iter_result: mlirlite.Value* = mlirlite.build_call(builder, iter_fn_name, iter_self, 1, iter_ret_ty, 0)
|
||
if iter_result is None: return 0
|
||
|
||
# 2. find {Class}.__next__ (with hidden __stop_iter_flag__ i1* param)
|
||
next_lookup: str = pool.alloc(128)
|
||
if next_lookup is None: return 0
|
||
viperlib.snprintf(next_lookup, 128, "%s.__next__", class_name)
|
||
next_fn: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, next_lookup)
|
||
if next_fn is None: self.Gen.TypeGenErr.fatal_error(for_node, f"OOP iteration: class {class_name} has no __next__ method")
|
||
next_ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(next_fn)
|
||
next_fn_name: str = mlirlite.function_get_name(next_fn)
|
||
if next_ret_ty is None or next_fn_name is None: return 0
|
||
|
||
# 3. loop-var alloca (element type = __next__ return type)
|
||
var_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, var_name)
|
||
new_vars: int = 0
|
||
if var_alloca is None:
|
||
var_alloca = self.Gen.VarGen._alloca_at_entry(builder, next_ret_ty)
|
||
if var_alloca is not None:
|
||
if self.Gen.VarGen.define_var(trans.SymTab, var_name, var_alloca) == 0: new_vars = 1
|
||
|
||
# 4. stop flag alloca (i1, init 0)
|
||
i1_ty: mlirlite.LLVMType* = mlirlite.Int1(pool)
|
||
flag_alloca: mlirlite.Value* = mlirlite.build_alloca(builder, i1_ty)
|
||
if flag_alloca is None: return 0
|
||
flag_zero: mlirlite.Value* = mlirlite.ConstInt(pool, i1_ty, 0, "0")
|
||
if flag_zero is not None: mlirlite.build_store(builder, flag_zero, flag_alloca)
|
||
|
||
# 5. create blocks cond/body/step/end
|
||
cnt: int = trans._label_counter
|
||
trans._label_counter = cnt + 1
|
||
name_buf: str = pool.alloc(32)
|
||
viperlib.snprintf(name_buf, 32, "oop.cond.%d", cnt)
|
||
cond_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
viperlib.snprintf(name_buf, 32, "oop.body.%d", cnt)
|
||
body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
viperlib.snprintf(name_buf, 32, "oop.step.%d", cnt)
|
||
step_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
viperlib.snprintf(name_buf, 32, "oop.end.%d", cnt)
|
||
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
if cond_bb is None or body_bb is None or step_bb is None or end_bb is None: return 0
|
||
|
||
# loop else: __next__ returning stop (exhausted) edge targets else_bb (else end_bb)
|
||
has_else_oop: int = self._LoopElseCount(for_node.orelse)
|
||
else_bb_oop: mlirlite.BasicBlock* = self._MakeLoopElseBlock(pool, func, name_buf, "oop", cnt, has_else_oop)
|
||
|
||
mlirlite.build_br(builder, cond_bb)
|
||
|
||
# 6. cond block: reset flag → call __next__(iter, flag) → check flag for done
|
||
mlirlite.position_at_end(builder, cond_bb)
|
||
if flag_zero is not None: mlirlite.build_store(builder, flag_zero, flag_alloca)
|
||
# __next__ arg 1: the iterator (bitcast to the first param type)
|
||
next_self: mlirlite.Value* = iter_result
|
||
next_first: mlirlite.Param* = mlirlite.function_get_param_head(next_fn)
|
||
if next_first is not None:
|
||
next_self_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(next_first)
|
||
if next_self_ty is not None and iter_result.Ty is not None: next_self = mlirlite.build_bitcast(builder, iter_result, next_self_ty)
|
||
if next_self is None: return 0
|
||
# arg list: arg1(iter).Next = arg2(flag); flag is null-terminated to avoid self-linking
|
||
next_self.Next = flag_alloca
|
||
flag_alloca.Next = None
|
||
next_val: mlirlite.Value* = mlirlite.build_call(builder, next_fn_name, next_self, 2, next_ret_ty, 0)
|
||
if next_val is None: return 0
|
||
stop_val: mlirlite.Value* = mlirlite.build_load(builder, i1_ty, flag_alloca)
|
||
if stop_val is None: return 0
|
||
flag_zero2: mlirlite.Value* = mlirlite.ConstInt(pool, i1_ty, 0, "0")
|
||
continue_i1: mlirlite.Value* = None
|
||
if flag_zero2 is not None: continue_i1 = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, stop_val, flag_zero2)
|
||
if continue_i1 is None: return 0
|
||
mlirlite.build_cond_br(builder, continue_i1, body_bb, else_bb_oop if else_bb_oop is not None else end_bb)
|
||
|
||
# 7. body block: store element → translate loop body → br step
|
||
mlirlite.position_at_end(builder, body_bb)
|
||
if var_alloca is not None: mlirlite.build_store(builder, next_val, var_alloca)
|
||
old_break: mlirlite.BasicBlock* = trans._break_bb
|
||
old_continue: mlirlite.BasicBlock* = trans._continue_bb
|
||
trans._break_bb = end_bb
|
||
trans._continue_bb = step_bb
|
||
body: list[ast.AST*]* = for_node.children
|
||
if body is not None:
|
||
body_count: sizet = len(body)
|
||
for bi in range(int(body_count)):
|
||
stmt: ast.AST* = body.get(bi)
|
||
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
|
||
trans._break_bb = old_break
|
||
trans._continue_bb = old_continue
|
||
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, step_bb)
|
||
|
||
# 8. step block → cond; loop-else tail (run orelse when exhausted), then position at end
|
||
mlirlite.position_at_end(builder, step_bb)
|
||
mlirlite.build_br(builder, cond_bb)
|
||
ret_oop: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_oop, end_bb)
|
||
mlirlite.position_at_end(builder, end_bb)
|
||
# append90: for loops branch to end_bb on normal exit → merge always reachable (normal tail)
|
||
trans._ctrl_merge_reachable = 1
|
||
return new_vars
|
||
|
||
def _handle_enumerate_iter(self, trans: HT.Translator*, for_node: ast.For*, t1: str, t2: str, call: ast.Call*) -> int:
|
||
"""Iterate enumerate(x) as for i, v in enumerate(x)."""
|
||
if call is None or call.args is None: return 0
|
||
cargs: list[ast.AST*]* = call.args
|
||
if len(cargs) < 1: self.Gen.TypeGenErr.fatal_error(for_node, "enumerate requires at least one argument")
|
||
return self._build_seq_loop(trans, for_node, t1, t2, cargs.get(0), None, 1)
|
||
|
||
def _handle_zip_iter(self, trans: HT.Translator*, for_node: ast.For*, t1: str, t2: str, call: ast.Call*) -> int:
|
||
"""Iterate zip(x, y) as for a, b in zip(x, y)."""
|
||
if call is None or call.args is None:
|
||
# fail-fast: zip argument missing
|
||
self.Gen.TypeGenErr.fatal_error(call, "zip arguments are missing", "SyntaxError")
|
||
cargs: list[ast.AST*]* = call.args
|
||
if len(cargs) < 2: self.Gen.TypeGenErr.fatal_error(for_node, "zip requires at least two arguments")
|
||
return self._build_seq_loop(trans, for_node, t1, t2, cargs.get(0), cargs.get(1), 2)
|
||
|
||
|
||
def InitStmtGen(self) -> int:
|
||
"""Initialize the StmtGen family fields (called by Generator on construction)."""
|
||
if self is None: return 1
|
||
self.CifEvalOk = 0
|
||
self.EnumTable = None
|
||
self.EnumCount = 0
|
||
self.DiagStmtCount = 0
|
||
return 0
|