diff --git a/App/lib/core/Gen/Call/CallGenMixin.vp b/App/lib/core/Gen/Call/CallGenMixin.vp index 66acf68..3b66f1e 100644 --- a/App/lib/core/Gen/Call/CallGenMixin.vp +++ b/App/lib/core/Gen/Call/CallGenMixin.vp @@ -1,4 +1,5 @@ import lib.core.Gen.GenTypes as GenTypes +import lib.core.CompilerFactory as CompilerFactory from stdint import * import ast import mlirlite @@ -9,6 +10,7 @@ import viperlib import lib.core.Gen.Translator as HT import lib.core.Gen.Type.TypeSpec as TSpec import lib.Stub.StubGen.VpiReader as VpiReader +import lib.core.VLogger as VLogger from . import CallGenBase import lib.core.Gen.GenApi as GenApi @@ -145,9 +147,117 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): cur_param = mlirlite.param_get_next(cur_param) return 0 + # _defaults_entry_from_vpi - 全局表里没有该函数的缺省值时,直接按定义模块 .vpi 签名临时造一条 + # 快照条目(kinds/ints/strs 由文本解析得到,字符串已深拷贝进 PermPool),仅本次调用使用。 + def _defaults_entry_from_vpi(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> GenTypes.FuncDefaultsEntry*: + """按 .vpi 签名临时构造缺省值条目;无缺省值或不可重建返回 None""" + cap: int = 128 + # 快照数组放 PermPool:条目可能在后续 pool reset 之后才被消费,放 pool 会悬空。 + kinds: int* = CompilerFactory.factory.PermPool.alloc(sizet(cap) * 4) + ints: int64* = CompilerFactory.factory.PermPool.alloc(sizet(cap) * 8) + strs: sizet* = CompilerFactory.factory.PermPool.alloc(sizet(cap) * 8) + if kinds is None or ints is None or strs is None: return None + enc: int = VpiReader.LookupSignDefaultsByName(pool, sha1, name, kinds, ints, strs) + if enc < 0: return None + dc: int = enc & 0xFF + pc: int = enc >> 8 + if dc <= 0: return None + ent_mem: bytes = pool.alloc(sizet(GenTypes.FuncDefaultsEntry.__sizeof__())) + if ent_mem is None: return None + ent: GenTypes.FuncDefaultsEntry* = GenTypes.FuncDefaultsEntry*(uint64(ent_mem)) + ent.Name = None + ent.ModuleSha1 = None + ent.Defaults = None + ent.DefaultCount = dc + ent.ParamCount = pc + ent.ConstKinds = kinds + ent.ConstInts = ints + ent.ConstStrs = strs + return ent + + # _fill_defaults_late - call_name 定型后按 {sha1 前缀}.{名字} 再补一次缺省值。 + # 早期补参用的是 dfx_callee_sha,链式接收者(`self.Gen.X.m(...)`)解析不出发起模块时它会 + # 退化成调用方 SHA1,表查不到;此时 call_name 已带定义模块 SHA1 前缀,是权威键,再补一次。 + # 返回补齐后的实参个数。 + def _fill_defaults_late(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, builder: mlirlite.IRBuilder*, trans: HT.Translator*, cl: ast.Call*, call_name: str, head: mlirlite.Value*, can: sizet) -> sizet: + """按 call_name 里的定义模块 SHA1 前缀补缺省参数;返回新的实参个数""" + if head is None or can == 0 or call_name is None: return can + if self.Gen.CallGenLib._is_sha1_mangled_call(call_name) == 0: return can + sh_buf: str = pool.alloc(17) + if sh_buf is None: return can + string.strncpy(sh_buf, call_name, 16) + sh_buf[16] = '\0' + cname: str = call_name + 17 + if cname[0] == '\0': return can + # 只做 SHA1 隔离的查找:表按 (名字, 定义模块 SHA1) 定位;表里没有就直接读该模块的 .vpi。 + # 绝不按裸名跨模块搜(`run` 这类名字在多个模块里都有,会拿错签名去补参/校验)。 + de: GenTypes.FuncDefaultsEntry* = self.Gen.CallGenLeafKit.find_func_defaults(cname, sh_buf) + if de is None: de = self._defaults_entry_from_vpi(pool, sh_buf, cname) + if de is None: return can + n: int = int(can) + if de.DefaultCount <= 0 or n >= de.ParamCount: return can + df_off: int = de.ParamCount - de.DefaultCount + df_i: int + for df_i in range(int(de.DefaultCount)): + df_pidx: int = df_off + df_i + if df_pidx < n: continue + df_val: mlirlite.Value* = self.Gen.CallGenLeafKit._build_const_from_snapshot(pool, mod, de, df_i, trans, builder) + if df_val is None: + self.Gen.TypeGenErr.fatal_error(cl, f"default argument {df_i + 1} of '{call_name}' cannot be rebuilt into a value; refusing to emit a call with fewer operands than the callee declares", "TypeError") + df_val.Next = None + df_tail: mlirlite.Value* = head + while df_tail.Next is not None: df_tail = df_tail.Next + df_tail.Next = df_val + n += 1 + # 补完仍少于声明形参:说明签名里的缺省值不够用,直接给出可读的 VPC Diag, + # 不要放行到 mlir-opt 变成 "incorrect number of operands"。 + if n < de.ParamCount: + self.Gen.TypeGenErr.fatal_error(cl, f"call to '{call_name}' passes {n} argument(s) but the callee declares {de.ParamCount} parameter(s); only {de.DefaultCount} usable default value(s) are available", "TypeError") + return sizet(n) + + # _fail_on_unimported_module_call - 模块限定调用的模块必须已导入(否则报 NameError) + # `mod.func(...)` 里的 mod 若确实是模块表里的模块、但本文件没 import,后续会把它当成 + # "外部 C 函数" 去猜返回类型,报出与真因无关的 TypeError + # (现场:vpui.vp 用了 viperlib.snprintf 却漏写 `import viperlib`,报的是 + # "external function return type is unregistered ... snprintf")。 + def _fail_on_unimported_module_call(self, pool: memhub.MemBuddy*, trans: HT.Translator*, cl: ast.Call*) -> int: + """Fail with NameError when `mod.f(...)` names a known module this file never imported.""" + if cl is None or cl.func is None or trans is None: return 0 + if cl.func.kind() != ast.ASTKind.Attribute: return 0 + at: ast.Attribute* = ast.Attribute*(cl.func) + if at.attr is None or at.value is None: return 0 + # 只处理一级模块段;多级路径(os._win32.exists)与表达式接收者交给别的路径 + if at.value.kind() != ast.ASTKind.Name: return 0 + nm: ast.Name* = ast.Name*(at.value) + if nm.id is None: return 0 + # obj.method():接收者是局部/全局变量,不是模块 + if trans.SymTab is not None: + if self.Gen.VarGen.lookup_var(trans.SymTab, nm.id) is not None: return 0 + # 已导入的模块 + if trans._imported_modules is not None: + if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, nm.id) != 0: return 0 + # import-as 别名 / from-import 名字 + if trans._from_imports is not None: + if self.Gen.ImportsGen.lookup_from_import(trans._from_imports, nm.id, 0) is not None: return 0 + # REPL 会话宽松:会话里允许后补 import + if trans.ReplCtx is not None: return 0 + # 类名优先:`TypeSpec.__sizeof__()` 是类静态调用,不是模块调用。类名与模块名同名时 + # (现场:TypeSpec.vp 里模块名 = 类名 = TypeSpec)不能因为"没 import 自己"而误报。 + # 先用文件级类名表(不依赖 StructTable 是否已注册),再补 (name, sha1) 精确查找。 + if pool is not None: + if self.Gen.StructGen.is_file_class(pool, nm.id) != 0: return 0 + if trans.ModuleSha1 is not None: + if self.Gen.StructGen.find_struct_by_module(nm.id, trans.ModuleSha1) is not None: return 0 + # 只有"确实是模块"才判;未定义名字交给其它路径 + if self.Gen.StructGen._resolve_module_sha1(nm.id, trans._from_imports) is None: return 0 + self.Gen.TypeGenErr.fatal_error(at, f"call '{nm.id}.{at.attr}(...)' targets module '{nm.id}', but this file does not import it; add `import {nm.id}`. Without the import the call degrades into an external C-function call and its return type cannot be resolved (pointer returns would be truncated to i32)", "NameError") + return 0 + def translate_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, node: ast.AST*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator* = None) -> mlirlite.Value*: """Translate a Call node and return the result Value.""" cl: ast.Call* = ast.Call*(node) + # 模块限定调用先校验"模块已导入":不导入会被降级成外部 C 函数,报出与真因无关的 TypeError + self._fail_on_unimported_module_call(pool, trans, cl) # Static symbols must not be used cross-module: fail fast on mod.fn(...) hitting another # module's static; the check lives in _CheckStaticCallCrossModule to keep this huge function inferable. self._CheckStaticCallCrossModule(pool, trans, cl) @@ -310,8 +420,8 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): # VPC compiles generic functions as plain functions, so drop [T] if a same-name function exists. if self.find_func_in_module(mod, gen_class_name) is not None: func_name = gen_class_name else: - # Builtin pointer/type subscript cast: (rawptr[list[int]])(lst), (t.CPtr[T])(x), - # (t.CArray[i8, N])(x); rawptr/CPtr/CArray are type subscripts, not generic templates. + # Builtin pointer/type subscript cast: (rawptr[list[int]])(lst), (rawptr[T])(x), + # (t.ArrayT[i8, N])(x); rawptr/PtrT/ArrayT are type subscripts, not generic templates. if cl.args is not None and len(cl.args) == 1: sub_cast_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, cl.func, trans._imported_modules, trans._from_imports, trans, 0) if sub_cast_ty is not None: @@ -685,7 +795,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): ff_nm2: str = mlirlite.function_get_name(found_func) if ff_nm2 is not None: if string.strncmp(ff_nm2, mod_sha1_chk, 16) != 0: - # CExport / t.State 型跨模块函数**定义与声明都用裸名**(stdlib.free、 + # ExportT / t.State 型跨模块函数**定义与声明都用裸名**(stdlib.free、 # CPython 的 PyList_Size 等):裸名不是"绑错了模块",必须放行。 # 否则这里会被清成 None → 关键字实参路径整体跳过 → 静默生成 # 零实参调用(现场:stdlib.free(p = p) → llvm.call @free() → mlir-opt @@ -755,6 +865,11 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): bi_ret = mlirlite.Double(pool) bi_p1 = mlirlite.Double(pool) bi_p2 = mlirlite.Double(pool) + elif string.strcmp(func_name, "abs") == 0: + # abs 也是语言内建(int -> int),由 builtins.vp 提供实现;不再落到 C 库的 + # `abs` 声明上,否则无 libc 的目标(VKernel)会 undefined symbol: abs。 + bi_ret = mlirlite.Int32(pool) + bi_p1 = mlirlite.Int32(pool) if bi_ret is not None: bi_sym: str = func_name if self.Gen.CallGenBase.is_cexport_func(bi_sha1, func_name) == 0: @@ -824,17 +939,42 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): if rr_sha1 is not None: VpiReader._ensure_vpi_ret_annot_loaded(rr_sha1, self.Gen.CallGenBase.TempDir) ret_annot_fc = VpiReader._lookup_vpi_ret_annot(rr_sha1, None, func_name) - if ret_annot_fc is not None: vpi_ret_ty = VpiReader._resolve_vpi_ret_annot_str(pool, ret_annot_fc, mlirlite.Int32(pool), mlirlite.Ptr(pool, mlirlite.Int8(pool))) + if ret_annot_fc is not None: + vpi_ret_ty = VpiReader._resolve_vpi_annot_with_typedefs(pool, mod_ret_sha1, ret_annot_fc, mlirlite.Int32(pool), mlirlite.Ptr(pool, mlirlite.Int8(pool))) + # Generic instantiation (`mlirlite.function_get_params -> GSList[Param]*`) + # needs on-demand specialization; the string resolver cannot build it. + if vpi_ret_ty is None: vpi_ret_ty = self.Gen.CallGenBase._resolve_generic_annot_str(trans, pool, ret_annot_fc) if vpi_ret_ty is not None: call_ret_ty = vpi_ret_ty elif mod_ret_sha1 is not None: - # Cross-module bare CExport function (e.g. mm.malloc -> @malloc): resolve the return type + # Cross-module bare ExportT function (e.g. mm.malloc -> @malloc): resolve the return type # exactly from the dependency module, else the default i32 truncates the pointer (mm.malloc root cause). - cross_ret: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, mod_ret_sha1, None, func_name) + cross_ret: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, mod_ret_sha1, None, func_name, trans) if cross_ret is None: if trans is not None and trans._declare_only == 0: cross_ret = self._LookupMethodRetTyFromTextLL(pool, mod_ret_sha1, None, func_name, 1) if cross_ret is not None: call_ret_ty = cross_ret else: - # Dependency text.ll not generated yet (Phase A middle pass): fatal_if_unknown=0, only a temporary placeholder. + # 结果被丢弃(独立调用语句)时不 fail-fast:占位 i32 无害,没必要中断编译。 + if trans is not None and trans._declare_only == 0 and trans._discard_result == 0: + vpi_rc_d: int = 0 + vpi_annot_d: str = None + if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: + vpi_rc_d = VpiReader._ensure_vpi_ret_annot_loaded(mod_ret_sha1, self.Gen.CallGenBase.TempDir) + vpi_annot_d = VpiReader._lookup_vpi_ret_annot(mod_ret_sha1, None, func_name) + annot_s_d: str = "-" + if vpi_annot_d is not None: annot_s_d = vpi_annot_d + # sha1 来源拆解:exact / suffix 各自返回什么,用来区分"存表里就是错的" + # 与"后缀匹配撞到了别的模块"(两者修法完全不同)。 + ex_s_d: str = "-" + if mod_path_ret is not None: + _ex_d: str = self.Gen.CallGenBase._lookup_module_sha1(mod_path_ret) + if _ex_d is not None: ex_s_d = _ex_d + sf_s_d: str = "-" + if mod_path_ret is not None: + _sf_d: str = self.Gen.CallGenBase._lookup_module_sha1_suffix(mod_path_ret) + if _sf_d is not None: sf_s_d = _sf_d + # 走 VPC Diag(源码行 + 调用栈),而不是单行 VLogger.error: + # 单行输出丢掉了位置与调用链,无法定位到底是谁在什么上下文里调用了它。 + self.Gen.TypeGenErr.fatal_error(node, f"cross-module function return type unresolved: {mod_path_ret}.{func_name} (sha1={mod_ret_sha1}, exact={ex_s_d}, suffix={sf_s_d}, tempdir_set={self.Gen.CallGenBase.TempDirLen}, vpi_load_rc={vpi_rc_d}, vpi_annot={annot_s_d}); a placeholder i32 would truncate a pointer return", "TypeError") call_ret_ty = self.Gen.CallGenLib._infer_external_func_ret_ty(pool, func_name, node, 0) else: # Bare call (cl.func is Name, mod_ret_sha1=None): from-import/closure/function-pointer targets are resolvable (placeholder, no fail-fast); pure external C functions fail-fast. @@ -885,15 +1025,15 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): return self.Gen.CallGenLeafKit.translate_closure_call(pool, builder, mod, fp_alloca, cargs, can, trans) # Function-pointer variable (t.Callable annotation -> i8** alloca): build the signature from args and call indirectly. if self.Gen.CallGenLeafKit._is_func_ptr_alloca(fp_alloca) != 0: - # Distinguish t.Callable (true function pointer) from t.CPtr (closure pointer): a t.CPtr + # Distinguish t.Callable (true function pointer) from rawptr (closure pointer): a rawptr # holds {fn_ptr, env_ptr} and must pass env, else a direct call jumps into closure data and crashes. ve_fp: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, func_name) is_cptr_annot_fp: int = 0 fp_annot: str = self.Gen.VarGen.var_annot_class_name(ve_fp) if fp_annot is not None: - # [S4] CPtr marker = annotation base name exactly "CPtr": t.Callable[[A],R] also resolves to + # [S4] PtrT marker = annotation base name exactly "PtrT": t.Callable[[A],R] also resolves to # Ptr(i8) (same Spec), so only the annotation name can carry the marker; substring matching would misfire. - if TSpec.SpecTypeNameBaseIs(fp_annot, "CPtr") != 0: is_cptr_annot_fp = 1 + if TSpec.SpecTypeNameBaseIs(fp_annot, "PtrT") != 0: is_cptr_annot_fp = 1 if is_cptr_annot_fp == 0: return self.Gen.CallGenLeafKit._translate_func_ptr_call(pool, builder, mod, fp_alloca, func_name, cargs, can, trans) return self.Gen.CallGenLeafKit.translate_closure_call(pool, builder, mod, fp_alloca, cargs, can, trans) @@ -1010,7 +1150,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): else: arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, ext_param_ty, arg) elif found_func is not None: # found_func present: coerce to the param type (e.g. stdlib.malloc(size: sizet)); a - # CDefine/i32 literal passed to an i64 param would otherwise leave call(i32) vs define(i64) mismatched. + # DefineT/i32 literal passed to an i64 param would otherwise leave call(i32) vs define(i64) mismatched. if fp is not None: if fpty is not None: # "str/bytes is not a generic pointer wildcard": a same-module bare function whose @@ -1101,6 +1241,9 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): if rr_src_dx is not None: rr_sha_dx: str = self._lookup_module_sha1(rr_src_dx) if rr_sha_dx is not None: gd_entry = self.Gen.CallGenLeafKit.find_func_defaults(func_name, rr_sha_dx) + # 键失配兜底:链式接收者(`self.Gen.ExprGenMixin.translate_value(...)`)解析不出发起模块, + # dfx_callee_sha 退化成调用方自己的 SHA1,限定查表落空 —— 由后面的 _fill_defaults_late + # 用 call_name 里的定义模块 SHA1 前缀(权威键)再补一次,这里不做跨模块裸名猜测。 # Override when df_default_count<=0 too: the caller table may hold a same-name entry with empty # Defaults (a VpiReader declare), so the real global entry must win or filling never triggers. if gd_entry is not None and (df_defaults is None or df_default_count <= 0): @@ -1176,7 +1319,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): fp_z = mlirlite.param_get_next(fp_z) fpi_z += 1 - # Use the actual function name from the module (may carry a SHA1 prefix); CExtern/State/CExport + # Use the actual function name from the module (may carry a SHA1 prefix); ExternT/State/ExportT # functions use the bare name. Both declare and define must use found_func.Name (mismatched names cause undefined references). call_name: str = func_name # Original function name (recorded for aliased imports, e.g. from X import parse as json_parse -> "parse"), @@ -1186,8 +1329,8 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): found_name: str = mlirlite.function_get_name(found_func) if found_name is not None: call_name = found_name - # CExport bare-name fallback: a cross-module stub declare may carry a SHA1 prefix while the - # definition uses @malloc, causing a link error; extract the sha1 and use the bare name if it is a CExport. + # ExportT bare-name fallback: a cross-module stub declare may carry a SHA1 prefix while the + # definition uses @malloc, causing a link error; extract the sha1 and use the bare name if it is a ExportT. if found_func.IsDeclared != 0: # Use the single source of truth GenApi.sha1_prefix_len (converged in C10). if GenApi.sha1_prefix_len(found_name) != 0: @@ -1258,7 +1401,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): rs_found = 1 call_name = func_name else: - # Original module has a plain def (not CExport/State): a vpi hit keeps the original + # Original module has a plain def (not ExportT/State): a vpi hit keeps the original # module sha (@{mod_sha1}.func) instead of falling back to a star candidate that would hijack resolution. orig_has_def: int = 0 if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: @@ -1268,7 +1411,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): # Original module does not define it: fall back to the first candidate (e.g. ast._lexer_init in ast.lexer). reexport_sha1: str = self._lookup_module_sha1(reexport_src) if reexport_sha1 is not None: mod_sha1 = reexport_sha1 - # CExport functions define with the bare name (@strlen), so the cross-module call must use the bare name too. + # ExportT functions define with the bare name (@strlen), so the cross-module call must use the bare name too. if rs_found != 0: pass # Re-export defining module already verified and bare name set. elif self.Gen.CallGenBase.is_cexport_func(mod_sha1, func_name) != 0: call_name = func_name @@ -1319,7 +1462,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): from_sha1: str = None if trans is not None and trans._from_imports is not None: from_sha1 = self._lookup_mod_sha1(pool, trans._from_imports, xmod_path, 0) if from_sha1 is not None: - # CExport functions define with the bare name, so the cross-module call must use the bare name. + # ExportT functions define with the bare name, so the cross-module call must use the bare name. if self.Gen.CallGenBase.is_cexport_func(from_sha1, func_name) != 0: call_name = func_name else: fmangled_len: sizet = string.strlen(from_sha1) + string.strlen(func_name) + 2 @@ -1328,6 +1471,22 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): viperlib.snprintf(fmangled, fmangled_len, "%s.%s", from_sha1, func_name) call_name = fmangled else: + # The base name is neither a local/closure variable nor a class: the + # attribute call can only be a module-qualified call, so an unresolved + # module means the name itself is undefined (missing import). Reporting + # "module SHA1 not found" here hid the real cause (现场: VLogger.error + # used without `import lib.core.VLogger as VLogger`). + base_defined: int = 0 + if trans is not None: + if self.Gen.VarGen.lookup_var(trans.SymTab, xmod_path) is not None: base_defined = 1 + if base_defined == 0 and GenApi.is_nonlocal_name(trans, xmod_path) != 0: base_defined = 1 + if base_defined == 0 and self.Gen.StructGen.find_struct(xmod_path) is not None: base_defined = 1 + if base_defined == 0: + err_msg_ne: str = pool.alloc(256) + if err_msg_ne is not None: + viperlib.snprintf(err_msg_ne, 256, "name '%s' is not defined (used as a module in '%s.%s'; missing an import?)", xmod_path, xmod_path, func_name) + self.Gen.TypeGenErr.fatal_error(xmod_at, err_msg_ne, "NameError") + self.Gen.TypeGenErr.fatal_error(xmod_at, f"name '{xmod_path}' is not defined", "NameError") err_msg2: str = pool.alloc(256) if err_msg2 is not None: viperlib.snprintf(err_msg2, 256, "Cross-module call '%s.%s': module SHA1 not found", xmod_path, func_name) @@ -1423,11 +1582,36 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase): cf_ret: mlirlite.LLVMType* = self._LookupMethodRetTyFromTextLL(pool, cf_sha1, None, cf_bare, 1) if cf_ret is not None: call_ret_ty = cf_ret else: + # 编译器内置(ord/chr/min/max):builtins.vp 只是注释桩,没有任何声明, + # 签名由上面 "builtins auto-link" 按约定合成(ord(i8)->i32 等), + # 因此不能按"跨模块缺 vpi/text.ll"判失败,否则任何 ord/chr 调用都会误报。 + bi_synth: int = 0 + if string.strcmp(cf_bare, "ord") == 0: bi_synth = 1 + elif string.strcmp(cf_bare, "chr") == 0: bi_synth = 1 + elif string.strcmp(cf_bare, "min") == 0: bi_synth = 1 + elif string.strcmp(cf_bare, "max") == 0: bi_synth = 1 + elif string.strcmp(cf_bare, "abs") == 0: bi_synth = 1 # Dependency text.ll does not exist (module not yet translated): Phase A translates files # in order (main.vp before its imported mbuddytest.vp), so keep the default type without fail-fast. - if self._dep_text_ll_exists(pool, cf_sha1) != 0: self.Gen.TypeGenErr.fatal_error(node, f"Cannot obtain the cross-module function return type precisely (neither vpi nor text.ll hit); the default i32 will truncate pointers: {call_name}", "TypeError") + if bi_synth == 0 and self._dep_text_ll_exists(pool, cf_sha1) != 0: self.Gen.TypeGenErr.fatal_error(node, f"Cannot obtain the cross-module function return type precisely (neither vpi nor text.ll hit); the default i32 will truncate pointers: {call_name}", "TypeError") stdlib.free(cf_sha1) stdlib.free(cf_bare) + # 迟到的补参:call_name 此时已带定义模块 SHA1 前缀(权威键),补上早期因链式接收者 + # 退化而成查不到的缺省值;必须在 _ensure_method_declare 之前完成,否则 declare 会按 + # 少参的实参个数被建短(同一函数出现两种 arity 声明)。 + can = self._fill_defaults_late(pool, mod, builder, trans, cl, call_name, head, can) + # 静态实参个数校验(以定义模块 .vpi 签名为准,self 不计):declare 此时可能还没创建, + # 而 _ensure_method_declare 又会按 can 建短 declare —— 旧逻辑会把这个错误一路藏到 + # mlir-opt 的 "incorrect number of operands"。含关键字实参或可变参数时跳过。 + if self.Gen.CallGenLib._is_sha1_mangled_call(call_name) != 0 and (cl.keywords is None or len(cl.keywords) == 0): + ar_sha: str = pool.alloc(17) + if ar_sha is not None: + string.strncpy(ar_sha, call_name, 16) + ar_sha[16] = '\0' + ar_name: str = call_name + 17 + ar_n: int = VpiReader.LookupParamCountByName(pool, ar_sha, ar_name) + if ar_n >= 0 and int(can) != ar_n: + self.Gen.TypeGenErr.fatal_error(cl, f"call to '{ar_name}' passes {int(can)} argument(s) but the definition declares {ar_n} parameter(s) (self excluded)", "TypeError") # Bare C library function (string.memset -> @memset): promote args to the standard signature # (third arg i32 literal -> i64) before creating the declare, so call/declare/define widths match. head = self.Gen.CallGenLib._promote_clib_call_args(pool, builder, mod, call_name, head, can)