|
| 1 | +# Debug info emission: converts IRStructurizer SourceLocation stacks to Tile IR debug attrs |
| 2 | + |
| 3 | +""" |
| 4 | + DebugInfoEmitter |
| 5 | +
|
| 6 | +Converts Julia source location information (from IRStructurizer's `source_location`) |
| 7 | +into Tile IR debug attributes (DIFile, DICompileUnit, DISubprogram, DILoc, CallSite). |
| 8 | +""" |
| 9 | +struct DebugInfoEmitter |
| 10 | + debug_attrs::DebugAttrTable |
| 11 | + subprogram_cache::Dict{Any, DebugAttrId} |
| 12 | + file_cache::Dict{Tuple{String,String}, DebugAttrId} |
| 13 | + compile_unit_cache::Dict{DebugAttrId, DebugAttrId} |
| 14 | +end |
| 15 | + |
| 16 | +function DebugInfoEmitter(debug_attrs::DebugAttrTable) |
| 17 | + DebugInfoEmitter( |
| 18 | + debug_attrs, |
| 19 | + Dict{Any, DebugAttrId}(), |
| 20 | + Dict{Tuple{String,String}, DebugAttrId}(), |
| 21 | + Dict{DebugAttrId, DebugAttrId}() |
| 22 | + ) |
| 23 | +end |
| 24 | + |
| 25 | +function get_file!(emitter::DebugInfoEmitter, filepath::Symbol) |
| 26 | + s = string(filepath) |
| 27 | + name = basename(s) |
| 28 | + dir = dirname(s) |
| 29 | + key = (name, dir) |
| 30 | + get!(emitter.file_cache, key) do |
| 31 | + file!(emitter.debug_attrs, name, dir) |
| 32 | + end |
| 33 | +end |
| 34 | + |
| 35 | +function get_compile_unit!(emitter::DebugInfoEmitter, file_attr::DebugAttrId) |
| 36 | + get!(emitter.compile_unit_cache, file_attr) do |
| 37 | + compile_unit!(emitter.debug_attrs, file_attr) |
| 38 | + end |
| 39 | +end |
| 40 | + |
| 41 | +""" |
| 42 | + get_subprogram!(emitter, loc; linkage_name) -> DebugAttrId |
| 43 | +
|
| 44 | +Get or create a DISubprogram for a source location. On Julia 1.12+, `loc.method` is a |
| 45 | +`Method`/`MethodInstance` with full metadata. On Julia 1.11, it's a `Symbol` and we |
| 46 | +use `loc.file`/`loc.line` instead. |
| 47 | +""" |
| 48 | +function get_subprogram!(emitter::DebugInfoEmitter, loc::SourceLocation; |
| 49 | + linkage_name::Union{String, Nothing}=nothing) |
| 50 | + method = loc.method |
| 51 | + # Use (method, linkage_name) as cache key to distinguish kernel entry from inlined copies |
| 52 | + cache_key = linkage_name !== nothing ? (method, linkage_name) : method |
| 53 | + get!(emitter.subprogram_cache, cache_key) do |
| 54 | + if method isa Method |
| 55 | + file_attr = get_file!(emitter, method.file) |
| 56 | + cu_attr = get_compile_unit!(emitter, file_attr) |
| 57 | + ln = @something linkage_name sanitize_name(string(method.name)) |
| 58 | + subprogram!(emitter.debug_attrs, file_attr, Int(method.line), |
| 59 | + string(method.name), ln, cu_attr, Int(method.line)) |
| 60 | + elseif method isa MethodInstance |
| 61 | + get_subprogram!(emitter, SourceLocation(method.def, loc.file, loc.line); |
| 62 | + linkage_name) |
| 63 | + else |
| 64 | + # Symbol (Julia 1.11) or unknown — use file/line from SourceLocation |
| 65 | + name = string(method) |
| 66 | + file_attr = get_file!(emitter, loc.file) |
| 67 | + cu_attr = get_compile_unit!(emitter, file_attr) |
| 68 | + ln = @something linkage_name sanitize_name(name) |
| 69 | + subprogram!(emitter.debug_attrs, file_attr, Int(loc.line), |
| 70 | + name, ln, cu_attr, Int(loc.line)) |
| 71 | + end |
| 72 | + end |
| 73 | +end |
| 74 | + |
| 75 | +""" |
| 76 | + make_diloc(emitter, loc::SourceLocation) -> DebugAttrId |
| 77 | +
|
| 78 | +Create a DILoc for a single source location entry, scoped to its method's subprogram. |
| 79 | +""" |
| 80 | +function make_diloc(emitter::DebugInfoEmitter, loc::SourceLocation; |
| 81 | + linkage_name::Union{String, Nothing}=nothing) |
| 82 | + sp = get_subprogram!(emitter, loc; linkage_name) |
| 83 | + loc!(emitter.debug_attrs, sp, string(loc.file), Int(loc.line), 0) |
| 84 | +end |
| 85 | + |
| 86 | +""" |
| 87 | + resolve_debug_attr!(emitter, sci, ssa_idx) -> DebugAttrId |
| 88 | +
|
| 89 | +Resolve the debug attribute for a statement at `ssa_idx`. Returns `DebugAttrId(0)` |
| 90 | +if no debug info is available. |
| 91 | +
|
| 92 | +The inlining stack from `source_location(sci, ssa_idx)` is converted to a chain of |
| 93 | +DILoc and CallSite attributes: innermost location wrapped by successive CallSite entries. |
| 94 | +""" |
| 95 | +function resolve_debug_attr!(emitter::DebugInfoEmitter, sci::StructuredIRCode, |
| 96 | + ssa_idx::Int; |
| 97 | + linkage_name::Union{String, Nothing}=nothing) |
| 98 | + stack = source_location(sci, ssa_idx) |
| 99 | + isempty(stack) && return DebugAttrId(0) |
| 100 | + |
| 101 | + # Build from innermost out |
| 102 | + attr = make_diloc(emitter, stack[end]) |
| 103 | + for i in length(stack)-1:-1:1 |
| 104 | + # Outermost frame gets the kernel's linkage_name |
| 105 | + ln = i == 1 ? linkage_name : nothing |
| 106 | + caller = make_diloc(emitter, stack[i]; linkage_name=ln) |
| 107 | + attr = call_site!(emitter.debug_attrs, attr, caller) |
| 108 | + end |
| 109 | + |
| 110 | + # Single entry (no inlining): use the kernel's linkage_name |
| 111 | + if length(stack) == 1 && linkage_name !== nothing |
| 112 | + attr = make_diloc(emitter, stack[1]; linkage_name) |
| 113 | + end |
| 114 | + |
| 115 | + return attr |
| 116 | +end |
| 117 | + |
| 118 | +""" |
| 119 | + make_func_debug_attr(emitter, sci; linkage_name) -> DebugAttrId |
| 120 | +
|
| 121 | +Create a function-level debug attribute (DILoc scoped to DISubprogram) for a kernel. |
| 122 | +""" |
| 123 | +function make_func_debug_attr(emitter::DebugInfoEmitter, sci::StructuredIRCode; |
| 124 | + linkage_name::String) |
| 125 | + # Find the kernel function from the first instruction with debug info |
| 126 | + for inst in instructions(sci.entry) |
| 127 | + stack = source_location(sci, inst) |
| 128 | + isempty(stack) && continue |
| 129 | + outer = stack[1] |
| 130 | + sp = get_subprogram!(emitter, outer; linkage_name) |
| 131 | + m = outer.method |
| 132 | + m isa MethodInstance && (m = m.def) |
| 133 | + file = m isa Method ? m.file : outer.file |
| 134 | + line = m isa Method ? Int(m.line) : Int(outer.line) |
| 135 | + return loc!(emitter.debug_attrs, sp, string(file), line, 0) |
| 136 | + end |
| 137 | + return DebugAttrId(0) |
| 138 | +end |
0 commit comments