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)。
1192 lines
71 KiB
Plaintext
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
|