mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
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:
co-authored by
Dana Jansens
parent
0baa74d96f
commit
a179bd461b
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user