Files
ViperC/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp
T

2314 lines
136 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 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 (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.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