from stdint import * import mlirlite import memhub import string import stdlib import viperlib import win32.win32file as win32file import lib.core.VLogger as VLogger # DebugInfoGen - emits function-level DWARF metadata (mlirlite text IR), mirroring TPC LLVMCG/DebugInfo.py node-for-node. # Active only under --traceback (SetEnabled); hooks come from the Gen layer. When disabled every entry point returns before touching DI APIs, keeping IR byte-identical. # ---- module-scope state (reset per module by BeginModule) ---- _g_enabled: int = 0 _g_finalized: int = 0 _g_mod: mlirlite.LLVMModule* = None _g_file_path: str = None _g_abs_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: mlirlite.IRBuilder* = None _g_depth: int = 0 # Push/Pop save stack: enclosing (sp id + 1, entry DILocation ref, builder) per level; +1 lets 0 mean "no enclosing function". # The saved builder makes SetStatement reject a builder not pushed by the current DISubprogram (e.g. an async state machine in a bracketed function); such instrs must carry no !dbg or LLVM's verifier fails. define DBG_STACK_MAX = 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). define DBG_LOC_SLOTS = 8192 define DBG_LOC_PROBE = 8 define DBG_LOC_KEY_STRIDE = 3 _g_loc_key: sizet* = None _g_loc_ref: str = None _g_loc_used: bytes = None # SetEnabled / IsEnabled - mirror the --traceback switch (flag -> _g_enabled, forwarded to mlirlite's debug call-site recorder). 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 # Forward the same switch to mlirlite's debug call-site recorder (under --traceback we emit a .vpcall side file for WriteStackMap+objdump). mlirlite.DebugSetEnabled(flag) 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: failed to allocate DWARF metadata buffer, aborting compilation", "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: mlirlite.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_abs_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_abs_file_path = None # Module-boundary fallback: clear any un-Pop'd debug function context (mid-translation failure) so the previous module's caller is not attributed here. mlirlite.DebugResetFunc() _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 - lazily create DIFile / !{ null } / DISubroutineType / DICompileUnit 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, _g_abs_file_path if _g_cu_id >= 0: return 0 mod: mlirlite.LLVMModule* = _g_mod if mod is None: return 1 # DIFile: absolute path (forward slashes), filename keeps the whole path. # Must be absolute: relative paths make LLVM's DWARF line records degrade to a bare "main.vp" that runtime cannot reload; absolutizing works whether LLVM keeps the full name or splits dir+basename. src: str = _g_file_path if src is None: src = "" slen: sizet = string.strlen(src) cap_n: sizet = slen + 4112 nbuf: str = pool.alloc(cap_n) if nbuf is None: VLogger.error("--traceback: failed to allocate DIFile path buffer, aborting compilation", "DWARF") return 1 if slen > 0: string.strncpy(nbuf, src, slen) nbuf[slen] = '\0' if slen > 0 and nbuf[0] != '/' and not (slen >= 2 and nbuf[1] == ':'): # Relative path -> prefix CWD via a win32file low-level call (as in Repl.vp), not os._mbuddy which the compiler process may not have initialized. CwdBuf: bytes = pool.alloc(4112) if CwdBuf is not None: CwdBuf[0] = '\0' nc: ULONG = win32file.GetCurrentDirectoryA(ULONG(4111), CwdBuf) if nc > 0: # Defensive: when the buffer is too small GetCurrentDirectoryA writes undefined content; force a NUL. CwdBuf[4110] = '\0' viperlib.snprintf(nbuf, cap_n, "%s/%s", CwdBuf, src) # Normalize all backslashes (the CWD segment may contain '\' too). flen: sizet = string.strlen(nbuf) ni: sizet = 0 for ni in range(int(flen)): if nbuf[ni] == '\\': nbuf[ni] = '/' # Save the absolute path for mlirlite's debug call-site recorder (.vpcall file paths must match DIFile: absolute + forward slash). _g_abs_file_path = nbuf # filename = whole path; directory = its prefix (both NUL-terminated, non-overlapping). fname: str = nbuf directory: str = "" slash: str = string.strrchr(nbuf, '/') if slash is not None: dlen: sizet = string.strlen(nbuf) - string.strlen(slash + 1) - 1 dirbuf: str = pool.alloc(dlen + 1) if dirbuf is not None: string.strncpy(dirbuf, nbuf, dlen) dirbuf[dlen] = '\0' directory = dirbuf dsize: sizet = string.strlen(fname) + string.strlen(directory) + 96 dbuf: str = pool.alloc(dsize) if dbuf is None: VLogger.error("--traceback: failed to allocate DIFile node buffer, aborting compilation", "DWARF") return 1 viperlib.snprintf(dbuf, dsize, "!DIFile(filename: \"%s\", directory: \"%s\")", fname, directory) fid: int = mlirlite.module_add_di_node(mod, pool, dbuf) if fid < 0: VLogger.error("--traceback: failed to allocate DIFile metadata node, aborting compilation", "DWARF") return 1 _g_file_id = fid # types: !{ null } placeholder list, then DISubroutineType referencing it nid: int = mlirlite.module_add_di_node(mod, pool, "!{ null }") if nid < 0: VLogger.error("--traceback: failed to allocate DISubroutineType type table, aborting compilation", "DWARF") return 1 _g_null_id = nid tbuf: str = pool.alloc(64) if tbuf is None: VLogger.error("--traceback: failed to allocate DISubroutineType buffer, aborting compilation", "DWARF") return 1 viperlib.snprintf(tbuf, 64, "!DISubroutineType(types: !%d)", _g_null_id) tid: int = mlirlite.module_add_di_node(mod, pool, tbuf) if tid < 0: VLogger.error("--traceback: failed to allocate DISubroutineType metadata node, aborting compilation", "DWARF") return 1 _g_subtype_id = tid cbuf: str = pool.alloc(256) if cbuf is None: VLogger.error("--traceback: failed to allocate DICompileUnit buffer, aborting compilation", "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 = mlirlite.module_add_di_node(mod, pool, cbuf) if cid < 0: VLogger.error("--traceback: failed to allocate DICompileUnit metadata node, aborting compilation", "DWARF") return 1 _g_cu_id = cid return 0 # _LocRefAt - read a cached DILocation ref string pointer. def _LocRefAt(slot: int) -> str: slotp: str* = (str*)(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 = mlirlite.module_add_di_node(_g_mod, pool, lbuf) if lid < 0: VLogger.error("--traceback: failed to allocate DILocation metadata node, aborting compilation", "DWARF") return None lref: str = _RefOf(pool, lid) if lref is None: VLogger.error("--traceback: failed to allocate DILocation reference buffer, aborting compilation", "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* = (str*)(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: mlirlite.IRBuilder*, func: mlirlite.Function*, line: int, col: int) -> int: """Attach a DISubprogram to one define and set the builder's entry DILocation.""" 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 function nesting depth exceeds the limit, aborting compilation", "DWARF") return 0 if _EnsureModuleNodes(pool) != 0: return 0 name: str = mlirlite.function_get_name(func) if name is None: VLogger.error("--traceback: function name is empty, cannot emit DISubprogram, aborting compilation", "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: failed to allocate DISubprogram buffer, aborting compilation", "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 = mlirlite.module_add_di_node(_g_mod, pool, spbuf) if sp_id < 0: VLogger.error("--traceback: failed to allocate DISubprogram metadata node, aborting compilation", "DWARF") return 0 sp_ref: str = _RefOf(pool, sp_id) if sp_ref is None: VLogger.error("--traceback: failed to allocate DISubprogram reference buffer, aborting compilation", "DWARF") return 0 mlirlite.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* = (str*)(uint64(_g_stk_ref) + sizet(_g_depth) * 8) rslot[0] = _g_cur_entry_ref bslot: str* = (str*)(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: failed to generate entry DILocation, aborting compilation", "DWARF") return 0 _g_cur_entry_ref = entry_ref mlirlite.builder_set_debug_location(builder, entry_ref) # Enter this define: tell mlirlite's call-site recorder the current symbol/file/def-line and write an F record (for the .vpcall file + WriteStackMap entry fallback line). mlirlite.DebugNoteFunc(name, _g_abs_file_path, dline) return 1 # PopFunction - restore the enclosing function's DI context. def PopFunction(builder: mlirlite.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* = (str*)(uint64(_g_stk_ref) + sizet(_g_depth) * 8) _g_cur_entry_ref = rslot[0] bslot: str* = (str*)(uint64(_g_stk_bld) + sizet(_g_depth) * 8) _g_cur_builder = (mlirlite.IRBuilder*)(bslot[0]) if builder is not None: mlirlite.builder_clear_debug_location(builder) # Leave this define: restore mlirlite's outer function context (for nested defines). mlirlite.DebugClearFunc() return 0 # SetStatement - point the current builder at a statement DILocation. def SetStatement(pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, line: int, col: int) -> int: """Update the builder's DILocation to the current statement (line precision).""" if _g_enabled == 0 or pool is None or builder is None: return 0 # Record the current statement line: C records from build_call in this statement use it as the call-site line. mlirlite.DebugNoteLine(line) if _g_cur_sp < 0: return 0 ref: str = _EnsureLocation(pool, _g_cur_sp, line, col) if ref is None: return 0 mlirlite.builder_set_debug_location(builder, ref) return 1 # _ModuleHasDefine - does the module emit any define (IsDeclared == 0)? def _ModuleHasDefine(mod: mlirlite.LLVMModule*) -> int: if mod is None: return 0 cur: mlirlite.Function* = mod.FuncHead steps: int = 0 while cur is not None: steps += 1 if steps > 100000: break if mlirlite.function_is_declared(cur) == 0: return 1 cur = mlirlite.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: mlirlite.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: this module emits a define but its IR carries no DWARF (!llvm.dbg.cu); its frames will show only addresses in the Ctrl+C stack", "DWARF") if _g_cu_id < 0: return 0 cu_ref: str = _RefOf(pool, _g_cu_id) if cu_ref is None: VLogger.error("--traceback: failed to allocate DICompileUnit reference buffer, aborting compilation", "DWARF") return 1 mlirlite.module_add_named_metadata(mod, pool, "llvm.dbg.cu", cu_ref) fbuf: str = pool.alloc(64) if fbuf is None: VLogger.error("--traceback: failed to allocate module-level debug flags buffer, aborting compilation", "DWARF") return 1 viperlib.snprintf(fbuf, 64, "!{i32 2, !\"Debug Info Version\", i32 3}") flags_id: int = mlirlite.module_add_di_node(mod, pool, fbuf) if flags_id < 0: VLogger.error("--traceback: failed to allocate llvm.module.flags metadata node, aborting compilation", "DWARF") return 1 fl_ref: str = _RefOf(pool, flags_id) if fl_ref is None: VLogger.error("--traceback: failed to allocate llvm.module.flags reference buffer, aborting compilation", "DWARF") return 1 mlirlite.module_add_named_metadata(mod, pool, "llvm.module.flags", fl_ref) return 0