fix: 泛型返回类型短路/数组迭代/非工作区崩溃

- CallGenMixin: 本模块 mangled 调用从当前 IR 取返回类型, 不查自己的 text.ll(未生成误告警)
- HandlesFor: Ptr([N x T]) 数组退化指针 load 后走数组迭代
- Config: 配置字段默认 None/0, 避免垃圾指针通过 is not None 检查
- main: 无 project 配置且无 --src 时 fail-fast 退出(非工作区崩溃)
This commit is contained in:
TermiNexus committed 2026-08-11 23:14:27 +08:00
1 parent e7b0fecb99
commit a649e4807d
4 files changed
+87 -53

No files matched your search

+20 -17
View File
@@ -19,23 +19,26 @@ import w32.fileio as fileio
_mbuddy: memhub.MemManager | t.CPtr
# 配置值(从 project.vpj 加载)
SourceDir: str
BuildDir: str
TempDir: str # = {BuildDir}/temp(自动计算)
OutputDir: str # = {BuildDir}/output(自动计算)
ProjectName: str
ProjectVersion: str
CompilerCmd: str
CompilerFlags: str # 编译器参数(空格分隔,从 compiler.flags 数组拼接)
LinkerCmd: str
LinkerFlags: str # 链接器参数(空格分隔,从 linker.flags 数组拼接)
LinkerOutput: str
TargetTriple: str
TargetDataLayout: str
Sha1SliceLevel: t.CInt # SHA1 切片层数(目录分散等级)
StrictMode: t.CInt
IncludesDir: str # includes 目录路径(从 includes 数组首元素,兼容单目录场景)
IncludesDirs: str # 全部 includes 目录(空格分隔,多 includes 目录支持)
# 默认 None/0:未加载 project.vpj 时字段为 null,调用方 is not None
# 检查才能正确兜底(否则垃圾指针通过检查 → 用非法路径 → 崩溃,
# 尤其非工作区执行无配置时)。
SourceDir: str = None
BuildDir: str = None
TempDir: str = None # = {BuildDir}/temp(自动计算)
OutputDir: str = None # = {BuildDir}/output(自动计算)
ProjectName: str = None
ProjectVersion: str = None
CompilerCmd: str = None
CompilerFlags: str = None # 编译器参数(空格分隔,从 compiler.flags 数组拼接)
LinkerCmd: str = None
LinkerFlags: str = None # 链接器参数(空格分隔,从 linker.flags 数组拼接)
LinkerOutput: str = None
TargetTriple: str = None
TargetDataLayout: str = None
Sha1SliceLevel: t.CInt = 0 # SHA1 切片层数(目录分散等级)
StrictMode: t.CInt = 0
IncludesDir: str = None # includes 目录路径(从 includes 数组首元素,兼容单目录场景)
IncludesDirs: str = None # 全部 includes 目录(空格分隔,多 includes 目录支持)
def Load_project_config(path: str) -> int:
+50 -36
View File
@@ -6291,45 +6291,59 @@ class CallGenMixin:
string.strncpy(cf_sha1, call_name, 16)
cf_sha1[16] = '\0'
string.strcpy(cf_bare, cf_last + 1)
cf_ret: llvmlite.LLVMType | t.CPtr = self._LookupMethodRetTyFromTextLL(
pool, cf_sha1, None, cf_bare, 1)
if cf_ret is not None:
call_ret_ty = cf_ret
# 本模块自身函数(泛型特化等):从当前 IR 已声明的函数
# 取返回类型;不走跨模块 text.ll 查找——自己的 text.ll
# 在 Phase A 翻译中尚未生成,直接查会误报"依赖未生成"
# 并保持默认类型(GenericTest 泛型 add/combine 等警告
# 根因)。未命中(特化尚未声明)保持默认推断。
is_local_mod: int = 0
if trans is not None and trans.ModuleSha1 is not None:
if string.strncmp(cf_sha1, trans.ModuleSha1, 16) == 0:
is_local_mod = 1
if is_local_mod != 0:
local_fn: llvmlite.Function | t.CPtr = self.find_func_in_module(mod, call_name)
if local_fn is not None and local_fn.RetTy is not None:
call_ret_ty = local_fn.RetTy
else:
cf_vpi: llvmlite.LLVMType | t.CPtr = self._lookup_method_ret_ty_via_vpi(
pool, cf_sha1, None, cf_bare)
if cf_vpi is not None:
call_ret_ty = cf_vpi
cf_ret: llvmlite.LLVMType | t.CPtr = self._LookupMethodRetTyFromTextLL(
pool, cf_sha1, None, cf_bare, 1)
if cf_ret is not None:
call_ret_ty = cf_ret
else:
# 区分两种失败场景:
# 1. 依赖模块 text.ll 存在但函数未命中 → 真错误,fail-fast
# 2. 依赖模块 text.ll 不存在(模块尚未翻译)→ Phase A 按
# 文件顺序翻译,main.vp 先于其 import 的 mbuddytest.vp
# (同项目多源文件)。此时标记 trans._deps_pending=1,
# 保持白名单类型继续,Phase A 多遍循环下遍重译——
# 届时依赖已生成 text.ll,可精确解析(治本:依赖先翻译)。
if self._dep_text_ll_exists(pool, cf_sha1) != 0:
cf_err: t.CChar | t.CPtr = VLogger.fmt_buf()
if cf_err is not None:
viperlib.snprintf(
cf_err, 1024,
"跨模块函数 '%s' 返回类型无法精确获取(text.ll/vpi 均未命中),"
"默认 i32 会截断指针/i64,请检查依赖模块是否已编译/注册", call_name)
VLogger.error(cf_err, "HandlesExprCall")
HandlesType.fatal_error(
node,
f"跨模块函数返回类型无法精确获取(text.ll/vpi 均未命中),默认 i32 会截断指针: {call_name}",
"TypeError")
cf_vpi: llvmlite.LLVMType | t.CPtr = self._lookup_method_ret_ty_via_vpi(
pool, cf_sha1, None, cf_bare)
if cf_vpi is not None:
call_ret_ty = cf_vpi
else:
if trans is not None:
trans._deps_pending = 1
cf_dbg: t.CChar | t.CPtr = VLogger.fmt_buf()
if cf_dbg is not None:
viperlib.snprintf(
cf_dbg, 1024,
"跨模块函数 '%s' 依赖模块 text.ll 尚未生成(Phase A 顺序),"
"标记重试,本遍保持默认类型", call_name)
VLogger.warning(cf_dbg, "HandlesExprCall")
# 区分两种失败场景:
# 1. 依赖模块 text.ll 存在但函数未命中 → 真错误,fail-fast
# 2. 依赖模块 text.ll 不存在(模块尚未翻译)→ Phase A 按
# 文件顺序翻译,main.vp 先于其 import 的 mbuddytest.vp
# (同项目多源文件)。此时标记 trans._deps_pending=1,
# 保持白名单类型继续,Phase A 多遍循环下遍重译——
# 届时依赖已生成 text.ll,可精确解析(治本:依赖先翻译)。
if self._dep_text_ll_exists(pool, cf_sha1) != 0:
cf_err: t.CChar | t.CPtr = VLogger.fmt_buf()
if cf_err is not None:
viperlib.snprintf(
cf_err, 1024,
"跨模块函数 '%s' 返回类型无法精确获取(text.ll/vpi 均未命中),"
"默认 i32 会截断指针/i64,请检查依赖模块是否已编译/注册", call_name)
VLogger.error(cf_err, "HandlesExprCall")
HandlesType.fatal_error(
node,
f"跨模块函数返回类型无法精确获取(text.ll/vpi 均未命中),默认 i32 会截断指针: {call_name}",
"TypeError")
else:
if trans is not None:
trans._deps_pending = 1
cf_dbg: t.CChar | t.CPtr = VLogger.fmt_buf()
if cf_dbg is not None:
viperlib.snprintf(
cf_dbg, 1024,
"跨模块函数 '%s' 依赖模块 text.ll 尚未生成(Phase A 顺序),"
"标记重试,本遍保持默认类型", call_name)
VLogger.warning(cf_dbg, "HandlesExprCall")
stdlib.free(cf_sha1)
stdlib.free(cf_bare)
# 跨模块调用:创建 declare 声明,避免 llc 报 undefined value
+7
View File
@@ -396,6 +396,13 @@ class ForHandle(HandlesBase.Mixin):
if elem_ty is None:
HandlesType.fatal_error(for_node, "指针迭代: 无法获取元素类型")
# 元素类型是数组(Ptr([N x T]),如 t.CArray 变量退化指针):
# load 出数组值后走数组迭代(按长度,非 null 终止)
if HandlesExpr.is_array_type(elem_ty) != 0:
arr_v2: llvmlite.Value | t.CPtr = llvmlite.build_load(builder, elem_ty, ptr_val)
if arr_v2 is not None:
return self._handle_array_iter(for_node, var_name, arr_v2)
# 元素类型必须是整数(用于 icmp eq 0 检查 null 终止符)
elem_bits: int = HandlesExpr.get_llvm_type_bits(elem_ty)
if elem_bits == 0:
+10
View File
@@ -369,6 +369,16 @@ def main() -> int:
# AST 解析:如果指定了 --src 或 --project,读取文件并解析为 AST 树
src_path: str = args.get_str("src")
# 无 project 配置且未指定 --src:无法构建,fail-fast 退出。
# (非工作区执行时 CWD 无 project.vpj,proj_loaded=0;若继续执行,
# 未初始化的 Config 字段可能为垃圾指针 → is not None 检查通过 →
# 用非法路径 → 崩溃且 ASan 堆栈来不及输出)
if proj_loaded == 0 and src_path is None:
if log is not None:
log.error("未找到 project.vpj(当前目录/上级目录)且未指定 --src,无法构建", "config")
argparse.release(args)
return 1
# === 多文件项目模式 ===
# 当 --project 指定但 --src 未指定时,扫描 source_dir 下所有 .vp/.py 文件编译
if src_path is None and project is not None and Config.SourceDir is not None: