Skip to content

Commit 390fdee

Browse files
maleadtclaude
andauthored
Add support for debug info. (#175)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9cc6e41 commit 390fdee

11 files changed

Lines changed: 290 additions & 11 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ CUDA_Tile_jll = "13.1"
3030
CompilerCaching = "0.2"
3131
EnumX = "1.0"
3232
GPUArrays = "11"
33-
IRStructurizer = "0.5"
33+
IRStructurizer = "0.5.1"
3434
julia = "1.11"

ext/CUDAExt.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ function emit_binary!(cache::CacheView, mi::Core.MethodInstance;
8383
compiled = false
8484
try
8585
write(input_path, bytecode)
86-
cmd = addenv(`$(CUDA_Compiler_jll.tileiras()) $input_path -o $output_path --gpu-name $(format_sm_arch(opts.sm_arch)) -O$(opt_level)`,
86+
cmd = addenv(`$(CUDA_Compiler_jll.tileiras()) $input_path -o $output_path --gpu-name $(format_sm_arch(opts.sm_arch)) -O$(opt_level) --lineinfo`,
8787
"CUDA_ROOT" => CUDA_Compiler_jll.artifact_dir)
8888
proc, log = run_and_collect(cmd)
8989
if !success(proc)

src/bytecode/writer.jl

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,69 @@ function items(table::DebugAttrTable)
5555
return pairs
5656
end
5757

58+
# Debug attribute tag bytes (matching NVIDIA's bytecode format)
59+
module DebugTag
60+
const DICompileUnit = 0x01
61+
const DIFile = 0x02
62+
const DILexicalBlock = 0x03
63+
const DILoc = 0x04
64+
const DISubprogram = 0x05
65+
const CallSite = 0x06
66+
end
67+
68+
function file!(table::DebugAttrTable, name::String, directory::String)
69+
buf = UInt8[DebugTag.DIFile]
70+
encode_varint!(buf, table.string_table[name].id)
71+
encode_varint!(buf, table.string_table[directory].id)
72+
return table[buf]
73+
end
74+
75+
function compile_unit!(table::DebugAttrTable, file::DebugAttrId)
76+
buf = UInt8[DebugTag.DICompileUnit]
77+
encode_varint!(buf, file.id)
78+
return table[buf]
79+
end
80+
81+
function subprogram!(table::DebugAttrTable, file::DebugAttrId, line::Int,
82+
name::String, linkage_name::String,
83+
compile_unit::DebugAttrId, scope_line::Int)
84+
buf = UInt8[DebugTag.DISubprogram]
85+
encode_varint!(buf, file.id)
86+
encode_varint!(buf, line)
87+
encode_varint!(buf, table.string_table[name].id)
88+
encode_varint!(buf, table.string_table[linkage_name].id)
89+
encode_varint!(buf, compile_unit.id)
90+
encode_varint!(buf, scope_line)
91+
return table[buf]
92+
end
93+
94+
function lexical_block!(table::DebugAttrTable, parent_scope::DebugAttrId,
95+
file::DebugAttrId, line::Int, column::Int)
96+
buf = UInt8[DebugTag.DILexicalBlock]
97+
encode_varint!(buf, parent_scope.id)
98+
encode_varint!(buf, file.id)
99+
encode_varint!(buf, line)
100+
encode_varint!(buf, column)
101+
return table[buf]
102+
end
103+
104+
function loc!(table::DebugAttrTable, scope::DebugAttrId,
105+
filename::String, line::Int, column::Int)
106+
buf = UInt8[DebugTag.DILoc]
107+
encode_varint!(buf, scope.id)
108+
encode_varint!(buf, table.string_table[filename].id)
109+
encode_varint!(buf, line)
110+
encode_varint!(buf, column)
111+
return table[buf]
112+
end
113+
114+
function call_site!(table::DebugAttrTable, callee::DebugAttrId, caller::DebugAttrId)
115+
buf = UInt8[DebugTag.CallSite]
116+
encode_varint!(buf, callee.id)
117+
encode_varint!(buf, caller.id)
118+
return table[buf]
119+
end
120+
58121
# SSA Value wrapper
59122
struct Value
60123
id::Int

src/compiler/codegen/control_flow.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ Values are stored in ctx.values by their original index.
99
"""
1010
function emit_block!(ctx::CGCtx, block::Block; skip_terminator::Bool=false)
1111
for inst in instructions(block)
12+
# Set debug location for this instruction
13+
if ctx.debug_emitter !== nothing
14+
ln = isempty(ctx.linkage_name) ? nothing : ctx.linkage_name
15+
ctx.cb.cur_debug_attr = resolve_debug_attr!(
16+
ctx.debug_emitter, ctx.sci, inst.ssa_idx; linkage_name=ln)
17+
end
1218
s = stmt(inst)
1319
if s isa ControlFlowOp
1420
emit_control_flow_op!(ctx, s, value_type(inst), inst.ssa_idx)

src/compiler/codegen/debug.jl

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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

src/compiler/codegen/kernel.jl

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ function emit_kernel!(writer::BytecodeWriter, func_buf::Vector{UInt8},
2121
const_argtypes::Union{Vector{Any}, Nothing} = nothing)
2222
tt = writer.type_table
2323
cb = CodeBuilder(writer.string_table, writer.constant_table, tt)
24-
ctx = CGCtx(; cb, tt, sci, sm_arch, cache)
24+
25+
# Create debug info emitter
26+
debug_emitter = DebugInfoEmitter(writer.debug_attr_table)
27+
28+
ctx = CGCtx(; cb, tt, sci, sm_arch, cache, debug_emitter, linkage_name=name)
2529

2630
# Determine which argument positions are const-seeded
2731
# const_argtypes is 1-indexed: [Const(f), arg2, arg3, ...]
@@ -71,11 +75,18 @@ function emit_kernel!(writer::BytecodeWriter, func_buf::Vector{UInt8},
7175
# Create entry hints if provided
7276
entry_hints = encode_entry_hints(writer, sm_arch, EntryHints(; num_ctas, occupancy))
7377

78+
# Create function-level debug attribute
79+
func_debug_attr = make_func_debug_attr(debug_emitter, sci; linkage_name=name)
80+
7481
# Create function
7582
cb = add_function!(writer, func_buf, name, param_types, result_types;
76-
is_entry, entry_hints)
83+
is_entry, entry_hints, func_debug_attr)
7784
ctx.cb = cb
7885

86+
# Set function-level debug attr as default so setup operations
87+
# (tensor views, constants, etc.) get the kernel's source location
88+
cb.cur_debug_attr = func_debug_attr
89+
7990
# Set up argument values
8091
arg_values = make_block_args!(cb, length(param_types))
8192

src/compiler/reflection.jl

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
export code_tiled
66
public code_typed, code_ircode, code_structured
77

8-
function disassemble_tileir(bytecode::Vector{UInt8})::String
8+
function disassemble_tileir(bytecode::Vector{UInt8}; debuginfo::Bool=false)::String
99
mktempdir() do dir
1010
input_path = joinpath(dir, "kernel.tile")
11-
output_path = joinpath(dir, "kernel.disasm")
1211
write(input_path, bytecode)
13-
read(`$(cuda_tile_translate()) --cudatilebc-to-mlir $input_path`, String)
12+
flags = `--cudatilebc-to-mlir`
13+
if debuginfo
14+
flags = `$flags --mlir-print-debuginfo`
15+
end
16+
read(`$(cuda_tile_translate()) $flags $input_path`, String)
1417
end
1518
end
1619

@@ -62,7 +65,7 @@ function code_structured(@nospecialize(f), @nospecialize(argtypes);
6265
ir, rettype = emit_julia(cache, mi; const_argtypes)
6366
sci, rettype, _ = emit_structured(ir, rettype)
6467
if optimize
65-
sci = deepcopy(sci)
68+
sci = copy(sci)
6669
run_passes!(sci)
6770
end
6871
[sci => rettype]
@@ -112,6 +115,7 @@ function code_tiled(io::IO, @nospecialize(f), @nospecialize(argtypes);
112115
num_ctas::Union{Int, Nothing}=nothing,
113116
occupancy::Union{Int, Nothing}=nothing,
114117
bytecode_version::VersionNumber=DEFAULT_BYTECODE_VERSION,
118+
debuginfo::Bool=false,
115119
world::UInt=Base.get_world_counter())
116120
stripped, const_argtypes = process_const_argtypes(f, argtypes)
117121
mi = lookup_method_instance(f, stripped; world)
@@ -124,7 +128,7 @@ function code_tiled(io::IO, @nospecialize(f), @nospecialize(argtypes);
124128
bytecode = emit_tile(sci, rettype, kernel_meta;
125129
name=sanitize_name(string(mi.def.name)),
126130
opts, cache, const_argtypes)
127-
print(io, disassemble_tileir(bytecode))
131+
print(io, disassemble_tileir(bytecode; debuginfo))
128132
end
129133
code_tiled(@nospecialize(f), @nospecialize(argtypes); kwargs...) =
130134
code_tiled(stdout, f, argtypes; kwargs...)

src/compiler/utils.jl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,21 @@ mutable struct CGCtx
291291

292292
# Active floating-point mode stack (pushed/popped by @fpmode via fpmode_begin/end)
293293
fpmode_stack::Vector{FPMode}
294+
295+
# Debug info emitter (DebugInfoEmitter or nothing)
296+
debug_emitter::Any
297+
298+
# Kernel linkage name (for debug info subprogram)
299+
linkage_name::String
294300
end
295301

296302
function CGCtx(; cb::CodeBuilder, tt::TypeTable, sci::StructuredIRCode,
297303
token_type::Union{TypeId, Nothing} = nothing,
298304
type_cache::Dict{Type, TypeId} = Dict{Type, TypeId}(),
299305
sm_arch::Union{VersionNumber, Nothing} = nothing,
300-
cache::CacheView)
306+
cache::CacheView,
307+
debug_emitter = nothing,
308+
linkage_name::String = "")
301309
CGCtx(
302310
Dict{Int, CGVal}(),
303311
Dict{Int, CGVal}(),
@@ -316,6 +324,8 @@ function CGCtx(; cb::CodeBuilder, tt::TypeTable, sci::StructuredIRCode,
316324
sm_arch,
317325
cache,
318326
FPMode[], # fpmode_stack
327+
debug_emitter,
328+
linkage_name,
319329
)
320330
end
321331

src/cuTile.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ module cuTile
33
using IRStructurizer
44
using IRStructurizer: Block, ControlFlowOp, BlockArgument,
55
YieldOp, ContinueOp, BreakOp, ConditionOp,
6-
IfOp, ForOp, WhileOp, LoopOp, Undef
6+
IfOp, ForOp, WhileOp, LoopOp, Undef,
7+
SourceLocation, source_location
78

89
using Base: compilerbarrier, donotdelete
910
using Core: MethodInstance, CodeInfo, SSAValue, Argument, SlotNumber,
@@ -45,6 +46,7 @@ include("compiler/passes/token_keys.jl")
4546
include("compiler/passes/token_order.jl")
4647
include("compiler/passes/dce.jl")
4748
include("compiler/passes/pipeline.jl")
49+
include("compiler/codegen/debug.jl")
4850
include("compiler/codegen/kernel.jl")
4951
include("compiler/codegen/control_flow.jl")
5052
include("compiler/codegen/statements.jl")

test/codegen/reflection.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,33 @@ end
4545
end
4646
end
4747

48+
@testset "Debug info" begin
49+
@testset "code_tiled debuginfo=true" begin
50+
@test @filecheck begin
51+
# Debug info entries can appear in any order
52+
@check_dag "di_file"
53+
@check_dag "di_compile_unit"
54+
@check_dag "name = \"reflect_vadd\""
55+
@check_dag "di_loc"
56+
@check_dag "callsite"
57+
@check_dag "name = \"bid\""
58+
@check_dag "name = \"load\""
59+
@check_dag "name = \"store\""
60+
61+
ct.code_tiled(reflect_vadd, TT3; debuginfo=true)
62+
end
63+
end
64+
65+
@testset "code_tiled default has no debug info" begin
66+
@test @filecheck begin
67+
@check_not "di_loc"
68+
@check_not "di_subprogram"
69+
@check_not "callsite"
70+
ct.code_tiled(reflect_vadd, TT3)
71+
end
72+
end
73+
end
74+
4875
@testset "Constant args" begin
4976
const_spec = ct.ArraySpec{1}(128, true, (0,), (32,))
5077
ConstTT = Tuple{ct.TileArray{Float32,1,const_spec}, ct.TileArray{Float32,1,const_spec},

0 commit comments

Comments
 (0)