181 lines
8.5 KiB
Python
181 lines
8.5 KiB
Python
from __future__ import annotations
|
||
|
||
import llvmlite.ir as ir
|
||
|
||
|
||
class DebugInfoEmitter:
|
||
"""函数级 DWARF 调试元数据发射器(llvmlite 文本 IR)。
|
||
|
||
每个 Module 一份 DICompileUnit + DIFile;每个 define 一份 DISubprogram
|
||
(is_distinct=True)挂在 func.metadata["dbg"] 上;函数体发射期间把 builder 的
|
||
默认调试位置设为 "当前函数 SP + 当前语句行/列" 的 DILocation,于是每条新建
|
||
指令(含所有 call)都自动带本函数 scope 的 !dbg —— 既有语句级粒度,又保证
|
||
llc 的 "inlinable function call ... must have a !dbg location" 校验通过、
|
||
不丢弃 DI。没有语句上下文时回退到函数入口位置(line=def 行、column=def 列)。
|
||
|
||
仅在 --dwarf(或 --asan 自动开启)时被实例化;把 Module 落成 .ll 文本之前
|
||
必须调用 Finalize() 补模块级 !llvm.dbg.cu 与 !llvm.module.flags
|
||
(Debug Info Version = 3),否则 llc 会丢弃调试信息或报无效 DI 警告。
|
||
"""
|
||
|
||
def __init__(self, module: ir.Module, source_path: str) -> None:
|
||
self.Module: ir.Module = module
|
||
normalized: str = (source_path or '').replace('\\', '/')
|
||
head: str
|
||
sep: int = normalized.rfind('/')
|
||
if sep < 0:
|
||
head, self.FileName = '', normalized
|
||
else:
|
||
head, self.FileName = normalized[:sep], normalized[sep + 1:]
|
||
self.Directory: str = head
|
||
self._File: ir.DIValue | None = None
|
||
self._CompileUnit: ir.DIValue | None = None
|
||
self._SubroutineType: ir.DIValue | None = None
|
||
self.Finalized: bool = False
|
||
|
||
def _GetFile(self) -> ir.DIValue:
|
||
if self._File is None:
|
||
self._File = self.Module.add_debug_info("DIFile", {
|
||
"filename": self.FileName,
|
||
"directory": self.Directory,
|
||
})
|
||
return self._File
|
||
|
||
def _GetCompileUnit(self) -> ir.DIValue:
|
||
if self._CompileUnit is None:
|
||
# emissionKind 必须是 DIToken("FullDebug")(写成 "Full" llc 报无效 DI)
|
||
self._CompileUnit = self.Module.add_debug_info("DICompileUnit", {
|
||
"language": ir.DIToken("DW_LANG_C99"),
|
||
"file": self._GetFile(),
|
||
"producer": "TPC",
|
||
"isOptimized": False,
|
||
"runtimeVersion": 0,
|
||
"emissionKind": ir.DIToken("FullDebug"),
|
||
}, is_distinct=True)
|
||
return self._CompileUnit
|
||
|
||
def _GetSubroutineType(self) -> ir.DIValue:
|
||
if self._SubroutineType is None:
|
||
# DISubprogram 必须带 type,缺失会让 llc 崩溃(0xC0000005)
|
||
self._SubroutineType = self.Module.add_debug_info("DISubroutineType", {
|
||
"types": self.Module.add_metadata([None]),
|
||
})
|
||
return self._SubroutineType
|
||
|
||
def AttachFunction(self, func: ir.Function, Line: int, Column: int) -> ir.DIValue:
|
||
"""为 define(有函数体)建 DISubprogram 并返回其入口 DILocation。
|
||
|
||
仅对 define 调用;declare/extern 没有函数体,不得挂 !dbg。
|
||
"""
|
||
sp: ir.DIValue = self.Module.add_debug_info("DISubprogram", {
|
||
"name": func.name,
|
||
"scope": self._GetFile(),
|
||
"file": self._GetFile(),
|
||
"type": self._GetSubroutineType(),
|
||
"line": Line,
|
||
"scopeLine": Line,
|
||
"unit": self._GetCompileUnit(),
|
||
"spFlags": ir.DIToken("DISPFlagDefinition"),
|
||
}, is_distinct=True)
|
||
func.metadata["dbg"] = sp
|
||
return self.Module.add_debug_info("DILocation", {
|
||
"line": Line,
|
||
"column": Column,
|
||
"scope": sp,
|
||
})
|
||
|
||
# ---------- 语句/指令级位置:按 Builder 上下文管理当前 DILocation ----------
|
||
#
|
||
# llvmlite 的 IRBuilder 支持"预设调试位置自动挂载":IRBuilder._insert 里
|
||
# `if self.debug_metadata is not None and 'dbg' not in instr.metadata:
|
||
# instr.metadata['dbg'] = self.debug_metadata`
|
||
# 即设置 builder.debug_metadata 之后,该 builder 新建的每条指令都会自动带
|
||
# `!dbg !N`。函数带 DISubprogram 时 llc 要求函数体内每个 call(对同模块有
|
||
# 定义的被调函数)都有 !dbg,否则报
|
||
# inlinable function call in a function with debug info must have a !dbg
|
||
# warning: ignoring invalid debug info in <file>
|
||
# 并丢弃整份 DI(llvm-dwarfdump 拿不到行表)。
|
||
#
|
||
# 当前 SP/入口位置直接存放在 IRBuilder 对象上(_TpcDbgSp / _TpcDbgEntry),
|
||
# 而不是发射器自身:builder 是"每函数一个"的(见 LlvmCodeGenerator.
|
||
# _NewIrBuilder 的唯一调用点 _EmitFunctionLlvm,且已有 body 的函数被
|
||
# existing_blocks 守卫提前返回),因此
|
||
# * 嵌套/特化函数发射会临时换 builder,恢复后自动回到外层函数的 SP,
|
||
# 不会把 scope 指向别的函数;
|
||
# * 新函数 = 新 builder = debug_metadata 默认 None,天然不跨函数/模块残留,
|
||
# 也无需在函数结束时强制清理(PopFunction 只是显式停用,防误用)。
|
||
|
||
def PushFunction(self, Builder: ir.IRBuilder, func: ir.Function,
|
||
Line: int, Column: int) -> ir.DIValue:
|
||
"""进入函数体:建 DISubprogram(func.metadata["dbg"])并把 Builder 的
|
||
默认调试位置设为入口 DILocation(line=def 行、column=def 列)。
|
||
|
||
返回入口 DILocation。此后该 Builder 新建的每条指令都自动带该位置,直到
|
||
SetStatement 覆盖 —— 保证函数体内所有 call 至少有合法 !dbg(llc 不再
|
||
因缺 !dbg 丢弃 DI)。
|
||
"""
|
||
Entry: ir.DIValue = self.AttachFunction(func, Line, Column)
|
||
Builder._TpcDbgSp = func.metadata["dbg"]
|
||
Builder._TpcDbgEntry = Entry
|
||
Builder.debug_metadata = Entry
|
||
return Entry
|
||
|
||
def SetStatement(self, Builder: ir.IRBuilder, Line: int, Column: int) -> None:
|
||
"""把 Builder 的默认调试位置切到(Line, Column)。
|
||
|
||
该语句之后新建的指令都带这个位置(scope 恒为当前函数 SP)。未 PushFunction
|
||
过的 Builder(无 DI 的函数、模块级代码、lambda/async 自建 builder)不做
|
||
任何事 —— 这些函数没有 DISubprogram,llc 不要求 !dbg,也绝不改动
|
||
builder.debug_metadata 的默认值。Line<=0(AST 无位置)回退到
|
||
CurrentOrEntry()。
|
||
"""
|
||
Sp: ir.DIValue | None = getattr(Builder, '_TpcDbgSp', None)
|
||
if Sp is None:
|
||
return
|
||
if Line <= 0:
|
||
Builder.debug_metadata = self.CurrentOrEntry(Builder)
|
||
return
|
||
Builder.debug_metadata = self.Module.add_debug_info("DILocation", {
|
||
"line": Line,
|
||
"column": Column if Column > 0 else 1,
|
||
"scope": Sp,
|
||
})
|
||
|
||
def CurrentOrEntry(self, Builder: ir.IRBuilder) -> ir.DIValue | None:
|
||
"""当前默认位置;尚未设置过语句位置时回退到函数入口 DILocation。
|
||
|
||
(add_debug_info 自带 (kind, operands) 缓存,同 (SP, 行, 列) 复用同一
|
||
DILocation 节点,不会因逐语句调用撑大元数据。)
|
||
"""
|
||
Current: ir.DIValue | None = getattr(Builder, 'debug_metadata', None)
|
||
if Current is not None:
|
||
return Current
|
||
return getattr(Builder, '_TpcDbgEntry', None)
|
||
|
||
def Clear(self, Builder: ir.IRBuilder) -> None:
|
||
"""停用该 Builder 的默认调试位置(debug_metadata 置回 None)。"""
|
||
Builder.debug_metadata = None
|
||
|
||
def PopFunction(self, Builder: ir.IRBuilder) -> None:
|
||
"""离开函数体:停用默认位置并作废该 Builder 的 SP 绑定。
|
||
|
||
Builder 随后被丢弃(或由调用方恢复为外层函数的 builder),SP 上下文不会
|
||
跨函数残留。
|
||
"""
|
||
self.Clear(Builder)
|
||
Builder._TpcDbgSp = None
|
||
Builder._TpcDbgEntry = None
|
||
|
||
def Finalize(self) -> None:
|
||
"""落 .ll 文本之前补模块级 named metadata;无任何函数时不做(no-op)。"""
|
||
if self.Finalized or self._CompileUnit is None:
|
||
return
|
||
self.Module.add_named_metadata("llvm.dbg.cu", self._CompileUnit)
|
||
flags: ir.MDValue = self.Module.add_metadata([
|
||
ir.Constant(ir.IntType(32), 2),
|
||
ir.MetaDataString(self.Module, "Debug Info Version"),
|
||
ir.Constant(ir.IntType(32), 3),
|
||
])
|
||
self.Module.add_named_metadata("llvm.module.flags", flags)
|
||
self.Finalized = True
|