refactor(codegen): one body pipeline for module functions and class methods
Module-level functions and class methods each carried their own copy of "resolve the
return type", "create the parameter allocas" and "emit the implicit epilogue". The copies
drifted: an unannotated method was still typed i32 by the class-method body path while its
declaration said void, so the epilogue emitted `llvm.return 0 : i32` inside a void function
and the MLIR verifier rejected it ("'llvm.return' op expected no operands", Scheduler._yield).
- FuncGenBase.resolve_func_ret_ty: the single return-type policy (annotation -> storage
marker -> __init__/__new__ hooks -> unannotated = procedure void -> fail-fast). Used by
the forward-declare pass, the body pass, class methods and generic specializations
- FuncGenBase.param_llvm_ty: the single parameter mapping (CVoid skipped, bare class ->
Ptr(struct), ArrayT -> element pointer), used by every signature and alloca site
- FuncGenBase.emit_func_epilogue: the single implicit-return implementation (procedure ->
ret void, unreachable merge, fail-fast for an annotated fall-through), shared by module
functions, methods and nested functions
- FuncGenBase.translate_func_body: the shared body pipeline (scope entry, self/parameter
storage, *args va_list, try/except out-params, pre-scan, capture preparation, slife,
statements, epilogue, state restore). ClassGenVTable._translate_method now only handles
method naming, vtable/overload registration and add_param
- the same policy reaches cross-module and generic declares: unannotated vpi functions and
methods declare void, generic specialization declares use the shared resolver, and the
list.append call sites emit a void call
- method parameters now carry the const / volatile / pointee-volatile / pure markers that
the module-level path always had
- FuncGenMixin: the two dry-run helpers that return ints are annotated, as the language
now requires (an unannotated def is a procedure)
Verified: 41 Test projects pass (MethodProcTest, CrossModuleSigTest, RawPtrParamTest,
AstTest, ListTest, OopInheritTest, ...), ViperC self-hosts with the new compiler, and the
6 tests that still fail reproduce identically with the previous compiler.
This commit is contained in:
1 parent
3b3cd42d1c
commit
eda8e4465f
8 files changed
+547
-683
No files matched your search
@@ -4062,8 +4062,10 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||||
# no annotation (fd.returns is None) is a legitimate default and keeps i32 below.
|
||||
if ret_ty is None and fd.returns is not None:
|
||||
GenApi.fatal_error(fd.returns, f"extern declaration '{fd.name}' return type annotation failed to resolve", "NameError")
|
||||
# default return i32
|
||||
if ret_ty is None: ret_ty = i32_ty
|
||||
# No annotation and no cached return text: a procedure (void), matching the definition side.
|
||||
# The vpi records "void" for unannotated functions, so this is the fallback when the entry is
|
||||
# missing; an i32 default here would split "declare i32" from "define void".
|
||||
if ret_ty is None: ret_ty = mlirlite.Void(pool)
|
||||
|
||||
# A value struct return type (e.g. Mat4) needs a full definition: declaring %Mat4 opaque makes
|
||||
# llc report "use of undefined type". Register the full definition.
|
||||
@@ -4202,7 +4204,9 @@ def _inject_method_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, m_
|
||||
# Unannotated __init__ -> Void, matching the definition side (ClassGenVTable always uses Void);
|
||||
# defaulting to i32 would split declare i32 / define void.
|
||||
if ret_ty is None and string.strcmp(m_fd.name, "__init__") == 0: ret_ty = mlirlite.Void(pool)
|
||||
if ret_ty is None: ret_ty = i32_ty
|
||||
# Unannotated method -> Void, matching the definition side (an unannotated method is a
|
||||
# procedure). The vpi records "void" for those; i32 here would split declare i32 / define void.
|
||||
if ret_ty is None: ret_ty = mlirlite.Void(pool)
|
||||
|
||||
# 跨模块方法**按值返回类**时,引用模块必须登记该具名结构体的完整定义:否则打印方法签名回查登记表,
|
||||
# 命中的只是指针引用登记的 opaque 前向声明 → MLIR 报 "still opaque at print time"。与
|
||||
|
||||
@@ -318,9 +318,15 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
|
||||
if ret_cm is None and cls_sha1 is not None: ret_cm = self._LookupMethodRetTyFromTextLL(pool, cls_sha1, class_name, method_name)
|
||||
if ret_cm is not None: call_ret_ty = ret_cm
|
||||
else:
|
||||
# Phase A: the dependency's text.ll is not generated yet, so keep
|
||||
# the default type and continue (dependency not ready).
|
||||
if cls_sha1 is None or (cls_sha1 is not None and self._dep_text_ll_exists(pool, cls_sha1) == 0):
|
||||
# Phase A: the dependency's text.ll is not generated yet. A generic class
|
||||
# instance (Class[T]) is emitted in *this* module only after all bodies, so
|
||||
# the call site cannot read its signature either. Predict the definition's
|
||||
# type from the template method: no return annotation means a procedure
|
||||
# (void), matching the unified body pipeline.
|
||||
pred_void: int = self._predicted_generic_method_is_void(pool, class_name, method_name)
|
||||
if pred_void != 0:
|
||||
call_ret_ty = mlirlite.Void(pool)
|
||||
elif cls_sha1 is None or (cls_sha1 is not None and self._dep_text_ll_exists(pool, cls_sha1) == 0):
|
||||
# Dependency not ready: keep default i32 (build_call(Void)
|
||||
# returns None and breaks expression translation).
|
||||
call_ret_ty = mlirlite.Int32(pool)
|
||||
@@ -589,6 +595,38 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
|
||||
if uint64(self_ptr.Ty) != uint64(cmop_i8_ptr_ty): cmop_bar_ptr = mlirlite.build_bitcast(builder, self_ptr, cmop_i8_ptr_ty)
|
||||
if cmop_bar_ptr is not None: mlirlite.build_load(builder, cmop_i8_ty, cmop_bar_ptr, 1)
|
||||
return call_res_cmop
|
||||
|
||||
# _predicted_generic_method_is_void - whether a call to a generic class instance's method will
|
||||
# land on a procedure. A generic instance (Class[T]) is emitted in this module only after all
|
||||
# bodies, so the call site cannot read its signature; the template method is the only source.
|
||||
# No return annotation = procedure = void, matching the unified body pipeline.
|
||||
def _predicted_generic_method_is_void(self, pool: memhub.MemBuddy*, class_name: str, method_name: str) -> int:
|
||||
"""1 when the template method (of a Class[T] instance) has no return annotation."""
|
||||
if class_name is None or method_name is None: return 0
|
||||
br: str = string.strchr(class_name, '[')
|
||||
if br is None: return 0
|
||||
bare_len: sizet = sizet(uint64(br) - uint64(class_name))
|
||||
if bare_len == 0: return 0
|
||||
bare: str = pool.alloc(bare_len + 1)
|
||||
if bare is None: return 0
|
||||
string.strncpy(bare, class_name, bare_len)
|
||||
bare[bare_len] = '\0'
|
||||
tpl: ast.ClassDef* = self.Gen.ClassGenGeneric._find_generic_template(bare)
|
||||
if tpl is None: return 0
|
||||
ch: list[ast.AST*]* = tpl.children
|
||||
if ch is None: return 0
|
||||
n: sizet = len(ch)
|
||||
for i in range(int(n)):
|
||||
st: ast.AST* = ch.get(i)
|
||||
if st is None: continue
|
||||
if st.kind() != ast.ASTKind.FunctionDef: continue
|
||||
mfd: ast.FunctionDef* = ast.FunctionDef*(st)
|
||||
if mfd is None or mfd.name is None: continue
|
||||
if string.strcmp(mfd.name, method_name) != 0: continue
|
||||
if mfd.returns is None: return 1
|
||||
return 0
|
||||
return 0
|
||||
|
||||
def _MarkWillFreeArgName(self, trans: HT.Translator*, arg_node: ast.AST*) -> int:
|
||||
"""P2 willFree:把实参变量槽标为"已失效";只处理简单变量名(1=标记过 / 0=未处理)。
|
||||
|
||||
@@ -1571,7 +1609,10 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
|
||||
mlirlite.value_set_next(lm_obj, None)
|
||||
mlirlite.value_set_next(ap_item_val, None)
|
||||
mlirlite.value_set_next(lm_obj, ap_item_val)
|
||||
ap_ret_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||||
# `_list.append(self, item)` has no return annotation: it is a
|
||||
# procedure, so the call must not produce a value (the definition
|
||||
# side emits `ret void`).
|
||||
ap_ret_ty: mlirlite.LLVMType* = mlirlite.Void(pool)
|
||||
self._ensure_method_declare(pool, mod, ap_mangled, ap_ret_ty, lm_obj, 2)
|
||||
return mlirlite.build_call(builder, ap_mangled, lm_obj, 2, ap_ret_ty, 0)
|
||||
|
||||
|
||||
@@ -1440,7 +1440,10 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
|
||||
mlirlite.value_set_next(lm_obj, None)
|
||||
mlirlite.value_set_next(ap_item_val, None)
|
||||
mlirlite.value_set_next(lm_obj, ap_item_val)
|
||||
ap_ret_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||||
# `_list.append(self, item)` has no return annotation: it
|
||||
# is a procedure, so the call must not produce a value
|
||||
# (the definition side emits `ret void`).
|
||||
ap_ret_ty: mlirlite.LLVMType* = mlirlite.Void(pool)
|
||||
self._ensure_method_declare(pool, mod, ap_mangled, ap_ret_ty, lm_obj, 2)
|
||||
return mlirlite.build_call(builder, ap_mangled, lm_obj, 2, ap_ret_ty, 0)
|
||||
|
||||
|
||||
@@ -374,12 +374,11 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
|
||||
saved_cur: str = self.Gen.TypeGenResolve.get_current_file()
|
||||
tpl_file: str = self._find_generic_func_template_file(template_key)
|
||||
if tpl_file is not None: self.Gen.TypeGenResolve.set_current_file(tpl_file)
|
||||
ret_ty: mlirlite.LLVMType* = None
|
||||
if spec_fd.returns is not None:
|
||||
# Value-reference markers are parameter-only: `-> &T` fails fast on a generic too.
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(spec_fd.returns, trans._imported_modules, "a function return type")
|
||||
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, spec_fd.returns, trans._imported_modules, trans._from_imports, trans, 0)
|
||||
if ret_ty is None: ret_ty = i32_ty
|
||||
# Return type: one shared policy (unannotated = procedure void), so a specialization's
|
||||
# declare agrees with the body the unified pipeline emits.
|
||||
gg_ctx: str = f"generic function '{template_fd.name}'"
|
||||
ret_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.resolve_func_ret_ty(trans, spec_fd, gg_ctx, 0, None, None, 0)
|
||||
if ret_ty is None: return None
|
||||
func: mlirlite.Function* = mlirlite.create_declare(pool, mod, spec_name, ret_ty)
|
||||
if func is not None and spec_fd.args is not None and spec_fd.args.kind() == ast.ASTKind.Arguments:
|
||||
ags: ast.Arguments* = ast.Arguments*(spec_fd.args)
|
||||
@@ -392,17 +391,9 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
|
||||
if pa is None or pa.kind() != ast.ASTKind.Arg: continue
|
||||
arg: ast.Arg* = ast.Arg*(pa)
|
||||
if arg.arg is None: continue
|
||||
param_ty: mlirlite.LLVMType* = i32_ty
|
||||
if arg.annotation is not None:
|
||||
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, trans._imported_modules, trans._from_imports, trans, 0)
|
||||
if resolved is not None:
|
||||
match resolved:
|
||||
case mlirlite.LLVMType.Struct(_, _, _):
|
||||
param_ty = mlirlite.Ptr(pool, resolved)
|
||||
case mlirlite.LLVMType.Array(aelem_ty, _):
|
||||
param_ty = mlirlite.Ptr(pool, aelem_ty)
|
||||
case _:
|
||||
param_ty = resolved
|
||||
# One shared parameter mapping (CVoid skipped, class -> Ptr, array -> element pointer).
|
||||
param_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.param_llvm_ty(trans, arg, gg_ctx, 0)
|
||||
if param_ty is None: continue
|
||||
pname: str = pool.alloc(32)
|
||||
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
|
||||
mlirlite.add_param(pool, func, param_ty, pname)
|
||||
|
||||
@@ -619,29 +619,13 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
|
||||
# SHA1 namespace mangling.
|
||||
mangled_name: str = self.Gen.FuncGenBase._mangle_name(trans, full_name)
|
||||
|
||||
# Infer the return type.
|
||||
ret_ty: mlirlite.LLVMType* = None
|
||||
if fd.returns is not None:
|
||||
# Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return).
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a method return type")
|
||||
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
|
||||
if ret_ty is None and fd.returns is not None:
|
||||
# t.State / t.ExportT / t.ExternT markers mean Void: such annotations carry no real return
|
||||
# type (written as "<original> | t.<marker>"); only the marker itself reaches here.
|
||||
if self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0) != 0 or self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "ExportT", 0) != 0 or self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "ExternT", 0) != 0:
|
||||
ret_ty = mlirlite.Void(pool)
|
||||
else:
|
||||
# [S4 fail-fast] return annotation failed to resolve: do not fall back to infer_return_type (silent mismatch).
|
||||
self.Gen.TypeGenErr.fatal_error(fd, f"method '{class_name}.{method_name}' return type annotation failed to resolve", "NameError")
|
||||
return 0
|
||||
if ret_ty is None:
|
||||
# __init__ and __before_init__ return void.
|
||||
if string.strcmp(method_name, "__init__") == 0: ret_ty = mlirlite.Void(pool)
|
||||
# __new__ returns Ptr(struct_ty) (the struct pointer serves as storage).
|
||||
elif string.strcmp(method_name, "__new__") == 0: ret_ty = mlirlite.Ptr(pool, struct_ty)
|
||||
else:
|
||||
param_types_str: str = self.Gen.TypeGenErr.build_param_types_str(pool, fd.args)
|
||||
ret_ty = self.Gen.TypeGenErr.infer_return_type(pool, fd.children, param_types_str)
|
||||
# Return type: one shared policy (annotation -> marker -> unannotated = procedure void).
|
||||
# The same call types the forward declare, so a declare and its body cannot disagree.
|
||||
m_ctx: str = f"method '{class_name}.{method_name}'"
|
||||
m_check: int = 1
|
||||
if sig_only != 0: m_check = 0
|
||||
ret_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.resolve_func_ret_ty(trans, fd, m_ctx, 1, method_name, struct_ty, m_check)
|
||||
if ret_ty is None: return 0
|
||||
|
||||
# Create/reuse the LLVM function. When the sig_only pre-scan already created a same-name
|
||||
# declaration, skip param/table setup and just clear IsDeclared to define (mirrors FuncGenDef).
|
||||
@@ -712,31 +696,13 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
|
||||
if is_static_m == 0: start_idx = 1
|
||||
for ai in range(int(start_idx), int(an)):
|
||||
arg: ast.Arg* = ast.Arg*(alist.get(ai))
|
||||
if arg is not None and arg.arg is not None:
|
||||
param_ty: mlirlite.LLVMType* = i32_ty
|
||||
if arg.annotation is not None:
|
||||
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0)
|
||||
if resolved is None:
|
||||
# [S4 fail-fast] annotation present but failed to resolve: no silent i32 (signature/call/impl mismatch).
|
||||
self.Gen.TypeGenErr.fatal_error(arg, f"method '{class_name}.{method_name}' param '{arg.arg}' type annotation failed to resolve", "NameError")
|
||||
return 0
|
||||
# Bare-class param -> Ptr(struct) (objects are pointers; by-value would dangle after append).
|
||||
# ArrayT param -> element pointer ty* (C array decay, same as function params).
|
||||
match resolved:
|
||||
case mlirlite.LLVMType.Void():
|
||||
# void is not a legal param type (only void* and typedef opaque structs); void*
|
||||
# is already merged into i8* upstream, so fail fast at compile time instead of emitting invalid IR.
|
||||
self.Gen.TypeGenErr.fatal_error(arg, f"method '{class_name}.{method_name}' param '{arg.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
|
||||
return 0
|
||||
case mlirlite.LLVMType.Struct(_, _, _):
|
||||
param_ty = mlirlite.Ptr(pool, resolved)
|
||||
case mlirlite.LLVMType.Array(aelem_ty, _):
|
||||
param_ty = mlirlite.Ptr(pool, aelem_ty)
|
||||
case _:
|
||||
param_ty = resolved
|
||||
pname: str = pool.alloc(32)
|
||||
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
|
||||
mlirlite.add_param(pool, func, param_ty, pname)
|
||||
if arg is None or arg.arg is None: continue
|
||||
# One shared type mapping: bare class -> Ptr(struct), ArrayT -> element pointer.
|
||||
param_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.param_llvm_ty(trans, arg, m_ctx, 0)
|
||||
if param_ty is None: continue
|
||||
pname: str = pool.alloc(32)
|
||||
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
|
||||
mlirlite.add_param(pool, func, param_ty, pname)
|
||||
# Register value-reference (&T / &temp T) parameter modes (index 0 = self for non-static methods).
|
||||
mode_buf: str = pool.alloc(an + 1)
|
||||
if mode_buf is not None:
|
||||
@@ -759,6 +725,12 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
|
||||
mode_buf[mode_cnt] = char(0)
|
||||
if has_ref_m != 0: self.Gen.CallGenBase._register_auto_addr_modes(mangled_name, mode_buf, mode_cnt)
|
||||
|
||||
# *args variadic: set IsVarArg=1 so the signature carries the ... suffix (the body
|
||||
# pipeline allocates the va_list buffer, same as a module-level function).
|
||||
if args_node is not None:
|
||||
m_ags_v: ast.Arguments* = ast.Arguments*(args_node)
|
||||
if m_ags_v.vararg is not None: func.IsVarArg = 1
|
||||
|
||||
# OOP iter protocol: __next__ gets a hidden __stop_iter_flag__ (i1*), set to 1 by raise
|
||||
# StopIteration. It is not counted in md_param_count; the for loop passes it explicitly.
|
||||
if string.strcmp(method_name, "__next__") == 0:
|
||||
@@ -834,201 +806,34 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
|
||||
tlmo: mlirlite.Value* = mlirlite.ConstInt(pool, i1_ty_m, 1, "1")
|
||||
if tlmo is not None: mlirlite.build_store(func_builder, tlmo, tls_m_ref)
|
||||
|
||||
# Enter the function scope.
|
||||
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_FUNCTION)
|
||||
# Receiver name for the body: a @classmethod's first parameter may be named `cls`...
|
||||
self_name_m: str = "self"
|
||||
if is_static_m == 0 and is_class_m != 0 and args_node is not None:
|
||||
cags_m: ast.Arguments* = ast.Arguments*(args_node)
|
||||
if cags_m.args is not None and len(cags_m.args) > 0:
|
||||
cfirst_m: ast.Arg* = ast.Arg*(cags_m.args.get(0))
|
||||
if cfirst_m is not None and cfirst_m.arg is not None: self_name_m = cfirst_m.arg
|
||||
skip_self_m: int = 0
|
||||
if is_static_m == 0: skip_self_m = 1
|
||||
|
||||
# Create and store alloca for self/cls (like other params); skip for @staticmethod. self must use
|
||||
# alloca: an SSA value would load once more, turning Ptr(struct_ty) into a struct value (ABI mismatch).
|
||||
if is_static_m == 0:
|
||||
self_alloca: mlirlite.Value* = mlirlite.build_alloca(func_builder, self_ptr_ty)
|
||||
if self_alloca is not None:
|
||||
self_var_name: str = "self"
|
||||
self_ssa_name: str = "%self"
|
||||
if is_class_m != 0 and args_node is not None:
|
||||
cags: ast.Arguments* = ast.Arguments*(args_node)
|
||||
if cags.args is not None and len(cags.args) > 0:
|
||||
cfirst: ast.Arg* = ast.Arg*(cags.args.get(0))
|
||||
if cfirst is not None and cfirst.arg is not None:
|
||||
self_var_name = cfirst.arg
|
||||
self_ssa_name_buf: str = pool.alloc(32)
|
||||
if self_ssa_name_buf is not None:
|
||||
viperlib.snprintf(self_ssa_name_buf, 32, "%%%s", cfirst.arg)
|
||||
self_ssa_name = self_ssa_name_buf
|
||||
self.Gen.VarGen.define_var(trans.SymTab, self_var_name, self_alloca)
|
||||
# Set the self/cls annotation class name (fallback for attribute lookup_field).
|
||||
self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, self_var_name, class_name)
|
||||
self_val: mlirlite.Value* = mlirlite.SSAValue(pool, self_ptr_ty, self_ssa_name)
|
||||
mlirlite.build_store(func_builder, self_val, self_alloca)
|
||||
|
||||
# Create and store alloca for other params (like plain functions; @staticmethod starts at 0).
|
||||
if args_node is not None:
|
||||
ags2: ast.Arguments* = ast.Arguments*(args_node)
|
||||
if ags2.args is not None:
|
||||
alist2: list[ast.AST*]* = ags2.args
|
||||
an2: sizet = len(alist2)
|
||||
start_idx2: int = 0
|
||||
if is_static_m == 0: start_idx2 = 1
|
||||
for ai2 in range(int(start_idx2), int(an2)):
|
||||
arg2: ast.Arg* = ast.Arg*(alist2.get(ai2))
|
||||
if arg2 is not None and arg2.arg is not None:
|
||||
param_ty2: mlirlite.LLVMType* = i32_ty
|
||||
if arg2.annotation is not None:
|
||||
resolved2: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg2.annotation, imported_modules, from_imports, trans, 0)
|
||||
if resolved2 is None:
|
||||
# fail-fast: param has an annotation but type resolution failed; no silent i32.
|
||||
self.Gen.TypeGenErr.fatal_error(arg2, f"method '{class_name}.{method_name}' argument '{arg2.arg}' failed to resolve type", "NameError")
|
||||
resolved2 = i32_ty # Unreachable (fatal_error already exits).
|
||||
if resolved2 is not None:
|
||||
# Keep in sync with add_param: bare class -> Ptr(struct) (objects are pointers); ArrayT -> element pointer ty*.
|
||||
match resolved2:
|
||||
case mlirlite.LLVMType.Void():
|
||||
# void is not a legal param type (same rule as above).
|
||||
self.Gen.TypeGenErr.fatal_error(arg2, f"method '{class_name}.{method_name}' param '{arg2.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
|
||||
return 0
|
||||
case mlirlite.LLVMType.Struct(_, _, _):
|
||||
param_ty2 = mlirlite.Ptr(pool, resolved2)
|
||||
case mlirlite.LLVMType.Array(aelem_ty2, _):
|
||||
param_ty2 = mlirlite.Ptr(pool, aelem_ty2)
|
||||
case _:
|
||||
param_ty2 = resolved2
|
||||
alloca: mlirlite.Value* = mlirlite.build_alloca(func_builder, param_ty2)
|
||||
if alloca is not None:
|
||||
self.Gen.VarGen.define_var(trans.SymTab, arg2.arg, alloca)
|
||||
# Store the raw annotation class name (call detection falls back from Ptr(i8) to a class-name lookup).
|
||||
if arg2.annotation is not None:
|
||||
cls_nm_oc: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(arg2.annotation, imported_modules, pool, trans._from_imports)
|
||||
if cls_nm_oc is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, arg2.arg, cls_nm_oc)
|
||||
pname2: str = pool.alloc(32)
|
||||
if pname2 is not None: viperlib.snprintf(pname2, 32, "%%%s", arg2.arg)
|
||||
param_val: mlirlite.Value* = mlirlite.SSAValue(pool, param_ty2, pname2)
|
||||
mlirlite.build_store(func_builder, param_val, alloca)
|
||||
|
||||
# OOP iter protocol: initialize __next__'s stop-flag (store 0) and save the pointer to trans
|
||||
# so raise StopIteration writes 1; the name must match add_param's "%__stop_iter_flag__".
|
||||
# OOP iter protocol: initialize __next__'s stop flag (store 0) and hand the pointer to
|
||||
# the body pipeline; raise StopIteration writes 1. Not counted in md_param_count.
|
||||
stop_flag_m: mlirlite.Value* = None
|
||||
if string.strcmp(method_name, "__next__") == 0:
|
||||
i1_ty_sf: mlirlite.LLVMType* = mlirlite.Int1(pool)
|
||||
if i1_ty_sf is not None:
|
||||
i1_ptr_ty_sf: mlirlite.LLVMType* = mlirlite.Ptr(pool, i1_ty_sf)
|
||||
if i1_ptr_ty_sf is not None:
|
||||
flag_ref_sf: mlirlite.Value* = mlirlite.SSAValue(pool, i1_ptr_ty_sf, "%__stop_iter_flag__")
|
||||
if flag_ref_sf is not None:
|
||||
trans._stop_iter_flag_ptr = flag_ref_sf
|
||||
stop_flag_m = mlirlite.SSAValue(pool, i1_ptr_ty_sf, "%__stop_iter_flag__")
|
||||
if stop_flag_m is not None:
|
||||
zero_i1_sf: mlirlite.Value* = mlirlite.ConstInt(pool, i1_ty_sf, 0, "0")
|
||||
if zero_i1_sf is not None: mlirlite.build_store(func_builder, zero_i1_sf, flag_ref_sf)
|
||||
if zero_i1_sf is not None: mlirlite.build_store(func_builder, zero_i1_sf, stop_flag_m)
|
||||
|
||||
# Save the module-level scope state.
|
||||
old_func: mlirlite.Function* = trans._cur_func
|
||||
old_builder: mlirlite.IRBuilder* = trans._cur_builder
|
||||
old_global_count: int = trans._global_name_count
|
||||
old_nonlocal_count: int = trans._nonlocal_name_count
|
||||
old_env_count: int = trans._closure_env_count
|
||||
old_stop_iter_flag: mlirlite.Value* = trans._stop_iter_flag_ptr
|
||||
|
||||
trans._cur_func = func
|
||||
trans._cur_builder = func_builder
|
||||
GenApi.clear_scope_names(trans)
|
||||
# P2 willFree 失效表:方法体也走独立路径 → 同样做函数作用域复位(栈槽地址会被后续函数复用)
|
||||
self.Gen.VarGen.freed_clear()
|
||||
|
||||
# M3 owned slife:方法体也走**独立**的转译路径(与 FuncGenMixin 的嵌套函数同款),
|
||||
# 必须自己抬升/弹回登记区间,否则方法内登记会落进外层的区间 → 内层出口 free 外层对象,
|
||||
# 外层出口再 free 一次 = double free(堆损坏)。
|
||||
old_slife_base: int = self.Gen.VarGen._SlifeReset(pool)
|
||||
|
||||
# Pre-scan the body to create local allocas; pre-scan Global/Nonlocal first, otherwise a
|
||||
# later-declared global is created as a local alloca and never written (reads all zeros).
|
||||
body: list[ast.AST*]* = fd.children
|
||||
if body is not None:
|
||||
bn: sizet = len(body)
|
||||
for bi in range(int(bn)):
|
||||
stmt: ast.AST* = body.get(bi)
|
||||
if stmt is not None: self.Gen.StmtGenDecl.pre_scan_scope_decls(trans, stmt)
|
||||
for bi in range(int(bn)):
|
||||
stmt: ast.AST* = body.get(bi)
|
||||
if stmt is not None: self.Gen.StmtGenDecl.pre_scan_allocas(trans, stmt)
|
||||
|
||||
# Translate the method body.
|
||||
if body is not None:
|
||||
bn2: sizet = len(body)
|
||||
for bi2 in range(int(bn2)):
|
||||
stmt2: ast.AST* = body.get(bi2)
|
||||
if stmt2 is not None: self.Gen.StmtGen.translate_stmt(trans, stmt2)
|
||||
|
||||
# Epilogue mirrors module-level functions: void/no-annotation falls through (ret void / ConstZero);
|
||||
# control-flow last stmt with unreachable merge -> unreachable; annotated non-void fall-through -> fail-fast.
|
||||
last_is_return: int = 0
|
||||
# Init bn3 early so it is not undefined when body is None.
|
||||
bn3: sizet = 0
|
||||
if body is not None:
|
||||
bn3 = len(body)
|
||||
if bn3 > 0:
|
||||
last_stmt_m: ast.AST* = body.get(bn3 - 1)
|
||||
if last_stmt_m is not None and last_stmt_m.kind() == ast.ASTKind.Return: last_is_return = 1
|
||||
if last_is_return == 0:
|
||||
if mlirlite.builder_cur_block_is_terminated(func_builder) == 0:
|
||||
last_is_ctrl: int = 0
|
||||
if bn3 > 0:
|
||||
last_stmt3_m: ast.AST* = body.get(bn3 - 1)
|
||||
if last_stmt3_m is not None:
|
||||
k3m: int = last_stmt3_m.kind()
|
||||
if k3m == ast.ASTKind.If or k3m == ast.ASTKind.While or k3m == ast.ASTKind.For or k3m == ast.ASTKind.Try or k3m == ast.ASTKind.With or k3m == ast.ASTKind.Match:
|
||||
last_is_ctrl = 1
|
||||
_is_void: int = 0
|
||||
if ret_ty is not None:
|
||||
match ret_ty:
|
||||
case mlirlite.LLVMType.Void():
|
||||
_is_void = 1
|
||||
case _:
|
||||
pass
|
||||
# Void detection at the annotation level (same as the module-level _is_void double-check):
|
||||
# t.CVoiX / bare CVoid markers and -> None all count as void; the TypePrint fallback is not reliable.
|
||||
if _is_void == 0 and fd.returns is not None:
|
||||
if self.Gen.TypeGenResolve._is_cvoid_marker(fd.returns, imported_modules) != 0: _is_void = 1
|
||||
elif fd.returns.kind() == ast.ASTKind.Constant:
|
||||
ret_none_cn_m: ast.Constant* = (ast.Constant*)(fd.returns)
|
||||
if ret_none_cn_m.const_kind == ast.CONST_NONE: _is_void = 1
|
||||
if _is_void != 0:
|
||||
# M3:隐式收尾也是函数出口,先释放本方法登记的 slife
|
||||
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
|
||||
mlirlite.build_ret_void(func_builder)
|
||||
elif last_is_ctrl != 0 and trans._ctrl_merge_reachable == 0:
|
||||
# Non-void with a control-flow last statement and a structurally unreachable merge ->
|
||||
# unreachable; if the merge is reachable the normal epilogue must run (unreachable on a reachable block is UB).
|
||||
mlirlite.build_unreachable(func_builder)
|
||||
elif fd.returns is None:
|
||||
# No return annotation -> treat as a void procedure, return a zero value.
|
||||
if ret_ty is not None:
|
||||
_m_zero_val: mlirlite.Value* = mlirlite.ConstZero(pool, ret_ty)
|
||||
if _m_zero_val is not None:
|
||||
# M3:隐式收尾出口,先释放 slife 再返回
|
||||
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
|
||||
mlirlite.build_ret(func_builder, _m_zero_val)
|
||||
else: self.Gen.TypeGenErr.fatal_error(fd, "method flow falls off the end without an explicit return", "TypeError")
|
||||
else: self.Gen.TypeGenErr.fatal_error(fd, "method flow falls off the end without an explicit return", "TypeError")
|
||||
else:
|
||||
# fail-fast: has a -> annotation but control truly falls off the end (traceback-style).
|
||||
if ret_ty is not None:
|
||||
rt_name_m: str = pool.alloc_tagged(4096, memhub.DETSITE_CLASSGENVTABLE_819)
|
||||
if rt_name_m is not None:
|
||||
mlirlite.TypePrint(rt_name_m, 4096, ret_ty, pool)
|
||||
if rt_name_m[0] == '\0': rt_name_m = "?"
|
||||
err_fn_m: str = pool.alloc(256)
|
||||
if err_fn_m is not None:
|
||||
viperlib.snprintf(err_fn_m, 256, "method '%s' flow falls off the end without an explicit return (return type %s), please add an explicit return (e.g. return 0 / return null)", method_name, rt_name_m)
|
||||
self.Gen.TypeGenErr.fatal_error(fd, err_fn_m, "TypeError")
|
||||
self.Gen.TypeGenErr.fatal_error(fd, "method flow falls off the end without an explicit return", "TypeError")
|
||||
else: self.Gen.TypeGenErr.fatal_error(fd, "method flow falls off the end without an explicit return", "TypeError")
|
||||
|
||||
# Restore the module-level scope.
|
||||
self.Gen.VarGen.exit_scope(trans.SymTab)
|
||||
# M3:弹回外层登记区间(本方法的释放已在各出口完成)
|
||||
self.Gen.VarGen._SlifeRestore(old_slife_base)
|
||||
trans._cur_func = old_func
|
||||
trans._cur_builder = old_builder
|
||||
trans._global_name_count = old_global_count
|
||||
trans._nonlocal_name_count = old_nonlocal_count
|
||||
trans._closure_env_count = old_env_count
|
||||
trans._stop_iter_flag_ptr = old_stop_iter_flag
|
||||
# The body itself: one shared pipeline with module-level functions
|
||||
# (FuncGenBase.translate_func_body) - scope entry, self/parameter storage, pre-scan,
|
||||
# capture preparation, statements, implicit return, state restore. Methods get no EH
|
||||
# output params (their call sites do not pass them), so eh_params stays 0.
|
||||
self.Gen.FuncGenBase.translate_func_body(trans, fd, func, func_builder, ret_ty, m_ctx, skip_self_m, self_ptr_ty, self_name_m, class_name, 0, 0, 1, stop_flag_m)
|
||||
|
||||
if dbg_pushed != 0: DebugInfoGen.PopFunction(func_builder)
|
||||
|
||||
|
||||
@@ -8,8 +8,13 @@ import stdlib
|
||||
import lib.core.VLogger as VLogger
|
||||
import lib.core.Gen.Translator as HT
|
||||
import lib.core.Gen.GenApi as GenApi
|
||||
import lib.core.Gen.GenTypes as GenTypes
|
||||
import lib.core.Gen.Call.CallGenBase as CallGenBase
|
||||
|
||||
# Symbol-table scope kind of a function body (same value as FuncGenMixin.SCOPE_FUNCTION;
|
||||
# a literal here keeps FuncGenBase from importing FuncGenMixin, which would be a cycle).
|
||||
define FG_SCOPE_FUNCTION = 1
|
||||
|
||||
# FuncGenBase - split from FuncGenMixin (stage D): attrs / naming / mangle / create_function helpers.
|
||||
novirtual class FuncGenBase(CallGenBase.CallGenBase):
|
||||
"""Shared helpers for function attributes, naming, mangling and creation."""
|
||||
@@ -279,4 +284,411 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase):
|
||||
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
|
||||
mlirlite.add_param(pool, func, param_ty, pname)
|
||||
|
||||
return func
|
||||
return func
|
||||
|
||||
# ============================================================================
|
||||
# Unified function-body pipeline (module-level functions and class methods).
|
||||
#
|
||||
# Both used to carry their own copy of "resolve the return type", "create the
|
||||
# parameter allocas" and "emit the implicit epilogue". The copies drifted: an
|
||||
# unannotated method was still typed i32 by the class-method body path while its
|
||||
# declaration said void, so the epilogue emitted `llvm.return 0 : i32` inside a
|
||||
# void function (MLIR: "'llvm.return' op expected no operands"). One
|
||||
# implementation per rule, shared by every caller, is what keeps the declaration
|
||||
# and the body from disagreeing again.
|
||||
# ============================================================================
|
||||
|
||||
# ty_is_void - whether an LLVM return type is the void type.
|
||||
def ty_is_void(self, ret_ty: mlirlite.LLVMType*) -> int:
|
||||
"""1 when ret_ty is void (the function is a procedure), else 0."""
|
||||
if ret_ty is None: return 0
|
||||
is_void: int = 0
|
||||
match ret_ty:
|
||||
case mlirlite.LLVMType.Void():
|
||||
is_void = 1
|
||||
case _:
|
||||
pass
|
||||
return is_void
|
||||
|
||||
# resolve_func_ret_ty - the single return-type policy for functions and methods.
|
||||
#
|
||||
# Order: annotation -> storage/marker annotations (State/ExportT/ExternT mean void)
|
||||
# -> __init__/__new__ hooks -> no annotation = procedure (void) -> fail-fast.
|
||||
# `check_value_return`: reject `def f(): return 1` (a procedure that returns a value)
|
||||
# at the body stage; the forward-declare pass skips it (defines may not be registered yet).
|
||||
def resolve_func_ret_ty(self, trans: HT.Translator*, fd: ast.FunctionDef*, ctx_label: str, is_method: int, method_name: str, struct_ty: mlirlite.LLVMType*, check_value_return: int) -> mlirlite.LLVMType*:
|
||||
"""Resolve a function/method return type; unannotated means procedure (void)."""
|
||||
if trans is None or fd is None: return None
|
||||
pool: memhub.MemBuddy* = trans.Pool
|
||||
imported_modules: str = trans._imported_modules
|
||||
from_imports: str = trans._from_imports
|
||||
|
||||
ret_ty: mlirlite.LLVMType* = None
|
||||
if fd.returns is not None:
|
||||
# Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return).
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a function return type")
|
||||
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
|
||||
# A lone ExportT annotation is an export modifier with no real type -> Void.
|
||||
if fd.returns.kind() == ast.ASTKind.Name:
|
||||
ce_at: ast.Name* = ast.Name*(fd.returns)
|
||||
if ce_at.id is not None and string.strcmp(ce_at.id, "ExportT") == 0: ret_ty = mlirlite.Void(pool)
|
||||
# A pure marker annotation (t.State/t.ExportT/t.ExternT) carries no real type.
|
||||
if ret_ty is None:
|
||||
if self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0) != 0: ret_ty = mlirlite.Void(pool)
|
||||
elif self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "ExportT", 0) != 0: ret_ty = mlirlite.Void(pool)
|
||||
elif self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "ExternT", 0) != 0: ret_ty = mlirlite.Void(pool)
|
||||
|
||||
# @c.CReturn was removed: encountering it is a compile-time error (use -> tuple[...]).
|
||||
if ret_ty is None: self.RejectCReturnDecorator(fd, fd.decorator_list)
|
||||
|
||||
if ret_ty is not None: return ret_ty
|
||||
|
||||
if fd.returns is not None:
|
||||
# Return annotation present but unresolvable: no silent inference, no i32 fallback
|
||||
# (that is how a declare and its body ended up with different types).
|
||||
self.Gen.TypeGenErr.fatal_error(fd, f"{ctx_label} return type annotation failed to resolve", "NameError")
|
||||
return None
|
||||
|
||||
# No annotation: a procedure. __init__/__new__ keep their fixed shapes.
|
||||
if is_method != 0 and method_name is not None:
|
||||
if string.strcmp(method_name, "__init__") == 0: return mlirlite.Void(pool)
|
||||
if string.strcmp(method_name, "__new__") == 0:
|
||||
if struct_ty is not None: return mlirlite.Ptr(pool, struct_ty)
|
||||
if check_value_return != 0:
|
||||
if self.Gen.TypeGenErr.body_has_value_return(pool, fd.children) != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(fd, f"{ctx_label} has no return type annotation but returns a non-None value, add an explicit return type annotation (e.g. -> int)", "TypeError")
|
||||
return None
|
||||
return mlirlite.Void(pool)
|
||||
|
||||
# param_llvm_ty - the single parameter-type mapping (signature and alloca must agree).
|
||||
# Returns None for a `CVoid` placeholder (the parameter is omitted), else the LLVM type:
|
||||
# bare class -> Ptr(struct), ArrayT -> element pointer (C array decay), others as resolved.
|
||||
def param_llvm_ty(self, trans: HT.Translator*, arg: ast.Arg*, ctx_label: str, pure_mode: int) -> mlirlite.LLVMType*:
|
||||
"""Map one parameter annotation to its LLVM type; None means the parameter is omitted."""
|
||||
if trans is None or arg is None or arg.arg is None: return None
|
||||
pool: memhub.MemBuddy* = trans.Pool
|
||||
imported_modules: str = trans._imported_modules
|
||||
from_imports: str = trans._from_imports
|
||||
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||||
|
||||
if arg.annotation is None: return i32_ty
|
||||
# CVoid means empty param: skip, do not add to the signature or allocate it.
|
||||
if self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imported_modules) != 0: return None
|
||||
|
||||
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0)
|
||||
if resolved is None:
|
||||
# [S4 fail-fast] annotation present but unresolvable: no silent i32 (ABI/call mismatch).
|
||||
self.Gen.TypeGenErr.fatal_error(arg, f"{ctx_label} parameter '{arg.arg}' type annotation failed to resolve", "NameError")
|
||||
return None
|
||||
if pure_mode != 0:
|
||||
if self.Gen.ExprGenBase.is_ptr_type(resolved) != 0 or resolved.kind() == mlirlite.LLVMType.Array:
|
||||
self.Gen.TypeGenErr.fatal_error(arg, f"{ctx_label} parameter '{arg.arg}' cannot be a pointer or array: pure parameters are immutable value copies (use str/bytes for a pure_str value, or a pure class value)", "TypeError")
|
||||
return None
|
||||
match resolved:
|
||||
case mlirlite.LLVMType.Void():
|
||||
# void is not a legal param type (only void* and typedef aliases).
|
||||
self.Gen.TypeGenErr.fatal_error(arg, f"{ctx_label} parameter '{arg.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
|
||||
return None
|
||||
case mlirlite.LLVMType.Struct(_, _, _):
|
||||
return mlirlite.Ptr(pool, resolved)
|
||||
case mlirlite.LLVMType.Array(aelem_ty, _):
|
||||
return mlirlite.Ptr(pool, aelem_ty)
|
||||
case _:
|
||||
return resolved
|
||||
|
||||
# emit_param_allocas - create the local storage for every parameter (plus self/cls and *args).
|
||||
# `skip_self`: the first parameter is the receiver, stored from the already-declared self slot.
|
||||
def emit_param_allocas(self, trans: HT.Translator*, fd: ast.FunctionDef*, builder: mlirlite.IRBuilder*, ctx_label: str, skip_self: int, self_ty: mlirlite.LLVMType*, self_name: str, self_class_name: str, pure_mode: int) -> int:
|
||||
"""Create allocas for self/cls and every parameter; register their var annotations."""
|
||||
if trans is None or fd is None or builder is None: return 0
|
||||
pool: memhub.MemBuddy* = trans.Pool
|
||||
|
||||
# The receiver: @staticmethod has none. It must live in an alloca, not an SSA value:
|
||||
# loading it once more would turn Ptr(struct) into a struct value (ABI mismatch).
|
||||
if skip_self != 0 and self_ty is not None:
|
||||
self_alloca: mlirlite.Value* = mlirlite.build_alloca(builder, self_ty)
|
||||
if self_alloca is not None:
|
||||
self_var_name: str = "self"
|
||||
if self_name is not None: self_var_name = self_name
|
||||
self.Gen.VarGen.define_var(trans.SymTab, self_var_name, self_alloca)
|
||||
# The annotation class name is the fallback for attribute lookup.
|
||||
if self_class_name is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, self_var_name, self_class_name)
|
||||
self_ssa_buf: str = pool.alloc(32)
|
||||
if self_ssa_buf is not None:
|
||||
viperlib.snprintf(self_ssa_buf, 32, "%%%s", self_var_name)
|
||||
self_val: mlirlite.Value* = mlirlite.SSAValue(pool, self_ty, self_ssa_buf)
|
||||
mlirlite.build_store(builder, self_val, self_alloca)
|
||||
|
||||
args_node: ast.Arguments* = fd.args
|
||||
if args_node is None: return 0
|
||||
ags2: ast.Arguments* = ast.Arguments*(args_node)
|
||||
if ags2.args is not None:
|
||||
alist2: list[ast.AST*]* = ags2.args
|
||||
an2: sizet = len(alist2)
|
||||
start_idx2: int = 0
|
||||
if skip_self != 0: start_idx2 = 1
|
||||
for ai2 in range(int(start_idx2), int(an2)):
|
||||
arg2: ast.Arg* = ast.Arg*(alist2.get(ai2))
|
||||
if arg2 is None or arg2.arg is None: continue
|
||||
param_ty2: mlirlite.LLVMType* = self.param_llvm_ty(trans, arg2, ctx_label, pure_mode)
|
||||
if param_ty2 is None: continue
|
||||
alloca: mlirlite.Value* = mlirlite.build_alloca(builder, param_ty2)
|
||||
if alloca is None: continue
|
||||
self.Gen.VarGen.define_var(trans.SymTab, arg2.arg, alloca)
|
||||
# Store the raw annotation class name (method-call detection falls back from Ptr(i8) to the class).
|
||||
if arg2.annotation is not None:
|
||||
cls_nm: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(arg2.annotation, trans._imported_modules, pool, trans._from_imports)
|
||||
if cls_nm is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, arg2.arg, cls_nm)
|
||||
# pure: str params are borrowed pure_str values; `self` is a borrowed receiver.
|
||||
# Frame lifetime makes both unable to escape.
|
||||
if pure_mode != 0:
|
||||
if string.strcmp(arg2.arg, "self") == 0: self.Gen.VarGen.set_var_life(trans.SymTab, arg2.arg, GenTypes.LIFE_FRAME)
|
||||
elif self.Gen.ExprGenBase._pure_str_struct_ty(pool, param_ty2) is not None: self.Gen.VarGen.set_var_life(trans.SymTab, arg2.arg, GenTypes.LIFE_FRAME)
|
||||
# t.ConstT / t.VolatileT / volatile pointee markers are per-parameter properties.
|
||||
if arg2.annotation is not None:
|
||||
if self.Gen.TypeGenErr.has_decorator_marker(arg2.annotation, "ConstT", 0) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, arg2.arg)
|
||||
if self.Gen.TypeGenErr.has_decorator_marker(arg2.annotation, "VolatileT", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, arg2.arg)
|
||||
if self.Gen.TypeGenErr.has_pointee_volatile_marker(arg2.annotation) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, arg2.arg)
|
||||
pname2: str = pool.alloc(32)
|
||||
if pname2 is not None: viperlib.snprintf(pname2, 32, "%%%s", arg2.arg)
|
||||
param_val: mlirlite.Value* = mlirlite.SSAValue(pool, param_ty2, pname2)
|
||||
vol_p2: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, arg2.arg))
|
||||
mlirlite.build_store(builder, param_val, alloca, vol_p2)
|
||||
|
||||
# *args: allocate a 24-byte va_list buffer, bitcast to i8*, call @llvm.va_start, register
|
||||
# the name. The buffer is set on trans so arg()/vararg-name translation can find it.
|
||||
if ags2.vararg is not None:
|
||||
vararg_node: ast.Arg* = ast.Arg*(ags2.vararg)
|
||||
if vararg_node is not None and vararg_node.arg is not None:
|
||||
i8_ty_va: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||||
i8_ptr_ty_va: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_va)
|
||||
va_buf_ty: mlirlite.LLVMType* = mlirlite.Array(pool, i8_ty_va, 24)
|
||||
va_list_alloca: mlirlite.Value* = mlirlite.build_alloca_aligned(builder, va_buf_ty, 16)
|
||||
if va_list_alloca is not None:
|
||||
va_list_addr: mlirlite.Value* = mlirlite.build_bitcast(builder, va_list_alloca, i8_ptr_ty_va)
|
||||
if va_list_addr is not None:
|
||||
self.Gen.VarGen.define_var(trans.SymTab, vararg_node.arg, va_list_alloca)
|
||||
mlirlite.build_call(builder, "llvm.va_start", va_list_addr, 1, mlirlite.Void(pool), 0)
|
||||
trans._va_list_ptr = va_list_addr
|
||||
trans._vararg_name = vararg_node.arg
|
||||
return 0
|
||||
|
||||
# add_eh_params - append the try/except output params (__eh_msg_out__/__eh_code_out__).
|
||||
# Returns 1 when they were added; the body pipeline then wires them onto trans.
|
||||
def add_eh_params(self, trans: HT.Translator*, fd: ast.FunctionDef*, func: mlirlite.Function*) -> int:
|
||||
"""Add the try/except output params; 1=added, 0=not needed."""
|
||||
if trans is None or fd is None or func is None: return 0
|
||||
if fd.name is not None and string.strcmp(fd.name, "main") == 0: return 0
|
||||
if self._body_has_raise(fd) == 0: return 0
|
||||
pool: memhub.MemBuddy* = trans.Pool
|
||||
i8_ty_eh: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||||
i8_ptr_ty_eh: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_eh)
|
||||
i8_ptr_ptr_ty_eh: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty_eh)
|
||||
i32_ty_eh: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||||
i32_ptr_ty_eh: mlirlite.LLVMType* = mlirlite.Ptr(pool, i32_ty_eh)
|
||||
if i8_ptr_ptr_ty_eh is None or i32_ptr_ty_eh is None: return 0
|
||||
mlirlite.add_param(pool, func, i8_ptr_ptr_ty_eh, "%__eh_msg_out__")
|
||||
mlirlite.add_param(pool, func, i32_ptr_ty_eh, "%__eh_code_out__")
|
||||
return 1
|
||||
|
||||
# emit_func_epilogue - the single implicit-return implementation.
|
||||
#
|
||||
# `unannotated_void`: 1 for module functions and methods (no annotation = procedure), 0 for
|
||||
# the promoted nested-function ABI, which keeps its legacy i32 default.
|
||||
def emit_func_epilogue(self, trans: HT.Translator*, fd: ast.FunctionDef*, builder: mlirlite.IRBuilder*, ret_ty: mlirlite.LLVMType*, ctx_label: str, unannotated_void: int) -> int:
|
||||
"""Emit the implicit return when the body falls off the end; never a value in a procedure."""
|
||||
if trans is None or fd is None or builder is None: return 0
|
||||
pool: memhub.MemBuddy* = trans.Pool
|
||||
mod: mlirlite.LLVMModule* = trans.Module
|
||||
|
||||
body: list[ast.AST*]* = fd.children
|
||||
last_is_return: int = 0
|
||||
bn3: sizet = 0
|
||||
if body is not None:
|
||||
bn3 = len(body)
|
||||
if bn3 > 0:
|
||||
last_stmt: ast.AST* = body.get(bn3 - 1)
|
||||
if last_stmt is not None and last_stmt.kind() == ast.ASTKind.Return: last_is_return = 1
|
||||
if last_is_return != 0: return 0
|
||||
if mlirlite.builder_cur_block_is_terminated(builder) != 0: return 0
|
||||
|
||||
last_is_ctrl: int = 0
|
||||
if bn3 > 0:
|
||||
last_stmt3: ast.AST* = body.get(bn3 - 1)
|
||||
if last_stmt3 is not None:
|
||||
k3: int = last_stmt3.kind()
|
||||
if k3 == ast.ASTKind.If or k3 == ast.ASTKind.While or k3 == ast.ASTKind.For or k3 == ast.ASTKind.Try or k3 == ast.ASTKind.With or k3 == ast.ASTKind.Match: last_is_ctrl = 1
|
||||
|
||||
# A procedure never returns a value: explicit type, plus the annotation-level checks
|
||||
# (`-> None` / t.CVoid) for the case where the REnum class-pattern match degrades.
|
||||
is_void: int = self.ty_is_void(ret_ty)
|
||||
if is_void == 0 and unannotated_void != 0:
|
||||
if fd.returns is None: is_void = 1
|
||||
elif self.Gen.TypeGenResolve._is_cvoid_marker(fd.returns, trans._imported_modules) != 0: is_void = 1
|
||||
elif fd.returns.kind() == ast.ASTKind.Constant:
|
||||
ret_none_cn: ast.Constant* = (ast.Constant*)(fd.returns)
|
||||
if ret_none_cn.const_kind == ast.CONST_NONE: is_void = 1
|
||||
|
||||
if is_void != 0:
|
||||
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
|
||||
mlirlite.build_ret_void(builder)
|
||||
return 0
|
||||
if last_is_ctrl != 0 and trans._ctrl_merge_reachable == 0:
|
||||
# Non-void with a control-flow last statement whose merge is structurally unreachable
|
||||
# (all branches return): the merge block still needs a terminator.
|
||||
mlirlite.build_unreachable(builder)
|
||||
return 0
|
||||
if fd.returns is None:
|
||||
if unannotated_void != 0:
|
||||
# Belt and braces: the policy says procedure, so never emit a value return.
|
||||
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
|
||||
mlirlite.build_ret_void(builder)
|
||||
return 0
|
||||
# Nested-function ABI: an unannotated nested def keeps a value return type.
|
||||
zero_val: mlirlite.Value* = mlirlite.ConstZero(pool, ret_ty)
|
||||
if zero_val is None:
|
||||
self.Gen.TypeGenErr.fatal_error(fd, f"{ctx_label}: the flow falls off the end and the return type has no zero value", "TypeError")
|
||||
return 0
|
||||
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
|
||||
mlirlite.build_ret(builder, zero_val)
|
||||
return 0
|
||||
|
||||
# Annotated non-void with a reachable fall-through: the compiler must not invent a value.
|
||||
if ret_ty is not None:
|
||||
rt_name: str = pool.alloc(4096)
|
||||
if rt_name is not None:
|
||||
mlirlite.TypePrint(rt_name, 4096, ret_ty, pool)
|
||||
if rt_name[0] == '\0': rt_name = "?"
|
||||
self.Gen.TypeGenErr.fatal_error(fd, f"{ctx_label} flow falls off the end of the function without an explicit return (return type {rt_name}), please add an explicit return (e.g. return 0 / return null)", "TypeError")
|
||||
self.Gen.TypeGenErr.fatal_error(fd, f"{ctx_label} flow falls off the end without an explicit return", "TypeError")
|
||||
return 0
|
||||
|
||||
# translate_func_body - the shared body pipeline: scope entry, parameter storage,
|
||||
# local pre-scan, capture preparation, statement translation, implicit return, restore.
|
||||
#
|
||||
# `eh_params`: 1 when add_eh_params() appended the try/except output params.
|
||||
# `unannotated_void`: see emit_func_epilogue.
|
||||
def translate_func_body(self, trans: HT.Translator*, fd: ast.FunctionDef*, func: mlirlite.Function*, builder: mlirlite.IRBuilder*, ret_ty: mlirlite.LLVMType*, ctx_label: str, skip_self: int, self_ty: mlirlite.LLVMType*, self_name: str, self_class_name: str, pure_mode: int, eh_params: int, unannotated_void: int, stop_iter_flag: mlirlite.Value*) -> int:
|
||||
"""Translate a function/method body end to end (one implementation for both)."""
|
||||
if trans is None or fd is None or func is None or builder is None: return 0
|
||||
pool: memhub.MemBuddy* = trans.Pool
|
||||
mod: mlirlite.LLVMModule* = trans.Module
|
||||
|
||||
# Save the enclosing function's state (nested defs/lambdas run inside a body).
|
||||
old_func: mlirlite.Function* = trans._cur_func
|
||||
old_builder: mlirlite.IRBuilder* = trans._cur_builder
|
||||
old_in_pure: int = trans._in_pure
|
||||
old_break_bb: mlirlite.BasicBlock* = trans._break_bb
|
||||
old_continue_bb: mlirlite.BasicBlock* = trans._continue_bb
|
||||
old_insw: int = trans._in_switch_case
|
||||
old_global_count: int = trans._global_name_count
|
||||
old_nonlocal_count: int = trans._nonlocal_name_count
|
||||
old_env_count: int = trans._closure_env_count
|
||||
old_ret_annot: ast.AST* = trans.CurFuncRetAnnot
|
||||
old_eh_msg_out: mlirlite.Value* = trans._eh_msg_out_param
|
||||
old_eh_code_out: mlirlite.Value* = trans._eh_code_out_param
|
||||
old_cur_func_has_eh: int = trans._cur_func_has_eh
|
||||
old_va_list_ptr: mlirlite.Value* = trans._va_list_ptr
|
||||
old_vararg_name: str = trans._vararg_name
|
||||
old_stop_iter_flag: mlirlite.Value* = trans._stop_iter_flag_ptr
|
||||
|
||||
# Enter the function scope: parameters and locals live in it.
|
||||
self.Gen.VarGen.enter_scope(trans.SymTab, FG_SCOPE_FUNCTION)
|
||||
|
||||
# arg()/va_list scope reset: a non-variadic function must not inherit the caller's
|
||||
# buffer (the printer emits values by NAME, so a stale name would bind to a local of
|
||||
# this function). emit_param_allocas re-sets it for a variadic callee.
|
||||
trans._va_list_ptr = None
|
||||
trans._vararg_name = None
|
||||
|
||||
# Stack slots for self/cls and every declared parameter.
|
||||
self.emit_param_allocas(trans, fd, builder, ctx_label, skip_self, self_ty, self_name, self_class_name, pure_mode)
|
||||
|
||||
# try/except: this function's own EH output params (added to the signature by the caller).
|
||||
trans._eh_msg_out_param = None
|
||||
trans._eh_code_out_param = None
|
||||
trans._cur_func_has_eh = 0
|
||||
if eh_params != 0:
|
||||
i8_ty_eh: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||||
i8_ptr_ty_eh: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_eh)
|
||||
i8_ptr_ptr_ty_eh: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty_eh)
|
||||
i32_ty_eh: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||||
i32_ptr_ty_eh: mlirlite.LLVMType* = mlirlite.Ptr(pool, i32_ty_eh)
|
||||
if i8_ptr_ptr_ty_eh is not None and i32_ptr_ty_eh is not None:
|
||||
trans._eh_msg_out_param = mlirlite.SSAValue(pool, i8_ptr_ptr_ty_eh, "%__eh_msg_out__")
|
||||
trans._eh_code_out_param = mlirlite.SSAValue(pool, i32_ptr_ty_eh, "%__eh_code_out__")
|
||||
trans._cur_func_has_eh = 1
|
||||
|
||||
# Switch the translation context to this function.
|
||||
trans._cur_func = func
|
||||
trans._cur_builder = builder
|
||||
trans.CurFuncRetAnnot = fd.returns
|
||||
trans._stop_iter_flag_ptr = stop_iter_flag
|
||||
self.Gen.VarGen.freed_clear()
|
||||
trans._break_bb = None
|
||||
trans._continue_bb = None
|
||||
trans._in_switch_case = 0
|
||||
GenApi.clear_scope_names(trans)
|
||||
# Nested-def bookkeeping is per function scope, never inherited.
|
||||
trans._nl_chain_done = 0
|
||||
trans._nl_chain_count = 0
|
||||
trans._nl_migrated = None
|
||||
|
||||
# Pre-scan: Global/Nonlocal first (they apply to the whole scope), then AnnAssign allocas.
|
||||
body: list[ast.AST*]* = fd.children
|
||||
if body is not None:
|
||||
bn: sizet = len(body)
|
||||
for bi in range(int(bn)):
|
||||
stmt: ast.AST* = body.get(bi)
|
||||
if stmt is not None: self.Gen.StmtGenDecl.pre_scan_scope_decls(trans, stmt)
|
||||
for bi in range(int(bn)):
|
||||
stmt: ast.AST* = body.get(bi)
|
||||
if stmt is not None: self.Gen.StmtGenDecl.pre_scan_allocas(trans, stmt)
|
||||
|
||||
# Captured locals must move to the heap before any statement runs: a `&local` handed out
|
||||
# earlier (widget registration, reverse pointers) would otherwise point at a stack copy.
|
||||
if body is not None:
|
||||
i8_ty_b: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||||
i8_ptr_ty_b: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_b)
|
||||
self.Gen.FuncGen._prepare_body_chain_nonlocals(trans, body, pool, builder, mod, i8_ty_b, i8_ptr_ty_b)
|
||||
self.Gen.FuncGen._prepare_body_captures(trans, body, pool, builder, mod, i8_ty_b, i8_ptr_ty_b)
|
||||
|
||||
# M3 owned slife: the registration range starts at this stack depth.
|
||||
old_slife_base: int = self.Gen.VarGen._SlifeReset(pool)
|
||||
trans._in_pure = pure_mode
|
||||
|
||||
# The body itself.
|
||||
if body is not None:
|
||||
bn2: sizet = len(body)
|
||||
for bi2 in range(int(bn2)):
|
||||
stmt2: ast.AST* = body.get(bi2)
|
||||
if stmt2 is not None:
|
||||
# Before a nested def: pre-scan its in-chain nonlocals and build the heap
|
||||
# (different levels may capture different variables; see _prepare_chain_nonlocals).
|
||||
if stmt2.kind() == ast.ASTKind.FunctionDef:
|
||||
self.Gen.FuncGen._prepare_chain_nonlocals(trans, stmt2, pool, builder, mod, mlirlite.Int8(pool), mlirlite.Ptr(pool, mlirlite.Int8(pool)))
|
||||
self.Gen.StmtGen.translate_stmt(trans, stmt2)
|
||||
|
||||
# Implicit return (one implementation for every caller).
|
||||
self.emit_func_epilogue(trans, fd, builder, ret_ty, ctx_label, unannotated_void)
|
||||
|
||||
# Leave the function scope and hand back the enclosing state.
|
||||
self.Gen.VarGen.exit_scope(trans.SymTab)
|
||||
self.Gen.VarGen._SlifeRestore(old_slife_base)
|
||||
trans._cur_func = old_func
|
||||
trans._cur_builder = old_builder
|
||||
trans._in_pure = old_in_pure
|
||||
trans._break_bb = old_break_bb
|
||||
trans._continue_bb = old_continue_bb
|
||||
trans._in_switch_case = old_insw
|
||||
trans._global_name_count = old_global_count
|
||||
trans._nonlocal_name_count = old_nonlocal_count
|
||||
trans._closure_env_count = old_env_count
|
||||
trans._va_list_ptr = old_va_list_ptr
|
||||
trans._vararg_name = old_vararg_name
|
||||
trans.CurFuncRetAnnot = old_ret_annot
|
||||
trans._eh_msg_out_param = old_eh_msg_out
|
||||
trans._eh_code_out_param = old_eh_code_out
|
||||
trans._cur_func_has_eh = old_cur_func_has_eh
|
||||
trans._stop_iter_flag_ptr = old_stop_iter_flag
|
||||
return 0
|
||||
@@ -499,28 +499,11 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
continue
|
||||
tp_names_fd: list[str]* = None
|
||||
|
||||
# Infer the return type
|
||||
ret_ty: mlirlite.LLVMType* = None
|
||||
if fd.returns is not None:
|
||||
# Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return).
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a function return type")
|
||||
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
|
||||
# A lone ExportT return annotation is an export modifier with no real type -> Void
|
||||
# (ExportT fell back to i32, forcing the tail check to return i32: mm.vp free root cause).
|
||||
if fd.returns.kind() == ast.ASTKind.Name:
|
||||
ce_at: ast.Name* = ast.Name*(fd.returns)
|
||||
if ce_at.id is not None and string.strcmp(ce_at.id, "ExportT") == 0: ret_ty = mlirlite.Void(pool)
|
||||
if ret_ty is None and fd.returns is not None:
|
||||
if self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0) != 0: ret_ty = mlirlite.Void(pool)
|
||||
# @c.CReturn decorator removed: encountering it is a compile-time error (use -> tuple[...] for multiple returns)
|
||||
if ret_ty is None: self.Gen.FuncGenBase.RejectCReturnDecorator(fd, fd.decorator_list)
|
||||
if ret_ty is None and fd.returns is not None:
|
||||
# Return annotation present but unresolvable: compile-time error, no silent i32 fallback
|
||||
# (that forced return i32 and misaligned cross-module return types). No annotation is a
|
||||
# procedure (void), matching the definition side and the vpi `def f():` form.
|
||||
self.Gen.TypeGenErr.fatal_error(fd, "function return type resolution failed: annotation is not resolvable", "TypeError")
|
||||
return 0
|
||||
if ret_ty is None: ret_ty = mlirlite.Void(pool)
|
||||
# Infer the return type (shared policy: annotation -> marker -> unannotated = procedure).
|
||||
# The value-return check runs in the body pass, where DefineT entries are registered.
|
||||
fd_ctx_fwd: str = f"function '{fd.name}'"
|
||||
ret_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.resolve_func_ret_ty(trans, fd, fd_ctx_fwd, 0, None, None, 0)
|
||||
if ret_ty is None: return 0
|
||||
|
||||
# Detect ExternT/State/ExportT
|
||||
has_extern: int = 0
|
||||
@@ -716,45 +699,10 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
|
||||
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||||
|
||||
# Infer the return type: prefer the return annotation
|
||||
ret_ty: mlirlite.LLVMType* = None
|
||||
if fd.returns is not None:
|
||||
# Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return).
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a function return type")
|
||||
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
|
||||
# A lone ExportT return annotation is an export modifier with no real type -> Void
|
||||
# (ExportT fell back to i32, forcing return i32: mm.vp free root cause). Mixed types keep the resolved result.
|
||||
if fd.returns.kind() == ast.ASTKind.Name:
|
||||
ce_at2: ast.Name* = ast.Name*(fd.returns)
|
||||
if ce_at2.id is not None and string.strcmp(ce_at2.id, "ExportT") == 0: ret_ty = mlirlite.Void(pool)
|
||||
|
||||
# If the return annotation is purely a decorator marker (e.g. t.State, no real type), use void.
|
||||
# Must be checked before infer_return_type, which would return at least i32.
|
||||
if ret_ty is None and fd.returns is not None:
|
||||
if self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0) != 0: ret_ty = mlirlite.Void(pool)
|
||||
|
||||
# @c.CReturn decorator removed: encountering it is a compile-time error (use -> tuple[...] for multiple returns)
|
||||
if ret_ty is None: self.Gen.FuncGenBase.RejectCReturnDecorator(fd, fd.decorator_list)
|
||||
|
||||
# If still None, infer by scanning return statements (at least i32)
|
||||
if ret_ty is None:
|
||||
# fail-fast: no return annotation (fd.returns is None) but the body returns a non-None value -> error, no silent
|
||||
# inference. Returning a value requires an explicit -> annotation; unannotated functions allow only procedure form.
|
||||
if fd.returns is None:
|
||||
if self.Gen.TypeGenErr.body_has_value_return(pool, fd.children) != 0:
|
||||
rtn_err: str = pool.alloc(256)
|
||||
if rtn_err is not None:
|
||||
viperlib.snprintf(rtn_err, 256, "function '%s' has no return type annotation but returns a non-None value, add an explicit return type annotation (e.g. -> int)", fd.name)
|
||||
self.Gen.TypeGenErr.fatal_error(fd, rtn_err, "TypeError")
|
||||
return 0
|
||||
self.Gen.TypeGenErr.fatal_error(fd, "function has no return type annotation but returns a non-None value", "TypeError")
|
||||
return 0
|
||||
# A procedure: void, so the implicit epilogue emits `ret void` instead of a value
|
||||
# (a value return inside a void function is rejected by the MLIR verifier).
|
||||
ret_ty = mlirlite.Void(pool)
|
||||
else:
|
||||
param_types_str: str = self.Gen.TypeGenErr.build_param_types_str(pool, fd.args)
|
||||
ret_ty = self.Gen.TypeGenErr.infer_return_type(pool, fd.children, param_types_str)
|
||||
# Return type: one shared policy (annotation -> marker -> unannotated = procedure void).
|
||||
tfd_ctx: str = f"function '{fd.name}'"
|
||||
ret_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.resolve_func_ret_ty(trans, fd, tfd_ctx, 0, None, None, 1)
|
||||
if ret_ty is None: return 0
|
||||
# Detect an extern declaration (t.ExternT or t.State): t.ExternT ignores the body and emits only a declare;
|
||||
# t.State = t.ExternT + t.ExportT. t.ExternT/t.State/t.ExportT get no SHA1 prefix and map to C library names.
|
||||
is_extern_decl: int = 0
|
||||
@@ -1031,7 +979,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, func_count, fd.name, func, max_funcs, tfd_defaults, tfd_default_count, tfd_param_count, trans.Pool) == 0:
|
||||
trans._func_count = func_count + 1
|
||||
|
||||
# Add parameters (with type annotation support)
|
||||
# Add parameters (one shared type mapping: bare class -> Ptr(struct), ArrayT -> element pointer).
|
||||
if args_node is not None:
|
||||
ags: ast.Arguments* = ast.Arguments*(args_node)
|
||||
if ags.args is not None:
|
||||
@@ -1039,54 +987,19 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
an: sizet = len(alist)
|
||||
for ai in range(int(an)):
|
||||
arg: ast.Arg* = ast.Arg*(alist.get(ai))
|
||||
if arg is not None and arg.arg is not None:
|
||||
# CVoid means empty param: skip, do not add to the function signature
|
||||
if arg.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imported_modules) != 0: continue
|
||||
param_ty: mlirlite.LLVMType* = i32_ty
|
||||
if arg.annotation is not None:
|
||||
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0)
|
||||
if resolved is None:
|
||||
# [S4 fail-fast] Annotation present but unresolvable: no silent i32
|
||||
# (wrong param type -> ABI misalignment / wrong field access)
|
||||
self.Gen.TypeGenErr.fatal_error(arg, f"param '{arg.arg}' type annotation failed to resolve", "NameError")
|
||||
return 0
|
||||
# Pure parameters are borrowed value copies, never pointers/arrays.
|
||||
if has_pure != 0:
|
||||
if self.Gen.ExprGenBase.is_ptr_type(resolved) != 0 or resolved.kind() == mlirlite.LLVMType.Array:
|
||||
self.Gen.TypeGenErr.fatal_error(arg, f"pure function '{fd.name}' parameter '{arg.arg}' cannot be a pointer or array: pure parameters are immutable value copies (use str/bytes for a pure_str value, or a pure class value)", "TypeError")
|
||||
return 0
|
||||
# Bare class param -> Ptr(struct); ArrayT param -> element pointer ty* (C array decay). Passing
|
||||
# Ptr(array) would make out[i]'s GEP stride by the whole array (garbage summary root cause).
|
||||
match resolved:
|
||||
case mlirlite.LLVMType.Void():
|
||||
# void is not a legal param type (see the same rule above)
|
||||
self.Gen.TypeGenErr.fatal_error(arg, f"param '{arg.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
|
||||
return 0
|
||||
case mlirlite.LLVMType.Struct(_, _, _):
|
||||
param_ty = mlirlite.Ptr(pool, resolved)
|
||||
case mlirlite.LLVMType.Array(aelem_ty, _):
|
||||
param_ty = mlirlite.Ptr(pool, aelem_ty)
|
||||
case _:
|
||||
param_ty = resolved
|
||||
pname: str = pool.alloc(32)
|
||||
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
|
||||
mlirlite.add_param(pool, func, param_ty, pname)
|
||||
if arg is None or arg.arg is None: continue
|
||||
param_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.param_llvm_ty(trans, arg, tfd_ctx, has_pure)
|
||||
if param_ty is None: continue
|
||||
pname: str = pool.alloc(32)
|
||||
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
|
||||
mlirlite.add_param(pool, func, param_ty, pname)
|
||||
|
||||
# *args variadic: set IsVarArg=1 to emit the ... suffix
|
||||
if ags.vararg is not None: func.IsVarArg = 1
|
||||
|
||||
# try-except: a body containing raise gets extra __eh_msg_out__ (i8**) / __eh_code_out__ (i32*) output params
|
||||
# (TPC HandlesFunctions.py L706-711; except main). Must be outside the args_node block so no-arg functions are covered.
|
||||
if string.strcmp(fd.name, "main") != 0:
|
||||
if self.Gen.FuncGenBase._body_has_raise(fd) != 0:
|
||||
i8_ty_eh: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||||
i8_ptr_ty_eh: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_eh)
|
||||
i8_ptr_ptr_ty_eh: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty_eh)
|
||||
i32_ty_eh: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||||
i32_ptr_ty_eh: mlirlite.LLVMType* = mlirlite.Ptr(pool, i32_ty_eh)
|
||||
if i8_ptr_ptr_ty_eh is not None and i32_ptr_ty_eh is not None:
|
||||
mlirlite.add_param(pool, func, i8_ptr_ptr_ty_eh, "%__eh_msg_out__")
|
||||
mlirlite.add_param(pool, func, i32_ptr_ty_eh, "%__eh_code_out__")
|
||||
# try-except: a body containing raise gets extra __eh_msg_out__ (i8**) / __eh_code_out__ (i32*) output
|
||||
# params (never main). The body pipeline wires them onto trans.
|
||||
eh_added: int = self.Gen.FuncGenBase.add_eh_params(trans, fd, func)
|
||||
|
||||
# Params added; fill the global func-table Spec
|
||||
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
|
||||
@@ -1157,303 +1070,11 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
tlone: mlirlite.Value* = mlirlite.ConstInt(pool, i1_ty_t, 1, "1")
|
||||
if tlone is not None: mlirlite.build_store(func_builder, tlone, tls_flag_ref)
|
||||
|
||||
# Enter the function scope (nested symbol table)
|
||||
self.Gen.VarGen.enter_scope(trans.SymTab, FuncGenMixin.SCOPE_FUNCTION)
|
||||
|
||||
# Generic function templates already returned early (see the top of this function); here fd.type_params is always
|
||||
# None (specialization clones clear it). The generic context is set by _specialize_generic_function before the call.
|
||||
|
||||
# arg()/va_list scope reset: save the ENCLOSING function's va_list state and start this body with
|
||||
# none of it. A non-variadic function must not inherit the previous function's buffer: that Value
|
||||
# belongs to another function while the printer emits it by NAME, so the name re-binds to
|
||||
# whatever local happens to carry it here (scene: viperlib vsnprintf emitted `va_arg ptr %9`
|
||||
# where %9 is `count_mode`'s i32 alloca -> va_arg loaded a garbage va_list pointer ->
|
||||
# access-violation inside vsnprintf). The *args setup below re-sets both for a variadic callee,
|
||||
# and old_va_list_ptr/old_vararg_name are restored after the body so that nested functions still
|
||||
# see the enclosing variadic function's buffer.
|
||||
old_va_list_ptr: mlirlite.Value* = trans._va_list_ptr
|
||||
old_vararg_name: str = trans._vararg_name
|
||||
trans._va_list_ptr = None
|
||||
trans._vararg_name = None
|
||||
|
||||
# Create allocas for the parameters
|
||||
if args_node is not None:
|
||||
ags2: ast.Arguments* = ast.Arguments*(args_node)
|
||||
if ags2.args is not None:
|
||||
alist2: list[ast.AST*]* = ags2.args
|
||||
an2: sizet = len(alist2)
|
||||
for ai2 in range(int(an2)):
|
||||
arg2: ast.Arg* = ast.Arg*(alist2.get(ai2))
|
||||
if arg2 is not None and arg2.arg is not None:
|
||||
# CVoid means empty param: skip, do not create an alloca
|
||||
if arg2.annotation is not None:
|
||||
if self.Gen.TypeGenResolve._is_builtin_marker(arg2.annotation, "CVoid", imported_modules) != 0: continue
|
||||
param_ty2: mlirlite.LLVMType* = i32_ty
|
||||
if arg2.annotation is not None:
|
||||
resolved2: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg2.annotation, imported_modules, from_imports, trans, 0)
|
||||
if resolved2 is None:
|
||||
# fail-fast: param has an annotation but is unresolvable; no silent i32
|
||||
self.Gen.TypeGenErr.fatal_error(arg2, f"argument '{arg2.arg}' failed to resolve type: {self.Gen.TypeGenResolve.annotation_to_str(arg2.annotation)}", "NameError")
|
||||
resolved2 = i32_ty # unreachable (fatal_error already exited)
|
||||
if resolved2 is not None:
|
||||
# Consistent with add_param: bare class param -> Ptr(struct); ArrayT param -> element pointer ty*
|
||||
# (array decay; the alloca type must match the param or storing ty* into [N x ty]* mismatches).
|
||||
match resolved2:
|
||||
case mlirlite.LLVMType.Void():
|
||||
# void is not a legal param type (see the same rule above)
|
||||
self.Gen.TypeGenErr.fatal_error(arg2, f"param '{arg2.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
|
||||
return 0
|
||||
case mlirlite.LLVMType.Struct(_, _, _):
|
||||
param_ty2 = mlirlite.Ptr(pool, resolved2)
|
||||
case mlirlite.LLVMType.Array(aelem_ty2, _):
|
||||
param_ty2 = mlirlite.Ptr(pool, aelem_ty2)
|
||||
case _:
|
||||
param_ty2 = resolved2
|
||||
alloca: mlirlite.Value* = mlirlite.build_alloca(func_builder, param_ty2)
|
||||
if alloca is not None:
|
||||
self.Gen.VarGen.define_var(trans.SymTab, arg2.arg, alloca)
|
||||
# Store the original annotation's class name (method-call detection falls back from Ptr(i8) to the class).
|
||||
# Pass pool: a generic Subscript returns a mangled name (aligned with the HandlesAnnAssign fix).
|
||||
cls_nm: str = None
|
||||
if arg2.annotation is not None: cls_nm = self.Gen.TypeGenResolve.extract_class_name_from_annotation(arg2.annotation, imported_modules, pool, trans._from_imports)
|
||||
if cls_nm is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, arg2.arg, cls_nm)
|
||||
# pure: str params are borrowed pure_str values and `self` is a
|
||||
# borrowed receiver. Frame lifetime makes both unable to escape.
|
||||
if has_pure != 0:
|
||||
if string.strcmp(arg2.arg, "self") == 0:
|
||||
self.Gen.VarGen.set_var_life(trans.SymTab, arg2.arg, GenTypes.LIFE_FRAME)
|
||||
elif resolved2 is not None and self.Gen.ExprGenBase._pure_str_struct_ty(pool, resolved2) is not None:
|
||||
self.Gen.VarGen.set_var_life(trans.SymTab, arg2.arg, GenTypes.LIFE_FRAME)
|
||||
# t.VolatileT param: marked at declaration time so loads/stores of this name carry volatile
|
||||
# (a volatile param object's read-modify-write must not be hoisted/merged, e.g. a spin-wait arg).
|
||||
if arg2.annotation is not None:
|
||||
# const param: reuse TPC's historical param criterion (only whether the annotation contains
|
||||
# t.ConstT, regardless of pointer depth); TPC HandlesFunctions IsParam=True uses the same condition.
|
||||
if self.Gen.TypeGenErr.has_decorator_marker(arg2.annotation, "ConstT", 0) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, arg2.arg)
|
||||
if self.Gen.TypeGenErr.has_decorator_marker(arg2.annotation, "VolatileT", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, arg2.arg)
|
||||
# rawptr[t.VolatileT | T] param (volatile pointee): dereferences in the body
|
||||
# (p[i]/p.field/c.Deref(p)) carry volatile.
|
||||
if self.Gen.TypeGenErr.has_pointee_volatile_marker(arg2.annotation) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, arg2.arg)
|
||||
pname2: str = pool.alloc(32)
|
||||
if pname2 is not None: viperlib.snprintf(pname2, 32, "%%%s", arg2.arg)
|
||||
param_val: mlirlite.Value* = mlirlite.SSAValue(pool, param_ty2, pname2)
|
||||
vol_p2: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, arg2.arg))
|
||||
mlirlite.build_store(func_builder, param_val, alloca, vol_p2)
|
||||
|
||||
# *args variadic: allocate a 24-byte va_list buffer, bitcast to i8*, call @llvm.va_start, register in the symbol table.
|
||||
# Must be inside the param alloca section but outside the EH block, or args is unregistered (NameError: 'args').
|
||||
if ags2.vararg is not None:
|
||||
vararg_node: ast.Arg* = ast.Arg*(ags2.vararg)
|
||||
if vararg_node is not None and vararg_node.arg is not None:
|
||||
vararg_name_str: str = vararg_node.arg
|
||||
# va_list buffer is 24 bytes (Win64 __va_list_tag = {i32,i32,i8*,i8*}) with 16-byte alignment
|
||||
# (aligned with TPC HandlesFunctions.py:1739-1741).
|
||||
i8_ty_va: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||||
i8_ptr_ty_va: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_va)
|
||||
va_buf_ty: mlirlite.LLVMType* = mlirlite.Array(pool, i8_ty_va, 24)
|
||||
va_list_alloca: mlirlite.Value* = mlirlite.build_alloca_aligned(func_builder, va_buf_ty, 16)
|
||||
if va_list_alloca is not None:
|
||||
# i8* address (pointing at the 24-byte va_list buffer) - the value semantics of the vararg name
|
||||
va_list_addr: mlirlite.Value* = mlirlite.build_bitcast(func_builder, va_list_alloca, i8_ptr_ty_va)
|
||||
if va_list_addr is not None:
|
||||
# Register in the symbol table (translate_name_value special-cases vararg names to return the address)
|
||||
self.Gen.VarGen.define_var(trans.SymTab, vararg_name_str, va_list_alloca)
|
||||
# Call @llvm.va_start(i8* %va_list_addr)
|
||||
void_ty_va: mlirlite.LLVMType* = mlirlite.Void(pool)
|
||||
mlirlite.build_call(func_builder, "llvm.va_start", va_list_addr, 1, void_ty_va, 0)
|
||||
# Save on trans for the arg(type) sugar and vararg-name translation
|
||||
trans._va_list_ptr = va_list_addr
|
||||
trans._vararg_name = vararg_name_str
|
||||
|
||||
# try-except: a new function scope starts with zero EH state. Save the enclosing function's EH output params
|
||||
# before and restore them after; otherwise nested functions corrupt the EH state chain.
|
||||
old_eh_msg_out: mlirlite.Value* = trans._eh_msg_out_param
|
||||
old_eh_code_out: mlirlite.Value* = trans._eh_code_out_param
|
||||
old_cur_func_has_eh: int = trans._cur_func_has_eh
|
||||
trans._eh_msg_out_param = None
|
||||
trans._eh_code_out_param = None
|
||||
trans._cur_func_has_eh = 0
|
||||
|
||||
# try-except: save this function's EH output params (__eh_msg_out__/__eh_code_out__) for translate_raise /
|
||||
# UnhandledBB to write and propagate. Functions without EH params are set to None.
|
||||
if string.strcmp(fd.name, "main") != 0 and self.Gen.FuncGenBase._body_has_raise(fd) != 0:
|
||||
i8_ty_eh2: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||||
i8_ptr_ty_eh2: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_eh2)
|
||||
i8_ptr_ptr_ty_eh2: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty_eh2)
|
||||
i32_ty_eh2: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||||
i32_ptr_ty_eh2: mlirlite.LLVMType* = mlirlite.Ptr(pool, i32_ty_eh2)
|
||||
if i8_ptr_ptr_ty_eh2 is not None and i32_ptr_ty_eh2 is not None:
|
||||
msg_out_ref: mlirlite.Value* = mlirlite.SSAValue(pool, i8_ptr_ptr_ty_eh2, "%__eh_msg_out__")
|
||||
code_out_ref: mlirlite.Value* = mlirlite.SSAValue(pool, i32_ptr_ty_eh2, "%__eh_code_out__")
|
||||
trans._eh_msg_out_param = msg_out_ref
|
||||
trans._eh_code_out_param = code_out_ref
|
||||
trans._cur_func_has_eh = 1
|
||||
|
||||
# Save module-level scope state (non-variable-table related only)
|
||||
old_func: mlirlite.Function* = trans._cur_func
|
||||
old_builder: mlirlite.IRBuilder* = trans._cur_builder
|
||||
old_global_count: int = trans._global_name_count
|
||||
old_nonlocal_count: int = trans._nonlocal_name_count
|
||||
old_env_count: int = trans._closure_env_count
|
||||
# old_va_list_ptr / old_vararg_name were saved above (before the *args setup) so that this body
|
||||
# could start from a clean va_list state; the restore below hands the enclosing value back.
|
||||
# Return-annotation context ([C①], one source of type-argument fallback for bare generic construction):
|
||||
# record this function's return annotation before entering the body and restore the outer value on exit.
|
||||
old_ret_annot: ast.AST* = trans.CurFuncRetAnnot
|
||||
# break/continue targets and the match-case flag must be cleared for the new function scope (see assignments below),
|
||||
# otherwise break/continue in a nested function inherits the outer (even cross-function) br targets.
|
||||
old_break_bb: mlirlite.BasicBlock* = trans._break_bb
|
||||
old_continue_bb: mlirlite.BasicBlock* = trans._continue_bb
|
||||
old_insw: int = trans._in_switch_case
|
||||
|
||||
trans._cur_func = func
|
||||
trans._cur_builder = func_builder
|
||||
trans.CurFuncRetAnnot = fd.returns
|
||||
# P2 willFree 失效表:**函数作用域复位**(表按栈槽地址记录,槽地址会被后续函数复用)
|
||||
self.Gen.VarGen.freed_clear()
|
||||
trans._break_bb = None
|
||||
trans._continue_bb = None
|
||||
trans._in_switch_case = 0
|
||||
# Clear the global/nonlocal name sets (new function scope)
|
||||
GenApi.clear_scope_names(trans)
|
||||
# Reset the in-chain nonlocal heap prebuild flag (built once per function; multiple nested function definitions
|
||||
# share one heap, preventing repeated malloc + double VarEntry redirection).
|
||||
trans._nl_chain_done = 0
|
||||
# 名字表与"已迁移"标记同样**按函数隔离**:它们只对当前函数体内的局部有效,
|
||||
# 跨函数累积会撞上 64 槽上限并让 later 名字被丢弃。
|
||||
trans._nl_chain_count = 0
|
||||
trans._nl_migrated = None
|
||||
|
||||
# Pre-scan the body: 1) Global/Nonlocal declarations first (Python semantics: they apply to the whole function
|
||||
# scope regardless of position, so register before any assignment); 2) AnnAssign to create local allocas early.
|
||||
body: list[ast.AST*]* = fd.children
|
||||
if body is not None:
|
||||
bn: sizet = len(body)
|
||||
for bi in range(int(bn)):
|
||||
stmt: ast.AST* = body.get(bi)
|
||||
if stmt is not None: self.Gen.StmtGenDecl.pre_scan_scope_decls(trans, stmt)
|
||||
for bi in range(int(bn)):
|
||||
stmt: ast.AST* = body.get(bi)
|
||||
if stmt is not None: self.Gen.StmtGenDecl.pre_scan_allocas(trans, stmt)
|
||||
|
||||
# 【修复】把"嵌套闭包捕获的局部对象 → 堆迁移"提前到**任何语句执行之前**。
|
||||
# 迁移会 malloc 新存储并把 VarEntry.Alloca 改指向它;若等到第一个嵌套 def 处才做,
|
||||
# 在此之前已经交出去的 `&local`(`_register_widget(&widget)` 存进 root.widgets、
|
||||
# 对象内部的反向指针)会永远指向栈副本 —— 现场:Terminal 的控件全注册在栈副本上,
|
||||
# 回车读 on_command 得 None(打印 `(no callback)`),按钮/滚动条回调也全丢。
|
||||
if body is not None:
|
||||
self.Gen.FuncGen._prepare_body_chain_nonlocals(trans, body, pool, func_builder, mod,
|
||||
mlirlite.Int8(pool), mlirlite.Ptr(pool, mlirlite.Int8(pool)))
|
||||
# 同上,但覆盖**隐式捕获**(嵌套 def 只是读外层局部、没有 nonlocal 语句)。
|
||||
# 这是 Terminal/Explorer 这类 UI 代码的主要路径。
|
||||
self.Gen.FuncGen._prepare_body_captures(trans, body, pool, func_builder, mod,
|
||||
mlirlite.Int8(pool), mlirlite.Ptr(pool, mlirlite.Int8(pool)))
|
||||
|
||||
# M3 owned slife(§4.2 函数级 scope guard):进入函数体前把登记区间起点抬到当前栈顶。
|
||||
# 表是函数间共享的一段栈式区间,嵌套函数各自独立;退出时由 _SlifeRestore 弹回外层起点。
|
||||
# 放在这里(而非更早)是因为上方存在多条提前返回分支(泛型模板/async/extern),
|
||||
# 只有从这里开始才保证"有 reset 必有 restore"。
|
||||
old_slife_base: int = self.Gen.VarGen._SlifeReset(pool)
|
||||
|
||||
# Translate the function body
|
||||
old_in_pure: int = trans._in_pure
|
||||
trans._in_pure = has_pure
|
||||
if body is not None:
|
||||
bn2: sizet = len(body)
|
||||
for bi2 in range(int(bn2)):
|
||||
stmt2: ast.AST* = body.get(bi2)
|
||||
if stmt2 is not None:
|
||||
# Before a nested function definition: pre-scan its in-chain nonlocals and build the heap
|
||||
# (for cases where different levels capture different variables; see _prepare_chain_nonlocals).
|
||||
if stmt2.kind() == ast.ASTKind.FunctionDef:
|
||||
self.Gen.FuncGen._prepare_chain_nonlocals(trans, stmt2, pool, func_builder, mod, mlirlite.Int8(pool), mlirlite.Ptr(pool, mlirlite.Int8(pool)))
|
||||
self.Gen.StmtGen.translate_stmt(trans, stmt2)
|
||||
|
||||
# If the last body statement is not Return, add an implicit ret
|
||||
last_is_return: int = 0
|
||||
# bn3 is initialized early so the fail-fast branch below does not reference an undefined bn3 when body is None.
|
||||
bn3: sizet = 0
|
||||
if body is not None:
|
||||
bn3 = len(body)
|
||||
if bn3 > 0:
|
||||
last_stmt: ast.AST* = body.get(bn3 - 1)
|
||||
if last_stmt is not None and last_stmt.kind() == ast.ASTKind.Return: last_is_return = 1
|
||||
if last_is_return == 0:
|
||||
if mlirlite.builder_cur_block_is_terminated(func_builder) == 0:
|
||||
# The compiler never invents a return value: falling off the end without an explicit return is fail-fast.
|
||||
# Legal endings: void -> ret void; unreachable merge (all branches terminated) -> unreachable; unannotated -> zero return.
|
||||
last_is_ctrl: int = 0
|
||||
if bn3 > 0:
|
||||
last_stmt3: ast.AST* = body.get(bn3 - 1)
|
||||
if last_stmt3 is not None:
|
||||
k3: int = last_stmt3.kind()
|
||||
if k3 == ast.ASTKind.If or k3 == ast.ASTKind.While or k3 == ast.ASTKind.For or k3 == ast.ASTKind.Try or k3 == ast.ASTKind.With or k3 == ast.ASTKind.Match: last_is_ctrl = 1
|
||||
_is_void: int = 0
|
||||
if ret_ty is not None:
|
||||
match ret_ty:
|
||||
case mlirlite.LLVMType.Void():
|
||||
_is_void = 1
|
||||
case _:
|
||||
pass
|
||||
# Annotation-level "no return value" criterion (a second check alongside the ret_ty type check): if the
|
||||
# REnum class-pattern match degrades, _is_void stays 0 and a void function is misreported as missing a return.
|
||||
if _is_void == 0 and fd.returns is not None:
|
||||
if self.Gen.TypeGenResolve._is_cvoid_marker(fd.returns, imported_modules) != 0: _is_void = 1
|
||||
elif fd.returns.kind() == ast.ASTKind.Constant:
|
||||
ret_none_cn: ast.Constant* = (ast.Constant*)(fd.returns)
|
||||
if ret_none_cn.const_kind == ast.CONST_NONE: _is_void = 1
|
||||
if _is_void != 0:
|
||||
# M3:隐式收尾也是一个函数出口,先释放本函数登记的 slife 再返回
|
||||
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
|
||||
mlirlite.build_ret_void(func_builder)
|
||||
elif last_is_ctrl != 0 and trans._ctrl_merge_reachable == 0:
|
||||
# A non-void function whose last statement is control flow with a structurally unreachable merge (all
|
||||
# branches return, no br to merge) -> LLVM unreachable. Reachability uses only `_ctrl_merge_reachable`.
|
||||
mlirlite.build_unreachable(func_builder)
|
||||
elif fd.returns is None:
|
||||
# No return annotation -> a procedure: the epilogue must never carry a value
|
||||
# (`llvm.return <val>` in a void function is rejected by the verifier).
|
||||
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
|
||||
mlirlite.build_ret_void(func_builder)
|
||||
else:
|
||||
# fail-fast: has a -> annotation but the flow falls off the end (pointing at the function definition
|
||||
# node; fatal_error prints a traceback-style error).
|
||||
if ret_ty is not None:
|
||||
# mlirlite has no type_get_name API; serialize the type with the real interface
|
||||
# TypePrint(buf, size, ty, pool).
|
||||
rt_name: str = pool.alloc_tagged(4096, memhub.DETSITE_FUNCGENDEF_792)
|
||||
if rt_name is not None:
|
||||
mlirlite.TypePrint(rt_name, 4096, ret_ty, pool)
|
||||
if rt_name[0] == '\0': rt_name = "?"
|
||||
err_fn: str = pool.alloc(256)
|
||||
if err_fn is not None:
|
||||
viperlib.snprintf(err_fn, 256, "function '%s' flow falls off the end of the function without an explicit return (return type %s), please add an explicit return (e.g. return 0 / return null)", fd.name, rt_name)
|
||||
self.Gen.TypeGenErr.fatal_error(node, err_fn, "TypeError")
|
||||
self.Gen.TypeGenErr.fatal_error(node, "function flow falls off the end without an explicit return", "TypeError")
|
||||
else: self.Gen.TypeGenErr.fatal_error(node, "function flow falls off the end without an explicit return", "TypeError")
|
||||
|
||||
# Restore module-level scope (exit the function scope)
|
||||
self.Gen.VarGen.exit_scope(trans.SymTab)
|
||||
# M3 owned slife:丢弃本函数的登记区间、弹回外层起点(释放已在各出口用 _SlifeEmitFrees 完成)
|
||||
self.Gen.VarGen._SlifeRestore(old_slife_base)
|
||||
trans._cur_func = old_func
|
||||
trans._cur_builder = old_builder
|
||||
trans._in_pure = old_in_pure
|
||||
trans._break_bb = old_break_bb
|
||||
trans._continue_bb = old_continue_bb
|
||||
trans._in_switch_case = old_insw
|
||||
trans._global_name_count = old_global_count
|
||||
trans._nonlocal_name_count = old_nonlocal_count
|
||||
trans._closure_env_count = old_env_count
|
||||
trans._va_list_ptr = old_va_list_ptr
|
||||
trans._vararg_name = old_vararg_name
|
||||
# Hand back the outer function's return annotation (see old_ret_annot above)
|
||||
trans.CurFuncRetAnnot = old_ret_annot
|
||||
# Restore the outer (caller) function's EH output params: this function's raise propagation uses its own params,
|
||||
# so hand them back on exit or nested/chained calls leave stale EH state and write to the wrong out pointer.
|
||||
trans._eh_msg_out_param = old_eh_msg_out
|
||||
trans._eh_code_out_param = old_eh_code_out
|
||||
trans._cur_func_has_eh = old_cur_func_has_eh
|
||||
# The body itself - scope entry, parameter storage, pre-scan, capture preparation,
|
||||
# statement translation, implicit return and state restore - is one shared pipeline
|
||||
# with class methods (FuncGenBase.translate_func_body). Keeping a single copy is what
|
||||
# stops a declare and its body from disagreeing on the return type.
|
||||
self.Gen.FuncGenBase.translate_func_body(trans, fd, func, func_builder, ret_ty, tfd_ctx, 0, None, None, None, has_pure, eh_added, 1, None)
|
||||
|
||||
# Custom decorator: register and generate a wrapper (aligned with TPC DecoratorPass.run). Module-level functions
|
||||
# only; wrapper name is __decor_wrap_{bare name}; call sites are rewritten via lookup_decor_wrapper.
|
||||
|
||||
@@ -248,23 +248,10 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
|
||||
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:
|
||||
# M3:隐式收尾也是函数出口,先释放本嵌套函数登记的 slife
|
||||
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
|
||||
# 零值必须与**本函数的返回类型**一致(原实现写死 const_int32(0):一旦返回类型改为
|
||||
# `char*` 之类,`ret i32 0` 与签名不符,llc 会直接拒绝)。ret_ty_n 见第 2 步。
|
||||
zero_val: mlirlite.Value* = mlirlite.ConstZero(pool, ret_ty_n)
|
||||
if zero_val is None:
|
||||
self.Gen.TypeGenErr.fatal_error(fd, f"nested function '{fd.name}': flow falls off the end without an implicit return and the return type has no zero value", "TypeError")
|
||||
else:
|
||||
mlirlite.build_ret(func_builder, zero_val)
|
||||
# 10. Implicit return (one shared implementation; the promoted-closure ABI keeps the
|
||||
# legacy "unannotated = i32" policy, hence unannotated_void=0).
|
||||
nf_ctx: str = f"nested function '{fd.name}'"
|
||||
self.Gen.FuncGenBase.emit_func_epilogue(trans, fd, func_builder, ret_ty_n, nf_ctx, 0)
|
||||
|
||||
# M3:弹回外层登记区间(释放已在本嵌套函数各出口完成)
|
||||
self.Gen.VarGen._SlifeRestore(old_slife_base)
|
||||
@@ -923,7 +910,7 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
|
||||
return self._build_nl_heaps(trans, pool, builder, mod, i8_ptr_ty, 0)
|
||||
|
||||
# _dry_define_annassign_names - 干跑用:把节点里所有 AnnAssign 目标登记成"局部名字"(不产生指令)
|
||||
def _dry_define_annassign_names(self, trans: HT.Translator*, node: ast.AST*, dummy: mlirlite.Value*):
|
||||
def _dry_define_annassign_names(self, trans: HT.Translator*, node: ast.AST*, dummy: mlirlite.Value*) -> int:
|
||||
"""Register every AnnAssign target inside node as a local name (no code emitted)"""
|
||||
if node is None or dummy is None: return 0
|
||||
k: int = node.kind()
|
||||
@@ -981,7 +968,7 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
|
||||
return 0
|
||||
|
||||
# _dry_define_nested_locals - 干跑用:把嵌套 def 的形参与 AnnAssign 局部登记成"当前作用域局部"
|
||||
def _dry_define_nested_locals(self, trans: HT.Translator*, fd: ast.FunctionDef*, pool: memhub.MemBuddy*, dummy_ty: mlirlite.LLVMType*):
|
||||
def _dry_define_nested_locals(self, trans: HT.Translator*, fd: ast.FunctionDef*, pool: memhub.MemBuddy*, dummy_ty: mlirlite.LLVMType*) -> int:
|
||||
"""Register the nested def's params and AnnAssign locals as locals (no alloca emitted)"""
|
||||
if fd is None or pool is None: return 0
|
||||
dummy: mlirlite.Value* = mlirlite.SSAValue(pool, dummy_ty, "%dry")
|
||||
|
||||
Reference in new issue
Block a user