From 05d5bfbf371b9ca05d8d208244ec22f8f7fce56e Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 14 Sep 2026 22:14:45 +0800 Subject: [PATCH] =?UTF-8?q?fix(tpv):=20=E4=BF=AE=20CreateProcessA=20?= =?UTF-8?q?=E9=87=8D=E5=AE=9A=E5=90=91=E6=9C=AA=E5=90=AF=E5=8A=A8=E5=A4=96?= =?UTF-8?q?=E9=83=A8=E5=91=BD=E4=BB=A4=EF=BC=8C=E5=B9=B6=E6=8A=8A=E6=8D=95?= =?UTF-8?q?=E8=8E=B7=E8=BE=93=E5=87=BA=E7=A7=BB=E5=88=B0=20error=20?= =?UTF-8?q?=E4=B9=8B=E5=89=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主因:_run_cmd_wait 置 STARTF_USESTDHANDLES 时只设了 hStdOutput/hStdError,hStdInput 仍是 memset 后的 NULL(0);Windows 要求三个句柄都有效且可继承,故 CreateProcessA 直接返回 0(表现为 exit=4294967295 即 -1,且 .llc.log 恒空、一次也没有 llc 输出)。日志文件改以 GENERIC_WRITE|GENERIC_READ 打开并令 hStdInput 同指该文件(llc 不读 stdin),三态句柄均不泄漏;无重定向时不置 USESTDHANDLES、退回继承控制台的行为不变。 另在 CreateProcessA 失败处立即取 w32.win32base.GetLastError 并以 VLogger.warning 打印错误码+命令+重定向文件路径(warning 而非 error:error 会 sys.exit,而此处运行在 llc worker 线程内)。 同时修正一处使功能完全失效的顺序问题:_log_captured_output 原先排在 VLogger.error 之后,而 error 内部 sys.exit(1),故捕获到的 llc stderr|stdout 永远不会打印(这正是只看到一行 llc 编译失败、.llc.log 留空的原因)。串行 compile_ll_to_obj 与并行 run_llc_parallel 两处均改为先打印捕获输出、再报错。 --- App/lib/Phase/BuildPipeline.vp | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/App/lib/Phase/BuildPipeline.vp b/App/lib/Phase/BuildPipeline.vp index c8657fa..6cd38e9 100644 --- a/App/lib/Phase/BuildPipeline.vp +++ b/App/lib/Phase/BuildPipeline.vp @@ -435,8 +435,10 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s viperlib.snprintf(log_path, len(compile_input) + 16, "%s.llc.log", compile_input) sys_ret: int = _run_cmd_wait(cmd, log_path) if sys_ret != 0: - VLogger.error(f"编译失败 (module={module_name})", "LLC") + # 必须先打捕获输出再报错:VLogger.error 内部 sys.exit(1),其后的语句永不执行 + # (否则 llc 的 stderr|stdout 永远打印不出来) _log_captured_output(log_path, module_name, cmd, sys_ret, "LLC") + VLogger.error(f"编译失败 (module={module_name})", "LLC") return 1 w32.win32file.DeleteFileA(log_path) return 0 @@ -449,7 +451,9 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s # - LlcTask 及命令串一律 stdlib.malloc:主池 mb 会被 reset_to(mark2) # 回收,线程运行期间字符串悬空 → 概率崩溃 # - worker 只调 _run_cmd_wait(内部仅 stdlib.malloc/CreateProcessA/ -# CreateFileA/CloseHandle),不做任何池分配(MemBuddy 分配非线程安全) +# CreateFileA/CloseHandle;CreateProcessA 失败时加一条 VLogger.warning +# 诊断,只走 CRT printf/控制台着色),不做任何池分配(MemBuddy 分配 +# 非线程安全) # - 依赖 CreateProcessA + Win32 句柄接口的多线程安全性 # ============================================================ # llc 并行最大线程数(参考 TPC Phase2Translator n_workers=os.cpu_count(),本机 16 核) @@ -597,7 +601,10 @@ def _run_cmd_wait(cmd: str, log_path: str = None) -> int: # 全仓 20+ 处同样写法)。NULL 与 INVALID 都不参与关闭,故关闭处需双重守卫。 log_fh: w32.win32base.HANDLE = None if log_path is not None: - log_fh = w32.win32file.CreateFileA(log_path, w32.win32file.GENERIC_WRITE, + # GENERIC_READ 一并打开:见下方 hStdInput 说明(置 USESTDHANDLES 后三个 + # std 句柄都要有效;llc 不读 stdin,读到空文件无副作用) + log_fh = w32.win32file.CreateFileA(log_path, + w32.win32file.GENERIC_WRITE | w32.win32file.GENERIC_READ, w32.win32file.FILE_SHARE_READ, t.CPtr(c.Addr(sa)), w32.win32file.CREATE_ALWAYS, @@ -605,12 +612,24 @@ def _run_cmd_wait(cmd: str, log_path: str = None) -> int: t.CPtr(0)) if log_fh != w32.win32base.INVALID_HANDLE_VALUE: si.dwFlags = win32process.STARTF_USESTDHANDLES + # 三个 std 句柄必须都是有效句柄:Windows 对 hStdInput 同样做复制/校验, + # hStdInput 保持 memset 后的 NULL(0) 会让 CreateProcessA 直接失败返回 0 + # → 外部命令根本没被启动(表现为 exit=-1 且捕获文件恒为空)。llc 不读 + # stdin,故 stdin 也指向该日志文件(已带 sa.bInheritHandle=1,可继承)。 + si.hStdInput = log_fh si.hStdOutput = log_fh si.hStdError = log_fh # create_flags=0:无重定向时子进程继承父控制台;有重定向时 stdio 走文件 ok: int = win32process.CreateProcessA( t.CPtr(0), cmd_line, t.CPtr(0), t.CPtr(0), 1, 0, t.CPtr(0), t.CPtr(0), t.CPtr(c.Addr(si)), t.CPtr(c.Addr(pi))) + # CreateProcessA 失败诊断:GetLastError 必须在返回后立即取(此后 stdlib.free/ + # CloseHandle/SetConsoleTextAttribute 等都会覆盖它)。用 warning 不用 error—— + # VLogger.error 会 sys.exit(1),而本函数可能在 worker 线程里执行;warning 只走 + # CRT printf + 控制台着色,不做 MemBuddy 池分配,满足 worker 线程安全约束。 + if ok == 0: + cp_err: uint32 = w32.win32base.GetLastError() + VLogger.warning(f"CreateProcessA 失败 (GetLastError={cp_err}) 外部命令未启动: 重定向文件={(log_path if log_path is not None else "无")} cmd={cmd}", "LLC") stdlib.free(cmd_line) # 子进程已持有文件句柄副本,父侧立即关闭,避免句柄泄漏 # 双重守卫:NULL(未重定向) 与 INVALID(打开失败) 都不关闭 @@ -707,11 +726,12 @@ def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int: tsk2: LlcTask | t.CPtr = (LlcTask | t.CPtr)(t.CVoid( uint64(tasks) + sizet(batch_start + j) * slot_size, t.CPtr)) if tsk2 is not None and tsk2.Result != 0: - # 失败:打印模块 sha1 + 退出码 + 完整命令,再读重定向文件打印 - # llc 的 stderr|stdout(.ll 路径即命令末参数) - VLogger.error(f"llc 编译失败: {(tsk2.ModuleSha1 if tsk2.ModuleSha1 is not None else "?")} (exit={tsk2.ExitCode} cmd={(tsk2.LlcCmd if tsk2.LlcCmd is not None else "?")})", "LLC") + # 失败:先读重定向文件打印 llc 的 stderr|stdout,再打模块 sha1 + 退出码 + # + 完整命令(.ll 路径即命令末参数)。顺序不可颠倒:VLogger.error 内部 + # sys.exit(1),其后的语句永不执行。 if tsk2.ModuleSha1 is not None and tsk2.LlcCmd is not None: _log_captured_output(tsk2.LogPath, tsk2.ModuleSha1, tsk2.LlcCmd, tsk2.ExitCode, "LLC") + VLogger.error(f"llc 编译失败: {(tsk2.ModuleSha1 if tsk2.ModuleSha1 is not None else "?")} (exit={tsk2.ExitCode} cmd={(tsk2.LlcCmd if tsk2.LlcCmd is not None else "?")})", "LLC") ret = 1 else: # 成功:丢弃捕获文件(成功路径零额外输出)