Skip to content

Commit a4b4041

Browse files
authored
Fix debug info for closured variables (#16393)
Declares the debug variables as they are created in `#setup_closure_vars`, not later when the `alloca` block is being populated. The `DW_OP_plus_uconst` is necessary to resolve to the correct variable address during debug time, attaching a debug variable to a `getelementptr` instruction will not work.
1 parent 76ec84c commit a4b4041

File tree

3 files changed

+56
-23
lines changed

3 files changed

+56
-23
lines changed

spec/compiler/codegen/debug_spec.cr

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,4 +331,18 @@ describe "Code gen: debug" do
331331
Int32
332332
CRYSTAL
333333
end
334+
335+
it "doesn't fail if Proc self is closured (#16382)" do
336+
codegen <<-CRYSTAL, debug: Crystal::Debug::All
337+
struct Proc
338+
def partial
339+
-> do
340+
self
341+
end
342+
end
343+
end
344+
345+
-> { }.partial.call
346+
CRYSTAL
347+
end
334348
end

src/compiler/crystal/codegen/debug.cr

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -321,9 +321,9 @@ module Crystal
321321
end
322322
end
323323

324-
def declare_variable(var_name, var_type, alloca, location, basic_block : LLVM::BasicBlock? = nil)
324+
def declare_variable(var_name, var_type, alloca, location, basic_block : LLVM::BasicBlock? = nil, offset : Int = 0)
325325
return false unless @debug.variables?
326-
declare_local(var_type, alloca, location, basic_block) do |scope, file, line_number, debug_type|
326+
declare_local(var_type, alloca, location, basic_block, offset) do |scope, file, line_number, debug_type|
327327
di_builder.create_auto_variable scope, var_name, file, line_number, debug_type, align_of(var_type)
328328
end
329329
end
@@ -332,7 +332,11 @@ module Crystal
332332
@program.target_machine.data_layout.abi_alignment(llvm_type(type)) * 8
333333
end
334334

335-
private def declare_local(type, alloca, location, basic_block : LLVM::BasicBlock? = nil, &)
335+
# see also the other DWARF enums in `crystal/dwarf/abbrev.cr` (note that
336+
# LLVM defines several custom opcodes outside the user extension range)
337+
DW_OP_plus_uconst = 0x23
338+
339+
private def declare_local(type, alloca, location, basic_block : LLVM::BasicBlock? = nil, offset : Int = 0, &)
336340
location = location.try &.expanded_location
337341
return false unless location
338342

@@ -346,7 +350,18 @@ module Crystal
346350
return false unless scope
347351

348352
var = yield scope, file, location.line_number, debug_type
349-
expr = di_builder.create_expression(nil, 0)
353+
354+
if offset != 0
355+
expr =
356+
{% if LibLLVM::IS_LT_140 %}
357+
di_builder.create_expression(Int64.static_array(DW_OP_plus_uconst, offset), 2)
358+
{% else %}
359+
di_builder.create_expression(UInt64.static_array(DW_OP_plus_uconst, offset), 2)
360+
{% end %}
361+
else
362+
expr = di_builder.create_expression(nil, 0)
363+
end
364+
350365
if basic_block
351366
block = basic_block
352367
else
@@ -355,16 +370,6 @@ module Crystal
355370
old_debug_location = @current_debug_location
356371
set_current_debug_location location
357372
if builder.current_debug_location != llvm_nil && (ptr = alloca)
358-
# FIXME: When debug records are used instead of debug intrinsics, it
359-
# seems inserting them into an empty BasicBlock will instead place them
360-
# in a totally different (next?) function where the variable doesn't
361-
# exist, leading to a "function-local metadata used in wrong function"
362-
# validation error. This might happen when e.g. all variables inside a
363-
# block are closured. Ideally every debug record should immediately
364-
# follow the variable it declares.
365-
{% unless LibLLVM::IS_LT_190 %}
366-
call(do_nothing_fun) if block.instructions.empty?
367-
{% end %}
368373
di_builder.insert_declare_at_end(ptr, var, expr, builder.current_debug_location_metadata, block)
369374
set_current_debug_location old_debug_location
370375
true

src/compiler/crystal/codegen/fun.cr

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class Crystal::CodeGenVisitor
117117
void_ptr = context.fun.params.first
118118
closure_type = llvm_typer.copy_type(context.closure_type.not_nil!)
119119
closure_ptr = pointer_cast void_ptr, closure_type.pointer
120-
setup_closure_vars target_def.vars, context.closure_vars.not_nil!, self.context, closure_type, closure_ptr
120+
setup_closure_vars target_def, context.closure_vars.not_nil!, self.context, closure_type, closure_ptr
121121
else
122122
context.reset_closure
123123
end
@@ -459,11 +459,12 @@ class Crystal::CodeGenVisitor
459459
context.fun.add_attribute LLVM::Attribute::NoInline if target_def.no_inline?
460460
end
461461

462-
def setup_closure_vars(def_vars, closure_vars, context, closure_type, closure_ptr)
462+
def setup_closure_vars(target_def, closure_vars, context, closure_type, closure_ptr)
463463
if context.closure_skip_parent
464464
parent_context = context.closure_parent_context.not_nil!
465-
setup_closure_vars(def_vars, parent_context.closure_vars.not_nil!, parent_context, closure_type, closure_ptr)
465+
setup_closure_vars(target_def, parent_context.closure_vars.not_nil!, parent_context, closure_type, closure_ptr)
466466
else
467+
def_vars = target_def.vars
467468
closure_vars.each_with_index do |var, i|
468469
# A closured var in this context might have the same name as
469470
# a local var in another context, for example if the local var
@@ -472,20 +473,33 @@ class Crystal::CodeGenVisitor
472473
def_var = def_vars.try &.[var.name]?
473474
next if def_var && !def_var.closured?
474475

475-
self.context.vars[var.name] = LLVMVar.new(gep(closure_type, closure_ptr, 0, i, var.name), var.type)
476+
if context.fun.naked?
477+
debug_variable_created = false
478+
else
479+
var_offset = llvm_typer.offset_of(closure_type, i)
480+
debug_variable_created = declare_variable(var.name, var.type, closure_ptr, target_def.location, offset: var_offset)
481+
end
482+
var_ptr = gep(closure_type, closure_ptr, 0, i, var.name)
483+
self.context.vars[var.name] = LLVMVar.new(var_ptr, var.type, debug_variable_created: debug_variable_created)
476484
end
477485

478486
if (closure_parent_context = context.closure_parent_context) &&
479487
(parent_vars = closure_parent_context.closure_vars)
480488
parent_closure_type = llvm_typer.copy_type(closure_parent_context.closure_type.not_nil!)
481489
parent_closure_ptr = gep(closure_type, closure_ptr, 0, closure_vars.size, "parent_ptr")
482490
parent_closure = load(parent_closure_type.pointer, parent_closure_ptr, "parent")
483-
setup_closure_vars(def_vars, parent_vars, closure_parent_context, parent_closure_type, parent_closure)
491+
setup_closure_vars(target_def, parent_vars, closure_parent_context, parent_closure_type, parent_closure)
484492
elsif closure_self = context.closure_self
485-
offset = context.closure_parent_context ? 1 : 0
486-
self_value = gep(closure_type, closure_ptr, 0, closure_vars.size + offset, "self")
487-
self_value = load(llvm_type(closure_self), self_value) unless context.type.passed_by_value?
488-
self.context.vars["self"] = LLVMVar.new(self_value, closure_self, true)
493+
self_index = closure_vars.size + (context.closure_parent_context ? 1 : 0)
494+
if context.fun.naked?
495+
debug_variable_created = false
496+
else
497+
self_offset = llvm_typer.offset_of(closure_type, self_index)
498+
debug_variable_created = declare_variable("self", closure_self, closure_ptr, target_def.location, offset: self_offset)
499+
end
500+
self_value = gep(closure_type, closure_ptr, 0, self_index, "self")
501+
self_value = load(llvm_type(closure_self), self_value) unless closure_self.passed_by_value?
502+
self.context.vars["self"] = LLVMVar.new(self_value, closure_self, true, debug_variable_created: debug_variable_created)
489503
end
490504
end
491505
end

0 commit comments

Comments
 (0)