fix(tpv): 撤销 llc 输出重定向,回归继承父控制台并保留 GetLastError 与退出码诊断
病因:_run_cmd_wait 置 STARTF_USESTDHANDLES 后所有 llc 均无法启动(CreateProcessA 返回 0,GetLastError=87 ERROR_INVALID_PARAMETER),整条构建被阻断。已逐项核对生成的 IR 并排除:STARTUPINFOA 布局(偏移 80/88/96、size 104 正确)、si.cb=104、GEP 字段索引 11/15/16/17 正确、CreateFileA 的 access/share/disposition/flags 正确、dwFlags=256 正确、CreateProcessA 十个参数正确;静态证据无法再推进,故撤回重定向。 移除:_run_cmd_wait 的 log_path 形参、CreateFileA 打开 .llc.log、STARTF_USESTDHANDLES 与三个 hStd 句柄赋值、父侧 CloseHandle 守卫;连带删除失效链路 _log_captured_output、CMD_OUTPUT_MAX、LlcTask.LogPath 及其 init/free 与成功路径 DeleteFileA。进程创建回到继承父控制台(dwFlags 保持 memset 的 0),llc 输出直接外显。 保留诊断(用户要求):CreateProcessA 失败后立即取 GetLastError 并以 VLogger.warning 打印(worker 线程内不能用 error,它会 sys.exit);串行 compile_ll_to_obj 与并行 run_llc_parallel 失败时均打印 模块sha1、退出码、完整命令;LlcTask.ExitCode 保留。
This commit is contained in:
1 parent
05d5bfbf37
commit
c1859a111c
1 file changed
+17
-113
+17
-113
@@ -314,44 +314,6 @@ def write_ir_to_file(ir_buf: bytes, ir_len: sizet, output_dir: str, module_name:
|
||||
return 0
|
||||
|
||||
|
||||
# 外部命令(llc/opt)捕获输出打印上限(字节):超出即截断,避免一次失败刷爆日志
|
||||
CMD_OUTPUT_MAX: t.CDefine = 8192
|
||||
|
||||
|
||||
def _log_captured_output(log_path: str, module_sha1: str, cmd: str,
|
||||
exit_code: int, tag: str) -> None:
|
||||
"""读取外部命令(llc/opt)的输出重定向文件并打印到日志。
|
||||
|
||||
内容 = 该命令的 stdout|stderr(_run_cmd_wait 把二者合并重定向到同一文件)。
|
||||
拿不到输出(无文件 / 打开失败 / 为空)时明确说明,不伪造。打印后删除该文件。
|
||||
"""
|
||||
if log_path is None:
|
||||
VLogger.error(f"命令输出未捕获(无重定向文件): module={module_sha1} rc={exit_code} cmd={cmd}", tag)
|
||||
return
|
||||
f: fileio.File | t.CPtr = fileio.File(log_path, fileio.MODE.R)
|
||||
if f.closed:
|
||||
VLogger.error(f"命令输出文件打开失败(无捕获): module={module_sha1} rc={exit_code} path={log_path} cmd={cmd}", tag)
|
||||
w32.win32file.DeleteFileA(log_path)
|
||||
return
|
||||
sz: int64 = f.size()
|
||||
cap: sizet = CMD_OUTPUT_MAX + 1
|
||||
buf: bytes = _mbuddy.alloc(cap)
|
||||
if buf is None:
|
||||
f.close()
|
||||
VLogger.error(f"命令输出缓冲分配失败(无捕获): module={module_sha1} rc={exit_code} cmd={cmd}", tag)
|
||||
w32.win32file.DeleteFileA(log_path)
|
||||
return
|
||||
n: int64 = f.read_all(buf, cap)
|
||||
f.close()
|
||||
if n <= 0:
|
||||
VLogger.error(f"命令输出为空(stderr|stdout 均无内容): module={module_sha1} rc={exit_code} cmd={cmd}", tag)
|
||||
elif sz > CMD_OUTPUT_MAX:
|
||||
VLogger.error(f"命令输出(前 {CMD_OUTPUT_MAX} 字节 / 共 {sz} 字节,已截断)module={module_sha1} rc={exit_code}:\n{buf}\n命令: {cmd}", tag)
|
||||
else:
|
||||
VLogger.error(f"命令输出({sz} 字节)module={module_sha1} rc={exit_code}:\n{buf}\n命令: {cmd}", tag)
|
||||
w32.win32file.DeleteFileA(log_path)
|
||||
|
||||
|
||||
def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: str, cc_flags: str) -> int:
|
||||
"""调用 llc 将 .ll 编译为 .obj
|
||||
|
||||
@@ -425,22 +387,12 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s
|
||||
return 1
|
||||
viperlib.snprintf(cmd, cmd_len, "%s %s -o %s %s", cc_cmd, cc_flags, obj_path, compile_input)
|
||||
|
||||
# 运行 llc,捕获 stdout|stderr 到临时文件:失败时把输出 + 完整命令 + 退出码
|
||||
# 一并打印(此前用 stdlib.system 直连控制台,无法在日志里带出输出)。
|
||||
# _run_cmd_wait 用 CreateProcessA 直启目标程序(避开 cmd/c 在 asan 下挂起),
|
||||
# 输出重定向到文件 —— 文件不会被写满阻塞,故无管道死锁;退出码同步返回。
|
||||
log_path: str = _mbuddy.alloc(len(compile_input) + 16)
|
||||
if log_path is None:
|
||||
return 1
|
||||
viperlib.snprintf(log_path, len(compile_input) + 16, "%s.llc.log", compile_input)
|
||||
sys_ret: int = _run_cmd_wait(cmd, log_path)
|
||||
# 用 CreateProcessA 直启 llc(避开 cmd/c 在 asan 下挂起),子进程继承父控制台:
|
||||
# stdout|stderr 直接显示;失败时打印模块 sha1 + 退出码 + 完整命令(退出码同步返回)
|
||||
sys_ret: int = _run_cmd_wait(cmd)
|
||||
if sys_ret != 0:
|
||||
# 必须先打捕获输出再报错: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")
|
||||
VLogger.error(f"编译失败 (module={module_name}, exit={sys_ret}, cmd={cmd})", "LLC")
|
||||
return 1
|
||||
w32.win32file.DeleteFileA(log_path)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -451,7 +403,7 @@ 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;CreateProcessA 失败时加一条 VLogger.warning
|
||||
# GetLastError/CloseHandle;CreateProcessA 失败时加一条 VLogger.warning
|
||||
# 诊断,只走 CRT printf/控制台着色),不做任何池分配(MemBuddy 分配
|
||||
# 非线程安全)
|
||||
# - 依赖 CreateProcessA + Win32 句柄接口的多线程安全性
|
||||
@@ -466,9 +418,8 @@ class LlcTask:
|
||||
LlcCmd: bytes # llc 命令
|
||||
ModuleSha1: bytes # 模块 SHA1(报错用)
|
||||
Result: int # worker 写:0 成功,非 0 失败
|
||||
# 以下字段追加在结尾:既有字段偏移不变;LlcTask.__sizeof__() 随之增大,
|
||||
# ExitCode 追加在结尾:既有字段偏移不变;LlcTask.__sizeof__() 随之增大,
|
||||
# 所有构造/初始化点均按 __sizeof__() 算首址并对整条 memset,无需改偏移。
|
||||
LogPath: bytes # worker 输出重定向文件路径(stdlib.malloc)
|
||||
ExitCode: int # worker 写:失败命令退出码(成功为 0)
|
||||
|
||||
|
||||
@@ -488,12 +439,6 @@ def init_llc_task(task: LlcTask | t.CPtr, ir_path: str, output_dir: str,
|
||||
if task.ModuleSha1 is None:
|
||||
return 1
|
||||
string.strcpy(task.ModuleSha1, module_name)
|
||||
# 输出重定向文件路径:<ir_path>.llc.log(与 .ll 同目录,每任务唯一)
|
||||
lp_len: sizet = len(ir_path) + 16
|
||||
task.LogPath = stdlib.malloc(lp_len)
|
||||
if task.LogPath is None:
|
||||
return 1
|
||||
viperlib.snprintf(task.LogPath, lp_len, "%s.llc.log", ir_path)
|
||||
# llc 命令
|
||||
cmd_len: sizet = len(cc_cmd) + len(cc_flags) \
|
||||
+ len(obj_path) + len(ir_path) + 64
|
||||
@@ -532,9 +477,6 @@ def free_llc_task(task: LlcTask | t.CPtr):
|
||||
if task.ModuleSha1 is not None:
|
||||
stdlib.free(task.ModuleSha1)
|
||||
task.ModuleSha1 = None
|
||||
if task.LogPath is not None:
|
||||
stdlib.free(task.LogPath)
|
||||
task.LogPath = None
|
||||
|
||||
|
||||
def _phase2_write_cmd(cmd: str) -> None:
|
||||
@@ -561,17 +503,15 @@ def _phase2_write_cmd(cmd: str) -> None:
|
||||
f.close()
|
||||
|
||||
|
||||
def _run_cmd_wait(cmd: str, log_path: str = None) -> int:
|
||||
def _run_cmd_wait(cmd: str) -> int:
|
||||
"""CreateProcessA 同步执行命令并等待退出,返回退出码。
|
||||
|
||||
替代 stdlib.system:Windows asan 环境下 cmd /c 经 CreateProcess 受
|
||||
RTL allocator hook 干扰可能挂起(LLVM issue #111166 同类,ClassArrayTest
|
||||
llc 固定卡死根因);CreateProcessA 直接执行目标程序。
|
||||
|
||||
log_path 非 None 时把子进程 stdout|stderr 重定向到该文件(STARTF_USE-
|
||||
STDHANDLES,二者合并写同一文件),供失败时读取打印;为 None 时继承父控制台
|
||||
(输出直接显示,同 system 语义)。重定向只用 CreateFileA/CloseHandle,不做
|
||||
池分配,worker 线程安全;文件不会写满阻塞,无管道死锁。
|
||||
llc 固定卡死根因);CreateProcessA 直接执行目标程序并继承父进程控制台
|
||||
(stdout/stderr 直接显示,同 system 语义)。全 stdlib.malloc/栈分配,
|
||||
worker 线程安全。CreateProcessA 返回 0 时立即取 GetLastError 并以
|
||||
VLogger.warning 打印(外部命令未启动)。
|
||||
"""
|
||||
if cmd is None:
|
||||
return -1
|
||||
@@ -594,32 +534,8 @@ def _run_cmd_wait(cmd: str, log_path: str = None) -> int:
|
||||
sa.lpSecurityDescriptor = t.CPtr(0)
|
||||
sa.bInheritHandle = 1
|
||||
si.cb = win32process.STARTUPINFOA.__sizeof__()
|
||||
# 输出重定向:sa 可继承 → 文件句柄随 CreateProcessA 复制给子进程;打开失败
|
||||
# 则不设 STARTF_USESTDHANDLES,退回继承控制台(不伪造捕获,日志会明说)。
|
||||
# 初值用 NULL(None) 而非 INVALID_HANDLE_VALUE:后者是整数常量(-1),赋给指针
|
||||
# 类型会触发"整数隐式转指针已禁止";仓库既有惯例是只与它做比较(下文 != 判断,
|
||||
# 全仓 20+ 处同样写法)。NULL 与 INVALID 都不参与关闭,故关闭处需双重守卫。
|
||||
log_fh: w32.win32base.HANDLE = None
|
||||
if log_path is not None:
|
||||
# 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,
|
||||
w32.win32file.FILE_ATTRIBUTE_NORMAL,
|
||||
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 走文件
|
||||
# create_flags=0:子进程继承父控制台(输出直接显示,同 system 语义);
|
||||
# si.dwFlags 保持 memset 后的 0(不置 STARTF_USESTDHANDLES、不设 std 句柄)
|
||||
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)))
|
||||
@@ -629,12 +545,8 @@ def _run_cmd_wait(cmd: str, log_path: str = None) -> int:
|
||||
# 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")
|
||||
VLogger.warning(f"CreateProcessA 失败 (GetLastError={cp_err}) 外部命令未启动: cmd={cmd}", "LLC")
|
||||
stdlib.free(cmd_line)
|
||||
# 子进程已持有文件句柄副本,父侧立即关闭,避免句柄泄漏
|
||||
# 双重守卫:NULL(未重定向) 与 INVALID(打开失败) 都不关闭
|
||||
if log_fh is not None and log_fh != w32.win32base.INVALID_HANDLE_VALUE:
|
||||
w32.win32base.CloseHandle(log_fh)
|
||||
if ok == 0:
|
||||
return -1
|
||||
win32sync.WaitForSingleObject(pi.hProcess, w32.win32base.INFINITE)
|
||||
@@ -653,12 +565,12 @@ def _llc_task_worker(arg: t.CPtr) -> t.CPtr:
|
||||
if task is None:
|
||||
return t.CPtr(0)
|
||||
if task.OptCmd is not None:
|
||||
opt_rc: int = _run_cmd_wait(task.OptCmd, task.LogPath)
|
||||
opt_rc: int = _run_cmd_wait(task.OptCmd)
|
||||
if opt_rc != 0:
|
||||
task.ExitCode = opt_rc
|
||||
task.Result = 1
|
||||
return t.CPtr(0)
|
||||
llc_rc: int = _run_cmd_wait(task.LlcCmd, task.LogPath)
|
||||
llc_rc: int = _run_cmd_wait(task.LlcCmd)
|
||||
task.ExitCode = llc_rc
|
||||
if llc_rc != 0:
|
||||
task.Result = 1
|
||||
@@ -726,17 +638,9 @@ 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:
|
||||
# 失败:先读重定向文件打印 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")
|
||||
# 失败:打印模块 sha1 + 退出码 + 完整命令(.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")
|
||||
ret = 1
|
||||
else:
|
||||
# 成功:丢弃捕获文件(成功路径零额外输出)
|
||||
if tsk2 is not None and tsk2.LogPath is not None:
|
||||
w32.win32file.DeleteFileA(tsk2.LogPath)
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user