Files

1190 lines
80 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Func.FuncGenMixin as FuncGenMixin
from stdint import *
import ast
import mlirlite
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
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.DebugInfoGen as DebugInfoGen
# FuncGenDef - function declaration / translate_function_def / decorator wrapper
# ============================================================
novirtual class FuncGenDef(CallGenBase.CallGenBase):
"""Function declarations, translate_function_def, and decorator wrappers."""
# _register_func_auto_addr_modes - record which parameters of fd are value references (&T / &temp T).
# Registered under the emitted symbol name so call sites can auto-address the argument.
def _register_func_auto_addr_modes(self, trans: HT.Translator*, name: str, fd: ast.FunctionDef*) -> int:
"""Register per-parameter value-reference modes for a compiled function definition."""
if trans is None or name is None or fd is None or fd.args is None: return 0
ags: ast.Arguments* = ast.Arguments*(fd.args)
if ags.args is None: return 0
alist: list[ast.AST*]* = ags.args
an: sizet = len(alist)
buf: bytes = stdlib.malloc(an + 1)
if buf is None: return 0
cnt: int = 0
has_ref: int = 0
imp: str = trans._imported_modules
for i in range(int(an)):
arg: ast.Arg* = ast.Arg*(alist.get(i))
if arg is None or arg.arg is None: continue
# CVoid params are not emitted into the signature, so they must not occupy a mode slot.
if arg.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imp) != 0: continue
m: int = 0
if arg.annotation is not None: m = self.Gen.TypeGenResolve.ref_param_mode(arg.annotation, imp)
if m == 1: buf[cnt] = char(49)
elif m == 2: buf[cnt] = char(50)
else: buf[cnt] = char(48)
if m != 0: has_ref = 1
cnt += 1
buf[cnt] = char(0)
if has_ref != 0: self.Gen.CallGenBase._register_auto_addr_modes(name, buf, cnt)
stdlib.free(buf)
return 1
# forward_declare_functions - pre-scan all top-level FunctionDefs and emit forward declares.
# Solves same-module forward references; translate_function_def later reuses the declare via find_func_in_module.
def forward_declare_functions(self, trans: HT.Translator*, tree: ast.AST*) -> int:
"""Pre-scan all top-level FunctionDefs and create forward declarations."""
if trans is None or tree is None: return 0
pool: memhub.MemBuddy* = trans.Pool
mod: mlirlite.LLVMModule* = trans.Module
if pool is None or mod is None: return 0
imported_modules: str = trans._imported_modules
from_imports: str = trans._from_imports
funcs_ptr: GenTypes.FuncEntry* = trans._funcs
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
ch: list[ast.AST*]* = tree.children
if ch is None: return 0
# Temporarily switch to declare-only mode while parsing signatures: generic class annotations trigger
# specialization, and translating method bodies now would forward-reference not-yet-declared functions.
saved_declare_only: int = trans._declare_only
trans._declare_only = 1
cn_count: sizet = len(ch)
# Pre-register this module's ClassDefs in declare-only mode (struct+fields+method declares, no bodies):
# stage 1a cannot resolve dependent CTypedef (vpi not yet injected); re-registering here keeps annotations resolvable.
pre_i: sizet
for pre_i in range(int(cn_count)):
pre_child: ast.AST* = ch.get(pre_i)
if pre_child is None: continue
if pre_child.kind() != ast.ASTKind.ClassDef: continue
pre_cd: ast.ClassDef* = ast.ClassDef*(pre_child)
if pre_cd is None or pre_cd.name is None: continue
pre_ex: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(pre_cd.name, trans.ModuleSha1)
if pre_ex is not None and pre_ex.FieldCount > 0: continue
self.Gen.ClassGenDef.translate_class_def(trans, pre_child)
for ci in range(int(cn_count)):
child: ast.AST* = ch.get(ci)
if child is None: continue
if child.kind() != ast.ASTKind.FunctionDef: continue
fd: ast.FunctionDef* = ast.FunctionDef*(child)
if fd is None or fd.name is None: continue
# PEP 695 generic functions emit no forward declare; they register as templates and are cloned by
# _specialize_generic_function at call sites. Old behavior (mapping T to int, compiling as ordinary) is gone.
if fd.type_params is not None:
self.Gen.ClassGenGeneric._register_generic_func_template(pool, fd, trans.ModuleSha1)
continue
tp_names_fd: list[str]* = None
# Infer the return type
ret_ty: mlirlite.LLVMType* = None
if fd.returns is not None:
# Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return).
self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a function return type")
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
# A lone CExport return annotation is an export modifier with no real type -> Void
# (CExport fell back to i32, forcing the tail check to return i32: mm.vp free root cause).
if fd.returns.kind() == ast.ASTKind.Name:
ce_at: ast.Name* = ast.Name*(fd.returns)
if ce_at.id is not None and string.strcmp(ce_at.id, "CExport") == 0: ret_ty = mlirlite.Void(pool)
if ret_ty is None and fd.returns is not None:
if self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0) != 0: ret_ty = mlirlite.Void(pool)
# @c.CReturn decorator removed: encountering it is a compile-time error (use -> tuple[...] for multiple returns)
if ret_ty is None: self.Gen.FuncGenBase.RejectCReturnDecorator(fd, fd.decorator_list)
if ret_ty is None and fd.returns is not None:
# Return annotation present but unresolvable: compile-time error, no silent i32 fallback
# (that forced return i32 and misaligned cross-module return types). No annotation keeps i32.
self.Gen.TypeGenErr.fatal_error(fd, "function return type resolution failed: annotation is not resolvable", "TypeError")
return 0
if ret_ty is None: ret_ty = i32_ty
# Detect CExtern/State/CExport
has_extern: int = 0
has_state: int = 0
has_export: int = 0
if fd.returns is not None:
has_extern = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "CExtern", 0)
has_state = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0)
has_export = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "CExport", 0)
# Decorator-form storage markers (@t.CExport/@t.CExtern/@t.State/@t.CStatic), aligned with TPC
# HandlesFunctions._extract_decorator_storage_flags. Use ClassGenBase._has_decorator, not self. (NoVTable).
if fd.decorator_list is not None:
if has_extern == 0 and self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "CExtern") == 1: has_extern = 1
if has_state == 0 and self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "State") == 1: has_state = 1
if has_export == 0 and self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "CExport") == 1: has_export = 1
is_extern_decl: int = 0
if has_extern != 0 or has_state != 0: is_extern_decl = 1
# static (no cross-module use): recognize both the annotation form and the decorator form and register;
# logic is kept in _RegisterStaticFunc (inlining a new branch would disturb this function's type inference).
self._RegisterStaticFunc(trans, fd)
# Compute the mangled name
if has_extern != 0 or has_state != 0 or has_export != 0 or string.strcmp(fd.name, "main") == 0: mangled_name: str = fd.name
else: mangled_name = self.Gen.FuncGenBase._mangle_func_name(trans, fd.name, 0)
# Record value-reference (&T / &temp T) parameter modes for call-site auto-address.
self._register_func_auto_addr_modes(trans, mangled_name, fd)
# Skip if the function already exists (e.g. duplicate definition)
existing: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name)
if existing is not None:
if tp_names_fd is not None: self.Gen.TypeGenBase.clear_generic_context()
continue
# Create the declare
func: mlirlite.Function* = mlirlite.create_declare(pool, mod, mangled_name, ret_ty)
if func is None:
if tp_names_fd is not None: self.Gen.TypeGenBase.clear_generic_context()
continue
# Extract default-argument info. Note: assign the attribute to an explicitly typed list[...]* local before
# calling __len__(), otherwise the list type is not recognized and the GEP base becomes i32 0 (llc error).
fd_args_node: ast.Arguments* = fd.args
fd_defaults: rawptr = None
fd_default_count: int = 0
fd_param_count: int = 0
if fd_args_node is not None:
fd_ags: ast.Arguments* = ast.Arguments*(fd_args_node)
if fd_ags.args is not None:
fd_alist: list[ast.AST*]* = fd_ags.args
fd_param_count = int(len(fd_alist))
if fd_ags.defaults is not None:
fd_dlist: list[ast.AST*]* = fd_ags.defaults
fd_defaults = fd_dlist
fd_default_count = int(len(fd_dlist))
# Register in the function table (using bare fd.name, not mangled_name)
max_funcs: int = 1024
cur_count: int = trans._func_count
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, cur_count, fd.name, func, max_funcs, fd_defaults, fd_default_count, fd_param_count, trans.Pool) == 0:
trans._func_count = cur_count + 1
# Register the CExport function in the global table
if (has_export != 0 or has_state != 0) and trans.ModuleSha1 is not None: self.Gen.CallGenBase.register_cexport_func(trans.ModuleSha1, fd.name)
# Add parameters
args_node: ast.Arguments* = fd.args
if args_node is not None:
ags: ast.Arguments* = ast.Arguments*(args_node)
if ags.args is not None:
alist: list[ast.AST*]* = ags.args
an: sizet = len(alist)
for ai in range(int(an)):
arg: ast.Arg* = ast.Arg*(alist.get(ai))
if arg is None or arg.arg is None: continue
# CVoid means empty param: skip
if arg.annotation is not None:
if self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imported_modules) != 0: continue
param_ty: mlirlite.LLVMType* = i32_ty
if arg.annotation is not None:
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0)
if resolved is None:
# [S4 fail-fast] Annotation present but unresolvable: no silent i32
# (declare/define signature mismatch -> ABI misalignment, runtime reads garbage from the stack)
self.Gen.TypeGenErr.fatal_error(arg, f"forward declaration param '{arg.arg}' type annotation failed to resolve", "NameError")
return 0
# Bare class param -> Ptr(struct) (object is a pointer); CArray param -> element pointer ty* (C array decay).
# Exception (keep in sync in 3 places): C ABI extern bare struct params pass by value %Struct with a full definition.
match resolved:
case mlirlite.LLVMType.Void():
# void is not a legal param type (void is only for void* and typedef of an opaque struct);
# fail-fast at compile time instead of emitting invalid IR. `void*` is already i8* upstream.
self.Gen.TypeGenErr.fatal_error(arg, f"param '{arg.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
return 0
case mlirlite.LLVMType.Struct(_, _, _):
if is_extern_decl != 0 and self.Gen.StructGen.find_struct_by_type(resolved) is not None:
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, resolved)
param_ty = resolved
else: param_ty = mlirlite.Ptr(pool, resolved)
case mlirlite.LLVMType.Array(aelem_ty, _):
param_ty = mlirlite.Ptr(pool, aelem_ty)
case _:
param_ty = resolved
pname: str = pool.alloc(32)
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
mlirlite.add_param(pool, func, param_ty, pname)
# *args variadic: set IsVarArg=1 to emit the ... suffix
if ags.vararg is not None: func.IsVarArg = 1
# Params added; fill the global func-table Spec (lets vtable derive type text after func is reset)
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func)
# Clear the generic-func context once used (prevents leaking into later functions' annotation resolution)
if tp_names_fd is not None: self.Gen.TypeGenBase.clear_generic_context()
# Restore the original declare mode
trans._declare_only = saved_declare_only
return 0
# translate_function_def - translate a function definition FunctionDef(name, args, body, ...).
# Single-trans arg mode: all shared state comes from trans; local scope via enter_scope/exit_scope.
def translate_function_def(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a function definition; returns the number of added variables (usually 0)."""
fd: ast.FunctionDef* = ast.FunctionDef*(node)
if fd is None or fd.name is None: return 0
# PEP 695 generic function templates are not emitted directly (same rule as forward_declare_functions):
# clones are emitted at call sites by _specialize_generic_function; here we only ensure the template is registered.
if fd.type_params is not None:
self.Gen.ClassGenGeneric._register_generic_func_template(trans.Pool, fd, trans.ModuleSha1)
return 0
# async def (VPC-M1 coroutine): FLAG_IS_ASYNC=1 (parser stores async def as FunctionDef with flags & 1)
# -> AsyncGenMixin state machine (create_<name>/resume_<name>); early-return so the plain path cannot suspend/resume.
if fd.flags is not None and (fd.flags & 1) != 0: return self.Gen.AsyncGen.translate_async_function(trans, node)
pool: memhub.MemBuddy* = trans.Pool
mod: mlirlite.LLVMModule* = trans.Module
imported_modules: str = trans._imported_modules
from_imports: str = trans._from_imports
funcs_ptr: GenTypes.FuncEntry* = trans._funcs
func_count: int = trans._func_count
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
# Infer the return type: prefer the return annotation
ret_ty: mlirlite.LLVMType* = None
if fd.returns is not None:
# Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return).
self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a function return type")
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
# A lone CExport return annotation is an export modifier with no real type -> Void
# (CExport fell back to i32, forcing return i32: mm.vp free root cause). Mixed types keep the resolved result.
if fd.returns.kind() == ast.ASTKind.Name:
ce_at2: ast.Name* = ast.Name*(fd.returns)
if ce_at2.id is not None and string.strcmp(ce_at2.id, "CExport") == 0: ret_ty = mlirlite.Void(pool)
# If the return annotation is purely a decorator marker (e.g. t.State, no real type), use void.
# Must be checked before infer_return_type, which would return at least i32.
if ret_ty is None and fd.returns is not None:
if self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0) != 0: ret_ty = mlirlite.Void(pool)
# @c.CReturn decorator removed: encountering it is a compile-time error (use -> tuple[...] for multiple returns)
if ret_ty is None: self.Gen.FuncGenBase.RejectCReturnDecorator(fd, fd.decorator_list)
# If still None, infer by scanning return statements (at least i32)
if ret_ty is None:
# fail-fast: no return annotation (fd.returns is None) but the body returns a non-None value -> error, no silent
# inference. Returning a value requires an explicit -> annotation; unannotated functions allow only procedure form.
if fd.returns is None:
if self.Gen.TypeGenErr.body_has_value_return(pool, fd.children) != 0:
rtn_err: str = pool.alloc(256)
if rtn_err is not None:
viperlib.snprintf(rtn_err, 256, "function '%s' has no return type annotation but returns a non-None value, add an explicit return type annotation (e.g. -> int)", fd.name)
self.Gen.TypeGenErr.fatal_error(fd, rtn_err, "TypeError")
return 0
self.Gen.TypeGenErr.fatal_error(fd, "function has no return type annotation but returns a non-None value", "TypeError")
return 0
param_types_str: str = self.Gen.TypeGenErr.build_param_types_str(pool, fd.args)
ret_ty = self.Gen.TypeGenErr.infer_return_type(pool, fd.children, param_types_str)
# Detect an extern declaration (t.CExtern or t.State): t.CExtern ignores the body and emits only a declare;
# t.State = t.CExtern + t.CExport. t.CExtern/t.State/t.CExport get no SHA1 prefix and map to C library names.
is_extern_decl: int = 0
has_export: int = 0
has_extern: int = 0
has_state: int = 0
if fd.returns is not None:
has_extern = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "CExtern", 0)
has_state = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0)
has_export = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "CExport", 0)
# t.CExtern/t.State ignore the body and only declare (whether the body is pass or not)
if has_extern != 0 or has_state != 0: is_extern_decl = 1
# Decorator-form storage markers (@t.CExport/@t.CExtern/@t.State/@t.CStatic), aligned with TPC
# HandlesFunctions._extract_decorator_storage_flags (same as forward_declare_functions). Use ClassGenBase._has_decorator.
if fd.decorator_list is not None:
if has_extern == 0 and self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "CExtern") == 1: has_extern = 1
if has_state == 0 and self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "State") == 1: has_state = 1
if has_export == 0 and self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "CExport") == 1: has_export = 1
if (has_extern != 0 or has_state != 0) and is_extern_decl == 0: is_extern_decl = 1
# @t.TLS one-time-init marker (aligned with TPC HandlesFunctions.py L1223-1231)
has_tls: int = 0
if fd.decorator_list is not None: has_tls = self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "TLS")
# Custom behavior decorator detection (@log/@skip/@repeat(3) etc.): Name/Name(args) decorators that are neither
# t storage markers nor builtins. Aligned with TPC HandlesFunctions.py L1161-1189 + DecoratorPass.py; first one wins.
has_custom_decor: int = 0
decor_handler: str = None
decor_arg0: int64 = 0
if fd.decorator_list is not None:
ddn: sizet = len(fd.decorator_list)
for ddi in range(int(ddn)):
deco: ast.AST* = fd.decorator_list.get(ddi)
if deco is None: continue
dk: int = deco.kind()
dname: str = None
if dk == ast.ASTKind.Name:
dnm: ast.Name* = ast.Name*(deco)
dname = dnm.id
elif dk == ast.ASTKind.Call:
dcl: ast.Call* = ast.Call*(deco)
if dcl.func is not None and dcl.func.kind() == ast.ASTKind.Name:
dnm2: ast.Name* = ast.Name*(dcl.func)
dname = dnm2.id
# @repeat(3): take the first constant int argument (aligned with TPC L1182-1184)
if dcl.args is not None and len(dcl.args) >= 1:
da0: ast.AST* = dcl.args.get(0)
if da0 is not None and da0.kind() == ast.ASTKind.Constant:
dc0: ast.Constant* = ast.Constant*(da0)
if dc0.const_kind == ast.CONST_INT: decor_arg0 = dc0.int_val
if dname is None: continue
# Skip builtin decorators
if string.strcmp(dname, "staticmethod") == 0 or string.strcmp(dname, "property") == 0 or string.strcmp(dname, "classmethod") == 0: continue
# Skip t-module storage markers (@t.CExport/@t.CExtern/@t.State/@t.TLS/@t.CStatic/@t.CInline/@t.CVTable/
# @t.NoVTable/@t.Object/@t.CEnum/@t.CUnion/@t.REnum/@t.CDefine). Attribute-form @t.xxx is not handled here.
# 另跳过**编译期生命期标注**(P2:规范形式 @life(willFree(...)) 与兼容叠加形式 @willFree(...)):
# 它们是纯标注、没有运行时装饰器函数。漏在这里会让 VPC 生成 __decor_wrap_<name> wrapper,
# 去调用并不存在的 @life/@willFree,并把被装饰的 extern def(如 stdlib.free)变成 internal 声明
# (现场:llc 报 "invalid linkage for function declaration")。
if string.strcmp(dname, "CExport") == 0 or string.strcmp(dname, "CExtern") == 0 or string.strcmp(dname, "State") == 0 or string.strcmp(dname, "TLS") == 0 or string.strcmp(dname, "CStatic") == 0 or string.strcmp(dname, "CInline") == 0 or string.strcmp(dname, "CVTable") == 0 or string.strcmp(dname, "NoVTable") == 0 or string.strcmp(dname, "Object") == 0 or string.strcmp(dname, "CEnum") == 0 or string.strcmp(dname, "CUnion") == 0 or string.strcmp(dname, "REnum") == 0 or string.strcmp(dname, "CDefine") == 0 or string.strcmp(dname, "life") == 0 or string.strcmp(dname, "willFree") == 0:
continue
has_custom_decor = 1
decor_handler = dname
break
# SHA1 namespace: t.CExtern/t.State/t.CExport use bare names (bare maps to C library symbols; a sha1 prefix
# would cause undefined reference). main also gets no prefix (program entry point).
if has_extern != 0 or has_state != 0 or has_export != 0 or string.strcmp(fd.name, "main") == 0: mangled_name: str = fd.name
else: mangled_name: str = self.Gen.FuncGenBase._mangle_func_name(trans, fd.name, 0)
# Record value-reference (&T / &temp T) parameter modes for call-site auto-address.
self._register_func_auto_addr_modes(trans, mangled_name, fd)
# Register the CExport function in the global table (for cross-module calls). t.CExport definitions use bare
# names (@strlen); cross-module calls consult the table to use the bare name instead of @{sha1}.func.
if (has_export != 0 or has_state != 0) and trans.ModuleSha1 is not None: self.Gen.CallGenBase.register_cexport_func(trans.ModuleSha1, fd.name)
if is_extern_decl != 0:
# Extern declaration function: emit a declare (declare only, no definition). First check whether
# forward_declare_functions already created it to avoid a duplicate declare.
existing_extern: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name)
if existing_extern is not None: return 0
func: mlirlite.Function* = mlirlite.create_declare(pool, mod, mangled_name, ret_ty)
if func is None: return 0
# Register in the function table
max_funcs_extern: int = 256
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, func_count, fd.name, func, max_funcs_extern, None, 0, 0, pool) == 0: trans._func_count = func_count + 1
# Add parameters (with type annotation support)
args_node_extern: ast.Arguments* = fd.args
if args_node_extern is not None:
ags_e: ast.Arguments* = ast.Arguments*(args_node_extern)
if ags_e.args is not None:
alist_e: list[ast.AST*]* = ags_e.args
an_e: sizet = len(alist_e)
for ai_e in range(int(an_e)):
arg_e: ast.Arg* = ast.Arg*(alist_e.get(ai_e))
if arg_e is not None and arg_e.arg is not None:
# CVoid means empty param: skip, do not add to the function signature
if arg_e.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(arg_e.annotation, "CVoid", imported_modules) != 0: continue
param_ty_e: mlirlite.LLVMType* = i32_ty
if arg_e.annotation is not None:
resolved_e: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg_e.annotation, imported_modules, from_imports, trans, 0)
if resolved_e is None:
# fail-fast: extern function param has an annotation but is unresolvable; no silent i32
# (a wrong declare signature shows up at the ABI layer)
self.Gen.TypeGenErr.fatal_error(arg_e, f"argument '{arg_e.arg}' failed to resolve type: {self.Gen.TypeGenResolve.annotation_to_str(arg_e.annotation)}", "NameError")
resolved_e = i32_ty # unreachable (fatal_error already exited)
if resolved_e is not None:
# Bare class param -> Ptr(struct); CArray param -> element pointer ty* (C decay). This branch is
# always a C ABI extern declare: bare struct params pass by value %Struct (keep in sync with 2 others).
match resolved_e:
case mlirlite.LLVMType.Void():
# void is not a legal param type (see the same rule above)
self.Gen.TypeGenErr.fatal_error(arg_e, f"param '{arg_e.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
case mlirlite.LLVMType.Struct(_, _, _):
if self.Gen.StructGen.find_struct_by_type(resolved_e) is not None:
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, resolved_e)
param_ty_e = resolved_e
else: param_ty_e = mlirlite.Ptr(pool, resolved_e)
case mlirlite.LLVMType.Array(aelem_ty_e, _):
param_ty_e = mlirlite.Ptr(pool, aelem_ty_e)
case _:
param_ty_e = resolved_e
pname_e: str = pool.alloc(32)
if pname_e is not None: viperlib.snprintf(pname_e, 32, "%%%s", arg_e.arg)
mlirlite.add_param(pool, func, param_ty_e, pname_e)
# Extract @c.Attribute decorator attributes and set them on the function
func_attrs_e: str = self.Gen.FuncGenBase.extract_func_attrs(pool, fd.decorator_list, imported_modules)
if func_attrs_e is not None: mlirlite.function_set_attrs(func, func_attrs_e)
# B5: section string attribute goes through attribute group #N
func_section_e: str = self.Gen.FuncGenBase.extract_func_section(pool, fd.decorator_list, imported_modules)
if func_section_e is not None: mlirlite.function_set_attr_group(func, func_section_e)
# Params added; fill the global func-table Spec
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func)
return 0
# args_node is used later for alloca creation; assign it early
args_node: ast.Arguments* = fd.args
# Check for an existing forward declare (created by forward_declare_functions)
func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name)
if func is not None and mlirlite.function_is_declared(func) != 0:
# Reuse the forward declare: clear IsDeclared and turn it into a define
func.IsDeclared = 0
# 参数名对齐(根因修复):复用的前置声明未必是 forward_declare_functions 按源码建的,也可能是
# **模块前言**里 Translator 建的 C 库声明(如 free 用合成名 "p")。而函数体是用**源码形参名**
# %<arg.arg> 生成参数 SSA 引用的(见下方 alloca 段的 SSAValue)。若签名沿用声明里的旧名,就会
# 打印出 `llvm.func @f(%p) { ... %ptr ... }`:入口实参是 %p,体内 %ptr 却在 f 内未定义,
# mlir-opt 会把 %ptr 解析到后续同名函数的入口实参并报 "region entry argument '%ptr' is already
# in use"(现场:VKernel 的 free 头 %p / 体 %ptr,被解析到 _slab_find_page(%ptr))。
# 故这里按源码形参名重写复用到的形参名,使签名与函数体一致。forward_declare_functions 建的前置
# 声明本就用源码名,此处对其幂等(同名重写)。
reused_p: mlirlite.Param* = mlirlite.function_get_param_head(func)
if args_node is not None:
reuse_ags: ast.Arguments* = ast.Arguments*(args_node)
if reuse_ags.args is not None:
reuse_list: list[ast.AST*]* = reuse_ags.args
reuse_n: sizet = len(reuse_list)
for reuse_i in range(int(reuse_n)):
rearg: ast.Arg* = ast.Arg*(reuse_list.get(reuse_i))
if rearg is None or rearg.arg is None: continue
# CVoid means empty param: not part of the signature (mirrors the add_param loops above)
if rearg.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(rearg.annotation, "CVoid", imported_modules) != 0: continue
if reused_p is None:
# 复用声明形参少于定义:签名与函数体必然错配,禁止静默,fail-fast
self.Gen.TypeGenErr.fatal_error(rearg, f"reused declaration for '{fd.name}' has fewer parameters than the definition", "TypeError")
return 0
reuse_pname: str = pool.alloc(32)
if reuse_pname is not None: viperlib.snprintf(reuse_pname, 32, "%%%s", rearg.arg)
reused_p.Name = reuse_pname
reused_p = mlirlite.param_get_next(reused_p)
# Set decorator attributes (not set when the forward declare was created)
func_attrs_reuse: str = self.Gen.FuncGenBase.extract_func_attrs(pool, fd.decorator_list, imported_modules)
if func_attrs_reuse is not None: mlirlite.function_set_attrs(func, func_attrs_reuse)
# B5: section string attribute goes through attribute group #N
func_section_reuse: str = self.Gen.FuncGenBase.extract_func_section(pool, fd.decorator_list, imported_modules)
if func_section_reuse is not None: mlirlite.function_set_attr_group(func, func_section_reuse)
else:
# Create a new LLVM function (using the SHA1-mangled name)
func = mlirlite.create_function(pool, mod, mangled_name, ret_ty)
if func is None:
VLogger.error(f"create_function {fd.name} failed", "FUNC")
return 0
# Extract @c.Attribute decorator attributes and set them on the function
func_attrs: str = self.Gen.FuncGenBase.extract_func_attrs(pool, fd.decorator_list, imported_modules)
if func_attrs is not None: mlirlite.function_set_attrs(func, func_attrs)
# B5: section string attribute goes through attribute group #N (LLVM rejects inline section="...")
func_section: str = self.Gen.FuncGenBase.extract_func_section(pool, fd.decorator_list, imported_modules)
if func_section is not None: mlirlite.function_set_attr_group(func, func_section)
# Extract default-argument info
tfd_defaults: rawptr = None
tfd_default_count: int = 0
tfd_param_count: int = 0
if args_node is not None:
tfd_ags: ast.Arguments* = ast.Arguments*(args_node)
if tfd_ags.args is not None:
tfd_alist: list[ast.AST*]* = tfd_ags.args
tfd_param_count = int(len(tfd_alist))
if tfd_ags.defaults is not None:
tfd_dlist: list[ast.AST*]* = tfd_ags.defaults
tfd_defaults = tfd_dlist
tfd_default_count = int(len(tfd_dlist))
# Register in the function table
max_funcs: int = 1024
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, func_count, fd.name, func, max_funcs, tfd_defaults, tfd_default_count, tfd_param_count, trans.Pool) == 0:
trans._func_count = func_count + 1
# Add parameters (with type annotation support)
if args_node is not None:
ags: ast.Arguments* = ast.Arguments*(args_node)
if ags.args is not None:
alist: list[ast.AST*]* = ags.args
an: sizet = len(alist)
for ai in range(int(an)):
arg: ast.Arg* = ast.Arg*(alist.get(ai))
if arg is not None and arg.arg is not None:
# CVoid means empty param: skip, do not add to the function signature
if arg.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imported_modules) != 0: continue
param_ty: mlirlite.LLVMType* = i32_ty
if arg.annotation is not None:
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0)
if resolved is None:
# [S4 fail-fast] Annotation present but unresolvable: no silent i32
# (wrong param type -> ABI misalignment / wrong field access)
self.Gen.TypeGenErr.fatal_error(arg, f"param '{arg.arg}' type annotation failed to resolve", "NameError")
return 0
# Bare class param -> Ptr(struct); CArray param -> element pointer ty* (C array decay). Passing
# Ptr(array) would make out[i]'s GEP stride by the whole array (garbage summary root cause).
match resolved:
case mlirlite.LLVMType.Void():
# void is not a legal param type (see the same rule above)
self.Gen.TypeGenErr.fatal_error(arg, f"param '{arg.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
return 0
case mlirlite.LLVMType.Struct(_, _, _):
param_ty = mlirlite.Ptr(pool, resolved)
case mlirlite.LLVMType.Array(aelem_ty, _):
param_ty = mlirlite.Ptr(pool, aelem_ty)
case _:
param_ty = resolved
pname: str = pool.alloc(32)
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
mlirlite.add_param(pool, func, param_ty, pname)
# *args variadic: set IsVarArg=1 to emit the ... suffix
if ags.vararg is not None: func.IsVarArg = 1
# try-except: a body containing raise gets extra __eh_msg_out__ (i8**) / __eh_code_out__ (i32*) output params
# (TPC HandlesFunctions.py L706-711; except main). Must be outside the args_node block so no-arg functions are covered.
if string.strcmp(fd.name, "main") != 0:
if self.Gen.FuncGenBase._body_has_raise(fd) != 0:
i8_ty_eh: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty_eh: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_eh)
i8_ptr_ptr_ty_eh: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty_eh)
i32_ty_eh: mlirlite.LLVMType* = mlirlite.Int32(pool)
i32_ptr_ty_eh: mlirlite.LLVMType* = mlirlite.Ptr(pool, i32_ty_eh)
if i8_ptr_ptr_ty_eh is not None and i32_ptr_ty_eh is not None:
mlirlite.add_param(pool, func, i8_ptr_ptr_ty_eh, "%__eh_msg_out__")
mlirlite.add_param(pool, func, i32_ptr_ty_eh, "%__eh_code_out__")
# Params added; fill the global func-table Spec
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func)
# Create the entry block
entry_blk: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "entry")
if entry_blk is None: return 0
# Create the function-specific builder
func_builder: mlirlite.IRBuilder* = mlirlite.new_builder(pool, func)
if func_builder is None: return 0
mlirlite.position_at_end(func_builder, entry_blk)
# --traceback: attach a DISubprogram to this define and set every instruction in the body to the entry DILocation
# (refined per statement); no-op when DI is disabled.
dbg_pushed: int = DebugInfoGen.PushFunction(pool, func_builder, func, fd.lineno, fd.col_offset + 1)
# @t.TLS one-time-init guard (aligned with TPC HandlesFunctions.py): the first call runs the body and sets
# __tls_init_done_{name}=1; later calls return the default. Without it every call re-runs TlsAlloc and loses state.
if has_tls != 0:
# Exact capacity: the reference below used to be 64 while the definition was 128, so a
# long function name produced a reference to a different (nonexistent) global.
tls_flag_cap: sizet = string.strlen(fd.name) + 24
tls_flag_name: str = pool.alloc(tls_flag_cap)
if tls_flag_name is not None:
viperlib.snprintf(tls_flag_name, tls_flag_cap, "__tls_init_done_%s", fd.name)
tls_flag_gv: mlirlite.GlobalVariable* = mlirlite.create_global_int(pool, mod, tls_flag_name, 1, 0)
if tls_flag_gv is not None:
i1_ty_t: mlirlite.LLVMType* = mlirlite.Int1(pool)
if i1_ty_t is not None:
# GlobalVariable is not a Value subclass (different field layout), so build_load/store reading
# ptr.Ty/ptr.Name as a Value misaligns. As in create_global_string, build a Value view (Ty=Ptr(i1), Name="@name").
tls_gv_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i1_ty_t)
tls_ref_cap: sizet = string.strlen(tls_flag_name) + 2
tls_ref_name: str = pool.alloc(tls_ref_cap)
if tls_ref_name is not None:
viperlib.snprintf(tls_ref_name, tls_ref_cap, "@%s", tls_flag_name)
tls_flag_ref: mlirlite.Value* = mlirlite.SSAValue(pool, tls_gv_ptr_ty, tls_ref_name)
if tls_flag_ref is not None:
tls_flag_ld: mlirlite.Value* = mlirlite.build_load(func_builder, i1_ty_t, tls_flag_ref)
# Unique block name (create_block does not auto-number; same-label conflict within a function)
lc_t: int = trans._label_counter
trans._label_counter = lc_t + 1
tls_bb_buf: str = pool.alloc(64)
if tls_bb_buf is not None:
viperlib.snprintf(tls_bb_buf, 64, "tls.skip.%d", lc_t)
tls_skip_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, tls_bb_buf)
viperlib.snprintf(tls_bb_buf, 64, "tls.cont.%d", lc_t)
tls_cont_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, tls_bb_buf)
if tls_skip_bb is not None and tls_cont_bb is not None:
mlirlite.build_cond_br(func_builder, tls_flag_ld, tls_skip_bb, tls_cont_bb)
# skip block: return a default value matching the return type
mlirlite.position_at_end(func_builder, tls_skip_bb)
match ret_ty:
case mlirlite.LLVMType.Void():
mlirlite.build_ret_void(func_builder)
case mlirlite.LLVMType.Int(_):
tlz0: mlirlite.Value* = mlirlite.ConstInt(pool, ret_ty, 0, "0")
if tlz0 is not None: mlirlite.build_ret(func_builder, tlz0)
case mlirlite.LLVMType.Float(_):
tlf0: mlirlite.Value* = mlirlite.ConstFloat(pool, ret_ty, 0.0)
if tlf0 is not None: mlirlite.build_ret(func_builder, tlf0)
case _:
tln0: mlirlite.Value* = mlirlite.ConstNull(pool, ret_ty, "null")
if tln0 is not None: mlirlite.build_ret(func_builder, tln0)
# cont block: set the flag=1 and continue the body
mlirlite.position_at_end(func_builder, tls_cont_bb)
tlone: mlirlite.Value* = mlirlite.ConstInt(pool, i1_ty_t, 1, "1")
if tlone is not None: mlirlite.build_store(func_builder, tlone, tls_flag_ref)
# Enter the function scope (nested symbol table)
self.Gen.VarGen.enter_scope(trans.SymTab, FuncGenMixin.SCOPE_FUNCTION)
# Generic function templates already returned early (see the top of this function); here fd.type_params is always
# None (specialization clones clear it). The generic context is set by _specialize_generic_function before the call.
# arg()/va_list scope reset: save the ENCLOSING function's va_list state and start this body with
# none of it. A non-variadic function must not inherit the previous function's buffer: that Value
# belongs to another function while the printer emits it by NAME, so the name re-binds to
# whatever local happens to carry it here (scene: viperlib vsnprintf emitted `va_arg ptr %9`
# where %9 is `count_mode`'s i32 alloca -> va_arg loaded a garbage va_list pointer ->
# access-violation inside vsnprintf). The *args setup below re-sets both for a variadic callee,
# and old_va_list_ptr/old_vararg_name are restored after the body so that nested functions still
# see the enclosing variadic function's buffer.
old_va_list_ptr: mlirlite.Value* = trans._va_list_ptr
old_vararg_name: str = trans._vararg_name
trans._va_list_ptr = None
trans._vararg_name = None
# Create allocas for the parameters
if args_node is not None:
ags2: ast.Arguments* = ast.Arguments*(args_node)
if ags2.args is not None:
alist2: list[ast.AST*]* = ags2.args
an2: sizet = len(alist2)
for ai2 in range(int(an2)):
arg2: ast.Arg* = ast.Arg*(alist2.get(ai2))
if arg2 is not None and arg2.arg is not None:
# CVoid means empty param: skip, do not create an alloca
if arg2.annotation is not None:
if self.Gen.TypeGenResolve._is_builtin_marker(arg2.annotation, "CVoid", imported_modules) != 0: continue
param_ty2: mlirlite.LLVMType* = i32_ty
if arg2.annotation is not None:
resolved2: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg2.annotation, imported_modules, from_imports, trans, 0)
if resolved2 is None:
# fail-fast: param has an annotation but is unresolvable; no silent i32
self.Gen.TypeGenErr.fatal_error(arg2, f"argument '{arg2.arg}' failed to resolve type: {self.Gen.TypeGenResolve.annotation_to_str(arg2.annotation)}", "NameError")
resolved2 = i32_ty # unreachable (fatal_error already exited)
if resolved2 is not None:
# Consistent with add_param: bare class param -> Ptr(struct); CArray param -> element pointer ty*
# (array decay; the alloca type must match the param or storing ty* into [N x ty]* mismatches).
match resolved2:
case mlirlite.LLVMType.Void():
# void is not a legal param type (see the same rule above)
self.Gen.TypeGenErr.fatal_error(arg2, f"param '{arg2.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
return 0
case mlirlite.LLVMType.Struct(_, _, _):
param_ty2 = mlirlite.Ptr(pool, resolved2)
case mlirlite.LLVMType.Array(aelem_ty2, _):
param_ty2 = mlirlite.Ptr(pool, aelem_ty2)
case _:
param_ty2 = resolved2
alloca: mlirlite.Value* = mlirlite.build_alloca(func_builder, param_ty2)
if alloca is not None:
self.Gen.VarGen.define_var(trans.SymTab, arg2.arg, alloca)
# Store the original annotation's class name (method-call detection falls back from Ptr(i8) to the class).
# Pass pool: a generic Subscript returns a mangled name (aligned with the HandlesAnnAssign fix).
cls_nm: str = None
if arg2.annotation is not None: cls_nm = self.Gen.TypeGenResolve.extract_class_name_from_annotation(arg2.annotation, imported_modules, pool, trans._from_imports)
if cls_nm is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, arg2.arg, cls_nm)
# t.CVolatile param: marked at declaration time so loads/stores of this name carry volatile
# (a volatile param object's read-modify-write must not be hoisted/merged, e.g. a spin-wait arg).
if arg2.annotation is not None:
# const param: reuse TPC's historical param criterion (only whether the annotation contains
# t.CConst, regardless of pointer depth); TPC HandlesFunctions IsParam=True uses the same condition.
if self.Gen.TypeGenErr.has_decorator_marker(arg2.annotation, "CConst", 0) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, arg2.arg)
if self.Gen.TypeGenErr.has_decorator_marker(arg2.annotation, "CVolatile", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, arg2.arg)
# rawptr[t.CVolatile | T] param (volatile pointee): dereferences in the body
# (p[i]/p.field/c.Deref(p)) carry volatile.
if self.Gen.TypeGenErr.has_pointee_volatile_marker(arg2.annotation) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, arg2.arg)
pname2: str = pool.alloc(32)
if pname2 is not None: viperlib.snprintf(pname2, 32, "%%%s", arg2.arg)
param_val: mlirlite.Value* = mlirlite.SSAValue(pool, param_ty2, pname2)
vol_p2: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, arg2.arg))
mlirlite.build_store(func_builder, param_val, alloca, vol_p2)
# *args variadic: allocate a 24-byte va_list buffer, bitcast to i8*, call @llvm.va_start, register in the symbol table.
# Must be inside the param alloca section but outside the EH block, or args is unregistered (NameError: 'args').
if ags2.vararg is not None:
vararg_node: ast.Arg* = ast.Arg*(ags2.vararg)
if vararg_node is not None and vararg_node.arg is not None:
vararg_name_str: str = vararg_node.arg
# va_list buffer is 24 bytes (Win64 __va_list_tag = {i32,i32,i8*,i8*}) with 16-byte alignment
# (aligned with TPC HandlesFunctions.py:1739-1741).
i8_ty_va: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty_va: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_va)
va_buf_ty: mlirlite.LLVMType* = mlirlite.Array(pool, i8_ty_va, 24)
va_list_alloca: mlirlite.Value* = mlirlite.build_alloca_aligned(func_builder, va_buf_ty, 16)
if va_list_alloca is not None:
# i8* address (pointing at the 24-byte va_list buffer) - the value semantics of the vararg name
va_list_addr: mlirlite.Value* = mlirlite.build_bitcast(func_builder, va_list_alloca, i8_ptr_ty_va)
if va_list_addr is not None:
# Register in the symbol table (translate_name_value special-cases vararg names to return the address)
self.Gen.VarGen.define_var(trans.SymTab, vararg_name_str, va_list_alloca)
# Call @llvm.va_start(i8* %va_list_addr)
void_ty_va: mlirlite.LLVMType* = mlirlite.Void(pool)
mlirlite.build_call(func_builder, "llvm.va_start", va_list_addr, 1, void_ty_va, 0)
# Save on trans for the arg(type) sugar and vararg-name translation
trans._va_list_ptr = va_list_addr
trans._vararg_name = vararg_name_str
# try-except: a new function scope starts with zero EH state. Save the enclosing function's EH output params
# before and restore them after; otherwise nested functions corrupt the EH state chain.
old_eh_msg_out: mlirlite.Value* = trans._eh_msg_out_param
old_eh_code_out: mlirlite.Value* = trans._eh_code_out_param
old_cur_func_has_eh: int = trans._cur_func_has_eh
trans._eh_msg_out_param = None
trans._eh_code_out_param = None
trans._cur_func_has_eh = 0
# try-except: save this function's EH output params (__eh_msg_out__/__eh_code_out__) for translate_raise /
# UnhandledBB to write and propagate. Functions without EH params are set to None.
if string.strcmp(fd.name, "main") != 0 and self.Gen.FuncGenBase._body_has_raise(fd) != 0:
i8_ty_eh2: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty_eh2: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_eh2)
i8_ptr_ptr_ty_eh2: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty_eh2)
i32_ty_eh2: mlirlite.LLVMType* = mlirlite.Int32(pool)
i32_ptr_ty_eh2: mlirlite.LLVMType* = mlirlite.Ptr(pool, i32_ty_eh2)
if i8_ptr_ptr_ty_eh2 is not None and i32_ptr_ty_eh2 is not None:
msg_out_ref: mlirlite.Value* = mlirlite.SSAValue(pool, i8_ptr_ptr_ty_eh2, "%__eh_msg_out__")
code_out_ref: mlirlite.Value* = mlirlite.SSAValue(pool, i32_ptr_ty_eh2, "%__eh_code_out__")
trans._eh_msg_out_param = msg_out_ref
trans._eh_code_out_param = code_out_ref
trans._cur_func_has_eh = 1
# Save module-level scope state (non-variable-table related only)
old_func: mlirlite.Function* = trans._cur_func
old_builder: mlirlite.IRBuilder* = trans._cur_builder
old_global_count: int = trans._global_name_count
old_nonlocal_count: int = trans._nonlocal_name_count
old_env_count: int = trans._closure_env_count
# old_va_list_ptr / old_vararg_name were saved above (before the *args setup) so that this body
# could start from a clean va_list state; the restore below hands the enclosing value back.
# Return-annotation context ([C①], one source of type-argument fallback for bare generic construction):
# record this function's return annotation before entering the body and restore the outer value on exit.
old_ret_annot: ast.AST* = trans.CurFuncRetAnnot
# break/continue targets and the match-case flag must be cleared for the new function scope (see assignments below),
# otherwise break/continue in a nested function inherits the outer (even cross-function) br targets.
old_break_bb: mlirlite.BasicBlock* = trans._break_bb
old_continue_bb: mlirlite.BasicBlock* = trans._continue_bb
old_insw: int = trans._in_switch_case
trans._cur_func = func
trans._cur_builder = func_builder
trans.CurFuncRetAnnot = fd.returns
# P2 willFree 失效表:**函数作用域复位**(表按栈槽地址记录,槽地址会被后续函数复用)
self.Gen.VarGen.freed_clear()
trans._break_bb = None
trans._continue_bb = None
trans._in_switch_case = 0
# Clear the global/nonlocal name sets (new function scope)
GenApi.clear_scope_names(trans)
# Reset the in-chain nonlocal heap prebuild flag (built once per function; multiple nested function definitions
# share one heap, preventing repeated malloc + double VarEntry redirection).
trans._nl_chain_done = 0
# Pre-scan the body: 1) Global/Nonlocal declarations first (Python semantics: they apply to the whole function
# scope regardless of position, so register before any assignment); 2) AnnAssign to create local allocas early.
body: list[ast.AST*]* = fd.children
if body is not None:
bn: sizet = len(body)
for bi in range(int(bn)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGenDecl.pre_scan_scope_decls(trans, stmt)
for bi in range(int(bn)):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGenDecl.pre_scan_allocas(trans, stmt)
# M3 owned slife(§4.2 函数级 scope guard):进入函数体前把登记区间起点抬到当前栈顶。
# 表是函数间共享的一段栈式区间,嵌套函数各自独立;退出时由 _SlifeRestore 弹回外层起点。
# 放在这里(而非更早)是因为上方存在多条提前返回分支(泛型模板/async/extern),
# 只有从这里开始才保证"有 reset 必有 restore"。
old_slife_base: int = self.Gen.VarGen._SlifeReset(pool)
# Translate the function body
if body is not None:
bn2: sizet = len(body)
for bi2 in range(int(bn2)):
stmt2: ast.AST* = body.get(bi2)
if stmt2 is not None:
# Before a nested function definition: pre-scan its in-chain nonlocals and build the heap
# (for cases where different levels capture different variables; see _prepare_chain_nonlocals).
if stmt2.kind() == ast.ASTKind.FunctionDef:
self.Gen.FuncGen._prepare_chain_nonlocals(trans, stmt2, pool, func_builder, mod, mlirlite.Int8(pool), mlirlite.Ptr(pool, mlirlite.Int8(pool)))
self.Gen.StmtGen.translate_stmt(trans, stmt2)
# If the last body statement is not Return, add an implicit ret
last_is_return: int = 0
# bn3 is initialized early so the fail-fast branch below does not reference an undefined bn3 when body is None.
bn3: sizet = 0
if body is not None:
bn3 = len(body)
if bn3 > 0:
last_stmt: ast.AST* = body.get(bn3 - 1)
if last_stmt is not None and last_stmt.kind() == ast.ASTKind.Return: last_is_return = 1
if last_is_return == 0:
if mlirlite.builder_cur_block_is_terminated(func_builder) == 0:
# The compiler never invents a return value: falling off the end without an explicit return is fail-fast.
# Legal endings: void -> ret void; unreachable merge (all branches terminated) -> unreachable; unannotated -> zero return.
last_is_ctrl: int = 0
if bn3 > 0:
last_stmt3: ast.AST* = body.get(bn3 - 1)
if last_stmt3 is not None:
k3: int = last_stmt3.kind()
if k3 == ast.ASTKind.If or k3 == ast.ASTKind.While or k3 == ast.ASTKind.For or k3 == ast.ASTKind.Try or k3 == ast.ASTKind.With or k3 == ast.ASTKind.Match: last_is_ctrl = 1
_is_void: int = 0
if ret_ty is not None:
match ret_ty:
case mlirlite.LLVMType.Void():
_is_void = 1
case _:
pass
# Annotation-level "no return value" criterion (a second check alongside the ret_ty type check): if the
# REnum class-pattern match degrades, _is_void stays 0 and a void function is misreported as missing a return.
if _is_void == 0 and fd.returns is not None:
if self.Gen.TypeGenResolve._is_cvoid_marker(fd.returns, imported_modules) != 0: _is_void = 1
elif fd.returns.kind() == ast.ASTKind.Constant:
ret_none_cn: ast.Constant* = (ast.Constant*)(fd.returns)
if ret_none_cn.const_kind == ast.CONST_NONE: _is_void = 1
if _is_void != 0:
# M3:隐式收尾也是一个函数出口,先释放本函数登记的 slife 再返回
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
mlirlite.build_ret_void(func_builder)
elif last_is_ctrl != 0 and trans._ctrl_merge_reachable == 0:
# A non-void function whose last statement is control flow with a structurally unreachable merge (all
# branches return, no br to merge) -> LLVM unreachable. Reachability uses only `_ctrl_merge_reachable`.
mlirlite.build_unreachable(func_builder)
elif fd.returns is None:
# No return annotation -> a no-value procedure function, return a zero default
if ret_ty is not None:
zero_val: mlirlite.Value* = mlirlite.ConstZero(pool, ret_ty)
if zero_val is not None:
# M3:隐式收尾出口,先释放 slife 再返回
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
mlirlite.build_ret(func_builder, zero_val)
else: self.Gen.TypeGenErr.fatal_error(node, "function flow falls off the end without an explicit return", "TypeError")
else: self.Gen.TypeGenErr.fatal_error(node, "function flow falls off the end without an explicit return", "TypeError")
else:
# fail-fast: has a -> annotation but the flow falls off the end (pointing at the function definition
# node; fatal_error prints a traceback-style error).
if ret_ty is not None:
# mlirlite has no type_get_name API; serialize the type with the real interface
# TypePrint(buf, size, ty, pool).
rt_name: str = pool.alloc_tagged(4096, memhub.DETSITE_FUNCGENDEF_792)
if rt_name is not None:
mlirlite.TypePrint(rt_name, 4096, ret_ty, pool)
if rt_name[0] == '\0': rt_name = "?"
err_fn: str = pool.alloc(256)
if err_fn is not None:
viperlib.snprintf(err_fn, 256, "function '%s' flow falls off the end of the function without an explicit return (return type %s), please add an explicit return (e.g. return 0 / return null)", fd.name, rt_name)
self.Gen.TypeGenErr.fatal_error(node, err_fn, "TypeError")
self.Gen.TypeGenErr.fatal_error(node, "function flow falls off the end without an explicit return", "TypeError")
else: self.Gen.TypeGenErr.fatal_error(node, "function flow falls off the end without an explicit return", "TypeError")
# Restore module-level scope (exit the function scope)
self.Gen.VarGen.exit_scope(trans.SymTab)
# M3 owned slife:丢弃本函数的登记区间、弹回外层起点(释放已在各出口用 _SlifeEmitFrees 完成)
self.Gen.VarGen._SlifeRestore(old_slife_base)
trans._cur_func = old_func
trans._cur_builder = old_builder
trans._break_bb = old_break_bb
trans._continue_bb = old_continue_bb
trans._in_switch_case = old_insw
trans._global_name_count = old_global_count
trans._nonlocal_name_count = old_nonlocal_count
trans._closure_env_count = old_env_count
trans._va_list_ptr = old_va_list_ptr
trans._vararg_name = old_vararg_name
# Hand back the outer function's return annotation (see old_ret_annot above)
trans.CurFuncRetAnnot = old_ret_annot
# Restore the outer (caller) function's EH output params: this function's raise propagation uses its own params,
# so hand them back on exit or nested/chained calls leave stale EH state and write to the wrong out pointer.
trans._eh_msg_out_param = old_eh_msg_out
trans._eh_code_out_param = old_eh_code_out
trans._cur_func_has_eh = old_cur_func_has_eh
# Custom decorator: register and generate a wrapper (aligned with TPC DecoratorPass.run). Module-level functions
# only; wrapper name is __decor_wrap_{bare name}; call sites are rewritten via lookup_decor_wrapper.
if has_custom_decor != 0:
wb_name: str = pool.alloc(128)
if wb_name is not None:
viperlib.snprintf(wb_name, 128, "__decor_wrap_%s", fd.name)
self.Gen.ClassGenBase.register_decorator(pool, fd.name, wb_name, decor_arg0)
self._gen_decor_wrapper(trans, pool, mod, func, fd.name, wb_name, decor_handler, decor_arg0)
if dbg_pushed != 0: DebugInfoGen.PopFunction(func_builder)
return 0
# _RegisterStaticFunc - register a static function (no cross-module use). Two equivalent forms (normalized by the
# parser): annotation form or decorator form; on hit, register (name, defining module SHA1) in the static symbol table.
def _RegisterStaticFunc(self, trans: HT.Translator*, fd: ast.FunctionDef*) -> int:
"""Register a static function when its annotation or @t.CStatic matches; 1=registered, 0=miss."""
if trans is None or fd is None or fd.name is None: return 0
if trans.ModuleSha1 is None: return 0
has: int = 0
if fd.returns is not None: has = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "CStatic", 0)
if has == 0 and fd.decorator_list is not None:
if self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "CStatic") == 1: has = 1
if has == 0: return 0
return self.Gen.CallGenBase.RegisterStaticSymbol(trans.ModuleSha1, fd.name)
# _gen_decor_wrapper - generate a decorator wrapper (aligned with TPC DecoratorPass.py _generate_single_wrapper v4).
# Blocks: entry -> recursive_call/decorated_entry -> loop.header/loop.body -> post; handler signature i32 decor(i8*, i8*, i32, i8*, i8*, i8*).
def _gen_decor_wrapper(self, trans: HT.Translator*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, func: mlirlite.Function*, func_name: str, wrapper_name: str, handler_name: str, decor_arg0: int64) -> mlirlite.Function*:
"""Generate a decorator wrapper function; returns the wrapper or None."""
if trans is None or pool is None or mod is None or func is None: return None
if func_name is None or wrapper_name is None or handler_name is None: return None
ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(func)
if ret_ty is None: return None
orig_call_name: str = mlirlite.function_get_name(func)
if orig_call_name is None: return None
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
# ---- Collect the original function's param types (by position, up to 16) ----
n_params: int = 0
param_tys: sizet* = pool.alloc(16 * 8)
if param_tys is None: return None
string.memset(param_tys, 0, 16 * 8)
pp: mlirlite.Param* = mlirlite.function_get_param_head(func)
while pp is not None:
pty: mlirlite.LLVMType* = mlirlite.param_get_ty(pp)
if pty is not None and n_params < 16:
param_tys[n_params] = sizet(pty)
n_params += 1
pp = mlirlite.param_get_next(pp)
# ---- Create the wrapper function and add params (named %a0..%aN) ----
wrapper: mlirlite.Function* = mlirlite.create_function(pool, mod, wrapper_name, ret_ty)
if wrapper is None: return None
pi: int = 0
for pi in range(int(n_params)):
pname_w: str = pool.alloc(16)
if pname_w is not None:
viperlib.snprintf(pname_w, 16, "%%a%d", pi)
mlirlite.add_param(pool, wrapper, (mlirlite.LLVMType*)(param_tys[pi]), pname_w)
# ---- Create blocks ----
entry_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, wrapper, "entry")
loop_h_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, wrapper, "loop.header")
loop_b_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, wrapper, "loop.body")
post_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, wrapper, "post")
if entry_bb is None or loop_h_bb is None or loop_b_bb is None or post_bb is None: return None
# Recursion guard flag (outermost): @__decor_rec_{wrapper} = global i8 0
rec_flag_ref: mlirlite.Value* = None
# Exact capacities: definition and reference used different fixed sizes (128 vs 96), so a
# long wrapper name produced two different globals (guard silently bypassed / undefined).
rec_flag_cap: sizet = string.strlen(wrapper_name) + 24
rec_flag_name: str = pool.alloc(rec_flag_cap)
if rec_flag_name is not None:
viperlib.snprintf(rec_flag_name, rec_flag_cap, "__decor_rec_%s", wrapper_name)
mlirlite.create_global_int(pool, mod, rec_flag_name, 8, 0)
rec_ref_cap: sizet = string.strlen(rec_flag_name) + 2
rec_ref_nm: str = pool.alloc(rec_ref_cap)
if rec_ref_nm is not None:
viperlib.snprintf(rec_ref_nm, rec_ref_cap, "@%s", rec_flag_name)
rec_flag_ref = mlirlite.SSAValue(pool, mlirlite.Ptr(pool, i8_ty), rec_ref_nm)
wb: mlirlite.IRBuilder* = mlirlite.new_builder(pool, wrapper)
if wb is None: return None
# Classify the return type
ret_is_void: int = 0
match ret_ty:
case mlirlite.LLVMType.Void():
ret_is_void = 1
case _:
pass
# ---- entry: recursion guard check (outermost) ----
mlirlite.position_at_end(wb, entry_bb)
loop_pred_bb: mlirlite.BasicBlock* = entry_bb
if rec_flag_ref is not None:
rec_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, wrapper, "recursive_call")
dec_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, wrapper, "decorated_entry")
if rec_bb is None or dec_bb is None: return None
rec_val: mlirlite.Value* = mlirlite.build_load(wb, i8_ty, rec_flag_ref)
rec_zero: mlirlite.Value* = mlirlite.ConstInt(pool, i8_ty, 0, "0")
rec_is: mlirlite.Value* = mlirlite.build_icmp(wb, mlirlite.ICMP_NE, rec_val, rec_zero)
mlirlite.build_cond_br(wb, rec_is, rec_bb, dec_bb)
# recursive_call: call the original function directly (skipping all decoration logic)
mlirlite.position_at_end(wb, rec_bb)
rec_args: mlirlite.Value* = None
ri: int = 0
for ri in range(int(n_params)):
an_r: str = pool.alloc(16)
if an_r is None: continue
viperlib.snprintf(an_r, 16, "%%a%d", ri)
av_r: mlirlite.Value* = mlirlite.SSAValue(pool, (mlirlite.LLVMType*)(param_tys[ri]), an_r)
if av_r is not None:
mlirlite.value_set_next(av_r, None)
if rec_args is None: rec_args = av_r
else:
# Append to the tail
rt: mlirlite.Value* = rec_args
while rt.Next is not None: rt = rt.Next
mlirlite.value_set_next(rt, av_r)
if ret_is_void != 0:
mlirlite.build_call(wb, orig_call_name, rec_args, n_params, ret_ty, 0)
mlirlite.build_ret_void(wb)
else:
rc_res: mlirlite.Value* = mlirlite.build_call(wb, orig_call_name, rec_args, n_params, ret_ty, 0)
if rc_res is not None: mlirlite.build_ret(wb, rc_res)
else: mlirlite.build_ret_void(wb)
# decorated_entry: set the recursion flag and continue the decoration path
mlirlite.position_at_end(wb, dec_bb)
rec_one: mlirlite.Value* = mlirlite.ConstInt(pool, i8_ty, 1, "1")
if rec_one is not None: mlirlite.build_store(wb, rec_one, rec_flag_ref)
loop_pred_bb = dec_bb
# ---- Common decoration path (entry or decorated_entry) ----
# 1. ctx = alloca [32 x i8]; ctx_ptr = bitcast i8*
ctx_ty: mlirlite.LLVMType* = mlirlite.Array(pool, i8_ty, 32)
ctx_alloca: mlirlite.Value* = mlirlite.build_alloca(wb, ctx_ty)
ctx_ptr: mlirlite.Value* = mlirlite.build_bitcast(wb, ctx_alloca, i8_ptr_ty)
# 2. Pack function params into a struct {param_types} (the decorator can read/write args_ptr)
args_alloca: mlirlite.Value* = None
args_ptr: mlirlite.Value* = None
if n_params > 0:
fields_head: mlirlite.ParamNode* = None
fk: int = 0
for fk in range(int(n_params - 1), -1, -1):
pn_n: mlirlite.ParamNode* = mlirlite.new_param_node(pool, (mlirlite.LLVMType*)(param_tys[fk]))
if pn_n is not None:
pn_n.Next = fields_head
fields_head = pn_n
args_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, fields_head, n_params, None)
if args_ty is not None:
args_alloca = mlirlite.build_alloca(wb, args_ty)
fi2: int = 0
for fi2 in range(int(n_params)):
fld_ty: mlirlite.LLVMType* = (mlirlite.LLVMType*)(param_tys[fi2])
fld_ptr: mlirlite.Value* = mlirlite.build_gep_struct(wb, args_ty, fld_ty, args_alloca, fi2)
if fld_ptr is not None:
fld_nm: str = pool.alloc(16)
if fld_nm is not None:
viperlib.snprintf(fld_nm, 16, "%%a%d", fi2)
fld_v: mlirlite.Value* = mlirlite.SSAValue(pool, fld_ty, fld_nm)
if fld_v is not None: mlirlite.build_store(wb, fld_v, fld_ptr)
args_ptr = mlirlite.build_bitcast(wb, args_alloca, i8_ptr_ty)
else: args_ptr = mlirlite.ConstNull(pool, i8_ptr_ty, "null")
# 3. Return value storage (zero-initialized; a safe default is returned when skipping the original)
ret_alloca: mlirlite.Value* = None
if ret_is_void == 0:
ret_alloca = mlirlite.build_alloca(wb, ret_ty)
ret_zero: mlirlite.Value* = None
match ret_ty:
case mlirlite.LLVMType.Int(_):
ret_zero = mlirlite.ConstInt(pool, ret_ty, 0, "0")
case mlirlite.LLVMType.Float(_):
ret_zero = mlirlite.ConstFloat(pool, ret_ty, 0.0)
case _:
ret_zero = mlirlite.ConstNull(pool, ret_ty, "null")
if ret_zero is not None and ret_alloca is not None: mlirlite.build_store(wb, ret_zero, ret_alloca)
# 4. Function-name string constant
fn_str: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(wb, pool, mod, func_name, trans)
if fn_str is None: fn_str = mlirlite.ConstNull(pool, i8_ptr_ty, "null")
# 5. handler declare (declared when untranslated; fixed signature i32 (i8*,i8*,i32,i8*,i8*,i8*))
if self.Gen.CallGenBase.find_func_in_module(mod, handler_name) is None:
handler_d: mlirlite.Function* = mlirlite.create_declare(pool, mod, handler_name, i32_ty)
if handler_d is not None:
mlirlite.add_param(pool, handler_d, i8_ptr_ty, "ctx")
mlirlite.add_param(pool, handler_d, i8_ptr_ty, "func_name")
mlirlite.add_param(pool, handler_d, i32_ty, "phase")
mlirlite.add_param(pool, handler_d, i8_ptr_ty, "args_ptr")
mlirlite.add_param(pool, handler_d, i8_ptr_ty, "ret_ptr")
mlirlite.add_param(pool, handler_d, i8_ptr_ty, "decor_args_ptr")
# 6. Pre-phase: handler returns the call count n_calls
ph0: mlirlite.Value* = mlirlite.ConstInt(pool, i32_ty, 0, "0")
null_ret: mlirlite.Value* = mlirlite.ConstNull(pool, i8_ptr_ty, "null")
null_dec: mlirlite.Value* = mlirlite.ConstNull(pool, i8_ptr_ty, "null")
# Arg linked list (6 separate values): ctx_ptr -> fn_str -> 0 -> args_ptr -> null -> null.
# Note: no self-link (null.Next = null would make build_call loop forever).
if ctx_ptr is not None: mlirlite.value_set_next(ctx_ptr, fn_str)
if fn_str is not None: mlirlite.value_set_next(fn_str, ph0)
if ph0 is not None: mlirlite.value_set_next(ph0, args_ptr)
if args_ptr is not None: mlirlite.value_set_next(args_ptr, null_ret)
if null_ret is not None: mlirlite.value_set_next(null_ret, null_dec)
n_calls: mlirlite.Value* = mlirlite.build_call(wb, handler_name, ctx_ptr, 6, i32_ty, 0)
# 7. Counter alloca (VPC uses an alloca counter instead of phi)
cnt_alloca: mlirlite.Value* = mlirlite.build_alloca(wb, i32_ty)
cnt_zero: mlirlite.Value* = mlirlite.ConstInt(pool, i32_ty, 0, "0")
if cnt_alloca is not None and cnt_zero is not None: mlirlite.build_store(wb, cnt_zero, cnt_alloca)
mlirlite.build_br(wb, loop_h_bb)
# 8. loop.header:cur = load cnt; cur < n_calls → body / post
mlirlite.position_at_end(wb, loop_h_bb)
cur_i: mlirlite.Value* = mlirlite.build_load(wb, i32_ty, cnt_alloca)
loop_cond: mlirlite.Value* = mlirlite.build_icmp(wb, mlirlite.ICMP_SLT, cur_i, n_calls)
mlirlite.build_cond_br(wb, loop_cond, loop_b_bb, post_bb)
# 9. loop.body: read back params (the decorator may have modified them) -> call the original -> store ret
mlirlite.position_at_end(wb, loop_b_bb)
call_args: mlirlite.Value* = None
if n_params > 0:
k: int = 0
for k in range(int(n_params)):
fld_ty_k: mlirlite.LLVMType* = (mlirlite.LLVMType*)(param_tys[k])
fld_pk: mlirlite.Value* = mlirlite.build_gep_struct(wb, args_ty, fld_ty_k, args_alloca, k)
if fld_pk is None: continue
fld_vk: mlirlite.Value* = mlirlite.build_load(wb, fld_ty_k, fld_pk)
if fld_vk is not None:
mlirlite.value_set_next(fld_vk, None)
if call_args is None: call_args = fld_vk
else:
ct: mlirlite.Value* = call_args
while ct.Next is not None: ct = ct.Next
mlirlite.value_set_next(ct, fld_vk)
res_v: mlirlite.Value* = mlirlite.build_call(wb, orig_call_name, call_args, n_params, ret_ty, 0)
if ret_alloca is not None and res_v is not None: mlirlite.build_store(wb, res_v, ret_alloca)
nxt_i: mlirlite.Value* = mlirlite.build_add(wb, cur_i, mlirlite.ConstInt(pool, i32_ty, 1, "1"))
if nxt_i is not None and cnt_alloca is not None: mlirlite.build_store(wb, nxt_i, cnt_alloca)
mlirlite.build_br(wb, loop_h_bb)
# 10. post: post-phase handler -> clear the recursion flag -> return the result
mlirlite.position_at_end(wb, post_bb)
ret_ptr: mlirlite.Value* = null_ret
if ret_alloca is not None: ret_ptr = mlirlite.build_bitcast(wb, ret_alloca, i8_ptr_ty)
ph1: mlirlite.Value* = mlirlite.ConstInt(pool, i32_ty, 1, "1")
# Post-phase arg chain: ctx_ptr -> fn_str -> 1 -> args_ptr -> ret_ptr -> null
if ctx_ptr is not None: mlirlite.value_set_next(ctx_ptr, fn_str)
if fn_str is not None: mlirlite.value_set_next(fn_str, ph1)
if ph1 is not None: mlirlite.value_set_next(ph1, args_ptr)
if args_ptr is not None: mlirlite.value_set_next(args_ptr, ret_ptr)
if ret_ptr is not None: mlirlite.value_set_next(ret_ptr, null_dec)
mlirlite.build_call(wb, handler_name, ctx_ptr, 6, i32_ty, 0)
if rec_flag_ref is not None:
rec_zero2: mlirlite.Value* = mlirlite.ConstInt(pool, i8_ty, 0, "0")
if rec_zero2 is not None: mlirlite.build_store(wb, rec_zero2, rec_flag_ref)
if ret_is_void != 0: mlirlite.build_ret_void(wb)
else:
final_v: mlirlite.Value* = mlirlite.build_load(wb, ret_ty, ret_alloca)
if final_v is not None: mlirlite.build_ret(wb, final_v)
else: mlirlite.build_ret_void(wb)
return wrapper