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: # 成功:丢弃捕获文件(成功路径零额外输出)