Files
ViperC/App/lib/core/Gen/DebugInfoGen.vp
T

379 lines
17 KiB
Plaintext

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 <sha1>.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