From eda8e4465feda76d4fa2741847ad1b9c7633a906 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 29 Oct 2026 19:37:55 +0800 Subject: [PATCH] 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. --- App/lib/Stub/StubGen/VpiReader.vp | 10 +- App/lib/core/Gen/Call/CallGenCall.vp | 49 ++- App/lib/core/Gen/Call/CallGenDispatch.vp | 5 +- App/lib/core/Gen/Class/ClassGenGeneric.vp | 25 +- App/lib/core/Gen/Class/ClassGenVTable.vp | 275 ++------------ App/lib/core/Gen/Func/FuncGenBase.vp | 414 ++++++++++++++++++++- App/lib/core/Gen/Func/FuncGenDef.vp | 427 ++-------------------- App/lib/core/Gen/Func/FuncGenMixin.vp | 25 +- 8 files changed, 547 insertions(+), 683 deletions(-) diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index f7f12db..609c845 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -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"。与 diff --git a/App/lib/core/Gen/Call/CallGenCall.vp b/App/lib/core/Gen/Call/CallGenCall.vp index cf25900..7efcb75 100644 --- a/App/lib/core/Gen/Call/CallGenCall.vp +++ b/App/lib/core/Gen/Call/CallGenCall.vp @@ -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) diff --git a/App/lib/core/Gen/Call/CallGenDispatch.vp b/App/lib/core/Gen/Call/CallGenDispatch.vp index ab4af66..c21f854 100644 --- a/App/lib/core/Gen/Call/CallGenDispatch.vp +++ b/App/lib/core/Gen/Call/CallGenDispatch.vp @@ -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) diff --git a/App/lib/core/Gen/Class/ClassGenGeneric.vp b/App/lib/core/Gen/Class/ClassGenGeneric.vp index e18fc90..37c3d33 100644 --- a/App/lib/core/Gen/Class/ClassGenGeneric.vp +++ b/App/lib/core/Gen/Class/ClassGenGeneric.vp @@ -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) diff --git a/App/lib/core/Gen/Class/ClassGenVTable.vp b/App/lib/core/Gen/Class/ClassGenVTable.vp index 8e0c168..e520245 100644 --- a/App/lib/core/Gen/Class/ClassGenVTable.vp +++ b/App/lib/core/Gen/Class/ClassGenVTable.vp @@ -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 " | t."); 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) diff --git a/App/lib/core/Gen/Func/FuncGenBase.vp b/App/lib/core/Gen/Func/FuncGenBase.vp index a6a7bb8..6e73e8f 100644 --- a/App/lib/core/Gen/Func/FuncGenBase.vp +++ b/App/lib/core/Gen/Func/FuncGenBase.vp @@ -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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/App/lib/core/Gen/Func/FuncGenDef.vp b/App/lib/core/Gen/Func/FuncGenDef.vp index ecdd3f1..a59ee04 100644 --- a/App/lib/core/Gen/Func/FuncGenDef.vp +++ b/App/lib/core/Gen/Func/FuncGenDef.vp @@ -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 ` 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. diff --git a/App/lib/core/Gen/Func/FuncGenMixin.vp b/App/lib/core/Gen/Func/FuncGenMixin.vp index c6b4175..ad4645a 100644 --- a/App/lib/core/Gen/Func/FuncGenMixin.vp +++ b/App/lib/core/Gen/Func/FuncGenMixin.vp @@ -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")