Files
ViperC/App/lib/core/Gen/Func/FuncGenMixin.vp
T
TermiNexus cd55a7e214 refactor: rawptr[T] -> T*(R14)+ 修 REnum 前向占位未就地填充
R14:rawptr[X] -> X*(56 文件 / 834 行改动);保留 6 处 rawptr[t.CPtr](存纯裸指针的规范例外)。 修复:class X(t.REnum) 的变体字段写成自引用 X* 时,字段解析在变体扫描期经 ensure_forward_class 建了一个 opaque 前向占位;末尾 register_struct 按 Ty 指针去重会再建第二个同名 entry,而 find_struct 取首个(占位,0 字段)→ X.__sizeof__() == 0,报「已注册但大小为 0(字段链表未填充)」。改为注册前若命中同模块前向占位则就地填充(与 ensure_forward_class 文档口径一致)。TPC 无此问题,属 TPV 侧缺口。 验证:自举三重重编 SUCCESS;全量 77/77 顺序回归通过(修复前卡在 LLvmLiteTest)。
2026-10-05 17:38:28 +08:00

1192 lines
71 KiB
Plaintext

import lib.core.Gen.GenTypes as GenTypes
import t, c
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
# ============================================================
# FuncGenMixin - function/method declaration and translation Mixin
# Scope type constant (uncertain)
define SCOPE_FUNCTION = 1
# ============================================================
# FuncGenMixin - function attribute/naming/declaration/translation
novirtual class FuncGenMixin(CallGenBase.CallGenBase):
"""Function/method translation Mixin (stateless)"""
# translate_nested_function_def - hoist a nested function to a top-level
# @__closure_{name}(i8* %env) and build its closure {fn_ptr, env_ptr} in the parent.
def translate_nested_function_def(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a nested function definition: hoist to a top-level function and build a closure"""
fd: ast.FunctionDef* = ast.FunctionDef*(node)
if fd is None or fd.name is None: return 0
pool: memhub.MemBuddy* = trans.Pool
mod: mlirlite.LLVMModule* = trans.Module
func_name: str = fd.name
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
# 1. Build the promoted name __closure_{name} (SHA1 prefix avoids cross-module clashes)
promoted_name: str = pool.alloc(64)
if promoted_name is not None: viperlib.snprintf(promoted_name, 64, "__closure_%s", func_name)
mangled_promoted: str = self.Gen.FuncGenBase._mangle_name(trans, promoted_name)
# 2. Create the promoted function: define i32 @__closure_{name}(i8* %env, args...)
func: mlirlite.Function* = mlirlite.create_function(pool, mod, mangled_promoted, i32_ty)
if func is None: return 0
mlirlite.add_param(pool, func, i8_ptr_ty, "%env")
# 2a. Nested function params join the promoted signature (i8* %env, args...);
# types go into the param_tys buffer (CVoid skipped, aligning with alloca indices).
imported_modules: str = trans._imported_modules
from_imports: str = trans._from_imports
param_tys: t.CPtr = pool.alloc(8 * 64)
if param_tys is not None: string.memset(param_tys, 0, 8 * 64)
param_count: int = 0
if fd.args is not None:
ags_n: ast.Arguments* = ast.Arguments*(fd.args)
if ags_n.args is not None:
alist_n: list[ast.AST*]* = ags_n.args
an_n: sizet = len(alist_n)
for ai_n in range(an_n):
arg_n: ast.Arg* = ast.Arg*(alist_n.get(ai_n))
if arg_n is None or arg_n.arg is None: continue
if arg_n.annotation is not None:
if self.Gen.TypeGenResolve._is_t_attr(arg_n.annotation, "CVoid", imported_modules) != 0: continue
pty_n: mlirlite.LLVMType* = i32_ty
if arg_n.annotation is not None:
resolved_n: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg_n.annotation, imported_modules, from_imports, trans, 0)
if resolved_n is not None:
match resolved_n:
case mlirlite.LLVMType.Struct(_, _, _):
pty_n = mlirlite.Ptr(pool, resolved_n)
case mlirlite.LLVMType.Array(aelem_ty_n, _):
pty_n = mlirlite.Ptr(pool, aelem_ty_n)
case _:
pty_n = resolved_n
if param_count < 64 and param_tys is not None:
pty_slot_n: str* = (str*)(uint64(param_tys) + param_count * 8)
pty_slot_n[0] = pty_n
pname_n: str = pool.alloc(32)
if pname_n is not None:
viperlib.snprintf(pname_n, 32, "%%%s", arg_n.arg)
mlirlite.add_param(pool, func, pty_n, pname_n)
param_count += 1
# 3. Create the entry block and builder
entry_blk: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "entry")
if entry_blk is None: return 0
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: promoted nested closure gets its own DISubprogram.
dbg_pushed_n: int = DebugInfoGen.PushFunction(pool, func_builder, func, node.lineno, node.col_offset + 1)
# 4. Enter the nested function scope (nested symbol table)
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_FUNCTION)
# 5. Create the _env_ptr alloca and store the %env parameter
env_alloca: mlirlite.Value* = mlirlite.build_alloca(func_builder, i8_ptr_ty)
if env_alloca is not None:
self.Gen.VarGen.define_var(trans.SymTab, "_env_ptr", env_alloca)
env_param_val: mlirlite.Value* = mlirlite.SSAValue(pool, i8_ptr_ty, "%env")
mlirlite.build_store(func_builder, env_param_val, env_alloca)
# 5a. Create alloca for nested function params so the body can reference them
# (e.g. for i in range(n) inside accumulate(n), result = val inside set_result(val)).
if fd.args is not None:
ags_p: ast.Arguments* = ast.Arguments*(fd.args)
if ags_p.args is not None:
alist_p: list[ast.AST*]* = ags_p.args
idx_p: int = 0
apn: sizet = len(alist_p)
for api in range(apn):
arg_p: ast.Arg* = ast.Arg*(alist_p.get(api))
if arg_p is None or arg_p.arg is None: continue
if arg_p.annotation is not None:
if self.Gen.TypeGenResolve._is_t_attr(arg_p.annotation, "CVoid", imported_modules) != 0: continue
if idx_p >= param_count: break
pty2_n: mlirlite.LLVMType* = i32_ty
if param_tys is not None:
ty_addr_n: uint64 = uint64(param_tys) + idx_p * 8
ty_slot_n: str* = (str*)(ty_addr_n)
pty2_n = (mlirlite.LLVMType*)(ty_slot_n[0])
if pty2_n is None: pty2_n = i32_ty
p_alloca: mlirlite.Value* = mlirlite.build_alloca(func_builder, pty2_n)
if p_alloca is not None:
self.Gen.VarGen.define_var(trans.SymTab, arg_p.arg, p_alloca)
if arg_p.annotation is not None:
cls_nm_p: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(arg_p.annotation, imported_modules, pool, trans._from_imports)
if cls_nm_p is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, arg_p.arg, cls_nm_p)
# const param: keep TPC's historical test (only whether the annotation contains
# t.CConst, not the pointer depth); both sides align per type-descriptors.md.
if self.Gen.TypeGenErr.has_decorator_marker(arg_p.annotation, "CConst", 0) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, arg_p.arg)
# t.CVolatile param: mark at declaration (same as the FuncGenDef main path) so
# load/store of this param name in the body carry volatile.
if self.Gen.TypeGenErr.has_decorator_marker(arg_p.annotation, "CVolatile", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, arg_p.arg)
# rawptr[t.CVolatile | T] param (volatile pointee): dereferences in the body carry
# volatile (same as the FuncGenDef main path).
if self.Gen.TypeGenErr.has_pointee_volatile_marker(arg_p.annotation) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, arg_p.arg)
pname2_n: str = pool.alloc(32)
if pname2_n is not None: viperlib.snprintf(pname2_n, 32, "%%%s", arg_p.arg)
p_val: mlirlite.Value* = mlirlite.SSAValue(pool, pty2_n, pname2_n)
vol_pn: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, arg_p.arg))
mlirlite.build_store(func_builder, p_val, p_alloca, vol_pn)
idx_p += 1
# 6. Save the parent function scope state (variable-table independent)
old_func: mlirlite.Function* = trans._cur_func
old_builder: mlirlite.IRBuilder* = trans._cur_builder
old_func_name: str = trans._cur_func_name
old_global_count: int = trans._global_name_count
old_nonlocal_count: int = trans._nonlocal_name_count
old_env_count: int = trans._closure_env_count
# break/continue targets and match-case flag must be cleared for the new scope (see
# assignments below); otherwise a nested break/continue inherits the outer target.
old_break_bb: mlirlite.BasicBlock* = trans._break_bb
old_continue_bb: mlirlite.BasicBlock* = trans._continue_bb
old_insw: int = trans._in_switch_case
# Separate nonlocal buffer: sharing [0] would overwrite the parent's set (ClosureTest2
# Test 7: level2's b clobbers level1's a). Restored after translation.
saved_nl_names: t.CPtr = trans._nonlocal_names
saved_nl_types: t.CPtr = trans._nonlocal_types
# 7. Switch to the nested function scope and clear scope names
trans._cur_func = func
trans._cur_builder = func_builder
trans._cur_func_name = func_name
GenApi.clear_scope_names(trans)
trans._break_bb = None
trans._continue_bb = None
trans._in_switch_case = 0
# Independent nonlocal buffers for the nested function (avoid clobbering the parent set)
trans._nonlocal_names = stdlib.malloc(HT.MAX_GLOBAL_NAMES * 8)
trans._nonlocal_types = stdlib.malloc(HT.MAX_GLOBAL_NAMES * 8)
if trans._nonlocal_names is not None: string.memset(trans._nonlocal_names, 0, HT.MAX_GLOBAL_NAMES * 8)
if trans._nonlocal_types is not None: string.memset(trans._nonlocal_types, 0, HT.MAX_GLOBAL_NAMES * 8)
# 8. Pre-scan the body: (a) Global/Nonlocal declarations (Python: whole scope),
# (b) AnnAssign locals to create their allocas early.
body: list[ast.AST*]* = fd.children
if body is not None:
bn: sizet = len(body)
for bi in range(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(bn):
stmt: ast.AST* = body.get(bi)
if stmt is not None: self.Gen.StmtGenDecl.pre_scan_allocas(trans, stmt)
# 8c. Implicit capture scan: outer locals read by the nested function become nonlocal
# (Python semantics). Branch AnnAssign locals are already pre-registered and excluded.
if body is not None:
bnc: sizet = len(body)
for bic in range(bnc):
stmtc: ast.AST* = body.get(bic)
if stmtc is not None: self._closure_scan_captures(trans, stmtc)
# 9. Translate the body (Nonlocal statements fill _nonlocal_names)
if body is not None:
bn2: sizet = len(body)
for bi2 in range(bn2):
stmt2: ast.AST* = body.get(bi2)
if stmt2 is not None: self.Gen.StmtGen.translate_stmt(trans, stmt2)
# 10. Implicit return
last_is_return: int = 0
if body is not None:
bn3: sizet = 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:
zero_val: mlirlite.Value* = mlirlite.const_int32(pool, 0)
mlirlite.build_ret(func_builder, zero_val)
# 11. Save the nested function's nonlocal info
nested_nonlocal_count: int = trans._nonlocal_name_count
# 12. Leave the nested function scope, back to the parent (keep _nonlocal_names for env)
self.Gen.VarGen.exit_scope(trans.SymTab)
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
if dbg_pushed_n != 0: DebugInfoGen.PopFunction(func_builder)
# NOTE: _nonlocal_names and _nonlocal_name_count still hold the nested function's values
# 13. Create the closure object in the parent function
parent_builder: mlirlite.IRBuilder* = old_builder
if parent_builder is None:
# No builder (module-level nested function?) - cannot create a closure, only restore state
trans._cur_func_name = old_func_name
trans._global_name_count = old_global_count
trans._nonlocal_name_count = old_nonlocal_count
trans._closure_env_count = old_env_count
trans._nonlocal_names = saved_nl_names
trans._nonlocal_types = saved_nl_types
return 0
# 13a. Create env: malloc(8 * nested_nonlocal_count) - an array of pointers, one
# 8-byte i8* slot per nonlocal variable pointing at the parent's alloca (shared).
env_ptr: mlirlite.Value* = None
if nested_nonlocal_count > 0:
env_size: int64 = nested_nonlocal_count * 8
env_size_val: mlirlite.Value* = mlirlite.const_int64(pool, env_size)
if env_size_val is not None:
env_size_val.Next = None
env_ptr = mlirlite.build_call(parent_builder, "malloc", env_size_val, 1, i8_ptr_ty, 0)
# Build heap storage per nonlocal and register it into env (runtime env chaining, no
# compile-time map): parent local -> new heap + redirect; parent nonlocal -> reuse heap.
if env_ptr is not None:
i8_ptr_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty)
for ni in range(nested_nonlocal_count):
# Read the name from the _nonlocal_names buffer
name_addr: uint64 = uint64(trans._nonlocal_names) + ni * 8
nl_slot: str* = (str*)(name_addr)
nl_name: str = nl_slot[0]
if nl_name is None: continue
heap_store: mlirlite.Value* = None
# 1. Prefer the global heap-address map (_prepare_chain_nonlocals already built heaps
# for chain nonlocals and stored @nlheap.*; shared across layers, e.g. Test 7).
gname_ck: str = self._nl_heap_global_name(pool, trans, nl_name)
if gname_ck is not None and self._nl_heap_global_exists(mod, gname_ck) != 0:
gv_ref0: mlirlite.Value* = self._nl_heap_global_ref(pool, trans, nl_name)
if gv_ref0 is not None: heap_store = mlirlite.build_load(parent_builder, i8_ptr_ty, gv_ref0)
if heap_store is None:
local_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, nl_name)
if local_alloca is not None and local_alloca.Ty is not None:
# Parent local: new heap storage + copy current value + redirect
nl_load_ty: mlirlite.LLVMType* = local_alloca.Ty.Pointee
if nl_load_ty is not None:
cur_val_nl: mlirlite.Value* = mlirlite.build_load(parent_builder, nl_load_ty, local_alloca)
# Heap size uses the type's byte size: arrays (e.g. [4 x i32]=16 bytes) must not be
# fixed at 8 (out-of-bounds write); pointers/scalars are >= 8 bytes.
st_size: int = self._type_byte_size(pool, nl_load_ty)
st_size_val: mlirlite.Value* = mlirlite.const_int64(pool, st_size)
if st_size_val is not None: st_size_val.Next = None
var_storage: mlirlite.Value* = mlirlite.build_call(parent_builder, "malloc", st_size_val, 1, i8_ptr_ty, 0)
if var_storage is not None:
# Copy the current value to heap storage
st_slot_nl: mlirlite.Value* = mlirlite.build_bitcast(parent_builder, var_storage, mlirlite.Ptr(pool, nl_load_ty))
if st_slot_nl is not None and cur_val_nl is not None: mlirlite.build_store(parent_builder, cur_val_nl, st_slot_nl)
# Redirect the parent's VarEntry.Alloca to the heap (parent reads/writes share it)
ve_nl: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nl_name)
heap_typed: mlirlite.Value* = mlirlite.build_bitcast(parent_builder, var_storage, mlirlite.Ptr(pool, nl_load_ty))
if ve_nl is not None and heap_typed is not None: ve_nl.Alloca = heap_typed
# Record into the global map (visible to later layers/parent)
gv_ref_new: mlirlite.Value* = None
if gname_ck is not None and self._nl_heap_global_exists(mod, gname_ck) == 0:
gv_new: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, gname_ck, i8_ptr_ty)
if gv_new is not None:
# Clear external linkage + zero-init (prevents undefined reference)
gv_new.Linkage = None
gv_new.Initializer = "null"
mlirlite.module_add_global(mod, gv_new)
gv_ref_new = self._nl_heap_global_ref(pool, trans, nl_name)
if gv_ref_new is not None: mlirlite.build_store(parent_builder, var_storage, gv_ref_new)
heap_store = var_storage
else:
# In the parent this variable is nonlocal (multi-level nesting): read the heap
# address from the parent env (chained sharing), do not allocate.
heap_store = self.Gen.StmtGenDecl._load_raw_var_ptr(trans, nl_name)
if heap_store is None: continue
# env[ni] = heap storage address (shared)
offset_val: mlirlite.Value* = mlirlite.const_int32(pool, ni * 8)
slot: mlirlite.Value* = mlirlite.build_gep(parent_builder, i8_ty, env_ptr, offset_val)
if slot is not None:
slot_typed: mlirlite.Value* = mlirlite.build_bitcast(parent_builder, slot, i8_ptr_ptr_ty)
if slot_typed is not None: mlirlite.build_store(parent_builder, heap_store, slot_typed)
# 13b. Create the closure struct: malloc(16)
closure_size_val: mlirlite.Value* = mlirlite.const_int64(pool, 16)
if closure_size_val is not None: closure_size_val.Next = None
closure_ptr: mlirlite.Value* = mlirlite.build_call(parent_builder, "malloc", closure_size_val, 1, i8_ptr_ty, 0)
if closure_ptr is None:
# malloc failed, restore state
trans._cur_func_name = old_func_name
trans._global_name_count = old_global_count
trans._nonlocal_name_count = old_nonlocal_count
trans._closure_env_count = old_env_count
trans._nonlocal_names = saved_nl_names
trans._nonlocal_types = saved_nl_types
return 0
# 13c. Store fn_ptr at offset 0; create the function pointer type i32(i8*)*.
fn_param_node: mlirlite.ParamNode* = mlirlite.new_param_node(pool, i8_ptr_ty)
fn_func_ty: mlirlite.LLVMType* = mlirlite.Func(pool, i32_ty, fn_param_node, 1)
fn_func_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, fn_func_ty)
# Create the function reference Value (type i32(i8*)*, using the SHA1 mangled name)
fn_ptr_name: str = pool.alloc(64)
if fn_ptr_name is not None:
# SHA1-prefixed names contain '.' so they need quoting (e.g. @"sha1.__closure_func")
if string.strchr(mangled_promoted, '.') is not None: viperlib.snprintf(fn_ptr_name, 64, "@\"%s\"", mangled_promoted)
else: viperlib.snprintf(fn_ptr_name, 64, "@%s", mangled_promoted)
fn_ptr_val: mlirlite.Value* = mlirlite.SSAValue(pool, fn_func_ptr_ty, fn_ptr_name)
# bitcast the function pointer to i8* (the closure stores i8*)
fn_as_i8ptr: mlirlite.Value* = mlirlite.build_bitcast(parent_builder, fn_ptr_val, i8_ptr_ty)
fn_slot: mlirlite.Value* = mlirlite.build_bitcast(parent_builder, closure_ptr, mlirlite.Ptr(pool, i8_ptr_ty))
if fn_slot is not None and fn_as_i8ptr is not None: mlirlite.build_store(parent_builder, fn_as_i8ptr, fn_slot)
# 13d. Store env_ptr at offset 8
eight_val: mlirlite.Value* = mlirlite.const_int32(pool, 8)
env_slot_addr: mlirlite.Value* = mlirlite.build_gep(parent_builder, i8_ty, closure_ptr, eight_val)
if env_slot_addr is not None:
env_slot: mlirlite.Value* = mlirlite.build_bitcast(parent_builder, env_slot_addr, mlirlite.Ptr(pool, i8_ptr_ty))
if env_slot is not None:
if env_ptr is not None: mlirlite.build_store(parent_builder, env_ptr, env_slot)
else:
# No nonlocal variables, store null
null_val: mlirlite.Value* = mlirlite.SSAValue(pool, i8_ptr_ty, "null")
mlirlite.build_store(parent_builder, null_val, env_slot)
# 13e. Store the closure pointer into the parent's local variable {func_name}
closure_alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(parent_builder, i8_ptr_ty)
if closure_alloca is not None:
mlirlite.build_store(parent_builder, closure_ptr, closure_alloca)
self.Gen.VarGen.define_var(trans.SymTab, func_name, closure_alloca)
# Register the closure variable name so calls can tell a closure from a plain function
# pointer variable (both allocas are i8**, which _is_func_ptr_alloca cannot distinguish).
self.Gen.CallGenLeafKit.register_closure_name(func_name)
# 14. Restore scope names (including the nonlocal set pointers)
trans._cur_func_name = old_func_name
trans._global_name_count = old_global_count
trans._nonlocal_name_count = old_nonlocal_count
trans._closure_env_count = old_env_count
trans._nonlocal_names = saved_nl_names
trans._nonlocal_types = saved_nl_types
return 1
# _closure_scan_captures - recursively register implicit captures of the nested body.
# Order must match step 13's env build; runs before body translation, so it is stable.
def _closure_scan_captures(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Recursively register implicit captures in a nested body as nonlocal"""
if node is None or trans is None: return 1
k: int = node.kind()
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is None: return 1
# Defined in the current scope (param / AnnAssign local, incl. branches) - not a capture
if self.Gen.VarGen.lookup_current(trans.SymTab, nm.id) is not None: return 1
# Already a registered nonlocal (explicit statement / already captured) - skip
if GenApi.is_nonlocal_name(trans, nm.id) != 0: return 1
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if ve is None or ve.Alloca is None or ve.Alloca.Ty is None: return 1
nl_ty: mlirlite.LLVMType* = ve.Alloca.Ty.Pointee
if nl_ty is None: return 1
# Module-level globals resolve directly, no env capture needed
mod_gv: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
if mod_gv is not None:
if uint64(mod_gv) == uint64(ve.Alloca): return 1
# Outer-function local -> implicit capture (register as nonlocal)
GenApi.add_nonlocal_name(trans, nm.id, nl_ty)
return 1
# ---- Statements ----
if k == ast.ASTKind.Assign:
asn: ast.Assign* = ast.Assign*(node)
if asn.value is not None: self._closure_scan_captures(trans, asn.value)
return 1
if k == ast.ASTKind.AnnAssign:
aan: ast.AnnAssign* = ast.AnnAssign*(node)
if aan.value is not None: self._closure_scan_captures(trans, aan.value)
return 1
if k == ast.ASTKind.AugAssign:
aun: ast.AugAssign* = ast.AugAssign*(node)
# The target is also read (reads the old value), so it participates in capture
if aun.target is not None: self._closure_scan_captures(trans, aun.target)
if aun.value is not None: self._closure_scan_captures(trans, aun.value)
return 1
if k == ast.ASTKind.If:
ifn: ast.If* = ast.If*(node)
if ifn.test is not None: self._closure_scan_captures(trans, ifn.test)
if ifn.children is not None:
bn_if: sizet = len(ifn.children)
for bi_if in range(bn_if): self._closure_scan_captures(trans, ifn.children.get(bi_if))
if ifn.orelse is not None:
bn_el: sizet = len(ifn.orelse)
for bi_el in range(bn_el): self._closure_scan_captures(trans, ifn.orelse.get(bi_el))
return 1
if k == ast.ASTKind.For:
for_n: ast.For* = ast.For*(node)
# The loop target is a new local, not captured
if for_n.iter is not None: self._closure_scan_captures(trans, for_n.iter)
if for_n.children is not None:
bn_f: sizet = len(for_n.children)
for bi_f in range(bn_f): self._closure_scan_captures(trans, for_n.children.get(bi_f))
if for_n.orelse is not None:
bn_fe: sizet = len(for_n.orelse)
for bi_fe in range(bn_fe): self._closure_scan_captures(trans, for_n.orelse.get(bi_fe))
return 1
if k == ast.ASTKind.While:
wh_n: ast.While* = ast.While*(node)
if wh_n.test is not None: self._closure_scan_captures(trans, wh_n.test)
if wh_n.children is not None:
bn_w: sizet = len(wh_n.children)
for bi_w in range(bn_w): self._closure_scan_captures(trans, wh_n.children.get(bi_w))
if wh_n.orelse is not None:
bn_we: sizet = len(wh_n.orelse)
for bi_we in range(bn_we): self._closure_scan_captures(trans, wh_n.orelse.get(bi_we))
return 1
if k == ast.ASTKind.Return:
rt_n: ast.Return* = ast.Return*(node)
if rt_n.value is not None: self._closure_scan_captures(trans, rt_n.value)
return 1
if k == ast.ASTKind.Expr:
ex_n: ast.Expr* = ast.Expr*(node)
if ex_n.value is not None: self._closure_scan_captures(trans, ex_n.value)
return 1
if k == ast.ASTKind.With:
wth: ast.With* = ast.With*(node)
if wth.items is not None:
bn_wi: sizet = len(wth.items)
for bi_wi in range(bn_wi):
wi_n: ast.AST* = wth.items.get(bi_wi)
if wi_n is not None and wi_n.kind() == ast.ASTKind.WithItem:
wi_c: ast.WithItem* = ast.WithItem*(wi_n)
if wi_c.context_expr is not None: self._closure_scan_captures(trans, wi_c.context_expr)
if wth.children is not None:
bn_wb: sizet = len(wth.children)
for bi_wb in range(bn_wb): self._closure_scan_captures(trans, wth.children.get(bi_wb))
return 1
if k == ast.ASTKind.Raise:
rs_n: ast.Raise* = ast.Raise*(node)
if rs_n.exc is not None: self._closure_scan_captures(trans, rs_n.exc)
if rs_n.cause is not None: self._closure_scan_captures(trans, rs_n.cause)
return 1
if k == ast.ASTKind.Assert:
as_n: ast.Assert* = ast.Assert*(node)
if as_n.test is not None: self._closure_scan_captures(trans, as_n.test)
if as_n.msg is not None: self._closure_scan_captures(trans, as_n.msg)
return 1
if k == ast.ASTKind.Try:
tr_n: ast.Try* = ast.Try*(node)
if tr_n.children is not None:
bn_t: sizet = len(tr_n.children)
for bi_t in range(bn_t): self._closure_scan_captures(trans, tr_n.children.get(bi_t))
if tr_n.handlers is not None:
bn_th: sizet = len(tr_n.handlers)
for bi_th in range(bn_th):
eh_n: ast.AST* = tr_n.handlers.get(bi_th)
if eh_n is not None and eh_n.kind() == ast.ASTKind.ExceptHandler:
eh_c: ast.ExceptHandler* = ast.ExceptHandler*(eh_n)
if eh_c.children is not None:
bn_ehb: sizet = len(eh_c.children)
for bi_ehb in range(bn_ehb): self._closure_scan_captures(trans, eh_c.children.get(bi_ehb))
if tr_n.orelse is not None:
bn_to: sizet = len(tr_n.orelse)
for bi_to in range(bn_to): self._closure_scan_captures(trans, tr_n.orelse.get(bi_to))
if tr_n.finalbody is not None:
bn_tf: sizet = len(tr_n.finalbody)
for bi_tf in range(bn_tf): self._closure_scan_captures(trans, tr_n.finalbody.get(bi_tf))
return 1
if k == ast.ASTKind.Match:
mt_n: ast.Match* = ast.Match*(node)
if mt_n.subject is not None: self._closure_scan_captures(trans, mt_n.subject)
if mt_n.cases is not None:
bn_mc: sizet = len(mt_n.cases)
for bi_mc in range(bn_mc):
mc_n: ast.AST* = mt_n.cases.get(bi_mc)
if mc_n is not None and mc_n.kind() == ast.ASTKind.MatchCase:
mc_c: ast.MatchCase* = ast.MatchCase*(mc_n)
if mc_c.guard is not None: self._closure_scan_captures(trans, mc_c.guard)
if mc_c.children is not None:
bn_mcb: sizet = len(mc_c.children)
for bi_mcb in range(bn_mcb): self._closure_scan_captures(trans, mc_c.children.get(bi_mcb))
return 1
# ---- Expressions ----
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop.left is not None: self._closure_scan_captures(trans, bop.left)
if bop.right is not None: self._closure_scan_captures(trans, bop.right)
return 1
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(node)
if uop.operand is not None: self._closure_scan_captures(trans, uop.operand)
return 1
if k == ast.ASTKind.BoolOp:
bop2: ast.BoolOp* = ast.BoolOp*(node)
if bop2.values is not None:
bn_bo: sizet = len(bop2.values)
for bi_bo in range(bn_bo): self._closure_scan_captures(trans, bop2.values.get(bi_bo))
return 1
if k == ast.ASTKind.Compare:
cmp: ast.Compare* = ast.Compare*(node)
if cmp.left is not None: self._closure_scan_captures(trans, cmp.left)
if cmp.comparators is not None:
bn_cmp: sizet = len(cmp.comparators)
for bi_cmp in range(bn_cmp): self._closure_scan_captures(trans, cmp.comparators.get(bi_cmp))
return 1
if k == ast.ASTKind.Call:
cal: ast.Call* = ast.Call*(node)
if cal.func is not None: self._closure_scan_captures(trans, cal.func)
if cal.args is not None:
bn_ca: sizet = len(cal.args)
for bi_ca in range(bn_ca): self._closure_scan_captures(trans, cal.args.get(bi_ca))
if cal.keywords is not None:
bn_ck: sizet = len(cal.keywords)
for bi_ck in range(bn_ck):
kw_n: ast.AST* = cal.keywords.get(bi_ck)
if kw_n is not None and kw_n.kind() == ast.ASTKind.Keyword:
kw_c: ast.Keyword* = ast.Keyword*(kw_n)
if kw_c.value is not None: self._closure_scan_captures(trans, kw_c.value)
return 1
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(node)
if sub.value is not None: self._closure_scan_captures(trans, sub.value)
if sub.slice is not None: self._closure_scan_captures(trans, sub.slice)
return 1
if k == ast.ASTKind.Attribute:
at_n: ast.Attribute* = ast.Attribute*(node)
if at_n.value is not None: self._closure_scan_captures(trans, at_n.value)
return 1
if k == ast.ASTKind.IfExp:
ie: ast.IfExp* = ast.IfExp*(node)
if ie.test is not None: self._closure_scan_captures(trans, ie.test)
if ie.body is not None: self._closure_scan_captures(trans, ie.body)
if ie.orelse is not None: self._closure_scan_captures(trans, ie.orelse)
return 1
if k == ast.ASTKind.Dict:
dc: ast.Dict* = ast.Dict*(node)
if dc.keys is not None:
bn_dk: sizet = len(dc.keys)
for bi_dk in range(bn_dk): self._closure_scan_captures(trans, dc.keys.get(bi_dk))
if dc.values is not None:
bn_dv: sizet = len(dc.values)
for bi_dv in range(bn_dv): self._closure_scan_captures(trans, dc.values.get(bi_dv))
return 1
if k == ast.ASTKind.Set:
st_n: ast.Set* = ast.Set*(node)
if st_n.elts is not None:
bn_set: sizet = len(st_n.elts)
for bi_set in range(bn_set): self._closure_scan_captures(trans, st_n.elts.get(bi_set))
return 1
if k == ast.ASTKind.List:
ls_n: ast.List* = ast.List*(node)
if ls_n.elts is not None:
bn_ls: sizet = len(ls_n.elts)
for bi_ls in range(bn_ls): self._closure_scan_captures(trans, ls_n.elts.get(bi_ls))
return 1
if k == ast.ASTKind.Tuple:
tp_n: ast.Tuple* = ast.Tuple*(node)
if tp_n.elts is not None:
bn_tp: sizet = len(tp_n.elts)
for bi_tp in range(bn_tp): self._closure_scan_captures(trans, tp_n.elts.get(bi_tp))
return 1
if k == ast.ASTKind.Starred:
sr_n: ast.Starred* = ast.Starred*(node)
if sr_n.value is not None: self._closure_scan_captures(trans, sr_n.value)
return 1
if k == ast.ASTKind.NamedExpr:
ne_n: ast.NamedExpr* = ast.NamedExpr*(node)
if ne_n.value is not None: self._closure_scan_captures(trans, ne_n.value)
return 1
if k == ast.ASTKind.Slice:
sl_n: ast.Slice* = ast.Slice*(node)
if sl_n.lower is not None: self._closure_scan_captures(trans, sl_n.lower)
if sl_n.upper is not None: self._closure_scan_captures(trans, sl_n.upper)
if sl_n.step is not None: self._closure_scan_captures(trans, sl_n.step)
return 1
if k == ast.ASTKind.JoinedStr:
js_n: ast.JoinedStr* = ast.JoinedStr*(node)
if js_n.values is not None:
bn_js: sizet = len(js_n.values)
for bi_js in range(bn_js): self._closure_scan_captures(trans, js_n.values.get(bi_js))
return 1
if k == ast.ASTKind.FormattedValue:
fv_n: ast.FormattedValue* = ast.FormattedValue*(node)
if fv_n.value is not None: self._closure_scan_captures(trans, fv_n.value)
if fv_n.format_spec is not None: self._closure_scan_captures(trans, fv_n.format_spec)
return 1
if k == ast.ASTKind.Await:
aw_n: ast.Await* = ast.Await*(node)
if aw_n.value is not None: self._closure_scan_captures(trans, aw_n.value)
return 1
if k == ast.ASTKind.Yield:
yd_n: ast.Yield* = ast.Yield*(node)
if yd_n.value is not None: self._closure_scan_captures(trans, yd_n.value)
return 1
if k == ast.ASTKind.YieldFrom:
yf_n: ast.YieldFrom* = ast.YieldFrom*(node)
if yf_n.value is not None: self._closure_scan_captures(trans, yf_n.value)
return 1
is_comp: int = 0
if k == ast.ASTKind.ListComp: is_comp = 1
elif k == ast.ASTKind.SetComp: is_comp = 1
elif k == ast.ASTKind.GeneratorExp: is_comp = 1
if is_comp != 0:
lc_n: ast.ListComp* = ast.ListComp*(node)
if lc_n.elt is not None: self._closure_scan_captures(trans, lc_n.elt)
if lc_n.generators is not None:
bn_gen: sizet = len(lc_n.generators)
for bi_gen in range(bn_gen):
cp_n: ast.AST* = lc_n.generators.get(bi_gen)
if cp_n is not None and cp_n.kind() == ast.ASTKind.Comprehension:
cp_c: ast.Comprehension* = ast.Comprehension*(cp_n)
if cp_c.iter is not None: self._closure_scan_captures(trans, cp_c.iter)
if cp_c.ifs is not None:
bn_cpi: sizet = len(cp_c.ifs)
for bi_cpi in range(bn_cpi): self._closure_scan_captures(trans, cp_c.ifs.get(bi_cpi))
return 1
if k == ast.ASTKind.DictComp:
dcn: ast.DictComp* = ast.DictComp*(node)
if dcn.key is not None: self._closure_scan_captures(trans, dcn.key)
if dcn.value is not None: self._closure_scan_captures(trans, dcn.value)
if dcn.generators is not None:
bn_dgen: sizet = len(dcn.generators)
for bi_dgen in range(bn_dgen):
cp_dn: ast.AST* = dcn.generators.get(bi_dgen)
if cp_dn is not None and cp_dn.kind() == ast.ASTKind.Comprehension:
cp_dc: ast.Comprehension* = ast.Comprehension*(cp_dn)
if cp_dc.iter is not None: self._closure_scan_captures(trans, cp_dc.iter)
if cp_dc.ifs is not None:
bn_dcpi: sizet = len(cp_dc.ifs)
for bi_dcpi in range(bn_dcpi): self._closure_scan_captures(trans, cp_dc.ifs.get(bi_dcpi))
return 1
return 1
# _type_byte_size - compute an LLVM type's byte size recursively (for nonlocal shared
# heap: arrays must not be fixed at 8). `case _` returns 8 because size is unknown.
def _type_byte_size(self, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*) -> int:
"""Compute an LLVM type's byte size recursively"""
if ty is None:
# [S4 fail-fast] Missing type -> no longer assume 8 bytes (under-allocation overruns neighbors)
self.Gen.TypeGenErr.fatal_error(None, "nonlocal 共享存储:类型缺失,无法计算尺寸", "TypeError")
return 0
match ty:
case mlirlite.LLVMType.Int(bits):
sz_i: int = (bits + 7) // 8
if sz_i < 8: sz_i = 8
return sz_i
case mlirlite.LLVMType.Ptr(_pe):
return 8
case mlirlite.LLVMType.Float(fb):
if fb == 64: return 8
if fb >= 128: return 16
return 4
case mlirlite.LLVMType.Array(et, cnt):
esz: int = self._type_byte_size(pool, et)
return esz * cnt
case _:
return 8
# ============================================================
# _nl_heap_global_name - global map name nlheap_{sha1}.{name} for a nonlocal heap address
def _nl_heap_global_name(self, pool: memhub.MemBuddy*, trans: HT.Translator*, name: str) -> str:
"""Build the nonlocal heap-address global map name nlheap_{sha1}.{name}"""
if pool is None or trans is None or name is None: return None
mangled: str = self.Gen.FuncGenBase._mangle_name(trans, name)
buf: str = pool.alloc(160)
if buf is not None: viperlib.snprintf(buf, 160, "nlheap_%s", mangled)
return buf
# ============================================================
# _nl_heap_global_ref - reference Value (i8**) for the global map variable
def _nl_heap_global_ref(self, pool: memhub.MemBuddy*, trans: HT.Translator*, name: str) -> mlirlite.Value*:
"""Get a reference Value for the nonlocal heap-address global map variable"""
if pool is None or trans is None or name is None: return None
gname: str = self._nl_heap_global_name(pool, trans, name)
if gname is None: return None
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
ref_buf: str = pool.alloc(176)
if ref_buf is None: return None
viperlib.snprintf(ref_buf, 176, "@\"%s\"", gname)
return mlirlite.SSAValue(pool, mlirlite.Ptr(pool, i8_ptr_ty), ref_buf)
# ============================================================
# _nl_heap_global_exists - check whether the global map variable already exists
def _nl_heap_global_exists(self, mod: mlirlite.LLVMModule*, gname: str) -> int:
"""Return 1 if the global map variable gname already exists in the module"""
if mod is None or gname is None: return 0
gcur: mlirlite.GlobalVariable* = mod.GlobalHead
while gcur is not None:
if gcur.Name is not None and string.strcmp(gcur.Name, gname) == 0: return 1
gcur = gcur.Next
return 0
# ============================================================
# _collect_chain_nonlocals - recursively gather nonlocal names in the nested chain (deduped)
def _collect_chain_nonlocals(self, trans: HT.Translator*, node: ast.AST*, names: t.CPtr, count: int) -> int:
"""Recursively collect all nonlocal declaration names in a nested FunctionDef chain (deduped)"""
if node is None or names is None: return count
fd: ast.FunctionDef* = ast.FunctionDef*(node)
if fd is None or fd.children is None: return count
ch: list[ast.AST*]* = fd.children
cn: sizet = len(ch)
for ci in range(cn):
st: ast.AST* = ch.get(ci)
if st is None: continue
k: int = st.kind()
if k == ast.ASTKind.FunctionDef: count = self._collect_chain_nonlocals(trans, st, names, count)
elif k == ast.ASTKind.Nonlocal:
nl_node: ast.Nonlocal* = ast.Nonlocal*(st)
if nl_node is not None and nl_node.names is not None:
nl_list: list[ast.AST*]* = nl_node.names
nn: sizet = len(nl_list)
for ni in range(nn):
nm_node: ast.AST* = nl_list.get(ni)
if nm_node is None: continue
nm_x: ast.Name* = ast.Name*(nm_node)
if nm_x is None or nm_x.id is None: continue
dup: int = 0
for di in range(count):
if string.strcmp((str)(uint64(names) + di * 8), nm_x.id) == 0:
dup = 1
break
if dup == 0 and count < 64:
nm_slot: str* = str*(uint64(names) + count * 8)
nm_slot[0] = nm_x.id
count += 1
return count
# _prepare_chain_nonlocals - pre-scan the nested chain's nonlocals and build heaps.
# Called before translate_nested_function_def so inner closures can load from @nlheap.*.
def _prepare_chain_nonlocals(self, trans: HT.Translator*, node: ast.AST*, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, i8_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*) -> int:
"""Pre-collect nonlocals in a nested function chain and build their heaps; returns 1 on success"""
if node is None or trans is None or builder is None or mod is None: return 0
# Build only once per function so sibling nested defs share one heap (avoids double
# malloc and double VarEntry.Alloca redirect desyncing counters).
if trans._nl_chain_done != 0: return 1
if trans._nl_chain_names is None:
trans._nl_chain_names = pool.alloc(8 * 64)
if trans._nl_chain_names is not None: string.memset(trans._nl_chain_names, 0, 8 * 64)
if trans._nl_chain_names is None: return 0
trans._nl_chain_count = self._collect_chain_nonlocals(trans, node, trans._nl_chain_names, 0)
trans._nl_chain_done = 1
for ci in range(trans._nl_chain_count):
nm_slot: str* = str*(uint64(trans._nl_chain_names) + ci * 8)
nl_name: str = nm_slot[0]
if nl_name is None: continue
# Build the heap only in the defining function (current top scope has an alloca)
local_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, nl_name)
if local_alloca is None or local_alloca.Ty is None: continue
nl_ty: mlirlite.LLVMType* = local_alloca.Ty.Pointee
if nl_ty is None: continue
gname: str = self._nl_heap_global_name(pool, trans, nl_name)
if gname is None: continue
# Build the heap and copy the current value each time this function runs, then store
# into the module map; do not skip just because the map variable exists.
st_size: int = self._type_byte_size(pool, nl_ty)
st_size_val: mlirlite.Value* = mlirlite.const_int64(pool, st_size)
if st_size_val is not None: st_size_val.Next = None
var_storage: mlirlite.Value* = mlirlite.build_call(builder, "malloc", st_size_val, 1, i8_ptr_ty, 0)
if var_storage is None: continue
cur_val: mlirlite.Value* = mlirlite.build_load(builder, nl_ty, local_alloca)
st_slot: mlirlite.Value* = mlirlite.build_bitcast(builder, var_storage, mlirlite.Ptr(pool, nl_ty))
if st_slot is not None and cur_val is not None: mlirlite.build_store(builder, cur_val, st_slot)
# Redirect VarEntry so the parent reads/writes the shared heap
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nl_name)
if ve is not None:
heap_typed: mlirlite.Value* = mlirlite.build_bitcast(builder, var_storage, mlirlite.Ptr(pool, nl_ty))
if heap_typed is not None: ve.Alloca = heap_typed
# Create/reuse the global map variable and store the heap address (cross-function visible)
gv_found: mlirlite.GlobalVariable* = None
gcur: mlirlite.GlobalVariable* = mod.GlobalHead
while gcur is not None:
if gcur.Name is not None and string.strcmp(gcur.Name, gname) == 0:
gv_found = gcur
break
gcur = gcur.Next
if gv_found is None:
gv_found = mlirlite.new_global_variable(pool, gname, i8_ptr_ty)
if gv_found is not None:
# Clear external linkage and zero-init, otherwise it prints as an external
# declaration with no definition -> link-time undefined reference.
gv_found.Linkage = None
gv_found.Initializer = "null"
mlirlite.module_add_global(mod, gv_found)
if gv_found is not None:
gv_ref: mlirlite.Value* = self._nl_heap_global_ref(pool, trans, nl_name)
if gv_ref is not None: mlirlite.build_store(builder, var_storage, gv_ref)
return 1
# translate_lambda - translate a lambda into a closure, same architecture as
# translate_nested_function_def: hoisted __lambda_{id}, captured heap slots, {fn,env}.
def translate_lambda(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Translate a lambda expression and return the closure pointer (i8*)"""
if node is None or trans is None or builder is None: return None
lam: ast.Lambda* = ast.Lambda*(node)
if lam 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)
i8_ptr_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty)
# 1. Lambda id + promoted function name. The counter lives on trans (_lambda_count):
# a new self field would crash TPC's Mixin class_members lookup (FuncGen.py L437).
lambda_id: int = trans._lambda_count
trans._lambda_count += 1
pname_buf: str = pool.alloc(64)
if pname_buf is None: return None
viperlib.snprintf(pname_buf, 64, "__lambda_%d", lambda_id)
mangled_promoted: str = self.Gen.FuncGenBase._mangle_name(trans, pname_buf)
# 2. Resolve params (name + type; default i32, resolved from the annotation)
param_names: str = pool.alloc(32 * 64) # 64 params x 32-byte names
if param_names is None: return None
string.memset(param_names, 0, 32 * 64)
param_tys: t.CPtr = pool.alloc(8 * 64) # LLVMType pointer slots
if param_tys is None: return None
string.memset(param_tys, 0, 8 * 64)
param_count: int = 0
if lam.args is not None:
ags: ast.Arguments* = ast.Arguments*(lam.args)
if ags.args is not None:
alist: list[ast.AST*]* = ags.args
pn: sizet = len(alist)
for pi in range(pn):
if param_count >= 64: break
arg_n: ast.Arg* = ast.Arg*(alist.get(pi))
if arg_n is None or arg_n.arg is None: continue
string.strncpy(param_names + param_count * 32, arg_n.arg, 31)
p_ty: mlirlite.LLVMType* = i32_ty
if arg_n.annotation is not None:
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg_n.annotation, trans._imported_modules, trans._from_imports, trans, 0)
if resolved is None:
# fail-fast: annotated lambda param failed to resolve; do not silently keep i32
self.Gen.TypeGenErr.fatal_error(arg_n, f"参数 '{arg_n.arg}' 类型解析失败: {self.Gen.TypeGenResolve.annotation_to_str(arg_n.annotation)}", "NameError")
resolved = i32_ty # unreachable (fatal_error already exited)
if resolved is not None:
match resolved:
case mlirlite.LLVMType.Struct(_, _, _):
p_ty = mlirlite.Ptr(pool, resolved)
case mlirlite.LLVMType.Array(aelem_ty_l, _):
p_ty = mlirlite.Ptr(pool, aelem_ty_l)
case _:
p_ty = resolved
ty_slot_p: str* = str*(uint64(param_tys) + param_count * 8)
ty_slot_p[0] = p_ty
param_count += 1
# 3. Collect captured variables (outer Name references in the body)
cap_names: str = pool.alloc(64 * 64) # 64 captures x 64-byte names
if cap_names is None: return None
string.memset(cap_names, 0, 64 * 64)
cap_allocas: t.CPtr = pool.alloc(8 * 64) # Parent alloca pointer slots
if cap_allocas is None: return None
string.memset(cap_allocas, 0, 8 * 64)
cap_tys: t.CPtr = pool.alloc(8 * 64) # Capture variable type slots
if cap_tys is None: return None
string.memset(cap_tys, 0, 8 * 64)
cap_count: int = self._lambda_scan_body(trans, lam.body, param_names, param_count, cap_names, cap_allocas, cap_tys, 0)
# 4. Create the promoted function __lambda_{id}(i8* %env, [args...])
func: mlirlite.Function* = mlirlite.create_function(pool, mod, mangled_promoted, i32_ty)
if func is None: return None
mlirlite.add_param(pool, func, i8_ptr_ty, "%env")
for pi2 in range(param_count):
ty_addr2: uint64 = uint64(param_tys) + pi2 * 8
ty_slot2: str* = str*(ty_addr2)
p_ty2: mlirlite.LLVMType* = (mlirlite.LLVMType*)(ty_slot2[0])
if p_ty2 is None: p_ty2 = i32_ty
pname_l: str = pool.alloc(32)
if pname_l is not None:
viperlib.snprintf(pname_l, 32, "%%%s", param_names + pi2 * 32)
mlirlite.add_param(pool, func, p_ty2, pname_l)
# 5. Entry block + builder
entry_blk: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "entry")
if entry_blk is None: return None
func_builder: mlirlite.IRBuilder* = mlirlite.new_builder(pool, func)
if func_builder is None: return None
mlirlite.position_at_end(func_builder, entry_blk)
# --traceback: promoted lambda gets its own DISubprogram.
dbg_pushed_l: int = DebugInfoGen.PushFunction(pool, func_builder, func, node.lineno, node.col_offset + 1)
# 6. Enter the lambda scope
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_FUNCTION)
# 7. _env_ptr alloca (stores the %env parameter)
env_alloca: mlirlite.Value* = mlirlite.build_alloca(func_builder, i8_ptr_ty)
if env_alloca is not None:
self.Gen.VarGen.define_var(trans.SymTab, "_env_ptr", env_alloca)
env_param_val: mlirlite.Value* = mlirlite.SSAValue(pool, i8_ptr_ty, "%env")
mlirlite.build_store(func_builder, env_param_val, env_alloca)
# 8. Save parent state and switch to the promoted function
old_func: mlirlite.Function* = trans._cur_func
old_builder: mlirlite.IRBuilder* = trans._cur_builder
old_func_name: str = trans._cur_func_name
old_global_count: int = trans._global_name_count
old_nonlocal_count: int = trans._nonlocal_name_count
old_env_count: int = trans._closure_env_count
# break/continue targets and match-case flag must be cleared for the new scope (see
# assignments below); otherwise a lambda break/continue inherits the outer target.
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._cur_func_name = mangled_promoted
GenApi.clear_scope_names(trans)
trans._break_bb = None
trans._continue_bb = None
trans._in_switch_case = 0
# 9. Captured variables: load values from env heap slots into local allocas
for ci in range(cap_count):
# Take the capture name via str pointer offset (like param_names + pi*32). Do not use
# (str)(cap_addr): TPV compiles that to itoa, corrupting define_var keys.
cap_slot: str = cap_names + ci * 64
if cap_slot[0] == '\0': continue
ty_addr3: uint64 = uint64(cap_tys) + ci * 8
ty_slot3: str* = (str*)(ty_addr3)
cap_ty: mlirlite.LLVMType* = (mlirlite.LLVMType*)(ty_slot3[0])
if cap_ty is None: continue
off_val: mlirlite.Value* = mlirlite.const_int32(pool, ci * 8)
slot_addr: mlirlite.Value* = mlirlite.build_gep(func_builder, i8_ty, env_param_val, off_val)
if slot_addr is None: continue
storage_addr: mlirlite.Value* = mlirlite.build_load(func_builder, i8_ptr_ty, slot_addr)
if storage_addr is None: continue
st_typed: mlirlite.Value* = mlirlite.build_bitcast(func_builder, storage_addr, mlirlite.Ptr(pool, cap_ty))
if st_typed is None: continue
cap_val: mlirlite.Value* = mlirlite.build_load(func_builder, cap_ty, st_typed)
if cap_val is None: continue
var_alloca: mlirlite.Value* = mlirlite.build_alloca(func_builder, cap_ty)
if var_alloca is not None:
mlirlite.build_store(func_builder, cap_val, var_alloca)
self.Gen.VarGen.define_var(trans.SymTab, cap_slot, var_alloca)
# 10. Parameter allocas
for pi3 in range(param_count):
ty_addr4: uint64 = uint64(param_tys) + pi3 * 8
ty_slot4: str* = (str*)(ty_addr4)
p_ty3: mlirlite.LLVMType* = (mlirlite.LLVMType*)(ty_slot4[0])
if p_ty3 is None:
# fail-fast: lambda param type slot missing (inconsistent state); do not keep i32
self.Gen.TypeGenErr.fatal_error(lam, f"lambda 参数 '{param_names + pi3 * 32}' 类型信息缺失", "InternalCompilerError")
p_ty3 = i32_ty # unreachable (fatal_error already exited)
pname_ref: str = pool.alloc(32)
if pname_ref is not None:
viperlib.snprintf(pname_ref, 32, "%%%s", param_names + pi3 * 32)
param_ref: mlirlite.Value* = mlirlite.SSAValue(pool, p_ty3, pname_ref)
arg_alloca: mlirlite.Value* = mlirlite.build_alloca(func_builder, p_ty3)
if arg_alloca is not None:
mlirlite.build_store(func_builder, param_ref, arg_alloca)
self.Gen.VarGen.define_var(trans.SymTab, param_names + pi3 * 32, arg_alloca)
# 11. Translate the body expression, coerce to i32 and return
body_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(func_builder, pool, mod, lam.body, None, 0, trans)
if body_val is not None and body_val.Ty is not None:
if self.Gen.ExprGenBase.get_llvm_type_bits(body_val.Ty) == 32 and self.Gen.ExprGenBase.is_ptr_type(body_val.Ty) == 0: mlirlite.build_ret(func_builder, body_val)
else:
ret_val: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(func_builder, body_val, i32_ty, lam.body)
if ret_val is not None: mlirlite.build_ret(func_builder, ret_val)
else:
# fail-fast: lambda body coercion failed; do not silently return 0
self.Gen.TypeGenErr.fatal_error(lam.body, "lambda 体表达式翻译失败", "TypeError")
else:
# fail-fast: lambda body translation failed; do not silently return 0
self.Gen.TypeGenErr.fatal_error(lam.body, "lambda 体表达式翻译失败", "TypeError")
# 12. Leave the lambda scope and restore the parent state
self.Gen.VarGen.exit_scope(trans.SymTab)
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._cur_func_name = old_func_name
trans._global_name_count = old_global_count
trans._nonlocal_name_count = old_nonlocal_count
trans._closure_env_count = old_env_count
if dbg_pushed_l != 0: DebugInfoGen.PopFunction(func_builder)
# 13. Create env in the parent (8-byte heap per capture, env[i] = heap address)
env_ptr: mlirlite.Value* = None
if cap_count > 0:
env_size: int64 = cap_count * 8
env_size_val: mlirlite.Value* = mlirlite.const_int64(pool, env_size)
if env_size_val is not None: env_size_val.Next = None
env_ptr = mlirlite.build_call(builder, "malloc", env_size_val, 1, i8_ptr_ty, 0)
if env_ptr is not None:
for ci3 in range(cap_count):
# Same as step 9: null-check via str pointer offset, avoiding itoa semantics
cap_slot3: str = cap_names + ci3 * 64
if cap_slot3[0] == '\0': continue
al_addr: uint64 = uint64(cap_allocas) + ci3 * 8
al_slot: str* = (str*)(al_addr)
var_alloca2: mlirlite.Value* = (mlirlite.Value*)(al_slot[0])
if var_alloca2 is None: continue
ty_addr5: uint64 = uint64(cap_tys) + ci3 * 8
ty_slot5: str* = (str*)(ty_addr5)
cap_ty2: mlirlite.LLVMType* = (mlirlite.LLVMType*)(ty_slot5[0])
if cap_ty2 is None: continue
# Heap size: bits/8, at least 8 bytes (fits pointers/scalars)
ty_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(cap_ty2)
st_size: int = 8
if ty_bits > 64: st_size = ty_bits // 8
st_size_val: mlirlite.Value* = mlirlite.const_int64(pool, st_size)
if st_size_val is not None: st_size_val.Next = None
var_storage: mlirlite.Value* = mlirlite.build_call(builder, "malloc", st_size_val, 1, i8_ptr_ty, 0)
if var_storage is not None:
cur_val: mlirlite.Value* = mlirlite.build_load(builder, cap_ty2, var_alloca2)
if cur_val is not None:
st_slot5: mlirlite.Value* = mlirlite.build_bitcast(builder, var_storage, mlirlite.Ptr(pool, cap_ty2))
if st_slot5 is not None: mlirlite.build_store(builder, cur_val, st_slot5)
off_val2: mlirlite.Value* = mlirlite.const_int32(pool, ci3 * 8)
slot2: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, env_ptr, off_val2)
if slot2 is not None:
slot_typed2: mlirlite.Value* = mlirlite.build_bitcast(builder, slot2, i8_ptr_ptr_ty)
if slot_typed2 is not None: mlirlite.build_store(builder, var_storage, slot_typed2)
# 14. Create the closure in the parent: malloc(16)
closure_size_val: mlirlite.Value* = mlirlite.const_int64(pool, 16)
if closure_size_val is not None: closure_size_val.Next = None
closure_ptr: mlirlite.Value* = mlirlite.build_call(builder, "malloc", closure_size_val, 1, i8_ptr_ty, 0)
if closure_ptr is None: return None
# 15. [0] = fn_ptr (function reference bitcast to i8*)
fn_param_node: mlirlite.ParamNode* = mlirlite.new_param_node(pool, i8_ptr_ty)
fn_func_ty: mlirlite.LLVMType* = mlirlite.Func(pool, i32_ty, fn_param_node, 1)
fn_func_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, fn_func_ty)
fn_ptr_name: str = pool.alloc(64)
if fn_ptr_name is not None:
if string.strchr(mangled_promoted, '.') is not None: viperlib.snprintf(fn_ptr_name, 64, "@\"%s\"", mangled_promoted)
else: viperlib.snprintf(fn_ptr_name, 64, "@%s", mangled_promoted)
fn_ptr_val: mlirlite.Value* = mlirlite.SSAValue(pool, fn_func_ptr_ty, fn_ptr_name)
fn_as_i8ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, fn_ptr_val, i8_ptr_ty)
fn_slot: mlirlite.Value* = mlirlite.build_bitcast(builder, closure_ptr, mlirlite.Ptr(pool, i8_ptr_ty))
if fn_slot is not None and fn_as_i8ptr is not None: mlirlite.build_store(builder, fn_as_i8ptr, fn_slot)
# 16. [8] = env_ptr (store null when there are no captures)
eight_val: mlirlite.Value* = mlirlite.const_int32(pool, 8)
env_slot_addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, closure_ptr, eight_val)
if env_slot_addr is not None:
env_slot: mlirlite.Value* = mlirlite.build_bitcast(builder, env_slot_addr, mlirlite.Ptr(pool, i8_ptr_ty))
if env_slot is not None:
if env_ptr is not None: mlirlite.build_store(builder, env_ptr, env_slot)
else:
null_val: mlirlite.Value* = mlirlite.SSAValue(pool, i8_ptr_ty, "null")
mlirlite.build_store(builder, null_val, env_slot)
# 17. Return the closure pointer (i8*)
return closure_ptr
# ============================================================
# _lambda_scan_body - recursively collect outer Name refs into cap_names/cap_allocas/cap_tys
def _lambda_scan_body(self, trans: HT.Translator*, node: ast.AST*, param_names: str, param_count: int, cap_names: str, cap_allocas: t.CPtr, cap_tys: t.CPtr, cap_count: int) -> int:
"""Recursively scan lambda body Name references and collect captured variables"""
if node is None or trans is None: return cap_count
k: int = node.kind()
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is None: return cap_count
# Skip lambda parameters
for pi in range(param_count):
if string.strcmp(param_names + pi * 32, nm.id) == 0: return cap_count
# Skip already-captured names
for ci in range(cap_count):
if string.strcmp(cap_names + ci * 64, nm.id) == 0: return cap_count
# Look up the current scope chain
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is None or alloca.Ty is None: return cap_count
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
if pointee is None: return cap_count
# Skip module-level globals (the body resolves them directly)
mod_gv: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
if mod_gv is not None:
if uint64(mod_gv) == uint64(alloca): return cap_count
if cap_count >= 64: return cap_count
string.strncpy(cap_names + cap_count * 64, nm.id, 63)
al_slot: str* = (str*)(uint64(cap_allocas) + cap_count * 8)
al_slot[0] = alloca
ty_slot: str* = (str*)(uint64(cap_tys) + cap_count * 8)
ty_slot[0] = pointee
return cap_count + 1
# Recurse into common expression children
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
cc1: int = self._lambda_scan_body(trans, bop.left, param_names, param_count, cap_names, cap_allocas, cap_tys, cap_count)
return self._lambda_scan_body(trans, bop.right, param_names, param_count, cap_names, cap_allocas, cap_tys, cc1)
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(node)
return self._lambda_scan_body(trans, uop.operand, param_names, param_count, cap_names, cap_allocas, cap_tys, cap_count)
if k == ast.ASTKind.BoolOp:
bop2: ast.BoolOp* = ast.BoolOp*(node)
cc2: int = cap_count
if bop2.values is not None:
vs: list[ast.AST*]* = bop2.values
vn: sizet = len(vs)
for vi in range(vn):
cc2 = self._lambda_scan_body(trans, vs.get(vi), param_names, param_count, cap_names, cap_allocas, cap_tys, cc2)
return cc2
if k == ast.ASTKind.Compare:
cmp: ast.Compare* = ast.Compare*(node)
cc3: int = self._lambda_scan_body(trans, cmp.left, param_names, param_count, cap_names, cap_allocas, cap_tys, cap_count)
if cmp.comparators is not None:
cl: list[ast.AST*]* = cmp.comparators
cn: sizet = len(cl)
for cvi in range(cn):
cc3 = self._lambda_scan_body(trans, cl.get(cvi), param_names, param_count, cap_names, cap_allocas, cap_tys, cc3)
return cc3
if k == ast.ASTKind.Call:
cal: ast.Call* = ast.Call*(node)
cc4: int = self._lambda_scan_body(trans, cal.func, param_names, param_count, cap_names, cap_allocas, cap_tys, cap_count)
if cal.args is not None:
als: list[ast.AST*]* = cal.args
an: sizet = len(als)
for ai in range(an):
cc4 = self._lambda_scan_body(trans, als.get(ai), param_names, param_count, cap_names, cap_allocas, cap_tys, cc4)
if cal.keywords is not None:
kws: list[ast.AST*]* = cal.keywords
kn: sizet = len(kws)
for ki in range(kn):
kw: ast.Keyword* = ast.Keyword*(kws.get(ki))
if kw is not None and kw.value is not None:
cc4 = self._lambda_scan_body(trans, kw.value, param_names, param_count, cap_names, cap_allocas, cap_tys, cc4)
return cc4
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(node)
cc5: int = self._lambda_scan_body(trans, sub.value, param_names, param_count, cap_names, cap_allocas, cap_tys, cap_count)
if sub.slice is not None:
cc5 = self._lambda_scan_body(trans, sub.slice, param_names, param_count, cap_names, cap_allocas, cap_tys, cc5)
return cc5
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
return self._lambda_scan_body(trans, at.value, param_names, param_count, cap_names, cap_allocas, cap_tys, cap_count)
if k == ast.ASTKind.IfExp:
ie: ast.IfExp* = ast.IfExp*(node)
cc6: int = self._lambda_scan_body(trans, ie.test, param_names, param_count, cap_names, cap_allocas, cap_tys, cap_count)
cc6 = self._lambda_scan_body(trans, ie.body, param_names, param_count, cap_names, cap_allocas, cap_tys, cc6)
return self._lambda_scan_body(trans, ie.orelse, param_names, param_count, cap_names, cap_allocas, cap_tys, cc6)
if k == ast.ASTKind.List:
ls: ast.List* = ast.List*(node)
cc7: int = cap_count
if ls.elts is not None:
es: list[ast.AST*]* = ls.elts
en: sizet = len(es)
for ei in range(en):
cc7 = self._lambda_scan_body(trans, es.get(ei), param_names, param_count, cap_names, cap_allocas, cap_tys, cc7)
return cc7
if k == ast.ASTKind.Lambda:
lam2: ast.Lambda* = ast.Lambda*(node)
return self._lambda_scan_body(trans, lam2.body, param_names, param_count, cap_names, cap_allocas, cap_tys, cap_count)
return cap_count