From 373a20c3e5faa3e4f041c18d2acbfedbf153acae Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 26 Sep 2026 19:27:23 +0800 Subject: [PATCH] =?UTF-8?q?perf(prof,gen):=20=E7=BC=96=E8=AF=91=E7=83=AD?= =?UTF-8?q?=E7=82=B9=E4=BC=98=E5=8C=96=20+=20=E5=86=85=E5=AD=98=E6=B1=A0?= =?UTF-8?q?=20OOM=20=E8=AF=8A=E6=96=AD=E6=8E=A5=E7=BA=BF=20+=20REnum=20?= =?UTF-8?q?=E6=8C=87=E9=92=88=E8=BD=BD=E8=8D=B7=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CallGenBase: 模块返回类型文本表改单趟登记+查找,find_func_in_module 合并为单趟链表遍历(PROF 热点) BuildPipeline: ensure_dir 增加快速路径(已存在目录直接返回) AssignGenMixin/Translator: alloc 失败打印 Pool/PermPool 统计与阶段上下文(SetAllocCtx) CallGenCtor: REnum 指针载荷补显式 build_ptrtoint 再 coerce/store;新增 DebugInfoGen.vp 其余为历史累积改动一并入库 --- App/lib/Phase/BuildPipeline.vp | 1124 +++++++++++++++++++- App/lib/Phase/Phase1/Phase1Main.vp | 13 + App/lib/Phase/Phase2/__init__.vp | 4 + App/lib/Stub/StubGen/VpiReader.vp | 11 +- App/lib/Stub/StubMerger/StubMergerBuild.vp | 38 + App/lib/Stub/StubMerger/StubMergerStore.vp | 13 +- App/lib/core/Gen/AssignGenMixin.vp | 11 + App/lib/core/Gen/Call/CallGenBase.vp | 157 ++- App/lib/core/Gen/Call/CallGenCtor.vp | 12 + App/lib/core/Gen/Class/ClassGenEnum.vp | 24 +- App/lib/core/Gen/Class/ClassGenVTable.vp | 9 + App/lib/core/Gen/DebugInfoGen.vp | 443 ++++++++ App/lib/core/Gen/Expr/ExprGenBase.vp | 25 +- App/lib/core/Gen/Expr/ExprGenName.vp | 32 +- App/lib/core/Gen/Func/FuncGenDef.vp | 10 + App/lib/core/Gen/Func/FuncGenMixin.vp | 13 + App/lib/core/Gen/Stmt/StmtGenCifMixin.vp | 8 + App/lib/core/Gen/Stmt/StmtGenMixin.vp | 7 + App/lib/core/Gen/Translator.vp | 146 ++- App/main.vp | 49 +- 20 files changed, 1984 insertions(+), 165 deletions(-) create mode 100644 App/lib/core/Gen/DebugInfoGen.vp diff --git a/App/lib/Phase/BuildPipeline.vp b/App/lib/Phase/BuildPipeline.vp index 24a466d..ec6fe0e 100644 --- a/App/lib/Phase/BuildPipeline.vp +++ b/App/lib/Phase/BuildPipeline.vp @@ -17,6 +17,7 @@ import viperlib import ast import lib.core.VLogger as VLogger import lib.core.Gen.Translator as HandlesTranslator +import lib.core.Gen.DebugInfoGen as DebugInfoGen import lib.Stub.StubMerger as StubMerger import lib.Stub.StubGen.Converter as StubConverter import lib.Projectrans.Config as Config @@ -40,6 +41,11 @@ _mbuddy: memhub.MemBuddy* # project.vpj 的 linker.static_libs / dynamic_libs 显式声明(TPV 不注入) _asan_mode: bool = 0 +# Traceback 模式(--traceback):强制编入 includes/traceback.vp 并注入启动期 +# Install() 构造器;llc 保留帧指针(-frame-pointer=all);链接 dbghelp; +# 链接成功后导出 .stackmap 地址→源行旁文件。 +_traceback_mode: bool = 0 + # --phase2command xxx.bat:不执行 llc/链接命令,只把 Phase B/B+/C 的 # llc(含 ASan opt)与 clang 链接命令写入指定 .bat 路径(由用户手动执行)。 # None = 正常执行(默认)。 @@ -53,6 +59,54 @@ SRC_BUF_SIZE: t.CDefine = 1048576 # 2^20 = 1MB # .vpi 存根缓冲区大小(256KB,与 Phase1 一致) PYI_BUF_SIZE: sizet = 262144 +# ============================================================ +# _llc_frame_ptr_flag - llc extra flag for --traceback +# +# Returns " -frame-pointer=all" (leading space so it concatenates into the llc +# flag sequence) while --traceback is on; "" otherwise, keeping the llc command +# byte-identical to the pre-switch behaviour. +# ============================================================ +def _llc_frame_ptr_flag() -> str: + if _traceback_mode != 0: + return " -frame-pointer=all" + return "" + + +# ============================================================ +# _append_traceback_dbghelp - append -ldbghelp for --traceback +# +# traceback.vp's main walk path uses StackWalk64 / SymInitialize / +# SymFunctionTableAccess64 / SymGetModuleBase64 (all in dbghelp); without this +# flag the link fails with undefined symbols. Libraries must follow the objects, +# so this is appended to linker_flags (not prepended). +# ============================================================ +def _append_traceback_dbghelp(linker_flags: str) -> str: + if _traceback_mode == 0 or linker_flags is None: + return linker_flags + need: sizet = string.strlen(linker_flags) + 16 + buf: str = _mbuddy.alloc(need) + if buf is None: + VLogger.error("--traceback: -ldbghelp 链接参数缓冲分配失败,终止编译", "TRACEBACK") + return linker_flags + viperlib.snprintf(buf, need, "%s -ldbghelp", linker_flags) + return buf + + +# ============================================================ +# _tb_basename_is_traceback - is this source file includes/traceback.vp +# ============================================================ +def _tb_basename_is_traceback(file_path: str) -> int: + if file_path is None: + return 0 + flen: sizet = string.strlen(file_path) + if flen < 12: + return 0 + if flen > 12: + prev: char = file_path[flen - 13] + if prev != '/' and prev != '\\': + return 0 + return 1 if string.strcmp(file_path + (flen - 12), "traceback.vp") == 0 else 0 + # ============================================================ # TranslateFileGetTrans - 翻译单个文件,返回 Translator 对象(供调用者 dump_ir) # @@ -153,6 +207,10 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, # P6: 注入语句/赋值/变量/导入/翻译驱动模块的单例 # 设置当前文件名(供报错使用) GenApi.set_current_file_type(file_path) + # --traceback: mirror the switch (BuildPipeline._traceback_mode) into the + # Gen-layer DWARF emitter. 0 => every DI entry point is a no-op and the IR + # stays byte-identical to the pre-DWARF output. + DebugInfoGen.SetEnabled(_traceback_mode) # 同步到 HandlesImports(import 时 SHA1 检查报错需要当前文件名,循环依赖无法直接读 HandlesType) GenApi.set_current_file_imports(file_path) # 清空调用栈(每个文件翻译开始前重置) @@ -174,6 +232,12 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, if ret != 0: VLogger.error(f"文件翻译失败: {file_path}", "Translate") return None + # --traceback: when this file is includes/traceback.vp, inject the startup + # Install() ctor into its IR (before the caller dumps stub/text.ll). The + # injector lives in the Gen layer, which already depends on llvmlite, so + # BuildPipeline gains no cross-module llvmlite symbol dependency. + if _traceback_mode != 0 and _tb_basename_is_traceback(file_path) != 0: + HandlesTranslator.InjectTracebackInstallCtor(tr) return tr @@ -205,6 +269,13 @@ def ensure_dir(path: str) -> int: path_len: sizet = len(path) if path_len == 0: return 0 + # 热点快速路径(PROF:ensure_dir 占 4.5%,22256 次调用每次逐级 CreateDirectoryA): + # 末级目录已存在则直接成功返回,省去 1 次分配 + N+1 次 CreateDirectoryA 系统调用。 + # 语义不变:原实现把"目录已存在"(CreateDirectoryA 失败)也视为成功。 + attr: ULONG = w32.win32file.GetFileAttributesA(path) + if attr != 0xFFFFFFFF and (attr & w32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: + return 0 + # 复制路径到可写缓冲区(逐级截断用) buf: bytes = _mbuddy.alloc(path_len + 1) if buf is None: return 1 @@ -332,11 +403,12 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s "opt -passes=forceattrs,asan -force-attribute=sanitize_address -asan-globals=false -o %s %s", p2_asan, ir_path) compile_input = p2_asan - p2_clen: sizet = len(cc_cmd) + len(cc_flags) + len(obj_path) + len(compile_input) + 64 + llc_fp: str = _llc_frame_ptr_flag() + p2_clen: sizet = len(cc_cmd) + len(cc_flags) + len(llc_fp) + len(obj_path) + len(compile_input) + 64 p2_cmd: bytes = _mbuddy.alloc(p2_clen) if p2_cmd is not None: - viperlib.snprintf(p2_cmd, p2_clen, "%s %s -o %s %s", - cc_cmd, cc_flags, obj_path, compile_input) + viperlib.snprintf(p2_cmd, p2_clen, "%s %s%s -o %s %s", + cc_cmd, cc_flags, llc_fp, obj_path, compile_input) if p2_opt_cmd is not None: _phase2_write_cmd(p2_opt_cmd) if p2_cmd is not None: @@ -361,11 +433,13 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s compile_input = asan_path # 构造命令: llc -filetype=obj -o {obj_path} ir_path - cmd_len: sizet = len(cc_cmd) + len(cc_flags) + len(obj_path) + len(compile_input) + 64 + llc_fp2: str = _llc_frame_ptr_flag() + cmd_len: sizet = len(cc_cmd) + len(cc_flags) + len(llc_fp2) + len(obj_path) + len(compile_input) + 64 cmd: bytes = _mbuddy.alloc(cmd_len) if cmd is None: return 1 - viperlib.snprintf(cmd, cmd_len, "%s %s -o %s %s", cc_cmd, cc_flags, obj_path, compile_input) + viperlib.snprintf(cmd, cmd_len, "%s %s%s -o %s %s", + cc_cmd, cc_flags, llc_fp2, obj_path, compile_input) # 用 CreateProcessA 直启 llc(避开 cmd/c 在 asan 下挂起),子进程继承父控制台: # stdout|stderr 直接显示;失败时打印模块 sha1 + 退出码 + 完整命令(退出码同步返回) @@ -435,13 +509,13 @@ def init_llc_task(task: LlcTask | t.CPtr, ir_path: str, output_dir: str, asan_path, ir_path) compile_input = asan_path # llc 命令(输入 = compile_input:非 ASan 为 .ll,ASan 为 .asan.ll) - cmd_len: sizet = len(cc_cmd) + len(cc_flags) \ - + len(obj_path) + len(compile_input) + 64 + llc_fp3: str = _llc_frame_ptr_flag() + cmd_len: sizet = len(cc_cmd) + len(cc_flags) + len(llc_fp3) + len(obj_path) + len(compile_input) + 64 task.LlcCmd = stdlib.malloc(cmd_len) if task.LlcCmd is None: return 1 - viperlib.snprintf(task.LlcCmd, cmd_len, "%s %s -o %s %s", - cc_cmd, cc_flags, obj_path, compile_input) + viperlib.snprintf(task.LlcCmd, cmd_len, "%s %s%s -o %s %s", + cc_cmd, cc_flags, llc_fp3, obj_path, compile_input) return 0 @@ -778,6 +852,9 @@ def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_f viperlib.snprintf(implib_path, implib_len, "%s/%s.dll.a", output_dir, linker_output) linker_flags = _append_host_export_implib(linker_flags, implib_path) + # --traceback: link dbghelp (after the host-export rewrite, so both survive) + linker_flags = _append_traceback_dbghelp(linker_flags) + # 收集 includes.binary 的 .obj 文件路径 EXTRA_BUF_SIZE: sizet = 32768 extra_objs: bytes = _mbuddy.alloc(EXTRA_BUF_SIZE) @@ -1039,6 +1116,8 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, if implib_path2 is not None: viperlib.snprintf(implib_path2, implib_len2, "%s.dll.a", linker_output) linker_flags = _append_host_export_implib(linker_flags, implib_path2) + # --traceback: link dbghelp (after the host-export rewrite) + linker_flags = _append_traceback_dbghelp(linker_flags) use_flags: str = linker_flags static_libs2: str = linker_static_libs if static_libs2 is None: @@ -1099,6 +1178,1033 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, return 0 +# ============================================================ +# --traceback: .stackmap sidecar writer (address -> source line) +# +# Ported from TPC Phase2Translator._WriteStackMap. Runs after a successful +# link: llvm-dwarfdump parses the final PE's .debug_line/.debug_info, every +# address is normalised to a runtime RVA (addr - ImageBase) and the result is +# written as .stackmap for the runtime traceback.vp handler. +# +# Layout (all little-endian): +# header 20B = "TPCS" | u32 version=1 | u32 entry_count | u32 str_count | u32 str_bytes +# entries = entry_count * 20B, strictly ascending rva: +# u64 rva | u32 file_off | u32 line | u32 func_off +# pool = str_bytes UTF-8 bytes, '\0'-terminated strings, offset 0 is an +# empty-string sentinel, func_off==0 means "no function name" +# +# Every failure aborts the build (VLogger.error exits); there is no fallback. +# ============================================================ + +# row record: u64 rva | u64 file_path | u32 line | u32 pad +TB_ROW_SIZE: t.CDefine = 24 +TB_ROW_OFF_FILE: t.CDefine = 8 +TB_ROW_OFF_LINE: t.CDefine = 16 +# func record: u64 low_rva | u64 high_rva | u64 name +TB_FUNC_SIZE: t.CDefine = 24 +# dir record: u32 idx | u32 pad | u64 name +TB_DIR_SIZE: t.CDefine = 16 +# file record: u32 idx | u32 dir_index | u64 name | u64 resolved_path +TB_FILE_SIZE: t.CDefine = 32 +# initial capacities for the (growable) row / func tables +TB_ROW_CAP0: t.CDefine = 65536 +TB_FUNC_CAP0: t.CDefine = 65536 +# fixed per-CU table capacities (exceeding either aborts the build) +TB_DIR_CAP: t.CDefine = 4096 +TB_FILE_CAP: t.CDefine = 65536 + + +class TbPeLayout: + Ok: int + ImageBase: uint64 + SizeOfImage: uint64 + TextRva: uint64 + + def __new__(self) -> t.CPtr: + return t.CPtr(_mbuddy.alloc(TbPeLayout.__sizeof__())) + + def __init__(self): + self.Ok = 0 + self.ImageBase = 0 + self.SizeOfImage = 0 + self.TextRva = 0 + + +class TbDump: + RowBuf: bytes + RowCap: int + RowCount: int + RawRowCount: int + FuncBuf: bytes + FuncCap: int + FuncCount: int + + def __new__(self) -> t.CPtr: + return t.CPtr(_mbuddy.alloc(TbDump.__sizeof__())) + + def __init__(self): + self.RowBuf = None + self.RowCap = 0 + self.RowCount = 0 + self.RawRowCount = 0 + self.FuncBuf = None + self.FuncCap = 0 + self.FuncCount = 0 + + +class TbPool: + Pool: bytes + PoolLen: int + Offs: bytes + OffCount: int + + def __new__(self) -> t.CPtr: + return t.CPtr(_mbuddy.alloc(TbPool.__sizeof__())) + + def __init__(self): + self.Pool = None + self.PoolLen = 0 + self.Offs = None + self.OffCount = 0 + + +# ---- little-endian byte helpers (no struct.pack in Viper) ---- +def _tb_u8(buf: bytes, off: sizet) -> uint32: + return uint32(int(buf[off]) & 0xFF) + + +def _tb_rd_u16(buf: bytes, off: sizet) -> uint32: + return _tb_u8(buf, off) | (_tb_u8(buf, off + 1) << 8) + + +def _tb_rd_u32(buf: bytes, off: sizet) -> uint32: + b0: uint32 = _tb_u8(buf, off) + b1: uint32 = _tb_u8(buf, off + 1) + b2: uint32 = _tb_u8(buf, off + 2) + b3: uint32 = _tb_u8(buf, off + 3) + return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) + + +def _tb_rd_u64(buf: bytes, off: sizet) -> uint64: + lo: uint64 = uint64(_tb_rd_u32(buf, off)) + hi: uint64 = uint64(_tb_rd_u32(buf, off + 4)) + return lo | (hi << 32) + + +def _tb_wr_u32(buf: bytes, off: sizet, v: uint64): + buf[off] = char(int(v & 0xFF)) + buf[off + 1] = char(int((v >> 8) & 0xFF)) + buf[off + 2] = char(int((v >> 16) & 0xFF)) + buf[off + 3] = char(int((v >> 24) & 0xFF)) + + +def _tb_wr_u64(buf: bytes, off: sizet, v: uint64): + _tb_wr_u32(buf, off, v & 0xFFFFFFFF) + _tb_wr_u32(buf, off + 4, (v >> 32) & 0xFFFFFFFF) + + +# ---- tiny string helpers (no re module) ---- +def _tb_cc(s: str, i: sizet) -> int: + """Byte value at s[i] as 0..255 (chars in DWARF text are ASCII).""" + if s is None: + return 0 + return int(s[i]) & 0xFF + + +def _tb_contains(s: str, sub: str) -> int: + if s is None or sub is None: + return 0 + return 1 if string.strstr(s, sub) is not None else 0 + + +def _tb_startswith(s: str, pfx: str) -> int: + if s is None or pfx is None: + return 0 + pl: sizet = string.strlen(pfx) + if string.strlen(s) < pl: + return 0 + return 1 if string.strncmp(s, pfx, pl) == 0 else 0 + + +def _tb_ltrim(s: str) -> str: + if s is None: + return None + i: sizet = 0 + while _tb_cc(s, i) == 32 or _tb_cc(s, i) == 9: + i += 1 + return s + i + + +def _tb_len_trimmed(s: str) -> sizet: + if s is None: + return 0 + n: sizet = string.strlen(s) + while n > 0: + c: int = _tb_cc(s, n - 1) + if c == 32 or c == 9 or c == 13: + n -= 1 + else: + break + return n + + +def _tb_dup_span(mb: memhub.MemBuddy*, src: str, n: sizet) -> str: + """Copy src[0..n) into a fresh NUL-terminated string.""" + if src is None: + return None + out: str = mb.alloc(n + 1) + if out is None: + return None + string.strncpy(out, src, n) + out[n] = '\0' + return out + + +def _tb_unquote(mb: memhub.MemBuddy*, v: str) -> str: + """Trim trailing spaces and strip one pair of surrounding quotes (TPC _Unquote).""" + if v is None: + return None + n: sizet = _tb_len_trimmed(v) + if n >= 2 and _tb_cc(v, 0) == 34 and _tb_cc(v, n - 1) == 34: + return _tb_dup_span(mb, v + 1, n - 2) + return _tb_dup_span(mb, v, n) + + +def _tb_is_abs_path(s: str) -> int: + if s is None: + return 0 + c0: int = _tb_cc(s, 0) + if c0 == 0: + return 0 + if c0 == 47 or c0 == 92: + return 1 + if _tb_cc(s, 1) == 58: + return 1 + return 0 + + +def _tb_join(mb: memhub.MemBuddy*, base: str, name: str) -> str: + """Join base and name with a separator unless base already ends in one.""" + if base is None or string.strlen(base) == 0: + return name + if name is None: + return base + bl: sizet = string.strlen(base) + nl: sizet = string.strlen(name) + buf: str = mb.alloc(bl + nl + 2) + if buf is None: + return name + last: int = _tb_cc(base, bl - 1) + if last == 47 or last == 92: + viperlib.snprintf(buf, bl + nl + 2, "%s%s", base, name) + else: + viperlib.snprintf(buf, bl + nl + 2, "%s/%s", base, name) + return buf + + +def _tb_parse_dec(s: str) -> int: + v: int = 0 + i: sizet = 0 + while _tb_cc(s, i) >= 48 and _tb_cc(s, i) <= 57: + v = v * 10 + (_tb_cc(s, i) - 48) + i += 1 + return v + + +def _tb_bracket_idx(s: str) -> int: + """Parse the N in '[N]' (first bracket group of the line).""" + i: sizet = 0 + while _tb_cc(s, i) != 0: + if _tb_cc(s, i) == 91: + return _tb_parse_dec(s + i + 1) + i += 1 + return -1 + + +def _tb_after_bracket(s: str) -> str: + i: sizet = 0 + while _tb_cc(s, i) != 0: + if _tb_cc(s, i) == 93: + return s + i + 1 + i += 1 + return None + + +def _tb_value_after_sep(s: str) -> str: + """Return the text after the first '=' or ':' (left-trimmed), or None.""" + if s is None: + return None + i: sizet = 0 + while _tb_cc(s, i) != 0: + c: int = _tb_cc(s, i) + if c == 61 or c == 58: + return _tb_ltrim(s + i + 1) + i += 1 + return None + + +def _tb_hex_digit(ch: int) -> int: + if ch >= 48 and ch <= 57: + return ch - 48 + if ch >= 97 and ch <= 102: + return ch - 87 + if ch >= 65 and ch <= 70: + return ch - 55 + return -1 + + +def _tb_hex_in_parens(s: str, key: str) -> uint64: + """Parse the 0x... value inside the parentheses right after key (DW_AT_low_pc (0x...)). + + Returns 0 when the strict "( 0x" form is absent; DWARF prints the address form + that way, and a 0 result is harmless (out of the image VA range -> dropped). + """ + p: str = string.strstr(s, key) + if p is None: + return 0 + p = p + string.strlen(key) + while _tb_cc(p, 0) == 32 or _tb_cc(p, 0) == 9: + p = p + 1 + if _tb_cc(p, 0) != 40: + return 0 + p = p + 1 + while _tb_cc(p, 0) == 32: + p = p + 1 + if _tb_cc(p, 0) != 48 or _tb_cc(p, 1) != 120: + return 0 + q: str = p + 2 + v: uint64 = 0 + i: sizet = 0 + hd: int = _tb_hex_digit(_tb_cc(q, 0)) + while hd >= 0: + v = (v << 4) | uint64(hd) + i += 1 + hd = _tb_hex_digit(_tb_cc(q, i)) + return v + + +def _tb_quoted_after(mb: memhub.MemBuddy*, s: str, key: str) -> str: + """Extract the first double-quoted string following key (DW_AT_name ("...")).""" + p: str = string.strstr(s, key) + if p is None: + return None + p = p + string.strlen(key) + q: str = string.strchr(p, 40) + if q is None: + return None + q = q + 1 + while _tb_cc(q, 0) == 32: + q = q + 1 + if _tb_cc(q, 0) != 34: + return None + q = q + 1 + start: str = q + n: sizet = 0 + while _tb_cc(q, n) != 0: + c: int = _tb_cc(q, n) + if c == 92: + if _tb_cc(q, n + 1) == 0: + break + n += 2 + continue + if c == 34: + break + n += 1 + return _tb_dup_span(mb, start, n) + + +def _tb_dir_lookup(dirs: bytes, count: int, idx: int) -> str: + i: int = 0 + while i < count: + if int(_tb_rd_u32(dirs, sizet(i) * TB_DIR_SIZE)) == idx: + return (str | t.CPtr)(_tb_rd_u64(dirs, sizet(i) * TB_DIR_SIZE + 8)) + i += 1 + return None + + +def _tb_file_rec(files: bytes, count: int, idx: int) -> int: + i: int = 0 + while i < count: + if int(_tb_rd_u32(files, sizet(i) * TB_FILE_SIZE)) == idx: + return i + i += 1 + return -1 + + +def _tb_resolve_file(mb: memhub.MemBuddy*, files: bytes, fcount: int, + dirs: bytes, dcount: int, frec: int) -> str: + """Resolve a file-table entry to a path (absolute name wins, else dir join).""" + roff: sizet = sizet(frec) * TB_FILE_SIZE + cached: uint64 = _tb_rd_u64(files, roff + 24) + if cached != 0: + return (str | t.CPtr)(cached) + name: str = (str | t.CPtr)(_tb_rd_u64(files, roff + 8)) + path: str = name + if name is not None and _tb_is_abs_path(name) == 0: + didx: int = int(_tb_rd_u32(files, roff + 4)) + base: str = _tb_dir_lookup(dirs, dcount, didx) + if base is not None and string.strlen(base) > 0: + if _tb_is_abs_path(base) == 0: + root: str = _tb_dir_lookup(dirs, dcount, 0) + if root is not None and _tb_is_abs_path(root) != 0: + base = _tb_join(mb, root, base) + path = _tb_join(mb, base, name) + if path is None or string.strlen(path) == 0: + path = "??" + _tb_wr_u64(files, roff + 24, t.CUInt64T(path)) + return path + + +def _tb_grow_rec(mb: memhub.MemBuddy*, buf: bytes, old_cap: int, + rec_size: int, new_cap: int) -> bytes: + nb: bytes = mb.alloc(sizet(new_cap) * sizet(rec_size)) + if nb is None: + return None + string.memcpy(nb, buf, sizet(old_cap) * sizet(rec_size)) + mb.free(buf) + return nb + + +def _tb_emit_row(mb: memhub.MemBuddy*, d: TbDump | t.CPtr, + addr: uint64, path: str, line: int) -> int: + if d is None: + return 1 + if d.RowCount >= d.RowCap: + ncap: int = d.RowCap * 2 + nb: bytes = _tb_grow_rec(mb, d.RowBuf, d.RowCap, TB_ROW_SIZE, ncap) + if nb is None: + VLogger.error("--traceback: DWARF 行记录表扩容失败,终止编译", "TRACEBACK") + return 1 + d.RowBuf = nb + d.RowCap = ncap + off: sizet = sizet(d.RowCount) * TB_ROW_SIZE + _tb_wr_u64(d.RowBuf, off, addr) + _tb_wr_u64(d.RowBuf, off + TB_ROW_OFF_FILE, t.CUInt64T(path)) + _tb_wr_u32(d.RowBuf, off + TB_ROW_OFF_LINE, uint64(line)) + d.RowCount = d.RowCount + 1 + d.RawRowCount = d.RawRowCount + 1 + return 0 + + +def _tb_emit_func(mb: memhub.MemBuddy*, d: TbDump | t.CPtr, + low: uint64, high: uint64, name: str) -> int: + if d is None: + return 1 + if d.FuncCount >= d.FuncCap: + ncap: int = d.FuncCap * 2 + nb: bytes = _tb_grow_rec(mb, d.FuncBuf, d.FuncCap, TB_FUNC_SIZE, ncap) + if nb is None: + VLogger.error("--traceback: DWARF 子程序表扩容失败,终止编译", "TRACEBACK") + return 1 + d.FuncBuf = nb + d.FuncCap = ncap + off: sizet = sizet(d.FuncCount) * TB_FUNC_SIZE + _tb_wr_u64(d.FuncBuf, off, low) + _tb_wr_u64(d.FuncBuf, off + 8, high) + _tb_wr_u64(d.FuncBuf, off + 16, t.CUInt64T(name)) + d.FuncCount = d.FuncCount + 1 + return 0 + + +def _tb_func_name_for(fbuf: bytes, fcount: int, rva: uint64) -> str: + """bisect_right over the (sorted) func low_pc table, then range check.""" + if fbuf is None or fcount <= 0: + return None + lo: int = 0 + hi: int = fcount + while lo < hi: + mid: int = lo + (hi - lo) / 2 + if _tb_rd_u64(fbuf, sizet(mid) * TB_FUNC_SIZE) <= rva: + lo = mid + 1 + else: + hi = mid + k: int = lo - 1 + if k < 0: + return None + low: uint64 = _tb_rd_u64(fbuf, sizet(k) * TB_FUNC_SIZE) + high: uint64 = _tb_rd_u64(fbuf, sizet(k) * TB_FUNC_SIZE + 8) + if low <= rva and rva < high: + return (str | t.CPtr)(_tb_rd_u64(fbuf, sizet(k) * TB_FUNC_SIZE + 16)) + return None + + +def _tb_intern(p: TbPool | t.CPtr, s: str) -> uint64: + """Intern s into the pool (linear scan; keeps offset 0 as the empty sentinel).""" + if p is None or s is None: + return 0 + k: int = 0 + while k < p.OffCount: + o: uint64 = _tb_rd_u64(p.Offs, sizet(k) * 8) + if string.strcmp(p.Pool + sizet(o), s) == 0: + return o + k += 1 + off: uint64 = uint64(p.PoolLen) + n: sizet = string.strlen(s) + string.memcpy(p.Pool + sizet(off), s, n) + p.Pool[sizet(off) + n] = '\0' + p.PoolLen = p.PoolLen + int(n) + 1 + _tb_wr_u64(p.Offs, sizet(p.OffCount) * 8, off) + p.OffCount = p.OffCount + 1 + return off + + +def _tb_read_pe_layout(mb: memhub.MemBuddy*, exe_path: str) -> TbPeLayout | t.CPtr: + """Read (ImageBase, SizeOfImage, .text RVA) from the PE headers.""" + lay: TbPeLayout | t.CPtr = TbPeLayout() + if lay is None: + return None + f: rawptr[fileio.File] = fileio.File(exe_path, fileio.MODE.R) + if f.closed: + return lay + HEAD: sizet = 4096 + head: bytes = mb.alloc(HEAD) + if head is None: + f.close() + return lay + got: LONG = f.read_all(head, HEAD) + f.close() + if got < 0x40: + return lay + if _tb_cc(head, 0) != 77 or _tb_cc(head, 1) != 90: + return lay + e_lfanew: uint64 = uint64(_tb_rd_u32(head, 0x3C)) + if e_lfanew + 24 > uint64(got): + return lay + if _tb_cc(head, sizet(e_lfanew)) != 80 or _tb_cc(head, sizet(e_lfanew) + 1) != 69: + return lay + coff: sizet = sizet(e_lfanew) + 4 + nsec: uint64 = uint64(_tb_rd_u16(head, coff + 2)) + optsz: uint64 = uint64(_tb_rd_u16(head, coff + 16)) + opt: sizet = coff + 20 + if uint64(opt) + 32 > uint64(got): + return lay + magic: uint64 = uint64(_tb_rd_u16(head, opt)) + img_base: uint64 = 0 + if magic == 0x20B: + img_base = _tb_rd_u64(head, opt + 24) + elif magic == 0x10B: + img_base = uint64(_tb_rd_u32(head, opt + 28)) + else: + return lay + size_img: uint64 = uint64(_tb_rd_u32(head, opt + 56)) + sec_off: sizet = opt + sizet(optsz) + text_rva: uint64 = 0x1000 + min_rva: uint64 = 0xFFFFFFFF + found_text: int = 0 + have_sec: int = 0 + si: uint64 = 0 + while si < nsec: + base: sizet = sec_off + sizet(si) * 40 + if uint64(base) + 40 > uint64(got): + break + va: uint64 = uint64(_tb_rd_u32(head, base + 12)) + have_sec = 1 + if va < min_rva: + min_rva = va + if _tb_cc(head, base) == 46 and _tb_cc(head, base + 1) == 116 \ + and _tb_cc(head, base + 2) == 101 and _tb_cc(head, base + 3) == 120 \ + and _tb_cc(head, base + 4) == 116: + text_rva = va + found_text = 1 + si += 1 + if found_text == 0 and have_sec != 0 and min_rva != 0xFFFFFFFF: + text_rva = min_rva + lay.ImageBase = img_base + lay.SizeOfImage = size_img + lay.TextRva = text_rva + lay.Ok = 1 + return lay + + +def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump | t.CPtr: + """Parse llvm-dwarfdump text into a row table (addr, path, line) and a func table.""" + d: TbDump | t.CPtr = TbDump() + if d is None: + return None + LINE_CAP: sizet = 8192 + d.RowBuf = mb.alloc(sizet(TB_ROW_CAP0) * TB_ROW_SIZE) + d.RowCap = TB_ROW_CAP0 + d.FuncBuf = mb.alloc(sizet(TB_FUNC_CAP0) * TB_FUNC_SIZE) + d.FuncCap = TB_FUNC_CAP0 + dirs: bytes = mb.alloc(sizet(TB_DIR_CAP) * TB_DIR_SIZE) + files: bytes = mb.alloc(sizet(TB_FILE_CAP) * TB_FILE_SIZE) + linebuf: bytes = mb.alloc(LINE_CAP) + if d.RowBuf is None or d.FuncBuf is None or dirs is None or files is None or linebuf is None: + VLogger.error("--traceback: dwarfdump 解析缓冲分配失败,终止编译", "TRACEBACK") + return None + + in_line: int = 0 + dir_count: int = 0 + file_count: int = 0 + pkind: int = 0 # 0=none, 1=file, 2=dir + pidx: int = 0 + pname: str = None + pdir: int = 0 + in_prog: int = 0 + cur_low: uint64 = 0 + cur_high: uint64 = 0 + cur_has_low: int = 0 + cur_has_high: int = 0 + cname: str = None + + dlen: sizet = string.strlen(dump) + pos: sizet = 0 + while pos < dlen: + ls: sizet = pos + while pos < dlen and _tb_cc(dump, pos) != 10: + pos += 1 + le: sizet = pos + if pos < dlen: + pos += 1 + if le > ls and _tb_cc(dump, le - 1) == 13: + le -= 1 + ll: sizet = le - ls + if ll == 0: + continue + if ll >= LINE_CAP: + ll = LINE_CAP - 1 + string.strncpy(linebuf, dump + ls, ll) + linebuf[ll] = '\0' + st: str = _tb_ltrim(linebuf) + + # ---- line-table section gate ---- + if _tb_contains(st, "debug_line contents") != 0: + in_line = 1 + continue + if _tb_contains(st, "contents:") != 0 and _tb_contains(st, "debug_") != 0: + in_line = 0 + continue + + if in_line != 0: + if _tb_startswith(st, "debug_line[") != 0: + # new CU: commit pending file, reset both tables + if pkind == 1 and pname is not None and file_count < TB_FILE_CAP: + _tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx)) + _tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir)) + _tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, t.CUInt64T(pname)) + _tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0)) + file_count += 1 + pkind = 0 + pname = None + pdir = 0 + dir_count = 0 + file_count = 0 + continue + if _tb_startswith(st, "file_names[") != 0: + if pkind == 1 and pname is not None and file_count < TB_FILE_CAP: + _tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx)) + _tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir)) + _tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, t.CUInt64T(pname)) + _tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0)) + file_count += 1 + pname = None + pdir = 0 + pkind = 1 + pidx = _tb_bracket_idx(st) + continue + if _tb_startswith(st, "directory_names[") != 0 or _tb_startswith(st, "include_directories[") != 0: + if pkind == 1 and pname is not None and file_count < TB_FILE_CAP: + _tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx)) + _tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir)) + _tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, t.CUInt64T(pname)) + _tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0)) + file_count += 1 + pname = None + pdir = 0 + pidx = _tb_bracket_idx(st) + val: str = _tb_value_after_sep(_tb_after_bracket(st)) + if val is not None and _tb_len_trimmed(val) > 0: + if dir_count < TB_DIR_CAP: + _tb_wr_u32(dirs, sizet(dir_count) * TB_DIR_SIZE, uint64(pidx)) + _tb_wr_u64(dirs, sizet(dir_count) * TB_DIR_SIZE + 8, t.CUInt64T(_tb_unquote(mb, val))) + dir_count += 1 + pkind = 0 + else: + pkind = 2 + continue + if _tb_startswith(st, "name:") != 0 and pkind != 0: + val2: str = _tb_unquote(mb, _tb_ltrim(st + 5)) + if pkind == 1: + pname = val2 + else: + if dir_count < TB_DIR_CAP and val2 is not None: + _tb_wr_u32(dirs, sizet(dir_count) * TB_DIR_SIZE, uint64(pidx)) + _tb_wr_u64(dirs, sizet(dir_count) * TB_DIR_SIZE + 8, t.CUInt64T(val2)) + dir_count += 1 + pkind = 0 + continue + if _tb_startswith(st, "dir_index:") != 0 and pkind == 1: + pdir = _tb_parse_dec(_tb_ltrim(st + 9)) + continue + if _tb_startswith(st, "Address") != 0 and _tb_contains(st, "Column") != 0: + if pkind == 1 and pname is not None and file_count < TB_FILE_CAP: + _tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx)) + _tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir)) + _tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, t.CUInt64T(pname)) + _tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0)) + file_count += 1 + pkind = 0 + pname = None + pdir = 0 + continue + if _tb_cc(linebuf, 0) == 48 and _tb_cc(linebuf, 1) == 120: + if pkind == 1 and pname is not None and file_count < TB_FILE_CAP: + _tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx)) + _tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir)) + _tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, t.CUInt64T(pname)) + _tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0)) + file_count += 1 + pkind = 0 + pname = None + pdir = 0 + ri: sizet = 2 + addr: uint64 = 0 + hd: int = _tb_hex_digit(_tb_cc(linebuf, ri)) + while hd >= 0: + addr = (addr << 4) | uint64(hd) + ri += 1 + hd = _tb_hex_digit(_tb_cc(linebuf, ri)) + while ri < ll and (_tb_cc(linebuf, ri) == 32 or _tb_cc(linebuf, ri) == 9): + ri += 1 + lno: int = 0 + while ri < ll and _tb_cc(linebuf, ri) >= 48 and _tb_cc(linebuf, ri) <= 57: + lno = lno * 10 + (_tb_cc(linebuf, ri) - 48) + ri += 1 + while ri < ll and (_tb_cc(linebuf, ri) == 32 or _tb_cc(linebuf, ri) == 9): + ri += 1 + while ri < ll and _tb_cc(linebuf, ri) >= 48 and _tb_cc(linebuf, ri) <= 57: + ri += 1 + while ri < ll and (_tb_cc(linebuf, ri) == 32 or _tb_cc(linebuf, ri) == 9): + ri += 1 + fidx: int = 0 + while ri < ll and _tb_cc(linebuf, ri) >= 48 and _tb_cc(linebuf, ri) <= 57: + fidx = fidx * 10 + (_tb_cc(linebuf, ri) - 48) + ri += 1 + if lno > 0: + frec: int = _tb_file_rec(files, file_count, fidx) + fpath: str = None + if frec >= 0: + fpath = _tb_resolve_file(mb, files, file_count, dirs, dir_count, frec) + if fpath is None: + fpath = "??" + if _tb_emit_row(mb, d, addr, fpath, lno) != 0: + return None + continue + + # ---- DWARF debug_info subprogram parsing ---- + if _tb_contains(linebuf, "DW_TAG_") != 0: + if in_prog != 0 and cur_has_low != 0 and cname is not None and string.strlen(cname) > 0: + hi: uint64 = cur_low + 1 + if cur_has_high != 0: + hi = cur_high + if hi <= cur_low: + hi = cur_low + cur_high + if hi > cur_low: + if _tb_emit_func(mb, d, cur_low, hi, cname) != 0: + return None + in_prog = 0 + cur_low = 0 + cur_high = 0 + cur_has_low = 0 + cur_has_high = 0 + cname = None + if _tb_contains(linebuf, "DW_TAG_subprogram") != 0: + in_prog = 1 + continue + if in_prog == 0: + continue + if _tb_contains(linebuf, "DW_AT_low_pc") != 0: + cur_low = _tb_hex_in_parens(linebuf, "DW_AT_low_pc") + cur_has_low = 1 + continue + if _tb_contains(linebuf, "DW_AT_high_pc") != 0: + cur_high = _tb_hex_in_parens(linebuf, "DW_AT_high_pc") + cur_has_high = 1 + continue + if _tb_contains(linebuf, "DW_AT_name") != 0 and cname is None: + cname = _tb_quoted_after(mb, linebuf, "DW_AT_name") + continue + + # trailing subprogram + if in_prog != 0 and cur_has_low != 0 and cname is not None and string.strlen(cname) > 0: + hi2: uint64 = cur_low + 1 + if cur_has_high != 0: + hi2 = cur_high + if hi2 <= cur_low: + hi2 = cur_low + cur_high + if hi2 > cur_low: + if _tb_emit_func(mb, d, cur_low, hi2, cname) != 0: + return None + return d + + +def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int: + """--traceback: export .stackmap after a successful link. 0 = ok.""" + if mb is None or exe_path is None: + VLogger.error("--traceback: WriteStackMap 参数为空,终止编译", "TRACEBACK") + return 1 + + lay: TbPeLayout | t.CPtr = _tb_read_pe_layout(mb, exe_path) + if lay is None or lay.Ok == 0: + VLogger.error(f"--traceback: 只支持 Windows PE 产物,但 {exe_path} 不是有效 PE(这是 --traceback 的要求)", "TRACEBACK") + return 1 + size_of_image: uint64 = lay.SizeOfImage + if size_of_image == 0: + VLogger.error(f"--traceback: 读到的 PE.SizeOfImage 非正({size_of_image}),无法界定 RVA 域(这是 --traceback 的要求)", "TRACEBACK") + return 1 + img_lo: uint64 = lay.ImageBase + img_hi: uint64 = lay.ImageBase + size_of_image + text_rva: uint64 = lay.TextRva + + # ---- 1) run llvm-dwarfdump (bare name: CreateProcessA resolves it via PATH) ---- + cmd_len: sizet = string.strlen(exe_path) + 64 + cmd: bytes = mb.alloc(cmd_len) + if cmd is None: + VLogger.error("--traceback: dwarfdump 命令缓冲分配失败,终止编译", "TRACEBACK") + return 1 + viperlib.snprintf(cmd, cmd_len, "llvm-dwarfdump --debug-line --debug-info \"%s\"", exe_path) + res: rawptr[subprocess.CompletedProcess] = subprocess.run(cmd, True, True) + if res is None or res.returncode != 0: + rc: int = -1 + if res is not None: + rc = res.returncode + VLogger.error(f"--traceback: 调用 llvm-dwarfdump 失败(返回码 {rc};请确认 PATH 内含 LLVM 工具),终止编译", "TRACEBACK") + return 1 + dump: str = res.stdout + if dump is None: + VLogger.error("--traceback: llvm-dwarfdump 无输出,终止编译", "TRACEBACK") + return 1 + + d: TbDump | t.CPtr = _tb_parse_dump(mb, dump) + if d is None: + return 1 + if d.RawRowCount == 0: + VLogger.error(f"--traceback: 未能从 {exe_path} 解析出任何 DWARF 行记录(.debug_line 为空?这是 --traceback 的要求)", "TRACEBACK") + return 1 + + # ---- 2) rows: normalise to RVA, same rva keeps the largest line ---- + n: int = d.RowCount + dropped_rows: int = 0 + kept: int = 0 + i: int = 0 + while i < n: + a: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE) + if a < img_lo or a >= img_hi: + dropped_rows += 1 + i += 1 + continue + if kept != i: + _tb_wr_u64(d.RowBuf, sizet(kept) * TB_ROW_SIZE, a - img_lo) + _tb_wr_u64(d.RowBuf, sizet(kept) * TB_ROW_SIZE + TB_ROW_OFF_FILE, + _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE)) + _tb_wr_u32(d.RowBuf, sizet(kept) * TB_ROW_SIZE + TB_ROW_OFF_LINE, + uint64(_tb_rd_u32(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_LINE))) + else: + _tb_wr_u64(d.RowBuf, sizet(kept) * TB_ROW_SIZE, a - img_lo) + kept += 1 + i += 1 + tmp: bytes = mb.alloc(TB_ROW_SIZE) + if tmp is None: + VLogger.error("--traceback: 行记录排序缓冲分配失败,终止编译", "TRACEBACK") + return 1 + si2: int = 1 + while si2 < kept: + string.memcpy(tmp, d.RowBuf + sizet(si2) * TB_ROW_SIZE, TB_ROW_SIZE) + sj: int = si2 + while sj > 0: + if _tb_rd_u64(d.RowBuf, sizet(sj - 1) * TB_ROW_SIZE) <= _tb_rd_u64(tmp, 0): + break + string.memcpy(d.RowBuf + sizet(sj) * TB_ROW_SIZE, + d.RowBuf + sizet(sj - 1) * TB_ROW_SIZE, TB_ROW_SIZE) + sj -= 1 + string.memcpy(d.RowBuf + sizet(sj) * TB_ROW_SIZE, tmp, TB_ROW_SIZE) + si2 += 1 + uniq: int = 0 + i = 0 + while i < kept: + rv: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE) + bl: uint64 = uint64(_tb_rd_u32(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_LINE)) + bp: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE) + j: int = i + 1 + while j < kept and _tb_rd_u64(d.RowBuf, sizet(j) * TB_ROW_SIZE) == rv: + lj: uint64 = uint64(_tb_rd_u32(d.RowBuf, sizet(j) * TB_ROW_SIZE + TB_ROW_OFF_LINE)) + if lj > bl: + bl = lj + bp = _tb_rd_u64(d.RowBuf, sizet(j) * TB_ROW_SIZE + TB_ROW_OFF_FILE) + j += 1 + if uniq != i: + _tb_wr_u64(d.RowBuf, sizet(uniq) * TB_ROW_SIZE, rv) + _tb_wr_u64(d.RowBuf, sizet(uniq) * TB_ROW_SIZE + TB_ROW_OFF_FILE, bp) + _tb_wr_u32(d.RowBuf, sizet(uniq) * TB_ROW_SIZE + TB_ROW_OFF_LINE, bl) + uniq += 1 + i = j + if dropped_rows > 0: + VLogger.warning(f"--traceback: 丢弃 {dropped_rows} 条无法归一化的 DWARF 行记录(地址不在影像 VA 区间,多为 COMDAT 去重副本)", "TRACEBACK") + if uniq == 0: + VLogger.error(f"--traceback: 从 {exe_path} 得到的行记录全部无法归一到运行期 RVA 域,终止编译", "TRACEBACK") + return 1 + + # ---- 3) subprograms: normalise + sort by low_rva ---- + fcount: int = d.FuncCount + fkept: int = 0 + dropped_funcs: int = 0 + fi: int = 0 + while fi < fcount: + low: uint64 = _tb_rd_u64(d.FuncBuf, sizet(fi) * TB_FUNC_SIZE) + if low < img_lo or low >= img_hi: + dropped_funcs += 1 + fi += 1 + continue + high: uint64 = _tb_rd_u64(d.FuncBuf, sizet(fi) * TB_FUNC_SIZE + 8) + lo_rva: uint64 = low - img_lo + hi_rva: uint64 = high - img_lo + if hi_rva <= lo_rva: + hi_rva = lo_rva + 1 + if hi_rva > size_of_image: + hi_rva = size_of_image + _tb_wr_u64(d.FuncBuf, sizet(fkept) * TB_FUNC_SIZE, lo_rva) + _tb_wr_u64(d.FuncBuf, sizet(fkept) * TB_FUNC_SIZE + 8, hi_rva) + _tb_wr_u64(d.FuncBuf, sizet(fkept) * TB_FUNC_SIZE + 16, + _tb_rd_u64(d.FuncBuf, sizet(fi) * TB_FUNC_SIZE + 16)) + fkept += 1 + fi += 1 + ftmp: bytes = mb.alloc(TB_FUNC_SIZE) + if ftmp is None: + VLogger.error("--traceback: 子程序排序缓冲分配失败,终止编译", "TRACEBACK") + return 1 + fj: int = 1 + while fj < fkept: + string.memcpy(ftmp, d.FuncBuf + sizet(fj) * TB_FUNC_SIZE, TB_FUNC_SIZE) + fk: int = fj + while fk > 0: + if _tb_rd_u64(d.FuncBuf, sizet(fk - 1) * TB_FUNC_SIZE) <= _tb_rd_u64(ftmp, 0): + break + string.memcpy(d.FuncBuf + sizet(fk) * TB_FUNC_SIZE, + d.FuncBuf + sizet(fk - 1) * TB_FUNC_SIZE, TB_FUNC_SIZE) + fk -= 1 + string.memcpy(d.FuncBuf + sizet(fk) * TB_FUNC_SIZE, ftmp, TB_FUNC_SIZE) + fj += 1 + if dropped_funcs > 0: + VLogger.warning(f"--traceback: 丢弃 {dropped_funcs} 个无法归一化的 DWARF 子程序(low_pc 不在影像 VA 区间)", "TRACEBACK") + + # ---- 4) string pool: exact upper bound, then intern (offset 0 = sentinel) ---- + pool_need: sizet = 1 + i = 0 + while i < uniq: + pth: str = (str | t.CPtr)(_tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE)) + pool_need += string.strlen(pth) + 1 + i += 1 + i = 0 + while i < fkept: + nm0: str = (str | t.CPtr)(_tb_rd_u64(d.FuncBuf, sizet(i) * TB_FUNC_SIZE + 16)) + if nm0 is not None: + pool_need += string.strlen(nm0) + 1 + i += 1 + p: TbPool | t.CPtr = TbPool() + if p is None: + VLogger.error("--traceback: 字符串池对象分配失败,终止编译", "TRACEBACK") + return 1 + p.Pool = mb.alloc(pool_need) + p.Offs = mb.alloc(sizet(uniq + fkept) * 8 + 64) + if p.Pool is None or p.Offs is None: + VLogger.error("--traceback: 字符串池缓冲分配失败,终止编译", "TRACEBACK") + return 1 + p.Pool[0] = '\0' + p.PoolLen = 1 + _tb_wr_u64(p.Offs, 0, uint64(0)) + p.OffCount = 1 + # file_off / func_off per unique row (u32 each) + ro: bytes = mb.alloc(sizet(uniq) * 8 + 8) + if ro is None: + VLogger.error("--traceback: 记录偏移缓冲分配失败,终止编译", "TRACEBACK") + return 1 + i = 0 + while i < uniq: + pth2: str = (str | t.CPtr)(_tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE)) + foff: uint64 = _tb_intern(p, pth2) + noff: uint64 = 0 + rv2: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE) + nm2: str = _tb_func_name_for(d.FuncBuf, fkept, rv2) + if nm2 is not None: + noff = _tb_intern(p, nm2) + _tb_wr_u32(ro, sizet(i) * 8, foff) + _tb_wr_u32(ro, sizet(i) * 8 + 4, noff) + i += 1 + + # ---- 5) self-checks (mirror the runtime LoadMapFile validation) ---- + prev_rva: uint64 = 0 + outside: int = 0 + plausible: int = 0 + ci: int = 0 + while ci < uniq: + cv: uint64 = _tb_rd_u64(d.RowBuf, sizet(ci) * TB_ROW_SIZE) + if ci > 0 and cv <= prev_rva: + VLogger.error(f"--traceback 自检失败:记录 RVA 非严格升序(第 {ci} 条会破坏运行期二分查找),终止编译", "TRACEBACK") + return 1 + prev_rva = cv + if cv >= size_of_image: + outside += 1 + if cv >= text_rva and cv < size_of_image: + plausible += 1 + ci += 1 + if outside > 0: + VLogger.error(f"--traceback 自检失败:有 {outside} 条记录的 RVA 落在 [0, SizeOfImage=0x{size_of_image:x}) 之外,终止编译", "TRACEBACK") + return 1 + if plausible <= 0: + VLogger.error("--traceback 自检失败:没有任何记录的 RVA 落在运行期可能出现的区间(RVA 口径疑似错误),终止编译", "TRACEBACK") + return 1 + # file_off must be >= 1: offset 0 is the empty-string sentinel and the runtime + # rejects a whole map whose record file_off is 0. + ci = 0 + while ci < uniq: + fo: uint64 = uint64(_tb_rd_u32(ro, sizet(ci) * 8)) + if fo == 0: + VLogger.error("--traceback 自检失败:存在 file_off==0 的记录(空路径,运行期会拒绝该旁文件),终止编译", "TRACEBACK") + return 1 + ci += 1 + + # ---- 6) serialise and write .stackmap ---- + total: sizet = 20 + sizet(uniq) * 20 + sizet(p.PoolLen) + out: bytes = mb.alloc(total) + if out is None: + VLogger.error("--traceback: 旁文件输出缓冲分配失败,终止编译", "TRACEBACK") + return 1 + out[0] = 'T' + out[1] = 'P' + out[2] = 'C' + out[3] = 'S' + _tb_wr_u32(out, 4, uint64(1)) + _tb_wr_u32(out, 8, uint64(uniq)) + _tb_wr_u32(out, 12, uint64(p.OffCount)) + _tb_wr_u32(out, 16, uint64(p.PoolLen)) + ri2: int = 0 + while ri2 < uniq: + roff2: sizet = 20 + sizet(ri2) * 20 + rv3: uint64 = _tb_rd_u64(d.RowBuf, sizet(ri2) * TB_ROW_SIZE) + ln3: uint64 = uint64(_tb_rd_u32(d.RowBuf, sizet(ri2) * TB_ROW_SIZE + TB_ROW_OFF_LINE)) + _tb_wr_u64(out, roff2, rv3) + _tb_wr_u32(out, roff2 + 8, uint64(_tb_rd_u32(ro, sizet(ri2) * 8))) + _tb_wr_u32(out, roff2 + 12, ln3) + _tb_wr_u32(out, roff2 + 16, uint64(_tb_rd_u32(ro, sizet(ri2) * 8 + 4))) + ri2 += 1 + string.memcpy(out + 20 + sizet(uniq) * 20, p.Pool, sizet(p.PoolLen)) + + map_len: sizet = string.strlen(exe_path) + 16 + map_path: str = mb.alloc(map_len) + if map_path is None: + VLogger.error("--traceback: 旁文件路径缓冲分配失败,终止编译", "TRACEBACK") + return 1 + viperlib.snprintf(map_path, map_len, "%s.stackmap", exe_path) + mf: rawptr[fileio.File] = fileio.File(map_path, fileio.MODE.W) + if mf.closed: + VLogger.error(f"--traceback: 写出旁文件失败(无法打开 {map_path}),终止编译", "TRACEBACK") + return 1 + mf.write(out, total) + mf.close() + VLogger.success(f"--traceback: 已写出地址→源行旁文件 {map_path}({uniq} 条记录,{total} 字节)", "TRACEBACK") + return 0 + + def run_pipeline(ir_buf: bytes, ir_len: sizet, temp_dir: str, output_dir: str, module_name: str, cc_cmd: str, cc_flags: str, diff --git a/App/lib/Phase/Phase1/Phase1Main.vp b/App/lib/Phase/Phase1/Phase1Main.vp index 163c4bf..7800cfc 100644 --- a/App/lib/Phase/Phase1/Phase1Main.vp +++ b/App/lib/Phase/Phase1/Phase1Main.vp @@ -27,6 +27,7 @@ import lib.Phase.Phase1.Phase1Topo as Phase1Topo import lib.Phase.Phase1 as Phase1 import lib.core.Gen.GenApi as GenApi +import lib.core.Gen.DebugInfoGen as DebugInfoGen # ============================================================ @@ -1027,6 +1028,11 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, tr.ModuleSha1 = sha1 tr.CurrentPackage = GenApi.compute_package_from_relpath(mb, entry.RelPath) GenApi.set_current_file_type(file_path) + # --traceback: mirror the DWARF switch into the Gen-layer emitter (the + # same BuildPipeline._traceback_mode that gates -frame-pointer=all / + # dbghelp / the Install() ctor). include/*.vp modules are compiled here, + # so this is what gives their frames DWARF line info. + DebugInfoGen.SetEnabled(BuildPipeline._traceback_mode) GenApi.set_current_file_imports(file_path) GenApi.set_current_module_sha1(sha1) GenApi.clear_cdefine_constants() @@ -1058,6 +1064,13 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, failed += 1 continue + # --traceback: inject the startup Install() ctor when translating traceback.vp + # (its stub/text.ll below are what Phase2 reuses to build the .obj). The + # injector lives in the Gen layer (already depends on llvmlite), so this + # module gains no cross-module llvmlite symbol dependency. + if BuildPipeline._traceback_mode != 0 and BuildPipeline._tb_basename_is_traceback(file_path) != 0: + HandlesTranslator.InjectTracebackInstallCtor(tr) + # 诊断:pre_rc 哨兵在 translate 前、覆盖不到 translate 期间的 AST/IR 写穿,故 dump 前立即检查存活块 header/canary # (rc=6 块尾 canary 损坏 = 越界写穿数据区),把「dump 时链表成环卡死」变成「定位到 translate 期间写穿发生的文件」。 post_rc: int = mb.check_alive_blocks(mark1) diff --git a/App/lib/Phase/Phase2/__init__.vp b/App/lib/Phase/Phase2/__init__.vp index 00a8918..2d00bc3 100644 --- a/App/lib/Phase/Phase2/__init__.vp +++ b/App/lib/Phase/Phase2/__init__.vp @@ -1694,6 +1694,10 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, if lret == 0: VLogger.success(f"输出: {exe_path}", "PhaseC") + # --traceback: export .stackmap (skipped under --phase2command, + # where nothing was actually linked). + if BuildPipeline._traceback_mode != 0 and BuildPipeline._phase2_cmd_path is None: + BuildPipeline.WriteStackMap(mb, exe_path) if args.get_bool("run") and BuildPipeline._phase2_cmd_path is not None: # phase2command 模式:exe 尚未链接(命令在 bat 中),跳过 --run VLogger.info("phase2command 模式:跳过 --run(exe 尚未链接)", "phase2cmd") diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 53476a2..10c74ed 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -2043,7 +2043,6 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, # 解析注解——根治跨上下文误报(如 t.CArray[str] 需要 't' 已导入)。 stub_imported_modules: str = None stub_from_imports: str = None - si: sizet for si in range(n): schild: ast.AST* = ch.get(si) if schild is None: @@ -2053,15 +2052,12 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, simp: ast.Import* = ast.Import*(schild) if simp.names is not None: sn_n: sizet = len(simp.names) - sni: sizet for sni in range(sn_n): sal: ast.Alias* = ast.Alias*(simp.names.get(sni)) if sal is not None and sal.name is not None: - stub_imported_modules = _stub_add_imported( - pool, stub_imported_modules, sal.name) + stub_imported_modules = _stub_add_imported(pool, stub_imported_modules, sal.name) if sal.asname is not None: - stub_imported_modules = _stub_add_imported( - pool, stub_imported_modules, sal.asname) + stub_imported_modules = _stub_add_imported(pool, stub_imported_modules, sal.asname) elif sk == ast.ASTKind.ImportFrom: sifr: ast.ImportFrom* = ast.ImportFrom*(schild) sifr_mod: str = sifr.module @@ -2083,8 +2079,7 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, string.strcpy(qbuf_r + pkg_len_r + 1, sifr_mod) sifr_mod_q = qbuf_r if sifr_mod is not None: - stub_imported_modules = _stub_add_imported( - pool, stub_imported_modules, sifr_mod_q) + stub_imported_modules = _stub_add_imported(pool, stub_imported_modules, sifr_mod_q) # 方案A:包 __init__.vpi 的重导出类以包 sha1 注册别名 entry # (ast.Attribute 定义在 ast.exprs 但归属 ast 包;查找端按包 # sha1 匹配,注册端按定义文件 sha1 → name+sha1 错位 fail-fast。 diff --git a/App/lib/Stub/StubMerger/StubMergerBuild.vp b/App/lib/Stub/StubMerger/StubMergerBuild.vp index d93d522..7e772c4 100644 --- a/App/lib/Stub/StubMerger/StubMergerBuild.vp +++ b/App/lib/Stub/StubMerger/StubMergerBuild.vp @@ -897,6 +897,40 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str, stdlib.free(worklist) return -1 + # 2b. --traceback: force the traceback runtime module into the reachable set + # (TPC: used_includes.add('traceback')). Its Install() symbol and the + # @llvm.global_ctors ctor are only produced by compiling this module, so a + # missing file is fail-fast rather than a silent "feature off" build. + if BuildPipeline._traceback_mode != 0: + tb_abs: t.CPtr = stdlib.malloc(8) + tb_rel: t.CPtr = stdlib.malloc(8) + tb_inc: t.CPtr = stdlib.malloc(8) + tb_found: int = -1 + if tb_abs is not None and tb_rel is not None and tb_inc is not None: + tb_abs_slot: sizet* = (sizet*)(tb_abs) + tb_rel_slot: sizet* = (sizet*)(tb_rel) + tb_inc_slot: sizet* = (sizet*)(tb_inc) + tb_abs_slot[0] = 0 + tb_rel_slot[0] = 0 + tb_inc_slot[0] = 0 + tb_found = IncludesScanner.ResolveModuleToPath( + includes_dirs, "traceback", tb_abs, tb_rel, tb_inc) + if tb_abs is not None: + stdlib.free(tb_abs) + if tb_rel is not None: + stdlib.free(tb_rel) + if tb_inc is not None: + stdlib.free(tb_inc) + if tb_found != 0: + stdlib.free(worklist) + VLogger.error(f"--traceback: 在 includes 目录中找不到 traceback.vp,无法编译并链接 Install()(请确认 project.vpj 的 includes 包含仓库 includes 目录;已扫描: {includes_dirs})", "Traceback") + return -1 + tb_wl: int = _AppendDepsToWorklist(worklist, wl_len, "traceback", None) + if tb_wl < 0: + stdlib.free(worklist) + return -1 + wl_len = sizet(tb_wl) + # 3. 工作列表 BFS:模块名 → 文件系统解析 → 读+hash+parse import processed: bytes = stdlib.malloc(StubMerger.REACH_WORKLIST_BUF) if processed is None: @@ -1069,6 +1103,10 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str, tokens_r: ast.Token* = ast.tokenize(lx_r) tree_r = ast.parse_tokens(mb, tokens_r) if tree_r is None: + # parse_tokens keeps the error detail (line/col/message) in the global + # channel only, so print the unified parse traceback before the one-line + # log; without it a reachable-includes syntax error is undiagnosable. + GenApi.dump_parse_traceback(abs_path) VLogger.error(f"可达 includes 解析失败: {abs_path}", "Reachable") stdlib.free(abs_path) stdlib.free(rel_path) diff --git a/App/lib/Stub/StubMerger/StubMergerStore.vp b/App/lib/Stub/StubMerger/StubMergerStore.vp index 1b9be24..0a591a9 100644 --- a/App/lib/Stub/StubMerger/StubMergerStore.vp +++ b/App/lib/Stub/StubMerger/StubMergerStore.vp @@ -580,24 +580,23 @@ def GetSha1StoreIncdirArrPtr() -> bytes: # 调用者直接遍历数组:sha1_arr[i*17]、mod_arr[i*64]、rel_arr[i*StubMerger.MAX_REL_PATH_LEN] # 内存归全局存储器所有,调用者不要 free # ============================================================ -def GetSha1StoreArr(out_sha1_arr: t.CPtr, out_mod_arr: t.CPtr, out_rel_arr: t.CPtr) -> int: +def GetSha1StoreArr(out_sha1_arr: rawptr[t.CPtr], out_mod_arr: rawptr[t.CPtr], out_rel_arr: rawptr[t.CPtr]) -> int: """获取全局存储器数组指针 - out_sha1_arr/out_mod_arr/out_rel_arr 是调用者提供的 t.CPtr 指针, - 函数将存储器数组指针写入这些位置。 - 返回条目数,-1 失败 + out_sha1_arr/out_mod_arr/out_rel_arr 是调用者提供的 t.CPtr 槽的地址(t.CPtr*), + 函数把存储器数组指针写入这些槽。返回条目数,-1 失败 """ if out_sha1_arr is None or out_mod_arr is None or out_rel_arr is None: return -1 if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None: return -1 - sp: t.CPtr = (t.CPtr | t.CPtr)(out_sha1_arr) # 存疑 + sp: rawptr[t.CPtr] = out_sha1_arr if sp is not None: sp[0] = StubMerger._sha1_store_arr - mp: t.CPtr = (t.CPtr | t.CPtr)(out_mod_arr) + mp: rawptr[t.CPtr] = out_mod_arr if mp is not None: mp[0] = StubMerger._sha1_store_mod - rp: t.CPtr = (t.CPtr | t.CPtr)(out_rel_arr) + rp: rawptr[t.CPtr] = out_rel_arr if rp is not None: rp[0] = StubMerger._sha1_store_rel return StubMerger._sha1_store_count diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 1023e11..1e83f9a 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -13,6 +13,7 @@ import lib.core.Gen.Translator as HT import lib.Stub.StubMerger.StubMergerStore as StubMergerStore import lib.core.Gen.GenTypes as GenTypes import lib.core.Gen.Generator as Generator +import lib.core.CompilerFactory as CompilerFactory import lib.Stub.StubGen.VpiReader as VpiReader import lib.core.Gen.GenApi as GenApi @@ -2632,6 +2633,16 @@ novirtual class AssignGenMixin: asg_target_ty = self.Gen.VarGen.repl_slot_ty(pool, asg_re) rhs_val: llvmlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, asgn.value, None, 0, trans, asg_target_ty) if rhs_val is None: + # 归属:RHS 为 None 多数是分配失败(池耗尽)。“[MEM] alloc 失败”只给池基址, + # 分不清是主池(mark/reset 回收)还是常驻池(从不 reset)满,也无法知道 + # 当前阶段/文件。故此处把两个池的用量与池标识一并打出来。 + mb_diag: memhub.MemBuddy* = CompilerFactory.factory.Pool + pp_diag: memhub.MemBuddy* = CompilerFactory.factory.PermPool + if mb_diag is not None: + mb_diag.dump_stats("主池(Pool)") + if pp_diag is not None: + pp_diag.dump_stats("常驻池(PermPool)") + stdio.fflush(None) # 增强错误信息:包含文件名 + lineno + AST 节点详情,便于定位 if asgn.value is not None: sha1: str = trans.ModuleSha1 diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index bee949a..7e5fa4f 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -514,50 +514,42 @@ novirtual class CallGenBase: if mod is None or name is None: return None name_len: sizet = string.strlen(name) - # 热点优化:原 4 趟完整链表扫描合并为 2 趟——趟 1 精确匹配(命中即返回)+ 收集首个本模块同名 mangled 候选;趟 2 收集首个 IsDeclared==0 后缀命中与首个任意后缀命中,扫描后按原优先级返回(语义不变) + # 热点优化:原「趟 1 精确匹配+本模块同名、趟 2 declare/任意后缀」两趟完整链表扫描 + # 合并为一趟——同一节点只取一次 function_get_name/NameLen/next(原两趟各取一次, + # 该链表平均数百节点且本函数每次调用都全链遍历,PROF 中本函数及其访问器约占 10%)。 + # 优先级与逐趟结果完全一致:精确匹配命中即返回 > 本模块同名 > declare 后缀 > 任意后缀; + # 同类候选均取链表首个命中。 cur: llvmlite.Function* = mod.FuncHead mangle_hit: llvmlite.Function* = None + declare_hit: llvmlite.Function* = None + any_hit: llvmlite.Function* = None while cur is not None: cur_name: str = llvmlite.function_get_name(cur) if cur_name is not None: # 快速路径:strcmp 判等蕴含首字符相等,首字符不同者跳过 strcmp(mangled 名以 sha1 十六进制打头,裸名多为字母/下划线) if cur_name[0] == name[0] and string.strcmp(cur_name, name) == 0: return cur - # 本模块同名候选:仅接受恰为 "{owner_sha1}.{name}" 的形态(不匹配 "{sha1}.Class.method");declare 与 define 同名同符号,是否 IsDeclared==0 均可返回 - if owner_sha1 is not None and mangle_hit is None: - # 名字长度改取 Function.NameLen(创建时一次算好),免去对热链 - # 每个节点重复 strlen(cur_name)(全程序 strlen 热点来源)。 - cur_len_o: sizet = llvmlite.function_get_name_len(cur) - if cur_len_o == 17 + name_len: - if string.strncmp(cur_name, owner_sha1, 16) == 0 and cur_name[16] == '.': - suffix_start_o: sizet = cur_len_o - name_len - if cur_name[suffix_start_o - 1] == '.': - match_o: int = 1 - for i in range(name_len): - if cur_name[suffix_start_o + i] != name[i]: - match_o = 0 - break - if match_o == 1: - mangle_hit = cur - cur = llvmlite.function_get_next(cur) - if mangle_hit is not None: - return mangle_hit - # 后缀匹配(.name):不跳过 declare(方法 stub 也是 declare,由调用处判断 IsDeclared 决定用裸名或 sha1 前缀名)。本模块同名 define 必须优先于跨模块 stub declare,无同名 define 时回退 stub declare - declare_hit: llvmlite.Function* = None - any_hit: llvmlite.Function* = None - cur = mod.FuncHead - while cur is not None: - cur_name2: str = llvmlite.function_get_name(cur) - if cur_name2 is not None: - # 名字长度改取 Function.NameLen(创建时一次算好),免去热链上 - # 每个节点重复 strlen(cur_name2);与 strlen(Name) 恒等,不改匹配语义。 + # 名字长度取 Function.NameLen(创建时一次算好),免去对热链每个节点重复 strlen cur_len: sizet = llvmlite.function_get_name_len(cur) + # 本模块同名候选:仅接受恰为 "{owner_sha1}.{name}" 的形态(不匹配 "{sha1}.Class.method");declare 与 define 同名同符号,是否 IsDeclared==0 均可返回 + if owner_sha1 is not None and mangle_hit is None and cur_len == 17 + name_len: + if string.strncmp(cur_name, owner_sha1, 16) == 0 and cur_name[16] == '.': + suffix_start_o: sizet = cur_len - name_len + if cur_name[suffix_start_o - 1] == '.': + match_o: int = 1 + for i in range(name_len): + if cur_name[suffix_start_o + i] != name[i]: + match_o = 0 + break + if match_o == 1: + mangle_hit = cur + # 后缀匹配(.name):不跳过 declare(方法 stub 也是 declare,由调用处判断 IsDeclared 决定用裸名或 sha1 前缀名) if cur_len > name_len + 1: suffix_start: sizet = cur_len - name_len - if cur_name2[suffix_start - 1] == '.': + if cur_name[suffix_start - 1] == '.': match: int = 1 for i in range(name_len): - if cur_name2[suffix_start + i] != name[i]: + if cur_name[suffix_start + i] != name[i]: match = 0 break if match == 1: @@ -568,6 +560,8 @@ novirtual class CallGenBase: if cur.IsDeclared == 0 and declare_hit is None: declare_hit = cur cur = llvmlite.function_get_next(cur) + if mangle_hit is not None: + return mangle_hit if declare_hit is not None: return declare_hit return any_hit @@ -947,23 +941,32 @@ novirtual class CallGenBase: return None return (str | t.CPtr)(v) - # _CacheModuleRetTyTexts:把某模块 text.ll 全部 define 行的返回类型写入持久缓存(键/值深拷贝进 PermPool,同名只记首次,只登记 "{sha1}." 开头的本模块 mangled 名);仅在成功读到 text.ll 后调用,负结果绝不缓存 + # _CacheModuleRetTyTexts:单趟扫描某模块 text.ll——把全部 define 行的返回类型写入持久缓存(键/值深拷贝进 PermPool,同名只记首次,只登记 "{sha1}." 开头的本模块 mangled 名),并在同一趟里返回首个匹配 search_buf 的 define 的返回类型(未匹配返回 None);仅在成功读到 text.ll 后调用,负结果绝不缓存 def _CacheModuleRetTyTexts(self, class_sha1: str, text_buf: bytes, - text_len: sizet) -> None: - """登记 text.ll 全部 define 行到持久缓存(建表/分配失败即不缓存,回退原扫描)""" - if self.Gen is None or class_sha1 is None or text_buf is None or text_len == 0: - return - perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool - if perm is None: - return - ht: hashtable.HashTable* = self.Gen.CallGenBase.MethodRetTyMap - if ht is None: - ht = hashtable.HashTable(perm) - if ht is None: - return - self.Gen.CallGenBase.MethodRetTyMap = ht - sha1_len: sizet = string.strlen(class_sha1) + text_len: sizet, pool: memhub.MemBuddy*, + search_buf: str, search_str_len: sizet) -> llvmlite.LLVMType*: + """单趟「登记 + 查找」text.ll 全部 define 行(建表/分配失败时只查找不登记)""" + if text_buf is None or text_len == 0: + return None + # 合并原「先全量登记一遍、再二次扫描查找」的两趟全文件遍历(PROF:两次扫描合计约 14%)。 + # 建表失败(PermPool 未就绪等)不影响查找:cache_on=0 时只走同趟查找。 + cache_on: int = 0 + ht: hashtable.HashTable* = None + perm: memhub.MemBuddy* = None + sha1_len: sizet = 0 + if self.Gen is not None and class_sha1 is not None: + perm = CompilerFactory.factory.PermPool + if perm is not None: + ht = self.Gen.CallGenBase.MethodRetTyMap + if ht is None: + ht = hashtable.HashTable(perm) + if ht is not None: + self.Gen.CallGenBase.MethodRetTyMap = ht + if ht is not None: + cache_on = 1 + sha1_len = string.strlen(class_sha1) pos: sizet = 0 + result_ty: llvmlite.LLVMType* = None while pos < text_len: ls: sizet = pos le: sizet = pos @@ -998,8 +1001,8 @@ novirtual class CallGenBase: break name_end += 1 name_len: sizet = name_end - name_start - # 只登记本模块 mangled 名 "{sha1}.{后缀}"(查询键恒为此前缀) - if name_len > sha1_len + 1: + # 只登记本模块 mangled 名 "{sha1}.{后缀}"(查询键恒为此前缀);表不可用时跳过登记 + if cache_on != 0 and name_len > sha1_len + 1: if string.strncmp(text_buf + name_start, class_sha1, sha1_len) == 0 \ and text_buf[name_start + sha1_len] == '.': key_buf: bytes = stdlib.malloc(name_len + 1) @@ -1018,7 +1021,16 @@ novirtual class CallGenBase: tcopy[rt_len] = '\0' ht.set_str(kcopy, tcopy) stdlib.free(key_buf) + # 同趟查找:首个与 search_buf 匹配、且后随 '"' / '(' / 行尾的 define(判定与原二次扫描逐字一致),先命中者为准 + if result_ty is None and name_start + search_str_len <= ls + ll: + if string.strncmp(text_buf + name_start, search_buf, search_str_len) == 0: + end_ch: int = 0 + if name_start + search_str_len < ls + ll: + end_ch = text_buf[name_start + search_str_len] + if end_ch == 34 or end_ch == 40 or end_ch == 0: + result_ty = self._ParseLLVMRetTyStr(pool, text_buf, ls + 7, at_pos) pos = le + 1 + return result_ty # _LookupMethodRetTyFromTextLL:读取 {temp_dir}/{sha1[0:2]}/{sha1}.text.ll (level=1),按 "{sha1}.{class_name}.{method_name}" 搜索 define 行并解析返回类型;未找到/文件不存在返回 None @@ -1108,51 +1120,10 @@ novirtual class CallGenBase: class_sha1, class_name, method_name) search_str_len: sizet = string.strlen(search_buf) - # 在 text.ll 中搜索 define 行包含 search_buf - buf_len: sizet = string.strlen(read_buf) - - # 已成功读到本模块 text.ll:登记其全部 define 行供后续 O(1) 命中(下方原扫描保留为未命中路径) - self._CacheModuleRetTyTexts(class_sha1, read_buf, buf_len) - - pos: sizet = 0 - result_ty: llvmlite.LLVMType* = None - - while pos < buf_len and result_ty is None: - ls: sizet = pos - le: sizet = pos - while le < buf_len: - if read_buf[le] == '\n': - break - le += 1 - ll: sizet = le - ls - - # define 行判定:快速路径先看行首字节是否 'd'(仅否定短路,不改变命中结果) - if ll >= 7 and read_buf[ls] == 'd' and string.strncmp(read_buf + ls, "define ", 7) == 0: - # 找 @ 符号 - at_pos: sizet = ls + 7 - while at_pos < ls + ll: - if read_buf[at_pos] == '@': - break - at_pos += 1 - if at_pos < ls + ll: - # 跳过可选引号 - name_start: sizet = at_pos + 1 - if name_start < ls + ll and read_buf[name_start] == '"': - name_start += 1 - # 检查是否匹配 search_buf - if name_start + search_str_len <= ls + ll: - if string.strncmp(read_buf + name_start, search_buf, search_str_len) == 0: - # 函数名边界检查:search 后必须是 '"'(带引号名)、'('(参数表)或行尾, - # 否则 "sha1.func" 会误匹配 "sha1.func_helper" 等前缀同名函数 - name_end: sizet = name_start + search_str_len - end_ch: int = 0 - if name_end < ls + ll: - end_ch = read_buf[name_end] - if end_ch == 34 or end_ch == 40 or end_ch == 0: - # 匹配!提取返回类型: [ls+7, at_pos) - result_ty = self._ParseLLVMRetTyStr(pool, read_buf, ls + 7, at_pos) - - pos = le + 1 + # 单趟扫描 text.ll:登记其全部 define 行供后续 O(1) 命中,并同趟取出首个匹配项。 + # 文件长度直接用 read_all 的返回值,省掉一次整文件 strlen。 + result_ty: llvmlite.LLVMType* = self._CacheModuleRetTyTexts( + class_sha1, read_buf, sizet(text_len), pool, search_buf, search_str_len) stdlib.free(search_buf) stdlib.free(read_buf) diff --git a/App/lib/core/Gen/Call/CallGenCtor.vp b/App/lib/core/Gen/Call/CallGenCtor.vp index c7ab31c..9b5bc6d 100644 --- a/App/lib/core/Gen/Call/CallGenCtor.vp +++ b/App/lib/core/Gen/Call/CallGenCtor.vp @@ -392,6 +392,18 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase): if arg_val is None: continue payload_ptr: llvmlite.Value* = llvmlite.build_gep_struct(builder, renum_ty, pfe.Ty, tmp, payload_idx) if payload_ptr is None: continue + # payload 槽位是 i32/i64(布局见 ClassGenEnum:{i32 __tag, _p1, _p2, ...}), + # 而变体成员可能是指针(Ptr.Pointee / Func.Ret / Array.ElemTy / Struct.Fields …)。 + # 这是编译器自身的表示转换(tagged union 打包),不是用户代码的隐式转换: + # 必须显式 ptrtoint 后存入,读取侧按成员类型 bitcast 槽位地址再 load(对称)。 + # 禁止隐式转换后此处不能再依赖 coerce_to_type 的静默 ptrtoint。 + if (self.Gen.ExprGenBase.is_ptr_type(arg_val.Ty) != 0 + and self.Gen.ExprGenBase.is_ptr_type(pfe.Ty) == 0 + and self.Gen.ExprGenBase.get_llvm_type_bits(pfe.Ty) != 0): + casted_arg: llvmlite.Value* = llvmlite.build_ptrtoint(builder, arg_val, pfe.Ty) + if casted_arg is None: + continue + arg_val = casted_arg # 类型转换并 store(payload 位置可能是 i32 或 i64,按字段类型 coerce) arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, pfe.Ty, arg) llvmlite.build_store(builder, arg_val, payload_ptr) diff --git a/App/lib/core/Gen/Class/ClassGenEnum.vp b/App/lib/core/Gen/Class/ClassGenEnum.vp index 5cf56a5..17df362 100644 --- a/App/lib/core/Gen/Class/ClassGenEnum.vp +++ b/App/lib/core/Gen/Class/ClassGenEnum.vp @@ -19,9 +19,9 @@ from . import ClassGenBase # register_enum 永不调用 → 所有 CEnum 注册失败 → 裸名枚举访问 NameError) # ============================================================ define ENUM_MEMBER_MAX_LOCAL = 128 -define RENUM_ENUM_CLASS_MAX = 64 -define RENUM_ENUM_VARIANT_PER_CLASS = 32 -define RENUM_ENUM_ALIAS_MAX = 512 +define RENUM_CLASS_MAX = 64 +define RENUM_VARIANT_PER_CLASS = 32 +define RENUM_ALIAS_MAX = 512 # ============================================================ # ClassGenEnum - ClassGenMixin 拆分(阶段 D):enum/union/renum 定义与变体表 @@ -1112,7 +1112,12 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase): self.RenumVariantTags = CompilerFactory.factory.PermPool.alloc(8 * RENUM_CLASS_MAX * RENUM_VARIANT_PER_CLASS) if (self.RenumClassNames is None or self.RenumClassVariantCounts is None or self.RenumVariantNames is None or self.RenumVariantTags is None): + # A None table means the perm-pool request failed (size 0 or pool + # exhausted). Returning here left the table empty while the caller + # still reported "registered", so match dispatch silently lost every + # REnum case. self.RenumClassNames = None + self.Gen.TypeGenErr.fatal_error(cd, f"REnum 变体表分配失败(class='{class_name}'):PermPool 返回 NULL,变体表不可用", "InternalCompilerError") return string.memset(self.RenumClassNames, 0, 8 * RENUM_CLASS_MAX) string.memset(self.RenumClassVariantCounts, 0, 8 * RENUM_CLASS_MAX) @@ -1140,6 +1145,9 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase): # 表已满 if self.RenumClassCount >= RENUM_CLASS_MAX: + # Silently dropping the class made its later `match` lose every case; a + # real limit breach must be reported instead. + self.Gen.TypeGenErr.fatal_error(cd, f"REnum 类数超过上限 {RENUM_CLASS_MAX}:无法注册 class='{class_name}'", "InternalCompilerError") return # 注册类名(strcpy 到 perm_pool:1a 每文件 reset 后 AST 裸指针悬空, @@ -1148,6 +1156,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase): cn_len_rv: sizet = string.strlen(class_name) cn_buf_rv: str = CompilerFactory.factory.PermPool.alloc(cn_len_rv + 1) if cn_buf_rv is None: + self.Gen.TypeGenErr.fatal_error(cd, f"REnum 类名副本分配失败(class='{class_name}'):PermPool 返回 NULL", "InternalCompilerError") return string.strcpy(cn_buf_rv, class_name) self.RenumClassNames[idx] = sizet(cn_buf_rv) @@ -1238,6 +1247,9 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase): vn_len_rv: sizet = string.strlen(vname_src) vn_buf_rv: str = CompilerFactory.factory.PermPool.alloc(vn_len_rv + 1) if vn_buf_rv is None: + # Breaking here registered a partial variant table: the class would + # keep some variants and silently lose every later one. + self.Gen.TypeGenErr.fatal_error(cd, f"REnum 变体名副本分配失败(class='{class_name}'):PermPool 返回 NULL", "InternalCompilerError") break string.strcpy(vn_buf_rv, vname_src) self.RenumVariantNames[name_offset] = sizet(vn_buf_rv) @@ -1359,7 +1371,10 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase): if (self.RenumAliasClass is None or self.RenumAliasField is None or self.RenumAliasIndex is None or self.RenumAliasTy is None or self.RenumAliasVariant is None): + # Same as the variant table: a silent bail left the alias table empty, + # so match bindings later failed with an unrelated NameError. self.RenumAliasClass = None + self.Gen.TypeGenErr.fatal_error(None, f"REnum 字段别名表分配失败(class='{class_name}'):PermPool 返回 NULL", "InternalCompilerError") return string.memset(self.RenumAliasClass, 0, 8 * RENUM_ALIAS_MAX) string.memset(self.RenumAliasField, 0, 8 * RENUM_ALIAS_MAX) @@ -1376,6 +1391,9 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase): CompilerFactory.factory.RenumAliasCount = self.RenumAliasCount if self.RenumAliasCount >= RENUM_ALIAS_MAX: + # Dropping the record silently made the variant field bind to a wrong-typed + # slot (or fail with NameError) instead of reporting the limit breach. + self.Gen.TypeGenErr.fatal_error(None, f"REnum 字段别名数超过上限 {RENUM_ALIAS_MAX}:无法注册 {class_name}.{field_name}", "InternalCompilerError") return # 去重检查(去指针化):class 槽位存 perm_pool 字符串拷贝,用 strcmp 内容比较—— diff --git a/App/lib/core/Gen/Class/ClassGenVTable.vp b/App/lib/core/Gen/Class/ClassGenVTable.vp index fcb9818..642dd9a 100644 --- a/App/lib/core/Gen/Class/ClassGenVTable.vp +++ b/App/lib/core/Gen/Class/ClassGenVTable.vp @@ -14,6 +14,7 @@ import lib.core.CompilerFactory as CompilerFactory from . import ClassGenBase import lib.core.Gen.GenApi as GenApi +import lib.core.Gen.DebugInfoGen as DebugInfoGen # 方法元数据 flag:TLS(@t.TLS 装饰器,对齐 ClassGenBase 本地常量;本地副本避免跨模块 CDefine 查找) @@ -757,6 +758,11 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase): return 0 llvmlite.position_at_end(func_builder, entry_blk) + # --traceback: attach a DISubprogram to this method define (same as the + # module-level function path). No-op when DI is disabled. + dbg_pushed: int = DebugInfoGen.PushFunction(pool, func_builder, func, + fd.lineno, fd.col_offset + 1) + # @t.TLS 一次性初始化守卫(对齐 TPC HandlesFunctions.py L1763-1792; # 与 FuncGenMixin.translate_function_def 同一实现,类方法同样支持) if is_tls_m != 0: @@ -1024,6 +1030,9 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase): trans._closure_env_count = old_env_count trans._stop_iter_flag_ptr = old_stop_iter_flag + if dbg_pushed != 0: + DebugInfoGen.PopFunction(func_builder) + return 0 # ============================================================ # _generate_before_init — 生成 __before_init__ 函数 diff --git a/App/lib/core/Gen/DebugInfoGen.vp b/App/lib/core/Gen/DebugInfoGen.vp new file mode 100644 index 0000000..fc300c4 --- /dev/null +++ b/App/lib/core/Gen/DebugInfoGen.vp @@ -0,0 +1,443 @@ +import t, c +from stdint import * +import llvmlite +import memhub +import string +import stdlib +import viperlib +import lib.core.VLogger as VLogger + +# ============================================================ +# DebugInfoGen - function-level DWARF metadata emitter (llvmlite text IR) +# +# Node layout mirrors TPC lib/core/LLVMCG/DebugInfo.py node-for-node: +# !DIFile -> !{ null } -> !DISubroutineType -> distinct !DICompileUnit +# -> distinct !DISubprogram (per define) -> !DILocation (per statement) +# module tail: !llvm.dbg.cu + !llvm.module.flags (Debug Info Version 3) +# +# Enabled only while --traceback is on: the Phase layer mirrors +# BuildPipeline._traceback_mode here via SetEnabled(). While disabled every +# entry point returns before touching any llvmlite DI API, so the emitted IR +# stays byte-identical to the pre-DWARF output. +# +# Scope/statement hooks are driven from the Gen layer: +# Translator.translate -> BeginModule / Finalize +# FuncGenDef.translate_function_def / ClassGenVTable._translate_method / +# FuncGenMixin (nested + lambda) -> PushFunction / PopFunction +# StmtGenMixin.translate_stmt -> SetStatement +# ============================================================ + +# ---- module-scope state (reset per module by BeginModule) ---- +_g_enabled: int = 0 +_g_finalized: int = 0 +_g_mod: llvmlite.LLVMModule* = None +_g_file_path: str = None +_g_file_id: int = -1 +_g_null_id: int = -1 +_g_subtype_id: int = -1 +_g_cu_id: int = -1 +_g_cur_sp: int = -1 +_g_cur_entry_ref: str = None +_g_cur_builder: llvmlite.IRBuilder* = None +_g_depth: int = 0 + +# Push/Pop save stack: enclosing (sp id + 1, entry DILocation ref, builder) +# per level. +1 so 0 means "no enclosing function" (sp ids are >= 0). The saved +# builder is what makes SetStatement reject a builder that is not the one the +# current DISubprogram was pushed on (e.g. an async state machine emitted inside +# a bracketed function): such instructions must carry no !dbg, otherwise LLVM's +# verifier rejects "!dbg attachment points at wrong subprogram for function". +DBG_STACK_MAX: t.CDefine = 256 +_g_stk_sp: sizet* = None +_g_stk_ref: str = None +_g_stk_bld: str = None + +# DILocation cache: direct-mapped, up to 8-way linear probe, key (sp, line, col). +DBG_LOC_SLOTS: t.CDefine = 8192 +DBG_LOC_PROBE: t.CDefine = 8 +DBG_LOC_KEY_STRIDE: t.CDefine = 3 +_g_loc_key: sizet* = None +_g_loc_ref: str = None +_g_loc_used: bytes = None + + +# ============================================================ +# SetEnabled / IsEnabled - mirror the --traceback switch +# ============================================================ +def SetEnabled(flag: bool) -> int: + """Enable/disable DI emission (mirrors BuildPipeline._traceback_mode).""" + global _g_enabled + if flag: + _g_enabled = 1 + else: + _g_enabled = 0 + return 0 + + +def IsEnabled() -> int: + return _g_enabled + + +# ============================================================ +# _EnsureStorage - lazily allocate the long-lived stack / cache buffers +# ============================================================ +def _EnsureStorage() -> int: + global _g_stk_sp, _g_stk_ref, _g_stk_bld, _g_loc_key, _g_loc_ref, _g_loc_used + if _g_stk_sp is not None: + return 0 + sbuf1: bytes = stdlib.malloc(DBG_STACK_MAX * 8) + sbuf2: bytes = stdlib.malloc(DBG_STACK_MAX * 8) + sbuf3: bytes = stdlib.malloc(DBG_STACK_MAX * 8) + kbuf: bytes = stdlib.malloc(DBG_LOC_SLOTS * DBG_LOC_KEY_STRIDE * 8) + rbuf: bytes = stdlib.malloc(DBG_LOC_SLOTS * 8) + ubuf: bytes = stdlib.malloc(DBG_LOC_SLOTS) + if sbuf1 is None or sbuf2 is None or sbuf3 is None or kbuf is None or rbuf is None or ubuf is None: + VLogger.error("--traceback: DWARF 元数据缓冲分配失败,终止编译", "DWARF") + return 1 + string.memset(sbuf1, 0, DBG_STACK_MAX * 8) + string.memset(sbuf2, 0, DBG_STACK_MAX * 8) + string.memset(sbuf3, 0, DBG_STACK_MAX * 8) + string.memset(kbuf, 0, DBG_LOC_SLOTS * DBG_LOC_KEY_STRIDE * 8) + string.memset(rbuf, 0, DBG_LOC_SLOTS * 8) + string.memset(ubuf, 0, DBG_LOC_SLOTS) + _g_stk_sp = (sizet*)(sbuf1) + _g_stk_ref = sbuf2 + _g_stk_bld = sbuf3 + _g_loc_key = (sizet*)(kbuf) + _g_loc_ref = rbuf + _g_loc_used = ubuf + return 0 + + +def _RefOf(pool: memhub.MemBuddy*, node_id: int) -> str: + """Allocate a persistent "!N" reference string for a DI node id.""" + buf: str = pool.alloc(24) + if buf is None: + return None + viperlib.snprintf(buf, 24, "!%d", node_id) + return buf + + +# ============================================================ +# BeginModule - start of one module translation +# ============================================================ +def BeginModule(mod: llvmlite.LLVMModule*, file_path: str) -> int: + """Reset per-module DI state. Call once at the start of each module.""" + global _g_mod, _g_file_path, _g_finalized, _g_file_id, _g_null_id + global _g_subtype_id, _g_cu_id, _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth + if _g_enabled == 0: + return 0 + if _EnsureStorage() != 0: + return 1 + _g_mod = mod + _g_file_path = file_path + _g_finalized = 0 + _g_file_id = -1 + _g_null_id = -1 + _g_subtype_id = -1 + _g_cu_id = -1 + _g_cur_sp = -1 + _g_cur_entry_ref = None + _g_cur_builder = None + _g_depth = 0 + if _g_loc_used is not None: + string.memset(_g_loc_used, 0, DBG_LOC_SLOTS) + return 0 + + +# ============================================================ +# _EnsureModuleNodes - DIFile / !{ null } / DISubroutineType / DICompileUnit +# (created lazily at the first define, once per module) +# ============================================================ +def _EnsureModuleNodes(pool: memhub.MemBuddy*) -> int: + global _g_file_id, _g_null_id, _g_subtype_id, _g_cu_id + if _g_cu_id >= 0: + return 0 + mod: llvmlite.LLVMModule* = _g_mod + if mod is None: + return 1 + # DIFile: basename + absolute directory, backslashes normalised to '/' + src: str = _g_file_path + if src is None: + src = "" + slen: sizet = string.strlen(src) + nbuf: str = pool.alloc(slen + 4) + if nbuf is None: + VLogger.error("--traceback: DIFile 路径缓冲分配失败,终止编译", "DWARF") + return 1 + if slen > 0: + string.strncpy(nbuf, src, slen) + nbuf[slen] = '\0' + ni: sizet = 0 + for ni in range(slen): + if nbuf[ni] == '\\': + nbuf[ni] = '/' + fname: str = nbuf + directory: str = "" + slash: str = string.strrchr(nbuf, '/') + if slash is not None: + fname = slash + 1 + slash[0] = '\0' + directory = nbuf + dsize: sizet = string.strlen(fname) + string.strlen(directory) + 96 + dbuf: str = pool.alloc(dsize) + if dbuf is None: + VLogger.error("--traceback: DIFile 节点缓冲分配失败,终止编译", "DWARF") + return 1 + viperlib.snprintf(dbuf, dsize, "!DIFile(filename: \"%s\", directory: \"%s\")", fname, directory) + fid: int = llvmlite.module_add_di_node(mod, pool, dbuf) + if fid < 0: + VLogger.error("--traceback: DIFile 元数据节点分配失败,终止编译", "DWARF") + return 1 + _g_file_id = fid + # types: !{ null } placeholder list, then DISubroutineType referencing it + nid: int = llvmlite.module_add_di_node(mod, pool, "!{ null }") + if nid < 0: + VLogger.error("--traceback: DISubroutineType 类型表分配失败,终止编译", "DWARF") + return 1 + _g_null_id = nid + tbuf: str = pool.alloc(64) + if tbuf is None: + VLogger.error("--traceback: DISubroutineType 缓冲分配失败,终止编译", "DWARF") + return 1 + viperlib.snprintf(tbuf, 64, "!DISubroutineType(types: !%d)", _g_null_id) + tid: int = llvmlite.module_add_di_node(mod, pool, tbuf) + if tid < 0: + VLogger.error("--traceback: DISubroutineType 元数据节点分配失败,终止编译", "DWARF") + return 1 + _g_subtype_id = tid + cbuf: str = pool.alloc(256) + if cbuf is None: + VLogger.error("--traceback: DICompileUnit 缓冲分配失败,终止编译", "DWARF") + return 1 + viperlib.snprintf(cbuf, 256, + "distinct !DICompileUnit(language: DW_LANG_C99, file: !%d, producer: \"TPC\", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)", + _g_file_id) + cid: int = llvmlite.module_add_di_node(mod, pool, cbuf) + if cid < 0: + VLogger.error("--traceback: DICompileUnit 元数据节点分配失败,终止编译", "DWARF") + return 1 + _g_cu_id = cid + return 0 + + +# ============================================================ +# _LocRefAt - read a cached DILocation ref string pointer +# ============================================================ +def _LocRefAt(slot: int) -> str: + slotp: str | t.CPtr = (t.CPtr)(uint64(_g_loc_ref) + sizet(slot) * 8) + r: str = slotp[0] + return r + + +# ============================================================ +# _EnsureLocation - DILocation ref for (sp, line, col), created + cached once +# ============================================================ +def _EnsureLocation(pool: memhub.MemBuddy*, sp: int, line: int, col: int) -> str: + if line <= 0: + return _g_cur_entry_ref + c: int = col + if c <= 0: + c = 1 + if _g_loc_used is None or _g_mod is None: + return None + h: uint64 = (uint64(sp) * 1000003 + uint64(line) * 31 + uint64(c)) & uint64(DBG_LOC_SLOTS - 1) + pi: int = 0 + while pi < DBG_LOC_PROBE: + slot: int = int((h + uint64(pi)) & uint64(DBG_LOC_SLOTS - 1)) + if _g_loc_used[slot] == 0: + break + kbase: int = slot * DBG_LOC_KEY_STRIDE + if int(_g_loc_key[kbase]) == sp and int(_g_loc_key[kbase + 1]) == line \ + and int(_g_loc_key[kbase + 2]) == c: + return _LocRefAt(slot) + pi += 1 + lbuf: str = pool.alloc(96) + if lbuf is None: + return None + viperlib.snprintf(lbuf, 96, "!DILocation(line: %d, column: %d, scope: !%d)", line, c, sp) + lid: int = llvmlite.module_add_di_node(_g_mod, pool, lbuf) + if lid < 0: + VLogger.error("--traceback: DILocation 元数据节点分配失败,终止编译", "DWARF") + return None + lref: str = _RefOf(pool, lid) + if lref is None: + VLogger.error("--traceback: DILocation 引用缓冲分配失败,终止编译", "DWARF") + return None + if pi < DBG_LOC_PROBE: + slot2: int = int((h + uint64(pi)) & uint64(DBG_LOC_SLOTS - 1)) + kbase2: int = slot2 * DBG_LOC_KEY_STRIDE + _g_loc_key[kbase2] = sizet(sp) + _g_loc_key[kbase2 + 1] = sizet(line) + _g_loc_key[kbase2 + 2] = sizet(c) + rslot: str | t.CPtr = (t.CPtr)(uint64(_g_loc_ref) + sizet(slot2) * 8) + rslot[0] = lref + _g_loc_used[slot2] = 1 + return lref + + +# ============================================================ +# PushFunction - create the DISubprogram for one define +# ============================================================ +def PushFunction(pool: memhub.MemBuddy*, builder: llvmlite.IRBuilder*, + func: llvmlite.Function*, line: int, col: int) -> int: + """Attach a DISubprogram to one define and set the builder's entry DILocation. + + Returns 1 when pushed (caller must call PopFunction), 0 when DI is disabled + or not applicable. Every instruction emitted afterwards on `builder` carries + ", !dbg !N" until the next SetStatement. + """ + global _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth + if _g_enabled == 0: + return 0 + if pool is None or builder is None or func is None or _g_mod is None: + return 0 + if _g_depth >= DBG_STACK_MAX: + VLogger.error("--traceback: DWARF 函数嵌套深度超上限,终止编译", "DWARF") + return 0 + if _EnsureModuleNodes(pool) != 0: + return 0 + name: str = llvmlite.function_get_name(func) + if name is None: + VLogger.error("--traceback: 函数名为空,无法发射 DISubprogram,终止编译", "DWARF") + return 0 + dline: int = line + if dline <= 0: + dline = 1 + dcol: int = col + if dcol <= 0: + dcol = 1 + ssize: sizet = string.strlen(name) + 320 + spbuf: str = pool.alloc(ssize) + if spbuf is None: + VLogger.error("--traceback: DISubprogram 缓冲分配失败,终止编译", "DWARF") + return 0 + viperlib.snprintf(spbuf, ssize, + "distinct !DISubprogram(name: \"%s\", scope: !%d, file: !%d, type: !%d, line: %d, scopeLine: %d, unit: !%d, spFlags: DISPFlagDefinition)", + name, _g_file_id, _g_file_id, _g_subtype_id, dline, dline, _g_cu_id) + sp_id: int = llvmlite.module_add_di_node(_g_mod, pool, spbuf) + if sp_id < 0: + VLogger.error("--traceback: DISubprogram 元数据节点分配失败,终止编译", "DWARF") + return 0 + sp_ref: str = _RefOf(pool, sp_id) + if sp_ref is None: + VLogger.error("--traceback: DISubprogram 引用缓冲分配失败,终止编译", "DWARF") + return 0 + llvmlite.function_set_dbg(func, sp_ref) + # save the enclosing function's DI context (LIFO), then switch to this one + _g_stk_sp[_g_depth] = sizet(_g_cur_sp + 1) + rslot: str | t.CPtr = (t.CPtr)(uint64(_g_stk_ref) + sizet(_g_depth) * 8) + rslot[0] = _g_cur_entry_ref + bslot: str | t.CPtr = (t.CPtr)(uint64(_g_stk_bld) + sizet(_g_depth) * 8) + bslot[0] = _g_cur_builder + _g_depth += 1 + _g_cur_sp = sp_id + _g_cur_builder = builder + entry_ref: str = _EnsureLocation(pool, sp_id, dline, dcol) + if entry_ref is None: + VLogger.error("--traceback: 入口 DILocation 生成失败,终止编译", "DWARF") + return 0 + _g_cur_entry_ref = entry_ref + llvmlite.builder_set_debug_location(builder, entry_ref) + return 1 + + +# ============================================================ +# PopFunction - restore the enclosing function's DI context +# ============================================================ +def PopFunction(builder: llvmlite.IRBuilder*) -> int: + global _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth + if _g_enabled == 0: + return 0 + if _g_depth <= 0: + return 0 + _g_depth -= 1 + _g_cur_sp = int(_g_stk_sp[_g_depth]) - 1 + rslot: str | t.CPtr = (t.CPtr)(uint64(_g_stk_ref) + sizet(_g_depth) * 8) + _g_cur_entry_ref = rslot[0] + bslot: str | t.CPtr = (t.CPtr)(uint64(_g_stk_bld) + sizet(_g_depth) * 8) + _g_cur_builder = (llvmlite.IRBuilder*)(bslot[0]) + if builder is not None: + llvmlite.builder_clear_debug_location(builder) + return 0 + + +# ============================================================ +# SetStatement - point the current builder at a statement DILocation +# ============================================================ +def SetStatement(pool: memhub.MemBuddy*, builder: llvmlite.IRBuilder*, + line: int, col: int) -> int: + """Update the builder's DILocation to the current statement (line precision). + + No-op outside a DI-attached define (module-level code / synthetic defines), + so instructions outside a function body never reference a function's scope. + """ + if _g_enabled == 0 or pool is None or builder is None: + return 0 + if _g_cur_sp < 0: + return 0 + ref: str = _EnsureLocation(pool, _g_cur_sp, line, col) + if ref is None: + return 0 + llvmlite.builder_set_debug_location(builder, ref) + return 1 + + +# ============================================================ +# _ModuleHasDefine - does the module emit any define (IsDeclared == 0)? +# ============================================================ +def _ModuleHasDefine(mod: llvmlite.LLVMModule*) -> int: + if mod is None: + return 0 + cur: llvmlite.Function* = mod.FuncHead + steps: int = 0 + while cur is not None: + steps += 1 + if steps > 100000: + break + if llvmlite.function_is_declared(cur) == 0: + return 1 + cur = llvmlite.function_get_next(cur) + return 0 + + +# ============================================================ +# Finalize - register !llvm.dbg.cu / !llvm.module.flags before dump_ir +# ============================================================ +def Finalize(pool: memhub.MemBuddy*) -> int: + """Module-level DI tail; idempotent per module, no-op when no SP was emitted.""" + global _g_finalized + if _g_enabled == 0: + return 0 + if _g_finalized != 0: + return 0 + mod: llvmlite.LLVMModule* = _g_mod + if mod is None or pool is None: + return 0 + _g_finalized = 1 + # Self-check (mirrors TPC Phase2Translator.py:3444): a module that emits a + # define but carries no DI at all loses line info for its frames. + if mod.DIMetaCount == 0 and _ModuleHasDefine(mod) != 0: + VLogger.warning("--traceback: 本模块发射了 define 但 IR 未携带 DWARF (!llvm.dbg.cu)——该模块帧在 Ctrl+C 栈里将只显示地址", "DWARF") + if _g_cu_id < 0: + return 0 + cu_ref: str = _RefOf(pool, _g_cu_id) + if cu_ref is None: + VLogger.error("--traceback: DICompileUnit 引用缓冲分配失败,终止编译", "DWARF") + return 1 + llvmlite.module_add_named_metadata(mod, pool, "llvm.dbg.cu", cu_ref) + fbuf: str = pool.alloc(64) + if fbuf is None: + VLogger.error("--traceback: 模块级调试标志缓冲分配失败,终止编译", "DWARF") + return 1 + viperlib.snprintf(fbuf, 64, "!{i32 2, !\"Debug Info Version\", i32 3}") + flags_id: int = llvmlite.module_add_di_node(mod, pool, fbuf) + if flags_id < 0: + VLogger.error("--traceback: llvm.module.flags 元数据节点分配失败,终止编译", "DWARF") + return 1 + fl_ref: str = _RefOf(pool, flags_id) + if fl_ref is None: + VLogger.error("--traceback: llvm.module.flags 引用缓冲分配失败,终止编译", "DWARF") + return 1 + llvmlite.module_add_named_metadata(mod, pool, "llvm.module.flags", fl_ref) + return 0 diff --git a/App/lib/core/Gen/Expr/ExprGenBase.vp b/App/lib/core/Gen/Expr/ExprGenBase.vp index 0be5e0a..8f385ba 100644 --- a/App/lib/core/Gen/Expr/ExprGenBase.vp +++ b/App/lib/core/Gen/Expr/ExprGenBase.vp @@ -363,12 +363,13 @@ novirtual class ExprGenBase: return llvmlite.build_inttoptr(builder, llvmlite.const_int64(builder.Pool, 0), target_ty) self.Gen.TypeGenErr.fatal_error(node, "整数隐式转指针已禁止:必须显式强转为指针(如 t.CPtr(x))才能把整数赋给指针类型", "CastError") return val - # 指针 → 整数: ptrtoint(当目标明确是整数而非指针时) - # 注意: target_bits == 8 时跳过 ptrtoint,走后面的 build_load 解引用首字符 - # 修复: buf[idx] = '\0' 中 '\0' 是 CONST_STR → i8* 指针, - # ptrtoint 会截断地址低 8 位(非 0),应 load 解引用取首字符 0 - if self.is_ptr_type(val.Ty) != 0 and target_bits != 0 and target_bits != 8 and self.is_ptr_type(target_ty) == 0: - return llvmlite.build_ptrtoint(builder, val, target_ty) + # Pointer -> integer: rejected (symmetric with the integer -> pointer rule above). + # No implicit pointer <-> integer conversion is allowed; an explicit cast is + # required. (The old code silently did ptrtoint, and for 8-bit targets fell + # through to the build_load branch below, copying the pointee's first byte.) + if self.is_ptr_type(val.Ty) != 0 and target_bits != 0 and self.is_ptr_type(target_ty) == 0: + self.Gen.TypeGenErr.fatal_error(node, "指针隐式转整数已禁止:必须显式强转(如 uint64(p) / t.CInt64T(p) / int(p))才能把指针赋给整数类型", "CastError") + return val if val_bits != 0 and target_bits != 0: if val_bits == target_bits: return val @@ -432,11 +433,15 @@ novirtual class ExprGenBase: if bc_agg is not None: return bc_agg return tmp_agg - # 指针 → 非指针值: build_load 解引用 - # 适用于: 指针 → 整数 (如 i8* → i8), 指针 → 结构体值 - # 当构造器返回 Ptr(Struct) 但目标变量是 Struct 值类型时,需要 load + # Pointer -> non-pointer value: only aggregate targets keep the implicit load + # (a constructor returning Ptr(Struct) held in a Struct-typed variable is + # reinterpreted in place). Scalar integer targets were already rejected above. if self.is_ptr_type(val.Ty) != 0 and self.is_ptr_type(target_ty) == 0: - return llvmlite.build_load(builder, target_ty, val) + target_kind: int = target_ty.kind() + if target_kind == llvmlite.LLVMType.Struct or target_kind == llvmlite.LLVMType.Array: + return llvmlite.build_load(builder, target_ty, val) + self.Gen.TypeGenErr.fatal_error(node, "指针隐式转非指针值已禁止:必须显式解引用(c.Deref(p) / *p)或显式强转(uint64(p))", "CastError") + return val # 指针 → 指针: bitcast (如 i8* → i8** 当目标是全局变量存储指针) if self.is_ptr_type(val.Ty) != 0 and self.is_ptr_type(target_ty) != 0: return llvmlite.build_bitcast(builder, val, target_ty) diff --git a/App/lib/core/Gen/Expr/ExprGenName.vp b/App/lib/core/Gen/Expr/ExprGenName.vp index 8b25a58..d292781 100644 --- a/App/lib/core/Gen/Expr/ExprGenName.vp +++ b/App/lib/core/Gen/Expr/ExprGenName.vp @@ -951,30 +951,14 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase): return None case _: pass - # [FIX 2026-08-31] 64 位标量 | t.CPtr 联合字段下标读回 - # (如 RenumClassNames: sizet*,联合解析为 i64* 8 字节 - # 槽数组):槽位按 Int64 load 得位模式,赋给 str(i8*)触发 int→ptr - # 禁令误报(ClassGenEnum.vp existing: str = self.RenumClassNames[i] - # 根因)。槽里实际存的是 perm_pool 字符串指针——联合声明即"槽位可能 - # 存指针",合法 C 联合读回应按指针读回。统一按 i8* load(同宽 8 字节, - # 位模式不变):指针目标直接 bitcast;整数目标隐式 ptrtoint 同样 - # 保持位模式(对齐 MatchGenMixin 的 REnum 槽位按声明类型读机制)。 - if trans is not None and sub.value is not None and sub.value.kind() == ast.ASTKind.Attribute: - fa_u64: str = self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, sub.value) - if fa_u64 is not None and self._annot_is_64bit_scalar(fa_u64) != 0: - match elem_ty2: - case llvmlite.LLVMType.Int(eb_u64): - if eb_u64 == 64: - cell_p_u64: llvmlite.Value* = llvmlite.build_gep(builder, elem_ty2, ptr_val, idx_val) - if cell_p_u64 is not None: - i8_ty_u64: llvmlite.LLVMType* = llvmlite.Int8(pool) - i8p_ty_u64: llvmlite.LLVMType* = llvmlite.Ptr(pool, i8_ty_u64) - i8pp_ty_u64: llvmlite.LLVMType* = llvmlite.Ptr(pool, i8p_ty_u64) - cast_cell_u64: llvmlite.Value* = llvmlite.build_bitcast(builder, cell_p_u64, i8pp_ty_u64) - if cast_cell_u64 is not None: - return llvmlite.build_load(builder, i8p_ty_u64, cast_cell_u64, vol_sub_gen) - case _: - pass + # 64-bit scalar pointer fields (e.g. `sizet*`, `uint64*`, written as + # `t.CUInt64T | t.CPtr`) keep the declared element type here: the slot is + # a u64, so it is read as i64 -- same element type the write path uses + # (see get_subscript_ptr). Loading it as i8* fabricated a pointer value + # out of an integer slot and forced the caller through an implicit + # ptrtoint / inttoptr, which the language forbids (it also made + # `MemBuddy.free_lists[i]` mis-type its element). Sources that store or + # read a pointer through such a slot must cast explicitly. # 自定义结构体 (如 self._ht[key]): 仅当类定义了 __getitem__ 才转发方法调用; # 否则普通结构体指针按下标做 GEP 数组访问 cls_nm_rd_gen: str = self._get_custom_struct_cls_nm(pool, elem_ty2) diff --git a/App/lib/core/Gen/Func/FuncGenDef.vp b/App/lib/core/Gen/Func/FuncGenDef.vp index 6a442cc..00c05a8 100644 --- a/App/lib/core/Gen/Func/FuncGenDef.vp +++ b/App/lib/core/Gen/Func/FuncGenDef.vp @@ -13,6 +13,7 @@ import lib.core.VLogger as VLogger import lib.core.Gen.Translator as HT import lib.core.Gen.Call.CallGenBase as CallGenBase import lib.core.Gen.GenApi as GenApi +import lib.core.Gen.DebugInfoGen as DebugInfoGen # ============================================================ @@ -658,6 +659,12 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): return 0 llvmlite.position_at_end(func_builder, entry_blk) + # --traceback: attach a DISubprogram to this define and set every + # instruction in the body to the entry DILocation (the statement hook + # refines it per statement). No-op when DI is disabled. + dbg_pushed: int = DebugInfoGen.PushFunction(pool, func_builder, func, + fd.lineno, fd.col_offset + 1) + # @t.TLS 一次性初始化守卫(对齐 TPC HandlesFunctions.py):首次调用执行函数体并置 # 全局标志 __tls_init_done_{name}=1,后续调用直接返回默认值。不实现则每次调用都 # TlsAlloc,provider 栈跨调用丢失。 @@ -1030,6 +1037,9 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): self.Gen.ClassGenBase.register_decorator(pool, fd.name, wb_name, decor_arg0) self._gen_decor_wrapper(trans, pool, mod, func, fd.name, wb_name, decor_handler, decor_arg0) + if dbg_pushed != 0: + DebugInfoGen.PopFunction(func_builder) + return 0 # ============================================================ # _gen_decor_wrapper - 生成装饰器 wrapper 函数(对齐 TPC DecoratorPass.py diff --git a/App/lib/core/Gen/Func/FuncGenMixin.vp b/App/lib/core/Gen/Func/FuncGenMixin.vp index 4a91cec..7663ffe 100644 --- a/App/lib/core/Gen/Func/FuncGenMixin.vp +++ b/App/lib/core/Gen/Func/FuncGenMixin.vp @@ -12,6 +12,7 @@ import lib.core.VLogger as VLogger import lib.core.Gen.Translator as HT import lib.core.Gen.Call.CallGenBase as CallGenBase import lib.core.Gen.GenApi as GenApi +import lib.core.Gen.DebugInfoGen as DebugInfoGen # ============================================================ @@ -107,6 +108,10 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase): if func_builder is None: return 0 llvmlite.position_at_end(func_builder, entry_blk) + # --traceback: promoted nested closure gets its own DISubprogram. + dbg_pushed_n: int = DebugInfoGen.PushFunction(pool, func_builder, func, + node.lineno, node.col_offset + 1) + # 4. 进入嵌套函数作用域(嵌套符号表) self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_FUNCTION) @@ -249,6 +254,8 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase): self.Gen.VarGen.exit_scope(trans.SymTab) trans._cur_func = old_func trans._cur_builder = old_builder + if dbg_pushed_n != 0: + DebugInfoGen.PopFunction(func_builder) # NOTE: _nonlocal_names 和 _nonlocal_name_count 仍为嵌套函数的值 # 13. 在父函数中创建闭包对象 @@ -1120,6 +1127,10 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase): return None llvmlite.position_at_end(func_builder, entry_blk) + # --traceback: promoted lambda gets its own DISubprogram. + dbg_pushed_l: int = DebugInfoGen.PushFunction(pool, func_builder, func, + node.lineno, node.col_offset + 1) + # 6. 进入 lambda 作用域 self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_FUNCTION) @@ -1209,6 +1220,8 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase): trans._global_name_count = old_global_count trans._nonlocal_name_count = old_nonlocal_count trans._closure_env_count = old_env_count + if dbg_pushed_l != 0: + DebugInfoGen.PopFunction(func_builder) # 13. 在父函数中创建 env(每个捕获变量 8 字节堆存储,env[i] = 堆地址) env_ptr: llvmlite.Value* = None diff --git a/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp b/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp index 871ad27..03f9e6f 100644 --- a/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp @@ -660,6 +660,14 @@ novirtual class StmtGenCifMixin: # 传 trans:无符号源类型(uint16/uint32/CSizeT 等)扩展用 zext, # 缺 trans 时 _node_type_unsigned 查不到变量注解 → 误 sext val = self.Gen.ExprGenBase.coerce_to_type(builder, val, ret_ty_rt, node, trans) + elif self.Gen.ExprGenBase.get_llvm_float_bits(val.Ty) != 0 \ + or self.Gen.ExprGenBase.get_llvm_float_bits(ret_ty_rt) != 0: + # Float alignment: SpecTypeBits reports 0 for Float/Double, so the + # integer branch above never runs and a double literal returned from a + # `-> float` function would emit `ret double` (llc: value doesn't match + # function result type 'float'). Coerce here (fpext/fptrunc/si2fp/fptosi), + # same as TPC's emit_return. + val = self.Gen.ExprGenBase.coerce_to_type(builder, val, ret_ty_rt, node, trans) llvmlite.build_ret(builder, val) return 0 diff --git a/App/lib/core/Gen/Stmt/StmtGenMixin.vp b/App/lib/core/Gen/Stmt/StmtGenMixin.vp index 4486fc3..b617ddd 100644 --- a/App/lib/core/Gen/Stmt/StmtGenMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenMixin.vp @@ -15,6 +15,7 @@ import lib.core.Gen.Translator as HT import lib.core.CompilerFactory as CompilerFactory import lib.Projectrans.Config as Config import lib.core.Gen.Stmt.StmtGenDeclMixin as StmtGenDeclMixin +import lib.core.Gen.DebugInfoGen as DebugInfoGen # ============================================================ @@ -32,6 +33,12 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): node: ast.AST*) -> int: """翻译单条语句,返回新增的变量数(深度守卫包装器,原逻辑见 _translate_stmt_impl)""" if trans is not None: + # --traceback: statement-level DWARF location (no-op outside a + # DI-attached define or when DI is disabled). 此后本语句生成的每条 + # 指令自动带 ", !dbg !N",保证函数体内每个 call 都有合法 !dbg。 + if node is not None and trans._cur_builder is not None: + DebugInfoGen.SetStatement(trans.Pool, trans._cur_builder, + node.lineno, node.col_offset + 1) if trans.TranslateDepth >= HT.TRANSLATE_MAX_DEPTH: self.Gen.TypeGenErr.fatal_error(node, "语句翻译嵌套过深(疑似 AST 自引用环或翻译 bug)", "RecursionError") trans.TranslateDepth += 1 diff --git a/App/lib/core/Gen/Translator.vp b/App/lib/core/Gen/Translator.vp index 0cc18eb..ac5016d 100644 --- a/App/lib/core/Gen/Translator.vp +++ b/App/lib/core/Gen/Translator.vp @@ -14,6 +14,7 @@ import lib.Projectrans.Config as Config import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild import lib.Stub.StubMerger.StubMergerStore as StubMergerStore import lib.core.Gen.Generator as Generator +import lib.core.Gen.DebugInfoGen as DebugInfoGen # ============================================================ # Gen/Translator.vp - 翻译器状态管理 + 主入口 @@ -441,6 +442,11 @@ novirtual class Translator: pool: memhub.MemBuddy* = _mbuddy + # OOM 归属:把「阶段/当前模块」写进 memhub 的分配上下文。池耗尽时 memhub 会打印 + # ctx=阶段/模块,从而知道是在翻译哪个模块时被吃满(SetAllocCtx 只存指针不拷贝, + # ModuleSha1 由调用方在 translate 前设置且与本次翻译同寿命)。此前无人设置该上下文。 + memhub.SetAllocCtx("translate", self.ModuleSha1) + # 初始化状态 self._init_state(pool) @@ -451,6 +457,9 @@ novirtual class Translator: return 1 self.Module = mod + # --traceback: reset per-module DWARF state (no-op when DI is disabled). + DebugInfoGen.BeginModule(mod, GenApi.get_current_file()) + # 设置目标(优先使用 project.vpj 中的配置) triple: str = Config.TargetTriple if triple is None: @@ -511,8 +520,12 @@ novirtual class Translator: # 用户已定义 main → 委托 HandlesMain 翻译模块级 self._translate_module_level(pool, mod, tree) - return 0 + # --traceback: register module-level !llvm.dbg.cu / !llvm.module.flags + # before the caller dumps the IR. Idempotent; no-op when no define got a + # DISubprogram (stub / _declare_only) or when DI is disabled. + DebugInfoGen.Finalize(pool) + return 0 # ============================================================ # 包装 main 函数(无用户 main 时)→ 委托 HandlesMain # ============================================================ @@ -808,6 +821,137 @@ novirtual class Translator: llvmlite.LLVMModulePrint(buf, size, self.Module, _mbuddy, mode) +# ============================================================ +# --traceback: startup Install() ctor injection +# +# Lives here (not in BuildPipeline) because this module already imports +# llvmlite. Calling llvmlite.* from a module without that import mangles the +# calls with the package (__init__) SHA1, which no object defines, instead of +# the defining submodule's SHA1 (link-time undefined symbol). BuildPipeline +# only decides whether to trigger the injection. +# +# Injects into the traceback module's IR: +# define void @__traceback_install_ctor() { entry: call @Install(); ret void } +# plus an appending-linkage @llvm.global_ctors so the CRT runs Install() once at +# process start (mirrors TPC BaseGen._RegisterTracebackInstallViaCtor). +# fail-fast: a pre-existing @llvm.global_ctors, or a missing Install symbol, +# aborts the build (silent degradation would leave the product without a map). +# ============================================================ + +# _tb_module_has_global_ctors - does the module already define @llvm.global_ctors +def _tb_module_has_global_ctors(mod: llvmlite.LLVMModule*) -> int: + if mod is None: + return 0 + g: llvmlite.GlobalVariable* = mod.GlobalHead + steps: sizet = 0 + while g is not None: + steps += 1 + if steps > 100000: + break + if g.Name is not None and string.strcmp(g.Name, "llvm.global_ctors") == 0: + return 1 + g = g.Next + return 0 + + +# _tb_find_install_function - locate this module's own Install() +# +# Accepts "<16hex>.Install" (the usual SHA1-mangled form) or the bare name. +def _tb_find_install_function(mod: llvmlite.LLVMModule*) -> llvmlite.Function*: + if mod is None: + return None + cur: llvmlite.Function* = mod.FuncHead + steps: sizet = 0 + while cur is not None: + steps += 1 + if steps > 100000: + return None + nm: str = llvmlite.function_get_name(cur) + if nm is not None: + if string.strcmp(nm, "Install") == 0: + return cur + nl: sizet = llvmlite.function_get_name_len(cur) + if nl == 24 and string.strcmp(nm + 16, ".Install") == 0: + return cur + cur = llvmlite.function_get_next(cur) + return None + + +# InjectTracebackInstallCtor - inject startup Install() ctor into the module +# +# NOTE: FunctionPrint has no linkage field, so the ctor is emitted with default +# (external) linkage instead of TPC's 'internal'; only traceback.vp defines it, +# so there is no symbol clash. +def InjectTracebackInstallCtor(tr: rawptr[Translator]) -> int: + if tr is None or tr.Module is None: + return 0 + if tr._declare_only != 0: + return 0 + pool: memhub.MemBuddy* = tr.Pool + if pool is None: + VLogger.error("--traceback: 内存池未初始化,无法注入 Install 构造器", "TRACEBACK") + return 1 + mod: llvmlite.LLVMModule* = tr.Module + if _tb_module_has_global_ctors(mod) != 0: + VLogger.error("--traceback: 模块内已存在 @llvm.global_ctors,无法注入 traceback 启动期构造器(不静默降级)", "TRACEBACK") + return 1 + install_fn: llvmlite.Function* = _tb_find_install_function(mod) + if install_fn is None: + VLogger.error("--traceback: 在 traceback 模块内找不到 Install() 函数,无法注入启动期构造器(不静默降级)", "TRACEBACK") + return 1 + + # 1) ctor: entry { call @Install(); ret void } + void_ty: llvmlite.LLVMType* = llvmlite.Void(pool) + ctor: llvmlite.Function* = llvmlite.new_function(pool, "__traceback_install_ctor", void_ty) + if ctor is None: + VLogger.error("--traceback: 构造器函数分配失败", "TRACEBACK") + return 1 + blk: llvmlite.BasicBlock* = llvmlite.new_basic_block(pool, "entry") + if blk is None: + VLogger.error("--traceback: 构造器基本块分配失败", "TRACEBACK") + return 1 + llvmlite.function_add_block(ctor, blk) + llvmlite.module_add_function(mod, ctor) + builder: llvmlite.IRBuilder* = llvmlite.new_builder(pool, ctor) + if builder is None: + VLogger.error("--traceback: 构造器 IRBuilder 分配失败", "TRACEBACK") + return 1 + llvmlite.position_at_end(builder, blk) + iname: str = llvmlite.function_get_name(install_fn) + install_ret: llvmlite.LLVMType* = llvmlite.function_get_ret_ty(install_fn) + llvmlite.build_call(builder, iname, None, 0, install_ret, 0) + llvmlite.build_ret_void(builder) + + # 2) @llvm.global_ctors = appending global [1 x {i32, void ()*, i8*}] [ ... ] + i32_ty: llvmlite.LLVMType* = llvmlite.Int32(pool) + i8_ty: llvmlite.LLVMType* = llvmlite.Int8(pool) + i8p_ty: llvmlite.LLVMType* = llvmlite.Ptr(pool, i8_ty) + fnv_ty: llvmlite.LLVMType* = llvmlite.Func(pool, void_ty, None, 0) + fnp_ty: llvmlite.LLVMType* = llvmlite.Ptr(pool, fnv_ty) + f1: llvmlite.ParamNode* = llvmlite.new_param_node(pool, i32_ty) + f2: llvmlite.ParamNode* = llvmlite.new_param_node(pool, fnp_ty) + f3: llvmlite.ParamNode* = llvmlite.new_param_node(pool, i8p_ty) + if f1 is None or f2 is None or f3 is None: + VLogger.error("--traceback: global_ctors 结构体字段分配失败", "TRACEBACK") + return 1 + llvmlite.paramnode_set_next(f1, f2) + llvmlite.paramnode_set_next(f2, f3) + elem_ty: llvmlite.LLVMType* = llvmlite.Struct(pool, f1, 3, None) + arr_ty: llvmlite.LLVMType* = llvmlite.Array(pool, elem_ty, 1) + if elem_ty is None or arr_ty is None: + VLogger.error("--traceback: global_ctors 类型构造失败", "TRACEBACK") + return 1 + gv: llvmlite.GlobalVariable* = llvmlite.new_global_variable(pool, "llvm.global_ctors", arr_ty) + if gv is None: + VLogger.error("--traceback: @llvm.global_ctors 全局变量分配失败", "TRACEBACK") + return 1 + llvmlite.global_set_linkage(gv, "appending") + gv.Initializer = "[{ i32, void ()*, i8* } { i32 65535, void ()* @__traceback_install_ctor, i8* null }]" + llvmlite.module_add_global(mod, gv) + VLogger.info("--traceback: 已注入启动期 Install() 构造器(@llvm.global_ctors)", "TRACEBACK") + return 0 + + # ============================================================ # Mixin - Handler 非多态基类(自 HandlesBase 迁入) # diff --git a/App/main.vp b/App/main.vp index e5c19c4..942b656 100644 --- a/App/main.vp +++ b/App/main.vp @@ -148,7 +148,8 @@ def main() -> int: parser.add_argument("--run", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Run the generated executable after compilation") parser.add_argument("--rebuild-includes", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Delete the includes.binary precompiled cache and rebuild all the includes") parser.add_argument("--clear-cache", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Clear .transpyc_cache global cache") - parser.add_argument("--asan", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "ASan mode: .ll first opt -passes=asan; ASan runtime libs must be declared in project.vpj (linker.static_libs)") + parser.add_argument("--asan", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "ASan mode: .ll first opt -passes=asan; ASan runtime libs must be declared in project.vpj (linker.static_libs)") + parser.add_argument("--traceback", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Traceback mode: force-compile includes/traceback.vp + Install() ctor, llc -frame-pointer=all, link -ldbghelp, export .stackmap") parser.add_argument("--phase2command", None, argparse.STRING, 0, None, False, argparse.STORE, "Do not run llc/link: write Phase2 llc(opt+llc) and clang link commands to the given .bat path") parser.add_argument("--nowarn-register", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Suppress Phase A dependency WARN") parser.add_argument("--detection", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Enable MemBuddy allocation detection (0xCDCD markers + record-table probe for out-of-bounds writes; applies to both mb and perm_pool)") @@ -214,6 +215,15 @@ Target: %s stdio.printf(" 处理: 去掉 --asan(如需排查内存问题,请用工程模式构建 ASan 版 TransPyV)\n") argparse.release(args) return 2 + # --traceback is also rejected under --repl: the fragment is a runtime-loaded + # DLL (DI metadata / startup ctor semantics undefined) and it has no .stackmap. + if args.get_bool("traceback"): + stdio.printf("FAIL: --repl 与 --traceback 互斥\n") + stdio.printf(" 原因: --traceback 依赖启动期构造器注入 + 本模块 DWARF 旁文件,\n") + stdio.printf(" 而片段是运行期 LoadLibraryA 载入的 DLL,二者语义未定义\n") + stdio.printf(" 处理: 去掉 --traceback(如需崩溃栈,请用工程模式构建 --traceback 版 TransPyV)\n") + argparse.release(args) + return 2 # === --repl: M1 交互式循环(表达式片段 → DLL → 调用回显)=== # 分支位置:见下方"工程配置加载"之后(Repl 需要 Config 的 compiler/linker/target 通道), @@ -255,19 +265,38 @@ Target: %s else: stdio.printf(" --clear-cache: False\n") stdio.printf("参数解析完成。\n\n") - # ASan 模式:includes 缓存用独立目录 .binary_asan 隔离 asan 插桩 obj,避免与非 asan obj 混链(undefined reference) - if args.get_bool("asan"): + # 模式开关:includes 缓存用独立后缀目录隔离,避免不同模式产物的 obj 混链 + # (ASan 插桩 obj / traceback 帧指针+ctor obj 与非对应模式不兼容)。 + # --asan 与 --traceback 可同开 ⇒ 后缀必须一次性按两个开关组合计算, + # 后续 --clean/--rebuild-includes 经 Config.get_includes_binary_dir() 自动覆盖。 + gb_asan: bool = args.get_bool("asan") + gb_traceback: bool = args.get_bool("traceback") + if gb_asan: BuildPipeline._asan_mode = True - Config._inc_bin_suffix = ".binary_asan" - log.info("ASan 模式已启用(opt -passes=asan 插桩; 运行库需 project.vpj 声明)", "asan") - # ASan 模式清理 asan includes 缓存(强制重建 asan 插桩 obj) - mf_inc_bin_asan: str = Config.get_includes_binary_dir() - if mf_inc_bin_asan is not None: - n_asan: int = Utils.CleanDir(mf_inc_bin_asan) - log.info(f"ASan 模式清理 includes 缓存: {n_asan} 个文件(将重建 asan obj)", "asan") else: BuildPipeline._asan_mode = False + if gb_traceback: + BuildPipeline._traceback_mode = True + else: + BuildPipeline._traceback_mode = False + if gb_asan and gb_traceback: + Config._inc_bin_suffix = ".binary_traceback_asan" + elif gb_traceback: + Config._inc_bin_suffix = ".binary_traceback" + elif gb_asan: + Config._inc_bin_suffix = ".binary_asan" + else: Config._inc_bin_suffix = ".binary" + if gb_asan: + log.info("ASan 模式已启用(opt -passes=asan 插桩; 运行库需 project.vpj 声明)", "asan") + if gb_traceback: + log.info("Traceback 模式已启用(llc 保留帧指针 + 链 dbghelp + Install 构造器 + .stackmap)", "traceback") + # 开启任一模式都清空对应后缀的 includes 缓存,强制重建匹配当前模式的 obj + if gb_asan or gb_traceback: + mf_inc_bin_mode: str = Config.get_includes_binary_dir() + if mf_inc_bin_mode is not None: + n_mode: int = Utils.CleanDir(mf_inc_bin_mode) + log.info(f"模式缓存清理: {mf_inc_bin_mode} 删除 {n_mode} 个文件(将按 {Config._inc_bin_suffix} 重建)", "mode") # === --phase2command: 只生成命令写入 bat(Phase B/B+ 的 llc 与 Phase C 的 clang 链接命令),不执行 llc/链接 === p2c: str = args.get_str("phase2command")