1190 lines
80 KiB
Plaintext
1190 lines
80 KiB
Plaintext
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 |