Start plumbing through debug info type information with function parameters/return value (#6410)

This adds just enough debug info for i32/int parameters and return
values, with a path forward for adding DWARF type metadata for other
types.

As it happens, return type information is carried separately from
parameter information:
* Return type information is carried in the `type` of the `DISubprogram`
  (as a `DISubroutineType` - which does carry parameter type information
  as well, but that's unused when the DWARF is emitted by LLVM)
* Parameter information is carried by `DILocalVariable`s with a non-zero
  `arg` value (representing the order of function parameters)

In the absence of locations for the parameters (future work), nothing
would usually keep the `DILocalVariable` live/reachable when emitting
DWARF - so for cases where this can happen (for clang, this happens in
optimized builds where all references to the parameter variable might be
optimized away) the variables can be "retained" in a list on the
`DISubprogram` - achieved by passing `AlwaysPreserve` parameter to
`createParameterVariable` (adds them to a list, then that list gets
attached to the `DISubprogram` when it's finalized later)

For now, any unsupported types are emitted as `void*` (except void
return, which is implemented as void) as a placeholder.

Given this example:
```
import Core library "io";
class MyClass {
}
fn Unsupported(v: MyClass) {
}
fn Ret() -> i32 {
  return 42;
}
fn Arg(x: i32) {
  Core.Print(x);
}
fn Run() {
}
```
this is the resulting DWARF:
```
DW_TAG_compile_unit
  DW_AT_name    ("test.carbon")
  DW_TAG_subprogram
    DW_AT_name  ("Unsupported")
    DW_TAG_formal_parameter
      DW_AT_type        (0x00000066 "void *")
  DW_TAG_subprogram
    DW_AT_name  ("Ret")
    DW_AT_type  (0x00000062 "int")
  DW_TAG_subprogram
    DW_AT_name  ("Arg")
    DW_TAG_formal_parameter
      DW_AT_type        (0x00000062 "int")
  DW_TAG_subprogram
    DW_AT_name  ("Run")
  DW_TAG_base_type
    DW_AT_name  ("int")
  DW_TAG_pointer_type
```
And the debugger:
```
(gdb) p Ret()
$1 = 42
(gdb) p Arg(4)
4
$2 = void
```

I'm not sure if there's a way this logic should be merged with the logic
for making the `llvm::Function` type (which the `DISubroutineType`
building code was inspired by/copied from) - since they're done at
different times/places, I don't think there's an easy way to do it in
one pass, but maybe the code can be shared (even if it's run twice) in
some generic `SemIR::Function` type walker.

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
This commit is contained in:
David Blaikie
2025-11-25 23:25:09 +00:00
committed by GitHub
co-authored by Dana Jansens
parent 0baa74d96f
commit a179bd461b
184 changed files with 8098 additions and 6049 deletions
+147 -57
View File
@@ -51,8 +51,9 @@ FileContext::FileContext(Context& context, const SemIR::File& sem_ir,
vlog_stream_(vlog_stream),
functions_(LoweredFunctionStore::MakeForOverwrite(sem_ir.functions())),
specific_functions_(sem_ir.specifics(), nullptr),
types_(LoweredTypeStore::MakeWithExplicitSize(
sem_ir.insts().GetIdTag(), sem_ir.insts().size(), nullptr)),
types_(LoweredTypeStore::MakeWithExplicitSize(sem_ir.insts().GetIdTag(),
sem_ir.insts().size(),
{nullptr, nullptr})),
constants_(LoweredConstantStore::MakeWithExplicitSize(
sem_ir.insts().GetIdTag(), sem_ir.insts().size(), nullptr)),
lowered_specifics_(sem_ir.generics(),
@@ -652,10 +653,11 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id,
llvm_function->addFnAttrs(attr_builder);
}
auto* subprogram =
BuildDISubprogram(declaration_function, specific_id, llvm_function);
FunctionContext function_lowering(
definition_context, llvm_function, *this, specific_id,
coalescer_.InitializeFingerprintForSpecific(specific_id),
definition_context.BuildDISubprogram(definition_function, llvm_function),
coalescer_.InitializeFingerprintForSpecific(specific_id), subprogram,
vlog_stream_);
// Add parameters to locals.
@@ -766,9 +768,79 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id,
// Emit fingerprint accumulated inside the function context.
function_lowering.EmitFinalFingerprint();
context().di_builder().finalizeSubprogram(subprogram);
}
auto FileContext::BuildDISubroutineType(const SemIR::Function& function,
SemIR::SpecificId specific_id)
-> llvm::DISubroutineType* {
auto implicit_param_patterns =
sem_ir().inst_blocks().GetOrEmpty(function.implicit_param_patterns_id);
auto param_patterns =
sem_ir().inst_blocks().GetOrEmpty(function.param_patterns_id);
auto* void_pointer_debug_type =
context().di_builder().createPointerType(nullptr, 8);
auto get_debug_type = [&](SemIR::TypeId type_id) -> llvm::DIType* {
CARBON_CHECK(type_id.has_value());
if (auto* type = GetTypeAndDIType(type_id).llvm_di_type) {
return type;
}
return void_pointer_debug_type;
};
auto return_info =
SemIR::ReturnTypeInfo::ForFunction(sem_ir(), function, specific_id);
if (function.return_slot_pattern_id.has_value()) {
// TODO: If int_repr.kind == SemIR::InitRepr::ByCopy - be sure the return
// type is tagged with indirect calling convention.
}
// TODO: Expose the `Call` parameter patterns in `Function`, and use them
// here.
llvm::SmallVector<llvm::Metadata*, 16> element_types;
element_types.push_back(return_info.type_id.has_value()
? get_debug_type(return_info.type_id)
: nullptr);
for (auto param_pattern_id : llvm::concat<const SemIR::InstId>(
implicit_param_patterns, param_patterns)) {
auto param_pattern_info = SemIR::Function::GetParamPatternInfoFromPatternId(
sem_ir(), param_pattern_id);
if (!param_pattern_info) {
continue;
}
if (param_pattern_info->inst.kind == SemIR::RefParamPattern::Kind) {
// TODO: Maybe make the parameter type a reference type.
}
auto param_type_id = ExtractScrutineeType(
sem_ir(), SemIR::GetTypeOfInstInSpecific(sem_ir(), specific_id,
param_pattern_info->inst_id));
switch (auto value_rep = SemIR::ValueRepr::ForType(sem_ir(), param_type_id);
value_rep.kind) {
case SemIR::ValueRepr::Unknown:
CARBON_FATAL("Lowering function with incomplete parameter type");
case SemIR::ValueRepr::Dependent:
CARBON_FATAL("Lowering function with dependent parameter type");
case SemIR::ValueRepr::None:
break;
case SemIR::ValueRepr::Copy:
case SemIR::ValueRepr::Custom:
case SemIR::ValueRepr::Pointer:
auto* param_type = get_debug_type(value_rep.type_id);
element_types.push_back(param_type);
break;
}
}
return context().di_builder().createSubroutineType(
context().di_builder().getOrCreateTypeArray(element_types),
llvm::DINode::FlagZero);
}
auto FileContext::BuildDISubprogram(const SemIR::Function& function,
SemIR::SpecificId specific_id,
const llvm::Function* llvm_function)
-> llvm::DISubprogram* {
if (!context().di_compile_unit()) {
@@ -778,16 +850,23 @@ auto FileContext::BuildDISubprogram(const SemIR::Function& function,
CARBON_CHECK(name, "Unexpected special name for function: {0}",
function.name_id);
auto loc = GetLocForDI(function.definition_id);
// TODO: Add more details here, including real subroutine type (once type
// information is built), etc.
return context().di_builder().createFunction(
llvm::DISubroutineType* subroutine_type =
BuildDISubroutineType(function, specific_id);
auto* subprogram = context().di_builder().createFunction(
context().di_compile_unit(), *name, llvm_function->getName(),
/*File=*/context().di_builder().createFile(loc.filename, ""),
/*LineNo=*/loc.line_number,
context().di_builder().createSubroutineType(
context().di_builder().getOrCreateTypeArray({})),
/*LineNo=*/loc.line_number, subroutine_type,
/*ScopeLine=*/0, llvm::DINode::FlagZero,
llvm::DISubprogram::SPFlagDefinition);
// Add a variable for each parameter, as that is where DWARF debug information
// comes from.
for (auto [argument_number, type] :
llvm::enumerate(llvm::drop_begin(subroutine_type->getTypeArray()))) {
context().di_builder().createParameterVariable(
subprogram, "", argument_number + 1, nullptr, 0, type,
/*AlwaysPreserve=*/true);
}
return subprogram;
}
// BuildTypeForInst is used to construct types for FileContext::BuildType below.
@@ -796,96 +875,107 @@ auto FileContext::BuildDISubprogram(const SemIR::Function& function,
template <typename InstT>
requires(InstT::Kind.is_type() == SemIR::InstIsType::Never)
static auto BuildTypeForInst(FileContext& /*context*/, InstT inst)
-> llvm::Type* {
-> FileContext::LoweredTypes {
CARBON_FATAL("Cannot use inst as type: {0}", inst);
}
template <typename InstT>
requires(InstT::Kind.is_symbolic_when_type())
static auto BuildTypeForInst(FileContext& context, InstT /*inst*/)
-> llvm::Type* {
-> FileContext::LoweredTypes {
// Treat non-monomorphized symbolic types as opaque.
return llvm::StructType::get(context.llvm_context());
return {llvm::StructType::get(context.llvm_context()), nullptr};
}
static auto BuildTypeForInst(FileContext& context, SemIR::ArrayType inst)
-> llvm::Type* {
return llvm::ArrayType::get(
context.GetType(context.sem_ir().types().GetTypeIdForTypeInstId(
inst.element_type_inst_id)),
*context.sem_ir().GetArrayBoundValue(inst.bound_id));
-> FileContext::LoweredTypes {
return {llvm::ArrayType::get(
context.GetType(context.sem_ir().types().GetTypeIdForTypeInstId(
inst.element_type_inst_id)),
*context.sem_ir().GetArrayBoundValue(inst.bound_id)),
nullptr};
}
static auto BuildTypeForInst(FileContext& context, SemIR::BoolType /*inst*/)
-> llvm::Type* {
-> FileContext::LoweredTypes {
// TODO: We may want to have different representations for `bool` storage
// (`i8`) versus for `bool` values (`i1`).
return llvm::Type::getInt1Ty(context.llvm_context());
return {llvm::Type::getInt1Ty(context.llvm_context()), nullptr};
}
static auto BuildTypeForInst(FileContext& context, SemIR::ClassType inst)
-> llvm::Type* {
-> FileContext::LoweredTypes {
auto object_repr_id = context.sem_ir()
.classes()
.Get(inst.class_id)
.GetObjectRepr(context.sem_ir(), inst.specific_id);
return context.GetType(object_repr_id);
return context.GetTypeAndDIType(object_repr_id);
}
template <typename InstT>
requires(SemIR::Internal::HasInstCategory<SemIR::AnyQualifiedType, InstT>)
static auto BuildTypeForInst(FileContext& context, InstT inst) -> llvm::Type* {
return context.GetType(
context.sem_ir().types().GetTypeIdForTypeInstId(inst.inner_id));
static auto BuildTypeForInst(FileContext& context, InstT inst)
-> FileContext::LoweredTypes {
return {context.GetType(
context.sem_ir().types().GetTypeIdForTypeInstId(inst.inner_id)),
nullptr};
}
static auto BuildTypeForInst(FileContext& context, SemIR::CustomLayoutType inst)
-> llvm::Type* {
-> FileContext::LoweredTypes {
auto layout = context.sem_ir().custom_layouts().Get(inst.layout_id);
return llvm::ArrayType::get(llvm::Type::getInt8Ty(context.llvm_context()),
layout[SemIR::CustomLayoutId::SizeIndex]);
return {llvm::ArrayType::get(llvm::Type::getInt8Ty(context.llvm_context()),
layout[SemIR::CustomLayoutId::SizeIndex]),
nullptr};
}
static auto BuildTypeForInst(FileContext& context,
SemIR::ImplWitnessAssociatedConstant inst)
-> llvm::Type* {
return context.GetType(inst.type_id);
-> FileContext::LoweredTypes {
return {context.GetType(inst.type_id), nullptr};
}
static auto BuildTypeForInst(FileContext& /*context*/,
SemIR::ErrorInst /*inst*/) -> llvm::Type* {
SemIR::ErrorInst /*inst*/)
-> FileContext::LoweredTypes {
// This is a complete type but uses of it should never be lowered.
return nullptr;
return {nullptr, nullptr};
}
static auto BuildTypeForInst(FileContext& context, SemIR::FloatType inst)
-> llvm::Type* {
return llvm::Type::getFloatingPointTy(context.llvm_context(),
inst.float_kind.Semantics());
-> FileContext::LoweredTypes {
return {llvm::Type::getFloatingPointTy(context.llvm_context(),
inst.float_kind.Semantics()),
nullptr};
}
static auto BuildTypeForInst(FileContext& context, SemIR::IntType inst)
-> llvm::Type* {
auto width =
-> FileContext::LoweredTypes {
auto width_inst =
context.sem_ir().insts().TryGetAs<SemIR::IntValue>(inst.bit_width_id);
CARBON_CHECK(width, "Can't lower int type with symbolic width");
return llvm::IntegerType::get(
context.llvm_context(),
context.sem_ir().ints().Get(width->int_id).getZExtValue());
CARBON_CHECK(width_inst, "Can't lower int type with symbolic width");
auto width = context.sem_ir().ints().Get(width_inst->int_id).getZExtValue();
return {llvm::IntegerType::get(context.llvm_context(), width),
context.context().di_builder().createBasicType(
"int", width,
inst.int_kind.is_signed() ? llvm::dwarf::DW_ATE_signed
: llvm::dwarf::DW_ATE_unsigned)};
}
static auto BuildTypeForInst(FileContext& context, SemIR::PointerType /*inst*/)
-> llvm::Type* {
return llvm::PointerType::get(context.llvm_context(), /*AddressSpace=*/0);
-> FileContext::LoweredTypes {
return {llvm::PointerType::get(context.llvm_context(), /*AddressSpace=*/0),
nullptr};
}
static auto BuildTypeForInst(FileContext& /*context*/,
SemIR::PatternType /*inst*/) -> llvm::Type* {
SemIR::PatternType /*inst*/)
-> FileContext::LoweredTypes {
CARBON_FATAL("Unexpected pattern type in lowering");
}
static auto BuildTypeForInst(FileContext& context, SemIR::StructType inst)
-> llvm::Type* {
-> FileContext::LoweredTypes {
auto fields = context.sem_ir().struct_type_fields().Get(inst.fields_id);
llvm::SmallVector<llvm::Type*> subtypes;
subtypes.reserve(fields.size());
@@ -893,11 +983,11 @@ static auto BuildTypeForInst(FileContext& context, SemIR::StructType inst)
subtypes.push_back(context.GetType(
context.sem_ir().types().GetTypeIdForTypeInstId(field.type_inst_id)));
}
return llvm::StructType::get(context.llvm_context(), subtypes);
return {llvm::StructType::get(context.llvm_context(), subtypes), nullptr};
}
static auto BuildTypeForInst(FileContext& context, SemIR::TupleType inst)
-> llvm::Type* {
-> FileContext::LoweredTypes {
// TODO: Investigate special-casing handling of empty tuples so that they
// can be collectively replaced with LLVM's void, particularly around
// function returns. LLVM doesn't allow declaring variables with a void
@@ -908,23 +998,23 @@ static auto BuildTypeForInst(FileContext& context, SemIR::TupleType inst)
for (auto type_id : context.sem_ir().types().GetBlockAsTypeIds(elements)) {
subtypes.push_back(context.GetType(type_id));
}
return llvm::StructType::get(context.llvm_context(), subtypes);
return {llvm::StructType::get(context.llvm_context(), subtypes), nullptr};
}
static auto BuildTypeForInst(FileContext& context, SemIR::TypeType /*inst*/)
-> llvm::Type* {
return context.GetTypeType();
-> FileContext::LoweredTypes {
return {context.GetTypeType(), nullptr};
}
static auto BuildTypeForInst(FileContext& context, SemIR::VtableType /*inst*/)
-> llvm::Type* {
return llvm::Type::getVoidTy(context.llvm_context());
-> FileContext::LoweredTypes {
return {llvm::Type::getVoidTy(context.llvm_context()), nullptr};
}
static auto BuildTypeForInst(FileContext& context,
SemIR::SpecificFunctionType /*inst*/)
-> llvm::Type* {
return llvm::PointerType::get(context.llvm_context(), 0);
-> FileContext::LoweredTypes {
return {llvm::PointerType::get(context.llvm_context(), 0), nullptr};
}
template <typename InstT>
@@ -938,14 +1028,14 @@ template <typename InstT>
SemIR::RequireSpecificDefinitionType, SemIR::UnboundElementType,
SemIR::WhereExpr, SemIR::WitnessType>())
static auto BuildTypeForInst(FileContext& context, InstT /*inst*/)
-> llvm::Type* {
-> FileContext::LoweredTypes {
// Return an empty struct as a placeholder.
// TODO: Should we model an interface as a witness table, or an associated
// entity as an index?
return llvm::StructType::get(context.llvm_context());
return {llvm::StructType::get(context.llvm_context()), nullptr};
}
auto FileContext::BuildType(SemIR::InstId inst_id) -> llvm::Type* {
auto FileContext::BuildType(SemIR::InstId inst_id) -> LoweredTypes {
// Use overload resolution to select the implementation, producing compile
// errors when BuildTypeForInst isn't defined for a given instruction.
CARBON_KIND_SWITCH(sem_ir_->insts().Get(inst_id)) {