Files
ViperC/App/lib/core/Gen/AsyncGenMixin.vp
T

2099 lines
114 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 t, c
from stdint import *
import ast
import mlirlite
from mlirlite import LLVMType
import memhub
import string
import viperlib
import stdio
import stdlib
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.core.Gen.Call.CallGenBase as CallGenBase
# ============================================================
# AsyncGenMixin - VPC-M1 coroutine translation (async def / await / coro.coro_run)
define SCOPE_FUNCTION = 1
novirtual class AsyncGenMixin(CallGenBase.CallGenBase):
"""Coroutine translation mixin (VPC-M1 state machine)"""
# Entry: async function definition. Called by FuncGenDef.translate_function_def after
# detecting fd.flags & FLAG_IS_ASYNC; early-returns so it never enters the normal path.
def translate_async_function(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate an async function definition; returns 0 on success"""
fd: ast.FunctionDef* = ast.FunctionDef*(node)
if fd is None or fd.name is None or trans is None: return 0
name: str = fd.name
pool: memhub.MemBuddy* = trans.Pool
mod: mlirlite.LLVMModule* = trans.Module
if pool is None or mod is None: return 0
# avoid duplicate generation: create_<name> already exists (multi-pass / dup def)
cname_buf: str = pool.alloc(64)
if cname_buf is not None: viperlib.snprintf(cname_buf, 64, "create_%s", name)
create_mangled: str = self.Gen.FuncGenBase._mangle_func_name(trans, cname_buf, 0)
if self.Gen.CallGenBase.find_func_in_module(mod, create_mangled) is not None: return 0
# register the async bare name (await target check / coro_run reverse lookup)
self._register_async_func(trans, name)
# M2: collect params -> trans._coro_params (char* array, 8 bytes per item)
trans._coro_param_count = 0
nparams: int = self._collect_param_names(trans, fd)
# collect locals (top-level AnnAssign/Assign Name targets -> frame slots)
trans._coro_local_count = 0
nlocals: int = self._collect_local_names(trans, fd)
# M3-M7: pre-scan extra slots (wake/for/await type tables + position arrays) + state ids.
# _coro_await_count = resume-block count; nseg = 1 entry + await_count.
trans._coro_extra_count = 0
trans._coro_hoist_count = 0
trans._coro_await_count = 0
trans._coro_uses_sleep = 0
trans._coro_scan_for_count = 0
trans._coro_for_counter = 0
self._scan_sleep_usage(trans, fd)
self._pre_scan_awaits(trans, fd)
nextra: int = trans._coro_extra_count
trans._coro_nseg = 1 + trans._coro_await_count
# frame struct %Coro_<name>
frame_ty: mlirlite.LLVMType* = self._build_frame_ty(pool, mod, trans, name, nparams, nlocals, nextra)
if frame_ty is None: return 0
self._register_coro_frame(trans, name, frame_ty)
# M3: register the wake-slot index (coro_run polling; wake is extra slot 0)
if trans._coro_uses_sleep != 0: self._register_coro_wake(trans, name, trans._coro_extra_base + 0)
# enter function scope so resume slot registration stays isolated from module scope
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_FUNCTION)
self._emit_create(pool, mod, trans, name, frame_ty, nparams)
self._emit_resume(pool, mod, trans, name, fd, frame_ty, nparams, nlocals, nextra)
self.Gen.VarGen.exit_scope(trans.SymTab)
return 0
# ============================================================
# M2: parameter collection (into frame param slots)
def _collect_param_names(self, trans: HT.Translator*, fd: ast.FunctionDef*) -> int:
"""Collect async parameter names into trans._coro_params (8-byte slots); slot index = 3 + i"""
if fd is None or fd.args is None or trans is None: return 0
if trans._coro_params is None: return 0
ags: ast.Arguments* = ast.Arguments*(fd.args)
cnt: int = 0
if ags.args is None: return 0
bn: sizet = len(ags.args)
for i in range(int(bn)):
a0: ast.AST* = ags.args.get(i)
if a0 is None: continue
aname: str = None
if a0.kind() == ast.ASTKind.Arg:
ar: ast.Arg* = ast.Arg*(a0)
aname = ar.arg
elif a0.kind() == ast.ASTKind.Name:
anm: ast.Name* = ast.Name*(a0)
aname = anm.id
if aname is None: continue
if cnt >= HT.MAX_CORO_VARS: break
addr: uint64 = uint64(trans._coro_params) + cnt * 8
slot: str* = (str*)(addr)
slot[0] = aname
cnt = cnt + 1
trans._coro_param_count = cnt
return cnt
# M3-M7: pre-scan extra slots + state ids in the same DFS order as translation.
# Extra-slot table _coro_extra_types (1=i32, 2=i64); position arrays map slot -> relative index.
def _pre_scan_awaits(self, trans: HT.Translator*, fd: ast.FunctionDef*) -> int:
"""Pre-scan the coroutine body to fill the extra-slot table and return its count"""
if fd is None or fd.children is None or trans is None: return 0
trans._coro_extra_count = 0
trans._coro_hoist_count = 0
trans._coro_await_count = 0
trans._coro_scan_for_count = 0
if trans._coro_uses_sleep != 0:
trans._coro_extra_types[0] = 2 # __coro_wake_us (i64) relative index 0
trans._coro_extra_count = 1
self._scan_stmts(trans, fd.children)
return trans._coro_extra_count
# ---------- pass 1: coro_sleep detection (decides the wake slot) ----------
def _scan_sleep_usage(self, trans: HT.Translator*, fd: ast.FunctionDef*):
if fd is None or fd.children is None or trans is None: return
self._scan_sleep_stmts(trans, fd.children)
def _scan_sleep_stmts(self, trans: HT.Translator*, stmts: list[ast.AST*]*):
if stmts is None or trans._coro_uses_sleep != 0: return
bn: sizet = len(stmts)
i: sizet = 0
for i in range(int(bn)): self._scan_sleep_stmt(trans, stmts.get(i))
def _scan_sleep_stmt(self, trans: HT.Translator*, stmt: ast.AST*):
if stmt is None or trans._coro_uses_sleep != 0: return
sk: int = stmt.kind()
if sk == ast.ASTKind.If:
ifn: ast.If* = ast.If*(stmt)
self._scan_sleep_expr(trans, ifn.test)
self._scan_sleep_stmts(trans, stmt.children)
self._scan_sleep_stmts(trans, ifn.orelse)
return
if sk == ast.ASTKind.While:
whn: ast.While* = ast.While*(stmt)
self._scan_sleep_expr(trans, whn.test)
self._scan_sleep_stmts(trans, stmt.children)
self._scan_sleep_stmts(trans, whn.orelse)
return
if sk == ast.ASTKind.For:
fn2: ast.For* = ast.For*(stmt)
self._scan_sleep_expr(trans, fn2.iter)
self._scan_sleep_stmts(trans, stmt.children)
self._scan_sleep_stmts(trans, fn2.orelse)
return
if sk == ast.ASTKind.Try:
trn: ast.Try* = ast.Try*(stmt)
self._scan_sleep_stmts(trans, stmt.children)
if trn.handlers is not None:
nh: sizet = len(trn.handlers)
hh: sizet = 0
for hh in range(int(nh)):
h0: ast.AST* = trn.handlers.get(hh)
if h0 is not None: self._scan_sleep_stmts(trans, h0.children)
self._scan_sleep_stmts(trans, trn.orelse)
self._scan_sleep_stmts(trans, trn.finalbody)
return
if sk == ast.ASTKind.Return:
rv: ast.Return* = ast.Return*(stmt)
self._scan_sleep_expr(trans, rv.value)
return
if sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
self._scan_sleep_expr(trans, aa.value)
return
if sk == ast.ASTKind.Assign:
as_: ast.Assign* = ast.Assign*(stmt)
self._scan_sleep_expr(trans, as_.value)
return
if sk == ast.ASTKind.Expr:
e: ast.Expr* = ast.Expr*(stmt)
self._scan_sleep_expr(trans, e.value)
return
def _scan_sleep_expr(self, trans: HT.Translator*, node: ast.AST*):
if node is None or trans._coro_uses_sleep != 0: return
k: int = node.kind()
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
if cl.func is not None:
if cl.func.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(cl.func)
if nm.id is not None and string.strcmp(nm.id, "coro_sleep") == 0:
trans._coro_uses_sleep = 1
return
elif cl.func.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(cl.func)
if at.attr is not None and string.strcmp(at.attr, "coro_sleep") == 0:
trans._coro_uses_sleep = 1
return
self._scan_sleep_expr(trans, cl.func)
if cl.args is not None:
nb: sizet = len(cl.args)
j: sizet = 0
for j in range(int(nb)): self._scan_sleep_expr(trans, cl.args.get(j))
return
if k == ast.ASTKind.BinOp:
b: ast.BinOp* = ast.BinOp*(node)
self._scan_sleep_expr(trans, b.left)
self._scan_sleep_expr(trans, b.right)
return
if k == ast.ASTKind.UnaryOp:
u: ast.UnaryOp* = ast.UnaryOp*(node)
self._scan_sleep_expr(trans, u.operand)
return
if k == ast.ASTKind.Compare:
cm: ast.Compare* = ast.Compare*(node)
self._scan_sleep_expr(trans, cm.left)
if cm.comparators is not None:
nb2: sizet = len(cm.comparators)
j2: sizet = 0
for j2 in range(int(nb2)): self._scan_sleep_expr(trans, cm.comparators.get(j2))
return
if k == ast.ASTKind.BoolOp:
bo: ast.BoolOp* = ast.BoolOp*(node)
if bo.values is not None:
nb3: sizet = len(bo.values)
j3: sizet = 0
for j3 in range(int(nb3)): self._scan_sleep_expr(trans, bo.values.get(j3))
return
if k == ast.ASTKind.IfExp:
ie: ast.IfExp* = ast.IfExp*(node)
self._scan_sleep_expr(trans, ie.test)
self._scan_sleep_expr(trans, ie.body)
self._scan_sleep_expr(trans, ie.orelse)
return
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
self._scan_sleep_expr(trans, at.value)
return
if k == ast.ASTKind.Subscript:
sb: ast.Subscript* = ast.Subscript*(node)
self._scan_sleep_expr(trans, sb.value)
self._scan_sleep_expr(trans, sb.slice)
return
if k == ast.ASTKind.Await:
aw: ast.Await* = ast.Await*(node)
self._scan_sleep_expr(trans, aw.value)
return
# ---------- pass 2: fill the extra-slot table + count state ids ----------
def _scan_stmts(self, trans: HT.Translator*, stmts: list[ast.AST*]*):
if stmts is None: return
bn: sizet = len(stmts)
i: sizet = 0
for i in range(int(bn)): self._scan_stmt(trans, stmts.get(i))
def _scan_stmt(self, trans: HT.Translator*, stmt: ast.AST*):
if stmt is None: return
sk: int = stmt.kind()
if sk == ast.ASTKind.If:
ifn: ast.If* = ast.If*(stmt)
if ifn.test is not None: self._scan_expr(trans, ifn.test)
self._scan_stmts(trans, stmt.children)
self._scan_stmts(trans, ifn.orelse)
return
if sk == ast.ASTKind.While:
whn: ast.While* = ast.While*(stmt)
if whn.test is not None: self._scan_expr(trans, whn.test)
self._scan_stmts(trans, stmt.children)
self._scan_stmts(trans, whn.orelse)
return
if sk == ast.ASTKind.For:
fn_: ast.For* = ast.For*(stmt)
if self._for_deep_await(stmt) != 0:
# contains await: must be a range-For (M3)
if self._is_range_for(stmt) == 0:
err_b2: str = trans.Pool.alloc(256)
if err_b2 is not None:
viperlib.snprintf(err_b2, 256, "[coro-M3] For inside a coroutine supports only range iteration (including await)")
self.Gen.TypeGenErr.fatal_error(stmt, err_b2, "SyntaxError")
return
# fill __f_idx_N / __f_len_N (i32)
if trans._coro_scan_for_count < HT.MAX_CORO_VARS: trans._coro_for_slots[trans._coro_scan_for_count] = trans._coro_extra_count
trans._coro_extra_types[trans._coro_extra_count] = 1
trans._coro_extra_count = trans._coro_extra_count + 1
trans._coro_extra_types[trans._coro_extra_count] = 1
trans._coro_extra_count = trans._coro_extra_count + 1
trans._coro_scan_for_count = trans._coro_scan_for_count + 1
self._scan_stmts(trans, stmt.children)
self._scan_stmts(trans, fn_.orelse)
return
# no await: delegate to standard For; recurse (nested For/Try may contain await)
self._scan_stmts(trans, stmt.children)
self._scan_stmts(trans, fn_.orelse)
return
if sk == ast.ASTKind.Try:
tr2: ast.Try* = ast.Try*(stmt)
self._scan_stmts(trans, stmt.children)
if tr2.handlers is not None:
nh2: sizet = len(tr2.handlers)
hh2: sizet = 0
for hh2 in range(int(nh2)):
h2: ast.AST* = tr2.handlers.get(hh2)
if h2 is not None: self._scan_stmts(trans, h2.children)
self._scan_stmts(trans, tr2.orelse)
self._scan_stmts(trans, tr2.finalbody)
return
if sk == ast.ASTKind.Raise:
rz: ast.Raise* = ast.Raise*(stmt)
if rz.exc is not None: self._scan_expr(trans, rz.exc)
return
if sk == ast.ASTKind.Return:
rv: ast.Return* = ast.Return*(stmt)
if rv.value is not None: self._scan_expr(trans, rv.value)
return
if sk == ast.ASTKind.Break or sk == ast.ASTKind.Continue: return
# top-level await (AnnAssign/Assign/Expr value is Await): count without hoisting
if sk == ast.ASTKind.AnnAssign or sk == ast.ASTKind.Assign or sk == ast.ASTKind.Expr:
if self._is_await_tail(stmt) != 0:
trans._coro_await_count = trans._coro_await_count + 1
return
# nested await inside a statement: DFS the value expression
self._scan_stmt_exprs(trans, stmt)
def _scan_stmt_exprs(self, trans: HT.Translator*, stmt: ast.AST*):
"""Scan statement value expressions for nested awaits"""
if stmt is None: return
sk: int = stmt.kind()
if sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
if aa.value is not None: self._scan_expr(trans, aa.value)
return
if sk == ast.ASTKind.Assign:
as_: ast.Assign* = ast.Assign*(stmt)
if as_.value is not None: self._scan_expr(trans, as_.value)
return
if sk == ast.ASTKind.Expr:
e: ast.Expr* = ast.Expr*(stmt)
if e.value is not None: self._scan_expr(trans, e.value)
return
# other statement types: fail fast at translation
def _scan_expr(self, trans: HT.Translator*, node: ast.AST*):
"""DFS the expression for Await: allocate a __await_N slot and count the state number"""
if node is None: return
k: int = node.kind()
if k == ast.ASTKind.Await:
# M3: await coro_sleep suspends via the wake slot, not __await_N (state id only)
aw_s: ast.Await* = ast.Await*(node)
if self._expr_await_is_sleep(aw_s) != 0:
trans._coro_await_count = trans._coro_await_count + 1
return
if trans._coro_hoist_count < HT.MAX_CORO_VARS: trans._coro_await_slots[trans._coro_hoist_count] = trans._coro_extra_count
trans._coro_extra_types[trans._coro_extra_count] = 1
trans._coro_extra_count = trans._coro_extra_count + 1
trans._coro_hoist_count = trans._coro_hoist_count + 1
trans._coro_await_count = trans._coro_await_count + 1
return
if k == ast.ASTKind.BinOp:
b: ast.BinOp* = ast.BinOp*(node)
self._scan_expr(trans, b.left)
self._scan_expr(trans, b.right)
return
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
if cl.func is not None: self._scan_expr(trans, cl.func)
if cl.args is not None:
nb: sizet = len(cl.args)
j: sizet = 0
for j in range(int(nb)): self._scan_expr(trans, cl.args.get(j))
return
if k == ast.ASTKind.UnaryOp:
u: ast.UnaryOp* = ast.UnaryOp*(node)
if u.operand is not None: self._scan_expr(trans, u.operand)
return
if k == ast.ASTKind.Compare:
cm: ast.Compare* = ast.Compare*(node)
if cm.left is not None: self._scan_expr(trans, cm.left)
if cm.comparators is not None:
nb2: sizet = len(cm.comparators)
j2: sizet = 0
for j2 in range(int(nb2)): self._scan_expr(trans, cm.comparators.get(j2))
return
if k == ast.ASTKind.BoolOp:
bo: ast.BoolOp* = ast.BoolOp*(node)
if bo.values is not None:
nb3: sizet = len(bo.values)
j3: sizet = 0
for j3 in range(int(nb3)): self._scan_expr(trans, bo.values.get(j3))
return
if k == ast.ASTKind.IfExp:
ie: ast.IfExp* = ast.IfExp*(node)
self._scan_expr(trans, ie.test)
self._scan_expr(trans, ie.body)
self._scan_expr(trans, ie.orelse)
return
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
if at.value is not None: self._scan_expr(trans, at.value)
return
if k == ast.ASTKind.Subscript:
sb: ast.Subscript* = ast.Subscript*(node)
if sb.value is not None: self._scan_expr(trans, sb.value)
if sb.slice is not None: self._scan_expr(trans, sb.slice)
return
# leaves like Name / Constant: no nested await
# ---------- For helpers (M3) ----------
def _for_deep_await(self, stmt: ast.AST*) -> int:
"""Return whether a For statement tree contains await"""
if stmt is None: return 0
fn3: ast.For* = ast.For*(stmt)
if fn3.iter is not None and self._find_expr_await(fn3.iter) is not None: return 1
if stmt.children is not None:
bn4: sizet = len(stmt.children)
i4: sizet = 0
for i4 in range(int(bn4)):
if self._stmt_deep_await(stmt.children.get(i4)) != 0: return 1
if fn3.orelse is not None:
bn5: sizet = len(fn3.orelse)
i5: sizet = 0
for i5 in range(int(bn5)):
if self._stmt_deep_await(fn3.orelse.get(i5)) != 0: return 1
return 0
def _stmt_deep_await(self, stmt: ast.AST*) -> int:
"""Return whether a statement tree contains await"""
if stmt is None: return 0
sk2: int = stmt.kind()
if sk2 == ast.ASTKind.If:
ifn2: ast.If* = ast.If*(stmt)
if ifn2.test is not None and self._find_expr_await(ifn2.test) is not None: return 1
if self._stmts_deep_await(stmt.children) != 0: return 1
return self._stmts_deep_await(ifn2.orelse)
if sk2 == ast.ASTKind.While:
whn2: ast.While* = ast.While*(stmt)
if whn2.test is not None and self._find_expr_await(whn2.test) is not None: return 1
if self._stmts_deep_await(stmt.children) != 0: return 1
return self._stmts_deep_await(whn2.orelse)
if sk2 == ast.ASTKind.For: return self._for_deep_await(stmt)
if sk2 == ast.ASTKind.Try:
tr3: ast.Try* = ast.Try*(stmt)
if self._stmts_deep_await(stmt.children) != 0: return 1
if tr3.handlers is not None:
nh3: sizet = len(tr3.handlers)
hh3: sizet = 0
for hh3 in range(int(nh3)):
h3: ast.AST* = tr3.handlers.get(hh3)
if h3 is not None and self._stmts_deep_await(h3.children) != 0: return 1
if self._stmts_deep_await(tr3.orelse) != 0: return 1
return self._stmts_deep_await(tr3.finalbody)
return self._stmt_has_await(stmt)
def _stmts_deep_await(self, stmts: list[ast.AST*]*) -> int:
if stmts is None: return 0
bn6: sizet = len(stmts)
i6: sizet = 0
for i6 in range(int(bn6)):
if self._stmt_deep_await(stmts.get(i6)) != 0: return 1
return 0
def _is_range_for(self, stmt: ast.AST*) -> int:
"""Return whether a for-loop is a range-for with a plain Name target and no await in its args"""
if stmt is None: return 0
fn4: ast.For* = ast.For*(stmt)
if fn4.target is None or fn4.target.kind() != ast.ASTKind.Name: return 0
if fn4.iter is None or fn4.iter.kind() != ast.ASTKind.Call: return 0
cl4: ast.Call* = ast.Call*(fn4.iter)
if cl4.func is None or cl4.func.kind() != ast.ASTKind.Name: return 0
nm4: ast.Name* = ast.Name*(cl4.func)
if nm4.id is None or string.strcmp(nm4.id, "range") != 0: return 0
if cl4.args is None: return 0
nb7: sizet = len(cl4.args)
if nb7 != 1 and nb7 != 2: return 0
j7: sizet = 0
for j7 in range(int(nb7)):
if self._find_expr_await(cl4.args.get(j7)) is not None: return 0
return 1
# ============================================================
# Local-variable collection (top-level AnnAssign / Assign Name targets)
def _collect_local_names(self, trans: HT.Translator*, fd: ast.FunctionDef*) -> int:
"""Collect frame local variable names into trans._coro_locals (8-byte slots)"""
if fd is None or fd.children is None or trans is None: return 0
if trans._coro_locals is None: return 0
trans._coro_local_count = 0
self._collect_locals_stmts(trans, fd.children)
return trans._coro_local_count
def _collect_locals_stmts(self, trans: HT.Translator*, stmts: list[ast.AST*]*):
if stmts is None: return
bn: sizet = len(stmts)
i: sizet = 0
for i in range(int(bn)): self._collect_locals_stmt(trans, stmts.get(i))
def _collect_locals_stmt(self, trans: HT.Translator*, stmt: ast.AST*):
if stmt is None: return
sk: int = stmt.kind()
if sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
if aa.target is not None and aa.target.kind() == ast.ASTKind.Name:
tn: ast.Name* = ast.Name*(aa.target)
self._add_local(trans, tn.id)
return
if sk == ast.ASTKind.Assign:
as_: ast.Assign* = ast.Assign*(stmt)
if as_.targets is not None and len(as_.targets) == 1:
t0: ast.AST* = as_.targets.get(0)
if t0 is not None and t0.kind() == ast.ASTKind.Name:
tn2: ast.Name* = ast.Name*(t0)
self._add_local(trans, tn2.id)
return
if sk == ast.ASTKind.If:
self._collect_locals_stmts(trans, stmt.children)
ifn: ast.If* = ast.If*(stmt)
self._collect_locals_stmts(trans, ifn.orelse)
return
if sk == ast.ASTKind.While:
self._collect_locals_stmts(trans, stmt.children)
whn: ast.While* = ast.While*(stmt)
self._collect_locals_stmts(trans, whn.orelse)
return
if sk == ast.ASTKind.For:
fn: ast.For* = ast.For*(stmt)
if self._for_deep_await(stmt) != 0:
if fn.target is not None and fn.target.kind() == ast.ASTKind.Name:
tn3: ast.Name* = ast.Name*(fn.target)
self._add_local(trans, tn3.id)
self._collect_locals_stmts(trans, stmt.children)
self._collect_locals_stmts(trans, fn.orelse)
return
if sk == ast.ASTKind.Try:
tr: ast.Try* = ast.Try*(stmt)
self._collect_locals_stmts(trans, stmt.children)
if tr.handlers is not None:
nh: sizet = len(tr.handlers)
hh: sizet = 0
for hh in range(int(nh)):
h0: ast.AST* = tr.handlers.get(hh)
if h0 is None: continue
hd: ast.ExceptHandler* = ast.ExceptHandler*(h0)
self._add_local(trans, hd.name)
self._collect_locals_stmts(trans, h0.children)
self._collect_locals_stmts(trans, tr.orelse)
self._collect_locals_stmts(trans, tr.finalbody)
return
def _add_local(self, trans: HT.Translator*, name: str):
if name is None: return
cnt: int = trans._coro_local_count
if cnt >= HT.MAX_CORO_VARS: return
addr: uint64 = uint64(trans._coro_locals) + cnt * 8
slot: str* = (str*)(addr)
slot[0] = name
trans._coro_local_count = cnt + 1
# Frame struct %Coro_<name> = { i32 state, i32 done, i32 ret, ... };
# slot index: 0=state 1=done 2=ret 3=exc 4=msg, then params / locals / extra slots.
def _build_frame_ty(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, name: str, nparams: int, nlocals: int, nextra: int) -> mlirlite.LLVMType*:
"""Build the frame struct, register it on the module and struct table, and return its LLVMType"""
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
# slot base (used by resume/await to compute slot indexes)
trans._coro_local_base = 5 + nparams
trans._coro_extra_base = trans._coro_local_base + nlocals
total: int = 5 + nparams + nlocals + nextra
head: mlirlite.ParamNode* = None
prev: mlirlite.ParamNode* = None
fi: int = 0
for fi in range(int(total)):
fty: mlirlite.LLVMType* = i32_ty
if fi == 4: fty = i8_ptr_ty
elif fi >= trans._coro_extra_base:
tidx: int = fi - trans._coro_extra_base
if tidx < nextra and trans._coro_extra_types is not None:
if trans._coro_extra_types[tidx] == 2: fty = i64_ty
pnode: mlirlite.ParamNode* = pool.alloc(mlirlite.ParamNode.__sizeof__())
if pnode is None: continue
string.memset(pnode, 0, mlirlite.ParamNode.__sizeof__())
pnode.Ty = fty
if head is None: head = pnode
if prev is not None: prev.Next = pnode
prev = pnode
ty_name: str = pool.alloc(128)
if ty_name is not None: viperlib.snprintf(ty_name, 128, "Coro_%s", name)
frame_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, head, total, ty_name)
if frame_ty is None: return None
mlirlite.module_add_named_type(mod, pool, frame_ty)
self.Gen.StructGen.register_struct(pool, ty_name, frame_ty, trans.ModuleSha1)
return frame_ty
# ============================================================
# create_<name>() -> %Coro_<name>*
def _emit_create(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, name: str, frame_ty: mlirlite.LLVMType*, nparams: int) -> int:
"""Emit create_<name>(args...): pop or malloc a frame, zero the header, store args, return it"""
if frame_ty is None: return 0
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)
i64_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i64_ty)
frame_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, frame_ty)
cname_buf2: str = pool.alloc(64)
if cname_buf2 is not None: viperlib.snprintf(cname_buf2, 64, "create_%s", name)
mangled: str = self.Gen.FuncGenBase._mangle_func_name(trans, cname_buf2, 0)
if self.Gen.CallGenBase.find_func_in_module(mod, mangled) is not None: return 0
func: mlirlite.Function* = mlirlite.create_function(pool, mod, mangled, frame_ptr_ty)
if func is None: return 0
# params (i32, frame slot 5+i). VPC has no Python "%" formatting ("%" is modulo),
# so names are built with snprintf ("%%arg" -> "%arg", matching resume's "%frame").
pi: int = 0
argn_buf: str = pool.alloc(16)
for pi in range(int(nparams)):
if argn_buf is not None: viperlib.snprintf(argn_buf, 16, "%%arg%d", pi)
mlirlite.add_param(pool, func, i32_ty, argn_buf)
# register in the function table (bare name create_<name>)
funcs_ptr: GenTypes.FuncEntry* = trans._funcs
cur_count: int = trans._func_count
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, cur_count, cname_buf2, func, 1024, None, 0, 0, trans.Pool) == 0: trans._func_count = cur_count + 1
entry: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "entry")
if entry is None: return 0
builder: mlirlite.IRBuilder* = mlirlite.new_builder(pool, func)
if builder is None: return 0
mlirlite.position_at_end(builder, entry)
frame_size: int = self.Gen.ClassGenEnum._get_type_size(frame_ty)
size_val: mlirlite.Value* = mlirlite.const_int64(pool, frame_size)
if size_val is None: return 0
size_val.Next = None
# M7 frame pool: i64 head (0=empty); first 8 bytes hold next (state/done zeroed on reuse)
pool_gv: mlirlite.GlobalVariable* = self._get_coro_pool(pool, mod, trans, name)
# GlobalVariable is not a Value: build a Value view (Ty=Ptr(i64), Name="@coro_pool_<name>")
pool_ref_name: str = pool.alloc(96)
if pool_ref_name is not None: viperlib.snprintf(pool_ref_name, 96, "@coro_pool_%s", name)
pool_ref: mlirlite.Value* = mlirlite.SSAValue(pool, i64_ptr_ty, pool_ref_name)
pool_head: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, pool_ref)
if pool_head is None: return 0
zero64: mlirlite.Value* = mlirlite.const_int64(pool, 0)
empty_cond: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, pool_head, zero64)
if empty_cond is None: return 0
hit_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "pool.hit")
miss_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "pool.miss")
join_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "pool.join")
if hit_bb is None or miss_bb is None or join_bb is None: return 0
mlirlite.build_cond_br(builder, empty_cond, miss_bb, hit_bb)
# hit: pop from pool (first 8 bytes are next)
mlirlite.position_at_end(builder, hit_bb)
node_next_ptr: mlirlite.Value* = mlirlite.build_inttoptr(builder, pool_head, i64_ptr_ty)
next_head: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, node_next_ptr)
if next_head is None: return 0
mlirlite.build_store(builder, next_head, pool_ref)
hit_frame: mlirlite.Value* = mlirlite.build_inttoptr(builder, pool_head, frame_ptr_ty)
if hit_frame is None: return 0
mlirlite.build_br(builder, join_bb)
# miss: malloc + zero the first 8 bytes (clear stale next from pooled frames)
mlirlite.position_at_end(builder, miss_bb)
raw: mlirlite.Value* = mlirlite.build_call(builder, "malloc", size_val, 1, i8_ptr_ty, 0)
if raw is None: return 0
miss_frame: mlirlite.Value* = mlirlite.build_bitcast(builder, raw, frame_ptr_ty)
if miss_frame is None: return 0
miss_head_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, miss_frame, i64_ptr_ty)
if miss_head_ptr is not None: mlirlite.build_store(builder, zero64, miss_head_ptr)
mlirlite.build_br(builder, join_bb)
# join: phi-select the frame + zero the 5 header fields + store params into slots
mlirlite.position_at_end(builder, join_bb)
inc1: mlirlite.PhiIncoming* = mlirlite.new_phi_incoming(pool, hit_frame, hit_bb)
inc2: mlirlite.PhiIncoming* = mlirlite.new_phi_incoming(pool, miss_frame, miss_bb)
if inc1 is None or inc2 is None: return 0
inc1.Next = inc2
frame_ref: mlirlite.Value* = mlirlite.build_phi(builder, frame_ptr_ty, inc1, 2)
if frame_ref is None: return 0
zero: mlirlite.Value* = mlirlite.const_int32(pool, 0)
null_p: mlirlite.Value* = mlirlite.ConstNull(pool, i8_ptr_ty, "null")
si: int = 0
for si in range(5):
if si == 4:
mslot: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i8_ptr_ty, frame_ref, 4)
if mslot is not None: mlirlite.build_store(builder, null_p, mslot)
else:
slot: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i32_ty, frame_ref, si)
if slot is not None: mlirlite.build_store(builder, zero, slot)
# store params into frame slots (index 5+i)
pj: int = 0
for pj in range(int(nparams)):
pslot: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i32_ty, frame_ref, 5 + pj)
if pslot is not None:
arnb: str = pool.alloc(16)
if arnb is not None: viperlib.snprintf(arnb, 16, "%%arg%d", pj)
arg_ref: mlirlite.Value* = mlirlite.SSAValue(pool, i32_ty, arnb)
if arg_ref is not None: mlirlite.build_store(builder, arg_ref, pslot)
mlirlite.build_ret(builder, frame_ref)
return 0
# ============================================================
# M7: per-frame-type global free-list pool head (i64, 0 = empty)
def _get_coro_pool(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, name: str) -> mlirlite.GlobalVariable*:
"""Get or create the coro_pool_<name> global (i64 pool head)"""
pn_buf: str = pool.alloc(128)
if pn_buf is not None: viperlib.snprintf(pn_buf, 128, "coro_pool_%s", name)
# idempotent: create/destroy both call this, so scan the module global list (no find_global API)
g: mlirlite.GlobalVariable* = mod.GlobalHead
while g is not None:
if g.Name is not None and string.strcmp(g.Name, pn_buf) == 0: return g
g = g.Next
return mlirlite.create_global_int(pool, mod, pn_buf, 64, 0)
def _register_coro_wake(self, trans: HT.Translator*, name: str, wake_idx: int):
"""Record the wake-slot index for an async name"""
if trans is None or name is None or trans._coro_wake_names is None: return
cnt: int = trans._coro_wake_count
if cnt >= HT.MAX_CORO_FUNCS: return
addr: uint64 = uint64(trans._coro_wake_names) + cnt * 8
slot: str* = (str*)(addr)
slot[0] = name
trans._coro_wake_idxs[cnt] = wake_idx
trans._coro_wake_count = cnt + 1
def _lookup_coro_wake(self, trans: HT.Translator*, name: str) -> int:
"""Return the wake-slot index for an async name, or -1 if none"""
if trans is None or name is None or trans._coro_wake_names is None: return -1
i: int = 0
for i in range(int(trans._coro_wake_count)):
addr: uint64 = uint64(trans._coro_wake_names) + i * 8
slot: str* = (str*)(addr)
if slot[0] is not None and string.strcmp(slot[0], name) == 0: return trans._coro_wake_idxs[i]
return -1
# ============================================================
# resume_<name>(%Coro_<name>*) -> i32 state machine (static segment splitting)
def _emit_resume(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, name: str, fd: ast.FunctionDef*, frame_ty: mlirlite.LLVMType*, nparams: int, nlocals: int, nextra: int) -> int:
"""Emit resume_<name>(frame) -> i32 state machine returning done (0=SUSPEND, 1=DONE)"""
if frame_ty is None: return 0
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
frame_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, frame_ty)
rname_buf: str = pool.alloc(64)
if rname_buf is not None: viperlib.snprintf(rname_buf, 64, "resume_%s", name)
mangled: str = self.Gen.FuncGenBase._mangle_func_name(trans, rname_buf, 0)
func: mlirlite.Function* = mlirlite.create_function(pool, mod, mangled, i32_ty)
if func is None: return 0
mlirlite.add_param(pool, func, frame_ptr_ty, "%frame")
funcs_ptr: GenTypes.FuncEntry* = trans._funcs
cur_count: int = trans._func_count
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, cur_count, rname_buf, func, 1024, None, 0, 1, trans.Pool) == 0:
trans._func_count = cur_count + 1
entry: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "entry")
if entry is None: return 0
builder: mlirlite.IRBuilder* = mlirlite.new_builder(pool, func)
if builder is None: return 0
mlirlite.position_at_end(builder, entry)
frame_arg: mlirlite.Value* = mlirlite.SSAValue(pool, frame_ptr_ty, "%frame")
if frame_arg is None: return 0
# GEP the 5 header slots (M4 exc / M7 msg)
i8_ptr_ty_r: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
i64_ty_r: mlirlite.LLVMType* = mlirlite.Int64(pool)
state_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i32_ty, frame_arg, 0)
done_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i32_ty, frame_arg, 1)
ret_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i32_ty, frame_arg, 2)
exc_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i32_ty, frame_arg, 3)
msg_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i8_ptr_ty_r, frame_arg, 4)
# GEP param slots -> register in the symbol table (index 5+i)
pj: int = 0
for pj in range(int(nparams)):
pname: str = self._read_param_name(trans, pj)
if pname is None: continue
pslot: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i32_ty, frame_arg, 5 + pj)
if pslot is not None: self.Gen.VarGen.define_var(trans.SymTab, pname, pslot)
# GEP local slots -> register in the symbol table
li: int = 0
for li in range(int(nlocals)):
lname: str = self._read_local_name(trans, li)
if lname is None: continue
slot: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i32_ty, frame_arg, trans._coro_local_base + li)
if slot is not None: self.Gen.VarGen.define_var(trans.SymTab, lname, slot)
# M3-M7: register extra slots (wake i64 / __f_idx/__f_len/__await i32).
# Name reverse lookup: wake=uses_sleep && ei==0; for=_coro_for_slots; else __await_M.
ei: int = 0
for ei in range(int(nextra)):
ety: int = 1
if trans._coro_extra_types is not None: ety = trans._coro_extra_types[ei]
ety_llvm: mlirlite.LLVMType* = i32_ty
if ety == 2: ety_llvm = i64_ty_r
eslot: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, ety_llvm, frame_arg, trans._coro_extra_base + ei)
if eslot is None: continue
# wake slot: store the pointer only, no SymTab entry (used by coro_run polling)
if trans._coro_uses_sleep != 0 and ei == 0:
trans._coro_wake_ptr = eslot
continue
ename: str = pool.alloc(16)
# for slot: _coro_for_slots[N] is __f_idx_N, +1 is __f_len_N
is_for: int = 0
is_len: int = 0
kf: int = 0
for kf in range(int(trans._coro_scan_for_count)):
if trans._coro_for_slots[kf] == ei:
is_for = 1
break
if trans._coro_for_slots[kf] + 1 == ei:
is_for = 1
is_len = 1
break
if is_for != 0:
if ename is not None:
if is_len != 0: viperlib.snprintf(ename, 16, "__f_len_%d", kf)
else: viperlib.snprintf(ename, 16, "__f_idx_%d", kf)
else:
# __await_M: reverse lookup the position array
found: int = 0
km: int = 0
for km in range(int(nextra)):
if trans._coro_await_slots[km] == ei:
found = 1
break
if found == 0: continue
if ename is not None: viperlib.snprintf(ename, 16, "__await_%d", km)
if ename is not None: self.Gen.VarGen.define_var(trans.SymTab, ename, eslot)
# state value + exit block
state_val: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, state_ptr)
exit_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.exit")
if exit_bb is None: return 0
# save resume context (shared by await suspension / resume blocks)
trans._coro_state_ptr = state_ptr
trans._coro_done_ptr = done_ptr
trans._coro_ret_ptr = ret_ptr
trans._coro_exc_ptr = exc_ptr
trans._coro_msg_ptr = msg_ptr
trans._coro_wake_ptr = None
trans._coro_exit_bb = exit_bb
trans._coro_frame = frame_arg
trans._coro_await_slot = None
# state0 is the entry; await states start at 1 (same DFS order as pre-scan)
trans._coro_next_state = 1
# translation-time indices (same order as pre-scan)
trans._coro_hoist_count = 0
trans._coro_for_counter = 0
trans._coro_try_stack_count = 0
trans._coro_break_bb = None
trans._coro_continue_bb = None
nseg: int = trans._coro_nseg
if nseg <= 0: nseg = 1
# pre-build state blocks + switch cases (one-shot; resume blocks are positioned later)
state_bbs: sizet* = pool.alloc(nseg * 8)
if state_bbs is None: return 0
trans._coro_state_bbs = state_bbs
sw_head: mlirlite.SwitchCase* = None
sw_tail: mlirlite.SwitchCase* = None
sw_count: int = 0
bb_name: str = pool.alloc(32)
si: int = 0
for si in range(int(nseg)):
if bb_name is not None: viperlib.snprintf(bb_name, 32, "state%d", si)
sbb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, bb_name)
if sbb is None: continue
state_bbs[si] = sizet(sbb)
sc: mlirlite.SwitchCase* = mlirlite.new_switch_case(pool, mlirlite.const_int32(pool, si), sbb)
if sc is not None:
if sw_head is None:
sw_head = sc
sw_tail = sc
else:
sw_tail.Next = sc
sw_tail = sc
sw_count = sw_count + 1
mlirlite.build_switch(builder, state_val, exit_bb, sw_head, sw_count)
# ---- state0 entry: recursively translate the whole body (If/While awaits resume dynamically).
# Set trans._cur_builder/_cur_func here; later position_at_end reuse the same builder object.
trans._cur_func = func
trans._cur_builder = builder
# P2 willFree 失效表:协程体是独立帧 → 入口同样复位
self.Gen.VarGen.freed_clear()
seg_bb: mlirlite.BasicBlock* = mlirlite.BasicBlock*(state_bbs[0])
mlirlite.position_at_end(builder, seg_bb)
# M3 硬约束锚点:登记"正在翻译其 body 的协程函数",CallGenCtor._translate_new_expr 据此对
# 协程体内的 `new heap slife` fail-fast(协程体被状态机拆分,释放点无法确定)。
# 保存/恢复旧值:协程里再定义协程时不会把外层的标记冲掉。
old_coro_body: mlirlite.Function* = trans._coro_body_func
trans._coro_body_func = func
self._handle_segment(trans, builder, pool, mod, fd.children)
trans._coro_body_func = old_coro_body
# body fell through -> implicit return (ret=0 + done=1 + br exit)
if mlirlite.builder_cur_block_is_terminated(builder) == 0:
zret: mlirlite.Value* = mlirlite.const_int32(pool, 0)
done_one: mlirlite.Value* = mlirlite.const_int32(pool, 1)
mlirlite.build_store(builder, zret, ret_ptr)
mlirlite.build_store(builder, done_one, done_ptr)
mlirlite.build_br(builder, exit_bb)
# exit block: load done and return (0=SUSPEND 1=DONE)
mlirlite.position_at_end(builder, exit_bb)
done_val: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, done_ptr)
mlirlite.build_ret(builder, done_val)
return 0
# ============================================================
# M2: recursive coroutine-body translation (Return/top-level await/If/While/nested await/plain)
def _handle_segment(self, trans: HT.Translator*, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, stmts: list[ast.AST*]*):
"""Recursively translate coroutine body statements into the current builder block"""
if stmts is None: return
bn: sizet = len(stmts)
i: sizet = 0
for i in range(int(bn)):
stmt: ast.AST* = stmts.get(i)
if stmt is None: continue
# current block is terminated (return/suspend) -> stop this segment
if mlirlite.builder_cur_block_is_terminated(builder) != 0: return
sk: int = stmt.kind()
if sk == ast.ASTKind.Return:
self._emit_coro_return(builder, pool, mod, trans, stmt)
continue
if sk == ast.ASTKind.If:
self._handle_coro_if(trans, builder, pool, mod, stmt)
continue
if sk == ast.ASTKind.While:
self._handle_coro_while(trans, builder, pool, mod, stmt)
continue
if sk == ast.ASTKind.Raise:
self._handle_coro_raise(builder, pool, mod, trans, stmt)
continue
if sk == ast.ASTKind.Try:
tr_: ast.Try* = ast.Try*(stmt)
has_fin: int = 0
has_exc: int = 0
if tr_.finalbody is not None and len(tr_.finalbody) > 0: has_fin = 1
if tr_.handlers is not None and len(tr_.handlers) > 0: has_exc = 1
if has_fin != 0 and has_exc == 0: self._handle_coro_try_finally(trans, builder, pool, mod, stmt)
else: self._handle_coro_try(trans, builder, pool, mod, stmt)
continue
if sk == ast.ASTKind.For:
if self._for_deep_await(stmt) != 0: self._handle_coro_for(trans, builder, pool, mod, stmt)
else:
# no await: delegate to the standard For
self.Gen.StmtGen.translate_stmt(trans, stmt)
continue
if sk == ast.ASTKind.Break:
if trans._coro_break_bb is not None: mlirlite.build_br(builder, trans._coro_break_bb)
continue
if sk == ast.ASTKind.Continue:
if trans._coro_continue_bb is not None: mlirlite.build_br(builder, trans._coro_continue_bb)
continue
if sk == ast.ASTKind.Pass: continue
# top-level await: AnnAssign/Assign/Expr value is Await -> suspend
if sk == ast.ASTKind.AnnAssign or sk == ast.ASTKind.Assign or sk == ast.ASTKind.Expr:
if self._is_await_tail(stmt) != 0:
target_name: str = self._get_assign_target_name(stmt)
if target_name is not None: trans._coro_await_slot = self.Gen.VarGen.lookup_var(trans.SymTab, target_name)
else:
# Expr(await ...): no target slot (discard return), clear stale slot
trans._coro_await_slot = None
aw_node: ast.Await* = self._get_await_value(stmt)
call_name: str = None
if aw_node is not None: call_name = self._extract_await_call_name(aw_node)
if call_name is not None and self.is_async_func(trans, call_name) != 0: self._emit_await(builder, pool, mod, trans, aw_node, call_name)
else:
if call_name is None: call_name = "(unknown)"
err_buf: str = pool.alloc(256)
if err_buf is not None:
viperlib.snprintf(err_buf, 256, "[coro-M2] await target '%s' is not an async function", call_name)
self.Gen.TypeGenErr.fatal_error(stmt, err_buf, "SyntaxError")
continue
# nested await in the statement -> hoist to __await_N
if self._stmt_has_await(stmt) != 0:
self._handle_nested_await_stmt(trans, builder, pool, mod, stmt)
continue
# other statements -> standard translation
self.Gen.StmtGen.translate_stmt(trans, stmt)
# ============================================================
# M2: coroutine If (awaits inside branches suspend/resume in place)
def _handle_coro_if(self, trans: HT.Translator*, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, stmt: ast.AST*):
ifn: ast.If* = ast.If*(stmt)
if ifn is None or ifn.test is None: return
# cif/celif (internal c.@CIf marker): compile-time prune, then translate only the taken branch
# so awaits inside it still suspend in place. The marker must never be lowered as a runtime value.
if self.Gen.StmtGenCif._is_cif_call(ifn.test) == 1:
if self.Gen.StmtGenCif._prune_cif_test(pool, trans, ifn.test) != 0:
self._handle_segment(trans, builder, pool, mod, ifn.children)
else:
self._handle_segment(trans, builder, pool, mod, ifn.orelse)
return
if self._find_expr_await(ifn.test) is not None:
err_b: str = pool.alloc(256)
if err_b is not None:
viperlib.snprintf(err_b, 256, "[coro-M2] await is not supported in an if condition")
self.Gen.TypeGenErr.fatal_error(stmt, err_b, "SyntaxError")
return
cond: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, ifn.test, None, 0, trans)
if cond is None: return
cond1: mlirlite.Value* = self._normalize_cond(builder, pool, mod, cond, trans)
func: mlirlite.Function* = trans._cur_func
then_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.then")
merge_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.end")
has_else: int = 0
if ifn.orelse is not None and len(ifn.orelse) > 0: has_else = 1
else_bb: mlirlite.BasicBlock* = None
if has_else != 0: else_bb = mlirlite.create_block(pool, func, "coro.else")
if cond1 is None or then_bb is None or merge_bb is None: return
mlirlite.build_cond_br(builder, cond1, then_bb, else_bb if else_bb is not None else merge_bb)
# then branch
mlirlite.position_at_end(builder, then_bb)
self._handle_segment(trans, builder, pool, mod, stmt.children)
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, merge_bb)
# else branch
if else_bb is not None:
mlirlite.position_at_end(builder, else_bb)
self._handle_segment(trans, builder, pool, mod, ifn.orelse)
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, merge_bb)
mlirlite.position_at_end(builder, merge_bb)
# ============================================================
# M2: coroutine While (condition block + body; awaits in the body suspend/resume in place)
def _handle_coro_while(self, trans: HT.Translator*, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, stmt: ast.AST*):
whn: ast.While* = ast.While*(stmt)
if whn is None: return
if whn.test is not None and self._find_expr_await(whn.test) is not None:
err_c: str = pool.alloc(256)
if err_c is not None:
viperlib.snprintf(err_c, 256, "[coro-M2] await is not supported in a while condition")
self.Gen.TypeGenErr.fatal_error(stmt, err_c, "SyntaxError")
return
func: mlirlite.Function* = trans._cur_func
cond_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.wcond")
body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.wbody")
after_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.wend")
if cond_bb is None or body_bb is None or after_bb is None: return
# break/continue targets (M2, single level)
prev_break: mlirlite.BasicBlock* = trans._coro_break_bb
prev_cont: mlirlite.BasicBlock* = trans._coro_continue_bb
trans._coro_break_bb = after_bb
trans._coro_continue_bb = cond_bb
mlirlite.build_br(builder, cond_bb)
# condition block
mlirlite.position_at_end(builder, cond_bb)
cond: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, whn.test, None, 0, trans)
if cond is None: cond = mlirlite.const_int32(pool, 0)
cond1: mlirlite.Value* = self._normalize_cond(builder, pool, mod, cond, trans)
mlirlite.build_cond_br(builder, cond1, body_bb, after_bb)
# loop body
mlirlite.position_at_end(builder, body_bb)
self._handle_segment(trans, builder, pool, mod, stmt.children)
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, cond_bb)
trans._coro_break_bb = prev_break
trans._coro_continue_bb = prev_cont
# loop exit block
mlirlite.position_at_end(builder, after_bb)
# ============================================================
# M2: nested await hoisting: a = b + await f() -> __await_N = await f(); a = b + __await_N
def _handle_nested_await_stmt(self, trans: HT.Translator*, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, stmt: ast.AST*):
"""Hoist all nested awaits in a statement to top-level temporaries in DFS order"""
guard: int = 0
while True:
aw_node: ast.Await* = self._extract_first_await(stmt)
if aw_node is None: break
if guard >= HT.MAX_CORO_VARS: break
guard = guard + 1
temp_name: str = pool.alloc(32)
if temp_name is not None: viperlib.snprintf(temp_name, 32, "__await_%d", trans._coro_hoist_count)
# target slot = __await_N extra slot (pre-scan positions, wake/for slots precede)
slot_idx: int = trans._coro_extra_base + trans._coro_await_slots[trans._coro_hoist_count]
trans._coro_hoist_count = trans._coro_hoist_count + 1
slot: mlirlite.Value* = mlirlite.build_gep_struct(builder, self._lookup_coro_frame_by_trans(trans), mlirlite.Int32(pool), trans._coro_frame, slot_idx)
trans._coro_await_slot = slot
call_name: str = self._extract_await_call_name(aw_node)
if call_name is None: break
self._emit_await(builder, pool, mod, trans, aw_node, call_name)
self._replace_await_with_name(pool, stmt, temp_name)
# hoisting done -> standard translation (resume block continues the rest of the expression)
self.Gen.StmtGen.translate_stmt(trans, stmt)
# ============================================================
# Nested-await detection / extraction / replacement (DFS order matches pre-scan)
def _stmt_has_await(self, stmt: ast.AST*) -> int:
aw: ast.Await* = self._extract_first_await(stmt)
if aw is not None: return 1
return 0
def _extract_first_await(self, stmt: ast.AST*) -> ast.Await*:
"""Return the first Await node in a statement (DFS field order), or None"""
if stmt is None: return None
sk: int = stmt.kind()
if sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
return self._find_expr_await(aa.value)
if sk == ast.ASTKind.Assign:
as_: ast.Assign* = ast.Assign*(stmt)
return self._find_expr_await(as_.value)
if sk == ast.ASTKind.Expr:
e: ast.Expr* = ast.Expr*(stmt)
return self._find_expr_await(e.value)
if sk == ast.ASTKind.Return:
rv: ast.Return* = ast.Return*(stmt)
return self._find_expr_await(rv.value)
return None
def _find_expr_await(self, node: ast.AST*) -> ast.Await*:
"""DFS the expression tree for the first Await"""
if node is None: return None
k: int = node.kind()
if k == ast.ASTKind.Await: return ast.Await*(node)
if k == ast.ASTKind.BinOp:
b: ast.BinOp* = ast.BinOp*(node)
r: ast.Await* = self._find_expr_await(b.left)
if r is not None: return r
return self._find_expr_await(b.right)
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
if cl.func is not None:
r2: ast.Await* = self._find_expr_await(cl.func)
if r2 is not None: return r2
if cl.args is not None:
nb: sizet = len(cl.args)
j: sizet = 0
for j in range(int(nb)):
r3: ast.Await* = self._find_expr_await(cl.args.get(j))
if r3 is not None: return r3
return None
if k == ast.ASTKind.UnaryOp:
u: ast.UnaryOp* = ast.UnaryOp*(node)
return self._find_expr_await(u.operand)
if k == ast.ASTKind.Compare:
cm: ast.Compare* = ast.Compare*(node)
r4: ast.Await* = self._find_expr_await(cm.left)
if r4 is not None: return r4
if cm.comparators is not None:
nb2: sizet = len(cm.comparators)
j2: sizet = 0
for j2 in range(int(nb2)):
r5: ast.Await* = self._find_expr_await(cm.comparators.get(j2))
if r5 is not None: return r5
return None
if k == ast.ASTKind.BoolOp:
bo: ast.BoolOp* = ast.BoolOp*(node)
if bo.values is not None:
nb3: sizet = len(bo.values)
j3: sizet = 0
for j3 in range(int(nb3)):
r6: ast.Await* = self._find_expr_await(bo.values.get(j3))
if r6 is not None: return r6
return None
if k == ast.ASTKind.IfExp:
ie: ast.IfExp* = ast.IfExp*(node)
r7: ast.Await* = self._find_expr_await(ie.test)
if r7 is not None: return r7
r8: ast.Await* = self._find_expr_await(ie.body)
if r8 is not None: return r8
return self._find_expr_await(ie.orelse)
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
return self._find_expr_await(at.value)
if k == ast.ASTKind.Subscript:
sb: ast.Subscript* = ast.Subscript*(node)
r9: ast.Await* = self._find_expr_await(sb.value)
if r9 is not None: return r9
return self._find_expr_await(sb.slice)
return None
def _replace_await_with_name(self, pool: memhub.MemBuddy*, stmt: ast.AST*, temp_name: str) -> int:
"""Replace the first Await in a statement tree with Name(temp_name) in DFS order"""
if stmt is None or temp_name is None: return 0
sk: int = stmt.kind()
if sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
if aa.value is not None: return self._replace_expr_await(pool, aa.value, temp_name)
return 0
if sk == ast.ASTKind.Assign:
as_: ast.Assign* = ast.Assign*(stmt)
if as_.value is not None: return self._replace_expr_await(pool, as_.value, temp_name)
return 0
if sk == ast.ASTKind.Expr:
e: ast.Expr* = ast.Expr*(stmt)
if e.value is not None: return self._replace_expr_await(pool, e.value, temp_name)
return 0
if sk == ast.ASTKind.Return:
rv: ast.Return* = ast.Return*(stmt)
if rv.value is not None: return self._replace_expr_await(pool, rv.value, temp_name)
return 0
return 0
def _replace_expr_await(self, pool: memhub.MemBuddy*, node: ast.AST*, temp_name: str) -> int:
"""Replace the first Await in an expression with Name(temp_name); returns 1/0/2"""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Await: return 2
if k == ast.ASTKind.BinOp:
b: ast.BinOp* = ast.BinOp*(node)
r: int = self._replace_expr_await(pool, b.left, temp_name)
if r == 1: return 1
if r == 2:
b.left = ast.Name(pool, temp_name, 0)
return 1
r2: int = self._replace_expr_await(pool, b.right, temp_name)
if r2 == 1: return 1
if r2 == 2:
b.right = ast.Name(pool, temp_name, 0)
return 1
return 0
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
if cl.func is not None:
r3: int = self._replace_expr_await(pool, cl.func, temp_name)
if r3 == 1: return 1
if r3 == 2:
cl.func = ast.Name(pool, temp_name, 0)
return 1
if cl.args is not None:
nb: sizet = len(cl.args)
j: sizet = 0
for j in range(int(nb)):
r4: int = self._replace_expr_await(pool, cl.args.get(j), temp_name)
if r4 == 1: return 1
if r4 == 2:
cl.args.set(j, ast.Name(pool, temp_name, 0))
return 1
return 0
if k == ast.ASTKind.UnaryOp:
u: ast.UnaryOp* = ast.UnaryOp*(node)
r5: int = self._replace_expr_await(pool, u.operand, temp_name)
if r5 == 1: return 1
if r5 == 2:
u.operand = ast.Name(pool, temp_name, 0)
return 1
return 0
if k == ast.ASTKind.Compare:
cm: ast.Compare* = ast.Compare*(node)
r6: int = self._replace_expr_await(pool, cm.left, temp_name)
if r6 == 1: return 1
if r6 == 2:
cm.left = ast.Name(pool, temp_name, 0)
return 1
if cm.comparators is not None:
nb2: sizet = len(cm.comparators)
j2: sizet = 0
for j2 in range(int(nb2)):
r7: int = self._replace_expr_await(pool, cm.comparators.get(j2), temp_name)
if r7 == 1: return 1
if r7 == 2:
cm.comparators.set(j2, ast.Name(pool, temp_name, 0))
return 1
return 0
if k == ast.ASTKind.BoolOp:
bo: ast.BoolOp* = ast.BoolOp*(node)
if bo.values is not None:
nb3: sizet = len(bo.values)
j3: sizet = 0
for j3 in range(int(nb3)):
r8: int = self._replace_expr_await(pool, bo.values.get(j3), temp_name)
if r8 == 1: return 1
if r8 == 2:
bo.values.set(j3, ast.Name(pool, temp_name, 0))
return 1
return 0
if k == ast.ASTKind.IfExp:
ie: ast.IfExp* = ast.IfExp*(node)
r9: int = self._replace_expr_await(pool, ie.test, temp_name)
if r9 == 1: return 1
if r9 == 2:
ie.test = ast.Name(pool, temp_name, 0)
return 1
r10: int = self._replace_expr_await(pool, ie.body, temp_name)
if r10 == 1: return 1
if r10 == 2:
ie.body = ast.Name(pool, temp_name, 0)
return 1
r11: int = self._replace_expr_await(pool, ie.orelse, temp_name)
if r11 == 1: return 1
if r11 == 2:
ie.orelse = ast.Name(pool, temp_name, 0)
return 1
return 0
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
r12: int = self._replace_expr_await(pool, at.value, temp_name)
if r12 == 1: return 1
if r12 == 2:
at.value = ast.Name(pool, temp_name, 0)
return 1
return 0
if k == ast.ASTKind.Subscript:
sb: ast.Subscript* = ast.Subscript*(node)
r13: int = self._replace_expr_await(pool, sb.value, temp_name)
if r13 == 1: return 1
if r13 == 2:
sb.value = ast.Name(pool, temp_name, 0)
return 1
r14: int = self._replace_expr_await(pool, sb.slice, temp_name)
if r14 == 1: return 1
if r14 == 2:
sb.slice = ast.Name(pool, temp_name, 0)
return 1
return 0
return 0
# ============================================================
# Condition normalization (i1 passes through; otherwise icmp ne 0)
def _normalize_cond(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, cond: mlirlite.Value*, trans: HT.Translator*) -> mlirlite.Value*:
"""Normalize a coroutine condition to i1, preferring __bool__ dispatch"""
return self.Gen.ExprGenOps.coerce_cond_to_bool(pool, builder, mod, cond, trans)
# ============================================================
# Parameter-name read (from the _coro_params table)
def _read_param_name(self, trans: HT.Translator*, idx: int) -> str:
if trans is None or trans._coro_params is None: return None
addr: uint64 = uint64(trans._coro_params) + idx * 8
slot: str* = (str*)(addr)
return slot[0]
# ============================================================
# Resume-block lookup (state{N} from the pre-built array)
def _get_state_bb(self, trans: HT.Translator*, state_no: int) -> mlirlite.BasicBlock*:
if trans is None or trans._coro_state_bbs is None: return None
if state_no < 0 or state_no >= trans._coro_nseg: return None
# state_bbs holds block pointers (sizet): index it, never take an element address,
# or position lands on an invalid block and the IR chain corrupts (RenumberSSA crash).
return mlirlite.BasicBlock*(trans._coro_state_bbs[state_no])
def _lookup_coro_frame_by_trans(self, trans: HT.Translator*) -> mlirlite.LLVMType*:
"""Return the frame type of the current coroutine, looked up from the registered frames"""
# resume GEP needs the frame type: take the Pointee of _coro_frame.Ty (Ptr(frame))
if trans is None or trans._coro_frame is None: return None
ft: mlirlite.LLVMType* = trans._coro_frame.Ty
if ft is None: return None
match ft:
case LLVMType.Ptr():
return ft.Pointee
case _:
return None
# ============================================================
# M3: await coro_sleep(ms) - suspend via the wake slot (coro_run polls to resume)
def _emit_await_sleep(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, node: ast.AST*) -> mlirlite.Value*:
"""Emit await coro_sleep(ms): store the wake deadline in the frame and suspend the parent"""
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
if node is None or trans is None or trans._coro_wake_ptr is None: return mlirlite.const_int32(pool, 0)
ms_val: mlirlite.Value* = mlirlite.const_int64(pool, 0)
aw_s: ast.Await* = ast.Await*(node)
if aw_s.value is not None and aw_s.value.kind() == ast.ASTKind.Call:
cl_s: ast.Call* = ast.Call*(aw_s.value)
if cl_s.args is not None and len(cl_s.args) > 0:
ms_v: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cl_s.args.get(0), None, 0, trans)
if ms_v is not None: ms_val = ms_v
# GetTickCount64() + ms(i64)
now_fn: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, "GetTickCount64")
if now_fn is None: now_fn = mlirlite.create_declare(pool, mod, "GetTickCount64", i64_ty)
if now_fn is None: return mlirlite.const_int32(pool, 0)
now_val: mlirlite.Value* = mlirlite.build_call(builder, "GetTickCount64", None, 0, i64_ty, 0)
if now_val is None: return mlirlite.const_int32(pool, 0)
wake_val: mlirlite.Value* = mlirlite.build_add(builder, now_val, ms_val)
if wake_val is None: return mlirlite.const_int32(pool, 0)
mlirlite.build_store(builder, wake_val, trans._coro_wake_ptr)
# parent suspend: new state id + br exit + position the resume block
new_state_s: int = trans._coro_next_state
trans._coro_next_state = new_state_s + 1
st_v_s: mlirlite.Value* = mlirlite.const_int32(pool, new_state_s)
mlirlite.build_store(builder, st_v_s, trans._coro_state_ptr)
mlirlite.build_br(builder, trans._coro_exit_bb)
resume_bb_s: mlirlite.BasicBlock* = self._get_state_bb(trans, new_state_s)
if resume_bb_s is not None: mlirlite.position_at_end(builder, resume_bb_s)
return mlirlite.const_int32(pool, 0)
# ============================================================
# M4-M6: raise inside a coroutine (exception type/value -> frame exc slot)
def _handle_coro_raise(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, stmt: ast.AST*):
"""Handle raise inside a coroutine: store the exception code/message, then jump to the handler or exit"""
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
exc_code: mlirlite.Value* = mlirlite.const_int32(pool, 1)
rz: ast.Raise* = ast.Raise*(stmt)
if rz is not None and rz.exc is not None:
code: int = self._resolve_raise_code(pool, rz.exc)
if code != 0:
exc_code = mlirlite.const_int32(pool, code)
# M7: exception message (first arg of Exception("msg"))
msg_v: mlirlite.Value* = self._resolve_raise_msg(builder, pool, mod, trans, rz.exc)
if msg_v is not None and trans._coro_msg_ptr is not None: mlirlite.build_store(builder, msg_v, trans._coro_msg_ptr)
else:
v: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, rz.exc, None, 0, trans)
if v is not None: exc_code = v
if trans._coro_exc_ptr is not None: mlirlite.build_store(builder, exc_code, trans._coro_exc_ptr)
# jump to try/except or terminate
if trans._coro_try_stack_count > 0:
tbb_r: mlirlite.BasicBlock* = mlirlite.BasicBlock*(trans._coro_try_stack[trans._coro_try_stack_count - 1])
mlirlite.build_br(builder, tbb_r)
else:
done_r: mlirlite.Value* = mlirlite.const_int32(pool, 1)
mlirlite.build_store(builder, done_r, trans._coro_done_ptr)
mlirlite.build_br(builder, trans._coro_exit_bb)
def _resolve_raise_code(self, pool: memhub.MemBuddy*, exc_node: ast.AST*) -> int:
"""Resolve the exception code of a raise target; return 0 for non-exception classes"""
if exc_node is None: return 0
name: str = None
if exc_node.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(exc_node)
name = nm.id
elif exc_node.kind() == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(exc_node)
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
nm2: ast.Name* = ast.Name*(cl.func)
name = nm2.id
elif exc_node.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(exc_node)
name = at.attr
if name is None: return 0
return self.Gen.ClassGenBase.lookup_exception_code(name)
def _resolve_raise_msg(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, exc_node: ast.AST*) -> mlirlite.Value*:
"""Return the exception message expression (first arg of Exception), or None"""
if exc_node is None or exc_node.kind() != ast.ASTKind.Call: return None
cl: ast.Call* = ast.Call*(exc_node)
if cl.args is None or len(cl.args) < 1: return None
return self.Gen.ExprGen.translate_value(builder, pool, mod, cl.args.get(0), None, 0, trans)
# ============================================================
# M5-M6: coroutine try/except (chained multi-except match + type codes + `as e` binding)
def _handle_coro_try(self, trans: HT.Translator*, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, stmt: ast.AST*):
"""Handle try/except inside a coroutine: match handlers in order and propagate unmatched exceptions"""
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
tr: ast.Try* = ast.Try*(stmt)
if tr is None: return
nhandlers: sizet = 0
if tr.handlers is not None: nhandlers = len(tr.handlers)
if nhandlers == 0:
err_t: str = pool.alloc(256)
if err_t is not None:
viperlib.snprintf(err_t, 256, "[coro-M6] try inside a coroutine requires an except clause")
self.Gen.TypeGenErr.fatal_error(stmt, err_t, "SyntaxError")
return
func: mlirlite.Function* = trans._cur_func
except_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.try_except")
merge_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.try_merge")
if except_bb is None or merge_bb is None: return
# push the try stack
if trans._coro_try_stack_count < HT.MAX_CORO_TRY:
trans._coro_try_stack[trans._coro_try_stack_count] = sizet(except_bb)
trans._coro_try_stack_count = trans._coro_try_stack_count + 1
# try body
self._handle_segment(trans, builder, pool, mod, stmt.children)
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, merge_bb)
if trans._coro_try_stack_count > 0: trans._coro_try_stack_count = trans._coro_try_stack_count - 1
# ---- dispatch: match the exc code against each except in order ----
mlirlite.position_at_end(builder, except_bb)
exc_val: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, trans._coro_exc_ptr)
# pre-build check/handler/unmatched blocks
check_bbs: sizet* = pool.alloc((nhandlers + 1) * 8)
handler_bbs: sizet* = pool.alloc(nhandlers * 8)
if check_bbs is None or handler_bbs is None: return
check_bbs[0] = sizet(except_bb)
hi: int = 0
for hi in range(int(nhandlers)):
# [FIX 2026-08-31] build block names with snprintf: "%" is not string formatting here,
# so the old "%d" form produced i32 garbage and triggered a false int->ptr cast error.
chk_name: str = pool.alloc(64)
exc_name: str = pool.alloc(64)
if chk_name is None or exc_name is None: return
viperlib.snprintf(chk_name, 64, "coro.exc_check_%d", hi)
viperlib.snprintf(exc_name, 64, "coro.except_%d", hi)
check_bbs[hi + 1] = sizet(mlirlite.create_block(pool, func, chk_name))
handler_bbs[hi] = sizet(mlirlite.create_block(pool, func, exc_name))
unmatched_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.try_reraise")
codes_buf: int* = pool.alloc(256)
# per handler: the check chain
for hi in range(int(nhandlers)):
h_node: ast.AST* = tr.handlers.get(hi)
check_bb: mlirlite.BasicBlock* = (mlirlite.BasicBlock*)(check_bbs[hi])
mlirlite.position_at_end(builder, check_bb)
catch_all: int = 0
ncodes: int = 0
if h_node is not None:
hd: ast.ExceptHandler* = ast.ExceptHandler*(h_node)
if hd.type is None: catch_all = 1
else:
ename: str = self._exception_type_name(pool, hd.type)
if ename is not None and string.strcmp(ename, "Exception") == 0: catch_all = 1
else: ncodes = self.Gen.ClassGenBase.collect_descendant_codes(ename, codes_buf, 64)
next_check: mlirlite.BasicBlock* = None
if hi + 1 < nhandlers: next_check = mlirlite.BasicBlock*(check_bbs[hi + 1])
else: next_check = unmatched_bb
handler_bb: mlirlite.BasicBlock* = (mlirlite.BasicBlock*)(handler_bbs[hi])
if catch_all != 0: mlirlite.build_br(builder, handler_bb)
else:
match_cond: mlirlite.Value* = None
ci: int = 0
for ci in range(int(ncodes)):
eq_v: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, exc_val, mlirlite.const_int32(pool, codes_buf[ci]))
if match_cond is None: match_cond = eq_v
else: match_cond = mlirlite.build_or(builder, match_cond, eq_v)
if match_cond is None: mlirlite.build_br(builder, next_check)
else: mlirlite.build_cond_br(builder, match_cond, handler_bb, next_check)
# no match: keep propagating (terminate the coroutine again)
mlirlite.position_at_end(builder, unmatched_bb)
done_u: mlirlite.Value* = mlirlite.const_int32(pool, 1)
mlirlite.build_store(builder, done_u, trans._coro_done_ptr)
mlirlite.build_br(builder, trans._coro_exit_bb)
# ---- each except handler ----
for hi in range(int(nhandlers)):
h_node2: ast.AST* = tr.handlers.get(hi)
handler_bb2: mlirlite.BasicBlock* = (mlirlite.BasicBlock*)(handler_bbs[hi])
mlirlite.position_at_end(builder, handler_bb2)
if h_node2 is not None:
hd2: ast.ExceptHandler* = ast.ExceptHandler*(h_node2)
# `as e`: bind the exception code to e's slot (a local collected by _collect_local_names)
if hd2.name is not None and trans._coro_exc_ptr is not None:
e_slot: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, hd2.name)
if e_slot is not None:
hv: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, trans._coro_exc_ptr)
mlirlite.build_store(builder, hv, e_slot)
# clear the frame exc after catching
z_clr: mlirlite.Value* = mlirlite.const_int32(pool, 0)
mlirlite.build_store(builder, z_clr, trans._coro_exc_ptr)
self._handle_segment(trans, builder, pool, mod, h_node2.children)
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, merge_bb)
mlirlite.position_at_end(builder, merge_bb)
def _exception_type_name(self, pool: memhub.MemBuddy*, type_node: ast.AST*) -> str:
"""Return the except type name (Name/Attribute), or None if unavailable"""
if type_node is None: return None
if type_node.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(type_node)
return nm.id
if type_node.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(type_node)
return at.attr
return None
# ============================================================
# M7: coroutine try/finally (no except)
def _handle_coro_try_finally(self, trans: HT.Translator*, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, stmt: ast.AST*):
"""Handle try/finally inside a coroutine: always run fbody, then re-propagate any pending exception"""
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
tr: ast.Try* = ast.Try*(stmt)
if tr is None: return
if tr.finalbody is not None and self._stmts_deep_await(tr.finalbody) != 0:
err_f: str = pool.alloc(256)
if err_f is not None:
viperlib.snprintf(err_f, 256, "[coro-M7] await is not yet supported inside finally")
self.Gen.TypeGenErr.fatal_error(stmt, err_f, "SyntaxError")
return
func: mlirlite.Function* = trans._cur_func
fin_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.fin")
fin_exc_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.fin_exc")
merge_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.fin_merge")
if fin_bb is None or fin_exc_bb is None or merge_bb is None: return
# push the try stack (exceptions jump to finally)
if trans._coro_try_stack_count < HT.MAX_CORO_TRY:
trans._coro_try_stack[trans._coro_try_stack_count] = sizet(fin_bb)
trans._coro_try_stack_count = trans._coro_try_stack_count + 1
self._handle_segment(trans, builder, pool, mod, stmt.children)
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_br(builder, fin_bb)
if trans._coro_try_stack_count > 0: trans._coro_try_stack_count = trans._coro_try_stack_count - 1
# fin_bb: record the exception flag + run fbody
mlirlite.position_at_end(builder, fin_bb)
was_exc: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, mlirlite.build_load(builder, i32_ty, trans._coro_exc_ptr), mlirlite.const_int32(pool, 0))
self._handle_segment(trans, builder, pool, mod, tr.finalbody)
if mlirlite.builder_cur_block_is_terminated(builder) == 0: mlirlite.build_cond_br(builder, was_exc, fin_exc_bb, merge_bb)
# fin_exc: keep propagating the exception after finally
mlirlite.position_at_end(builder, fin_exc_bb)
done_f: mlirlite.Value* = mlirlite.const_int32(pool, 1)
mlirlite.build_store(builder, done_f, trans._coro_done_ptr)
mlirlite.build_br(builder, trans._coro_exit_bb)
mlirlite.position_at_end(builder, merge_bb)
# ============================================================
# M3: coroutine range-For (with await) turned into While (__f_idx/__f_len frame slots)
def _handle_coro_for(self, trans: HT.Translator*, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, stmt: ast.AST*):
"""Lower a range-for loop with await into a while loop over frame index/length slots"""
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
if self._is_range_for(stmt) == 0:
err_g: str = pool.alloc(256)
if err_g is not None:
viperlib.snprintf(err_g, 256, "[coro-M3] For inside a coroutine supports only range iteration (including await)")
self.Gen.TypeGenErr.fatal_error(stmt, err_g, "SyntaxError")
return
fn: ast.For* = ast.For*(stmt)
k: int = trans._coro_for_counter
trans._coro_for_counter = k + 1
idx_idx: int = trans._coro_extra_base + trans._coro_for_slots[k]
len_idx: int = idx_idx + 1
idx_slot: mlirlite.Value* = mlirlite.build_gep_struct(builder, self._lookup_coro_frame_by_trans(trans), i32_ty, trans._coro_frame, idx_idx)
len_slot: mlirlite.Value* = mlirlite.build_gep_struct(builder, self._lookup_coro_frame_by_trans(trans), i32_ty, trans._coro_frame, len_idx)
if idx_slot is None or len_slot is None: return
# start/end (range 1 or 2 args)
cl: ast.Call* = ast.Call*(fn.iter)
start_val: mlirlite.Value* = None
end_val: mlirlite.Value* = None
nargs: int = int(len(cl.args))
if nargs == 1:
start_val = mlirlite.const_int32(pool, 0)
end_val = self.Gen.ExprGen.translate_value(builder, pool, mod, cl.args.get(0), None, 0, trans)
else:
start_val = self.Gen.ExprGen.translate_value(builder, pool, mod, cl.args.get(0), None, 0, trans)
end_val = self.Gen.ExprGen.translate_value(builder, pool, mod, cl.args.get(1), None, 0, trans)
if start_val is None: start_val = mlirlite.const_int32(pool, 0)
if end_val is None: end_val = mlirlite.const_int32(pool, 0)
mlirlite.build_store(builder, start_val, idx_slot)
mlirlite.build_store(builder, end_val, len_slot)
# loop structure
func: mlirlite.Function* = trans._cur_func
cond_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.fcond")
body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.fbody")
after_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.fend")
if cond_bb is None or body_bb is None or after_bb is None: return
prev_break: mlirlite.BasicBlock* = trans._coro_break_bb
prev_cont: mlirlite.BasicBlock* = trans._coro_continue_bb
trans._coro_break_bb = after_bb
trans._coro_continue_bb = cond_bb
mlirlite.build_br(builder, cond_bb)
# condition block
mlirlite.position_at_end(builder, cond_bb)
idx_v: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, idx_slot)
len_v: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, len_slot)
cmp_v: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_SLT, idx_v, len_v)
mlirlite.build_cond_br(builder, cmp_v, body_bb, after_bb)
# loop body: target = idx; <body>; idx += 1
mlirlite.position_at_end(builder, body_bb)
if fn.target is not None and fn.target.kind() == ast.ASTKind.Name:
tn: ast.Name* = ast.Name*(fn.target)
tgt_slot: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, tn.id)
if tgt_slot is not None:
idx_v2: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, idx_slot)
mlirlite.build_store(builder, idx_v2, tgt_slot)
self._handle_segment(trans, builder, pool, mod, stmt.children)
if mlirlite.builder_cur_block_is_terminated(builder) == 0:
idx_v3: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, idx_slot)
one_v: mlirlite.Value* = mlirlite.const_int32(pool, 1)
nxt_v: mlirlite.Value* = mlirlite.build_add(builder, idx_v3, one_v)
mlirlite.build_store(builder, nxt_v, idx_slot)
mlirlite.build_br(builder, cond_bb)
trans._coro_break_bb = prev_break
trans._coro_continue_bb = prev_cont
mlirlite.position_at_end(builder, after_bb)
# ============================================================
# M3/M4/M7: coro.coro_destroy / coro_exception / coro_exception_msg / coro_gather
def translate_coro_destroy(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator*) -> mlirlite.Value*:
"""coro.coro_destroy(frame): return the frame to the free-list pool"""
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
if cl is None or trans is None or cl.args is None or len(cl.args) < 1: return mlirlite.const_int32(pool, 0)
async_name: str = self._var_to_async(trans, cl.args.get(0))
if async_name is None: return mlirlite.const_int32(pool, 0)
frame_ty: mlirlite.LLVMType* = self._lookup_coro_frame(trans, async_name)
if frame_ty is None: return mlirlite.const_int32(pool, 0)
frame_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, frame_ty)
frame_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cl.args.get(0), None, 0, trans)
if frame_val is None: return mlirlite.const_int32(pool, 0)
frame_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, frame_val, frame_ptr_ty)
if frame_ptr is None: return mlirlite.const_int32(pool, 0)
# return to pool: frame's first 8 bytes hold the old head; head = this frame (i64)
pool_gv: mlirlite.GlobalVariable* = self._get_coro_pool(pool, mod, trans, async_name)
# GlobalVariable is not a Value: build a Value view (Ty=Ptr(i64), Name="@coro_pool_<name>")
i64_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i64_ty)
pool_ref_name: str = pool.alloc(96)
if pool_ref_name is not None: viperlib.snprintf(pool_ref_name, 96, "@coro_pool_%s", async_name)
pool_ref: mlirlite.Value* = mlirlite.SSAValue(pool, i64_ptr_ty, pool_ref_name)
pool_head: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, pool_ref)
node_next: mlirlite.Value* = mlirlite.build_bitcast(builder, frame_ptr, i64_ptr_ty)
mlirlite.build_store(builder, pool_head, node_next)
frame_i64: mlirlite.Value* = mlirlite.build_ptrtoint(builder, frame_ptr, i64_ty)
mlirlite.build_store(builder, frame_i64, pool_ref)
return mlirlite.const_int32(pool, 0)
def translate_coro_exception(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator*) -> mlirlite.Value*:
"""coro.coro_exception(frame): return the frame exception code (0 = none)"""
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
if cl is None or trans is None or cl.args is None or len(cl.args) < 1: return mlirlite.const_int32(pool, 0)
async_name: str = self._var_to_async(trans, cl.args.get(0))
if async_name is None: return mlirlite.const_int32(pool, 0)
frame_ty: mlirlite.LLVMType* = self._lookup_coro_frame(trans, async_name)
if frame_ty is None: return mlirlite.const_int32(pool, 0)
frame_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, frame_ty)
frame_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cl.args.get(0), None, 0, trans)
if frame_val is None: return mlirlite.const_int32(pool, 0)
frame_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, frame_val, frame_ptr_ty)
if frame_ptr is None: return mlirlite.const_int32(pool, 0)
exc_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i32_ty, frame_ptr, 3)
return mlirlite.build_load(builder, i32_ty, exc_ptr)
def translate_coro_exception_msg(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator*) -> mlirlite.Value*:
"""coro.coro_exception_msg(frame): return the frame exception message pointer, or null"""
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
if cl is None or trans is None or cl.args is None or len(cl.args) < 1: return mlirlite.ConstNull(pool, i8_ptr_ty, "null")
async_name: str = self._var_to_async(trans, cl.args.get(0))
if async_name is None: return mlirlite.ConstNull(pool, i8_ptr_ty, "null")
frame_ty: mlirlite.LLVMType* = self._lookup_coro_frame(trans, async_name)
if frame_ty is None: return mlirlite.ConstNull(pool, i8_ptr_ty, "null")
frame_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, frame_ty)
frame_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cl.args.get(0), None, 0, trans)
if frame_val is None: return mlirlite.ConstNull(pool, i8_ptr_ty, "null")
frame_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, frame_val, frame_ptr_ty)
if frame_ptr is None: return mlirlite.ConstNull(pool, i8_ptr_ty, "null")
msg_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i8_ptr_ty, frame_ptr, 4)
return mlirlite.build_load(builder, i8_ptr_ty, msg_ptr)
def translate_coro_gather(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator*) -> mlirlite.Value*:
"""coro.coro_gather(frames...): resume all coroutines to completion and return the finished count"""
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
if cl is None or trans is None or cl.args is None: return mlirlite.const_int32(pool, 0)
nb: sizet = len(cl.args)
j: sizet = 0
for j in range(int(nb)):
arg0: ast.AST* = cl.args.get(j)
async_name: str = self._var_to_async(trans, arg0)
if async_name is None: continue
frame_ty: mlirlite.LLVMType* = self._lookup_coro_frame(trans, async_name)
if frame_ty is None: continue
frame_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, frame_ty)
frame_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg0, None, 0, trans)
if frame_val is None: continue
frame_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, frame_val, frame_ptr_ty)
if frame_ptr is None: continue
rn_buf: str = pool.alloc(64)
if rn_buf is not None: viperlib.snprintf(rn_buf, 64, "resume_%s", async_name)
resume_mangled: str = self.Gen.FuncGenBase._mangle_func_name(trans, rn_buf, 0)
if self.Gen.CallGenBase.find_func_in_module(mod, resume_mangled) is None: continue
func: mlirlite.Function* = trans._cur_func
loop_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "gather.loop")
after_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "gather.after")
if loop_bb is None or after_bb is None: continue
mlirlite.build_br(builder, loop_bb)
mlirlite.position_at_end(builder, loop_bb)
frame_ptr.Next = None
status: mlirlite.Value* = mlirlite.build_call(builder, resume_mangled, frame_ptr, 1, i32_ty, 0)
done_cond: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, status, mlirlite.const_int32(pool, 0))
mlirlite.build_cond_br(builder, done_cond, after_bb, loop_bb)
mlirlite.position_at_end(builder, after_bb)
return mlirlite.const_int32(pool, 0)
def _var_to_async(self, trans: HT.Translator*, arg0: ast.AST*) -> str:
"""Map a variable node to its async function name, or None if not found"""
if trans is None or arg0 is None or arg0.kind() != ast.ASTKind.Name: return None
nm: ast.Name* = ast.Name*(arg0)
if nm.id is None: return None
return self._lookup_coro_var(trans, nm.id)
# ============================================================
# await expression translation (shared by segment tail / translate_value Await branch)
def _emit_await(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, node: ast.AST*, call_name: str) -> mlirlite.Value*:
"""Inline-translate await async_fn(args...): create the child frame, resume it to DONE, store its result"""
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i8_ptr_ty_a: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
if node is None or call_name is None or trans is None: return mlirlite.const_int32(pool, 0)
# M3: await coro_sleep(ms) - suspend until the wake time (frame wake slot)
if string.strcmp(call_name, "coro_sleep") == 0: return self._emit_await_sleep(builder, pool, mod, trans, node)
child_frame_ty: mlirlite.LLVMType* = self._lookup_coro_frame(trans, call_name)
if child_frame_ty is None: return mlirlite.const_int32(pool, 0)
child_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, child_frame_ty)
cc_buf: str = pool.alloc(64)
if cc_buf is not None: viperlib.snprintf(cc_buf, 64, "create_%s", call_name)
create_mangled: str = self.Gen.FuncGenBase._mangle_func_name(trans, cc_buf, 0)
rc_buf: str = pool.alloc(64)
if rc_buf is not None: viperlib.snprintf(rc_buf, 64, "resume_%s", call_name)
resume_mangled: str = self.Gen.FuncGenBase._mangle_func_name(trans, rc_buf, 0)
if self.Gen.CallGenBase.find_func_in_module(mod, create_mangled) is None or self.Gen.CallGenBase.find_func_in_module(mod, resume_mangled) is None:
# async functions must be defined first (no forward refs); silently return 0
return mlirlite.const_int32(pool, 0)
# M2: child-call args (await fn(a, b) -> create_fn(a, b))
args_head: mlirlite.Value* = None
args_tail: mlirlite.Value* = None
args_count: int = 0
aw_n: ast.Await* = ast.Await*(node)
if aw_n.value is not None and aw_n.value.kind() == ast.ASTKind.Call:
cl_a: ast.Call* = ast.Call*(aw_n.value)
if cl_a.args is not None:
nb: sizet = len(cl_a.args)
j: sizet = 0
for j in range(int(nb)):
av: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cl_a.args.get(j), None, 0, trans)
if av is None: av = mlirlite.const_int32(pool, 0)
av.Next = None
if args_head is None:
args_head = av
args_tail = av
else:
args_tail.Next = av
args_tail = av
args_count = args_count + 1
# child create (M2 with args) + resume
child: mlirlite.Value* = mlirlite.build_call(builder, create_mangled, args_head, args_count, child_ptr_ty, 0)
if child is None: return mlirlite.const_int32(pool, 0)
child.Next = None
mlirlite.build_call(builder, resume_mangled, child, 1, i32_ty, 0)
# GEP the child ret slot [0,2] and load
child_ret_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, child_frame_ty, i32_ty, child, 2)
child_ret: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, child_ret_ptr)
# M4: child exception propagation - child exc != 0 -> parent exc/msg + jump try or terminate
child_exc_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, child_frame_ty, i32_ty, child, 3)
child_exc: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, child_exc_ptr)
zero32_x: mlirlite.Value* = mlirlite.const_int32(pool, 0)
exc_ok_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, trans._cur_func, "coro.ok")
exc_bad_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, trans._cur_func, "coro.exc")
if exc_ok_bb is None or exc_bad_bb is None: return mlirlite.const_int32(pool, 0)
exc_cond: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, child_exc, zero32_x)
mlirlite.build_cond_br(builder, exc_cond, exc_ok_bb, exc_bad_bb)
# exc path: parent exc/msg slots + jump to try or terminate
mlirlite.position_at_end(builder, exc_bad_bb)
if trans._coro_exc_ptr is not None: mlirlite.build_store(builder, child_exc, trans._coro_exc_ptr)
if trans._coro_msg_ptr is not None:
child_msg_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, child_frame_ty, i8_ptr_ty_a, child, 4)
child_msg: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty_a, child_msg_ptr)
mlirlite.build_store(builder, child_msg, trans._coro_msg_ptr)
if trans._coro_try_stack_count > 0:
tbb: mlirlite.BasicBlock* = mlirlite.BasicBlock*(trans._coro_try_stack[trans._coro_try_stack_count - 1])
mlirlite.build_br(builder, tbb)
else:
done_x: mlirlite.Value* = mlirlite.const_int32(pool, 1)
mlirlite.build_store(builder, done_x, trans._coro_done_ptr)
mlirlite.build_br(builder, trans._coro_exit_bb)
# normal path: store into the parent await slot (if any) + suspend
mlirlite.position_at_end(builder, exc_ok_bb)
slot: mlirlite.Value* = trans._coro_await_slot
if slot is not None: mlirlite.build_store(builder, child_ret, slot)
# parent suspend: new state id (matching the pre-scan DFS order)
new_state: int = trans._coro_next_state
trans._coro_next_state = new_state + 1
st_v: mlirlite.Value* = mlirlite.const_int32(pool, new_state)
mlirlite.build_store(builder, st_v, trans._coro_state_ptr)
mlirlite.build_br(builder, trans._coro_exit_bb)
# resume block state{new_state}: position immediately (pre-built case; translation continues)
resume_bb: mlirlite.BasicBlock* = self._get_state_bb(trans, new_state)
if resume_bb is not None: mlirlite.position_at_end(builder, resume_bb)
return child_ret
# ============================================================
# coro.coro_run(frame) inline translation
def translate_coro_run(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator*) -> mlirlite.Value*:
"""Inline-translate coro.coro_run(frame): resolve the async name, resume to DONE, and return the result"""
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
if cl is None or trans is None: return mlirlite.const_int32(pool, 0)
if cl.args is None or len(cl.args) < 1: return mlirlite.const_int32(pool, 0)
arg0: ast.AST* = cl.args.get(0)
if arg0 is None or arg0.kind() != ast.ASTKind.Name: return mlirlite.const_int32(pool, 0)
nm: ast.Name* = ast.Name*(arg0)
if nm.id is None: return mlirlite.const_int32(pool, 0)
async_name: str = self._lookup_coro_var(trans, nm.id)
if async_name is None: return mlirlite.const_int32(pool, 0)
frame_ty: mlirlite.LLVMType* = self._lookup_coro_frame(trans, async_name)
if frame_ty is None: return mlirlite.const_int32(pool, 0)
frame_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, frame_ty)
# frame variable value (i8* / Ptr(frame)) -> bitcast to Ptr(frame)
frame_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg0, None, 0, trans)
if frame_val is None: return mlirlite.const_int32(pool, 0)
frame_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, frame_val, frame_ptr_ty)
if frame_ptr is None: return mlirlite.const_int32(pool, 0)
func: mlirlite.Function* = trans._cur_func
if func is None: return mlirlite.const_int32(pool, 0)
rn_buf: str = pool.alloc(64)
if rn_buf is not None: viperlib.snprintf(rn_buf, 64, "resume_%s", async_name)
resume_mangled: str = self.Gen.FuncGenBase._mangle_func_name(trans, rn_buf, 0)
if self.Gen.CallGenBase.find_func_in_module(mod, resume_mangled) is None: return mlirlite.const_int32(pool, 0)
loop_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.loop")
check_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.check")
after_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.after")
if loop_bb is None or check_bb is None or after_bb is None: return mlirlite.const_int32(pool, 0)
mlirlite.build_br(builder, loop_bb)
mlirlite.position_at_end(builder, loop_bb)
frame_ptr.Next = None
status: mlirlite.Value* = mlirlite.build_call(builder, resume_mangled, frame_ptr, 1, i32_ty, 0)
zero_cmp: mlirlite.Value* = mlirlite.const_int32(pool, 0)
done_cond: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, status, zero_cmp)
mlirlite.build_cond_br(builder, done_cond, after_bb, check_bb)
# M3: SUSPEND -> wake polling (continue if GetTickCount64 >= wake or wake==0, else Sleep(1))
mlirlite.position_at_end(builder, check_bb)
wake_idx_r: int = self._lookup_coro_wake(trans, async_name)
if wake_idx_r < 0: mlirlite.build_br(builder, loop_bb)
else:
i64_ty_r2: mlirlite.LLVMType* = mlirlite.Int64(pool)
wake_ptr_r: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i64_ty_r2, frame_ptr, wake_idx_r)
wake_v_r: mlirlite.Value* = mlirlite.build_load(builder, i64_ty_r2, wake_ptr_r)
now_v_r: mlirlite.Value* = mlirlite.build_call(builder, "GetTickCount64", None, 0, i64_ty_r2, 0)
not_wake_r: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, wake_v_r, mlirlite.const_int64(pool, 0))
due_r: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_UGE, now_v_r, wake_v_r)
cont_r: mlirlite.Value* = mlirlite.build_or(builder, not_wake_r, due_r)
wsleep_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.wsleep")
wdue_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "coro.wdue")
mlirlite.build_cond_br(builder, cont_r, wdue_bb, wsleep_bb)
mlirlite.position_at_end(builder, wsleep_bb)
sleep_one: mlirlite.Value* = mlirlite.const_int32(pool, 1)
mlirlite.build_call(builder, "Sleep", sleep_one, 1, mlirlite.Void(pool), 0)
mlirlite.build_br(builder, loop_bb)
mlirlite.position_at_end(builder, wdue_bb)
mlirlite.build_br(builder, loop_bb)
mlirlite.position_at_end(builder, after_bb)
ret_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, frame_ty, i32_ty, frame_ptr, 2)
ret_val: mlirlite.Value* = mlirlite.build_load(builder, i32_ty, ret_ptr)
return ret_val
# ============================================================
# Public queries / registration
def is_async_func(self, trans: HT.Translator*, name: str) -> int:
"""Return whether name is registered as an async function (1 = yes, 0 = no)"""
if trans is None or name is None or trans._async_funcs is None: return 0
cnt: int = trans._async_func_count
i: int = 0
for i in range(int(cnt)):
addr: uint64 = uint64(trans._async_funcs) + i * 8
slot: str* = (str*)(addr)
if slot[0] is not None and string.strcmp(slot[0], name) == 0: return 1
return 0
def register_coro_var(self, trans: HT.Translator*, var_name: str, func_name: str) -> int:
"""Record the variable-name to async-function-name mapping used by coro.coro_run"""
if trans is None or var_name is None or func_name is None: return 1
if trans._coro_var_async is None: return 1
cnt: int = trans._coro_var_async_count
i: int = 0
for i in range(int(cnt)):
addr: uint64 = uint64(trans._coro_var_async) + i * 16
pair: str* = (str*)(addr)
if pair[0] is not None and string.strcmp(pair[0], var_name) == 0: return 0
if cnt >= HT.MAX_CORO_VARS: return 1
pair2_addr: uint64 = uint64(trans._coro_var_async) + cnt * 16
pair2: str* = (str*)(pair2_addr)
pair2[0] = var_name
pair2[1] = func_name
trans._coro_var_async_count = cnt + 1
return 0
def _register_async_func(self, trans: HT.Translator*, name: str) -> int:
"""Register an async function bare name in the _async_funcs table"""
if trans is None or name is None or trans._async_funcs is None: return 1
cnt: int = trans._async_func_count
i: int = 0
for i in range(int(cnt)):
addr: uint64 = uint64(trans._async_funcs) + i * 8
slot: str* = (str*)(addr)
if slot[0] is not None and string.strcmp(slot[0], name) == 0: return 0
if cnt >= HT.MAX_CORO_FUNCS: return 1
addr2: uint64 = uint64(trans._async_funcs) + cnt * 8
slot2: str* = (str*)(addr2)
slot2[0] = name
trans._async_func_count = cnt + 1
return 0
def _register_coro_frame(self, trans: HT.Translator*, name: str, frame_ty: mlirlite.LLVMType*) -> int:
"""Register an async name to frame type in the _coro_frames table"""
if trans is None or name is None or frame_ty is None: return 1
if trans._coro_frames is None: return 1
cnt: int = trans._coro_frame_count
i: int = 0
for i in range(int(cnt)):
addr: uint64 = uint64(trans._coro_frames) + i * 16
pair: str* = (str*)(addr)
if pair[0] is not None and string.strcmp(pair[0], name) == 0:
pair[1] = str(frame_ty)
return 0
if cnt >= HT.MAX_CORO_FUNCS: return 1
pair2_addr: uint64 = uint64(trans._coro_frames) + cnt * 16
pair2: str* = (str*)(pair2_addr)
pair2[0] = name
pair2[1] = str(frame_ty)
trans._coro_frame_count = cnt + 1
return 0
def _lookup_coro_frame(self, trans: HT.Translator*, name: str) -> mlirlite.LLVMType*:
"""Look up the frame type by async name, or None if not found"""
if trans is None or name is None or trans._coro_frames is None: return None
cnt: int = trans._coro_frame_count
i: int = 0
for i in range(int(cnt)):
addr: uint64 = uint64(trans._coro_frames) + i * 16
pair: str* = (str*)(addr)
if pair[0] is not None and string.strcmp(pair[0], name) == 0:
if pair[1] is not None:
ty_addr: sizet = sizet(pair[1])
return (mlirlite.LLVMType*)(ty_addr)
return None
return None
def _lookup_coro_var(self, trans: HT.Translator*, var_name: str) -> str:
"""Reverse-look-up the async function name by variable name, or None if not found"""
if trans is None or var_name is None or trans._coro_var_async is None: return None
cnt: int = trans._coro_var_async_count
i: int = 0
for i in range(int(cnt)):
addr: uint64 = uint64(trans._coro_var_async) + i * 16
pair: str* = (str*)(addr)
if pair[0] is not None and string.strcmp(pair[0], var_name) == 0: return pair[1]
return None
# ============================================================
# Segment-translation helpers
def _emit_coro_return(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, stmt: ast.AST*) -> None:
"""Special-case Return inside a coroutine: store the return slot, set done, and branch to exit"""
rv_node: ast.Return* = ast.Return*(stmt)
ret_val: mlirlite.Value* = None
if rv_node is not None and rv_node.value is not None:
if rv_node.value.kind() != ast.ASTKind.Await: ret_val = self.Gen.ExprGen.translate_value(builder, pool, mod, rv_node.value, None, 0, trans)
if ret_val is None: ret_val = mlirlite.const_int32(pool, 0)
mlirlite.build_store(builder, ret_val, trans._coro_ret_ptr)
done_one: mlirlite.Value* = mlirlite.const_int32(pool, 1)
mlirlite.build_store(builder, done_one, trans._coro_done_ptr)
mlirlite.build_br(builder, trans._coro_exit_bb)
def _expr_await_is_sleep(self, aw: ast.Await*) -> int:
"""Return whether an Await target is coro_sleep (Name or coro.coro_sleep)"""
if aw is None or aw.value is None: return 0
if aw.value.kind() != ast.ASTKind.Call: return 0
cl: ast.Call* = ast.Call*(aw.value)
if cl.func is None: return 0
if cl.func.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(cl.func)
if nm.id is not None and string.strcmp(nm.id, "coro_sleep") == 0: return 1
elif cl.func.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(cl.func)
if at.attr is not None and string.strcmp(at.attr, "coro_sleep") == 0: return 1
return 0
def _is_await_tail(self, stmt: ast.AST*) -> int:
"""Return whether a statement is a segment tail, i.e. its value is directly an Await"""
if stmt is None: return 0
sk: int = stmt.kind()
if sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
if aa.value is not None and aa.value.kind() == ast.ASTKind.Await: return 1
elif sk == ast.ASTKind.Assign:
as_: ast.Assign* = ast.Assign*(stmt)
if as_.value is not None and as_.value.kind() == ast.ASTKind.Await:
if as_.targets is not None and len(as_.targets) == 1: return 1
elif sk == ast.ASTKind.Expr:
e: ast.Expr* = ast.Expr*(stmt)
if e.value is not None and e.value.kind() == ast.ASTKind.Await: return 1
return 0
def _get_assign_target_name(self, stmt: ast.AST*) -> str:
"""Return the target name of an AnnAssign/Assign to a single Name, or None"""
if stmt is None: return None
sk: int = stmt.kind()
if sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
if aa.target is not None and aa.target.kind() == ast.ASTKind.Name:
tn: ast.Name* = ast.Name*(aa.target)
return tn.id
elif sk == ast.ASTKind.Assign:
as_: ast.Assign* = ast.Assign*(stmt)
if as_.targets is not None and len(as_.targets) == 1:
t0: ast.AST* = as_.targets.get(0)
if t0 is not None and t0.kind() == ast.ASTKind.Name:
tn2: ast.Name* = ast.Name*(t0)
return tn2.id
return None
def _get_await_value(self, stmt: ast.AST*) -> ast.Await*:
"""Return the Await value node of a segment-tail statement, or None"""
if stmt is None: return None
sk: int = stmt.kind()
if sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
if aa.value is not None and aa.value.kind() == ast.ASTKind.Await: return ast.Await*(aa.value)
elif sk == ast.ASTKind.Assign:
as_: ast.Assign* = ast.Assign*(stmt)
if as_.value is not None and as_.value.kind() == ast.ASTKind.Await: return ast.Await*(as_.value)
elif sk == ast.ASTKind.Expr:
ex: ast.Expr* = ast.Expr*(stmt)
if ex.value is not None and ex.value.kind() == ast.ASTKind.Await: return ast.Await*(ex.value)
return None
def _extract_await_call_name(self, aw_node: ast.Await*) -> str:
"""Extract the async function name or coro_sleep from an Await call, or None"""
if aw_node is None or aw_node.value is None: return None
if aw_node.value.kind() == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(aw_node.value)
if cl.func is not None:
if cl.func.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(cl.func)
return nm.id
if cl.func.kind() == ast.ASTKind.Attribute:
# coro.coro_sleep(ms): M3 sleep suspension
at: ast.Attribute* = ast.Attribute*(cl.func)
if at.attr is not None and string.strcmp(at.attr, "coro_sleep") == 0: return "coro_sleep"
return None
def _read_local_name(self, trans: HT.Translator*, idx: int) -> str:
"""Return the idx-th local variable name from trans._coro_locals, or None if out of range"""
if trans is None or trans._coro_locals is None: return None
addr: uint64 = uint64(trans._coro_locals) + idx * 8
slot: str* = (str*)(addr)
return slot[0]