Skip to content

Commit

Permalink
[Clang] Emit DW_TAG_template_alias for template aliases (#87623)
Browse files Browse the repository at this point in the history
Fix issue #54624

Add front end flags -gtemplate-alias (also a cc1 flag) and -gno-template-alias
to enable/disable usage of the feature. It's enabled by default in the front
end for SCE debugger tuning only.

GCC emits DW_TAG_typedef for template aliases (as does Clang with this feature
disabled), and GDB and LLDB appear not to support DW_TAG_template_alias.

The -Xclang option -gsimple-template-names=mangled is treated the same as
=full, which is not a regression from current behaviour for template
aliases.

The current implementation omits defaulted arguments and as a consequence
also omits empty parameter packs that come after defaulted arguments. Again,
this isn't a regression as the DW_TAG_typedef name doesn't contain defaulted
arguments.

LLVM support added in #88943

Added template-alias.cpp - Check the metadata construction & interaction with
      -gsimple-template-names.
Added variadic-template-alias.cpp - Check template parameter packs work.
Added defaulted-template-alias.cpp - Check defaulted args (don't) work.
Modified debug-options.c - Check Clang generates correct cc1 flags.
  • Loading branch information
OCHyams committed Apr 18, 2024
1 parent 76600ae commit e772a26
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 47 deletions.
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DebugOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ DEBUGOPT(CodeViewCommandLine, 1, 0)
/// Whether emit extra debug info for sample pgo profile collection.
DEBUGOPT(DebugInfoForProfiling, 1, 0)

/// Whether to emit DW_TAG_template_alias for template aliases.
DEBUGOPT(DebugTemplateAlias, 1, 0)

/// Whether to emit .debug_gnu_pubnames section instead of .debug_pubnames.
DEBUGOPT(DebugNameTable, 2, 0)

Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -4309,6 +4309,8 @@ def gsplit_dwarf_EQ : Joined<["-"], "gsplit-dwarf=">, Group<g_flags_Group>,
Values<"split,single">;
def gno_split_dwarf : Flag<["-"], "gno-split-dwarf">, Group<g_flags_Group>,
Visibility<[ClangOption, CLOption, DXCOption]>;
def gtemplate_alias : Flag<["-"], "gtemplate-alias">, Group<g_flags_Group>, Visibility<[ClangOption, CC1Option]>;
def gno_template_alias : Flag<["-"], "gno-template-alias">, Group<g_flags_Group>, Visibility<[ClangOption]>;
def gsimple_template_names : Flag<["-"], "gsimple-template-names">, Group<g_flags_Group>;
def gsimple_template_names_EQ
: Joined<["-"], "gsimple-template-names=">,
Expand Down
157 changes: 112 additions & 45 deletions clang/lib/CodeGen/CGDebugInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,44 @@ llvm::DIType *CGDebugInfo::CreateType(const BlockPointerType *Ty,
return DBuilder.createPointerType(EltTy, Size);
}

static llvm::SmallVector<TemplateArgument>
GetTemplateArgs(const TemplateDecl *TD, const TemplateSpecializationType *Ty) {
assert(Ty->isTypeAlias());
// TemplateSpecializationType doesn't know if its template args are
// being substituted into a parameter pack. We can find out if that's
// the case now by inspecting the TypeAliasTemplateDecl template
// parameters. Insert Ty's template args into SpecArgs, bundling args
// passed to a parameter pack into a TemplateArgument::Pack. It also
// doesn't know the value of any defaulted args, so collect those now
// too.
SmallVector<TemplateArgument> SpecArgs;
ArrayRef SubstArgs = Ty->template_arguments();
for (const NamedDecl *Param : TD->getTemplateParameters()->asArray()) {
// If Param is a parameter pack, pack the remaining arguments.
if (Param->isParameterPack()) {
SpecArgs.push_back(TemplateArgument(SubstArgs));
break;
}

// Skip defaulted args.
// FIXME: Ideally, we wouldn't do this. We can read the default values
// for each parameter. However, defaulted arguments which are dependent
// values or dependent types can't (easily?) be resolved here.
if (SubstArgs.empty()) {
// If SubstArgs is now empty (we're taking from it each iteration) and
// this template parameter isn't a pack, then that should mean we're
// using default values for the remaining template parameters (after
// which there may be an empty pack too which we will ignore).
break;
}

// Take the next argument.
SpecArgs.push_back(SubstArgs.front());
SubstArgs = SubstArgs.drop_front();
}
return SpecArgs;
}

llvm::DIType *CGDebugInfo::CreateType(const TemplateSpecializationType *Ty,
llvm::DIFile *Unit) {
assert(Ty->isTypeAlias());
Expand All @@ -1332,6 +1370,31 @@ llvm::DIType *CGDebugInfo::CreateType(const TemplateSpecializationType *Ty,
auto PP = getPrintingPolicy();
Ty->getTemplateName().print(OS, PP, TemplateName::Qualified::None);

SourceLocation Loc = AliasDecl->getLocation();

if (CGM.getCodeGenOpts().DebugTemplateAlias) {
auto ArgVector = ::GetTemplateArgs(TD, Ty);
TemplateArgs Args = {TD->getTemplateParameters(), ArgVector};

// FIXME: Respect DebugTemplateNameKind::Mangled, e.g. by using GetName.
// Note we can't use GetName without additional work: TypeAliasTemplateDecl
// doesn't have instantiation information, so
// TypeAliasTemplateDecl::getNameForDiagnostic wouldn't have access to the
// template args.
std::string Name;
llvm::raw_string_ostream OS(Name);
TD->getNameForDiagnostic(OS, PP, /*Qualified=*/false);
if (CGM.getCodeGenOpts().getDebugSimpleTemplateNames() !=
llvm::codegenoptions::DebugTemplateNamesKind::Simple ||
!HasReconstitutableArgs(Args.Args))
printTemplateArgumentList(OS, Args.Args, PP);

llvm::DIDerivedType *AliasTy = DBuilder.createTemplateAlias(
Src, Name, getOrCreateFile(Loc), getLineNumber(Loc),
getDeclContextDescriptor(AliasDecl), CollectTemplateParams(Args, Unit));
return AliasTy;
}

// Disable PrintCanonicalTypes here because we want
// the DW_AT_name to benefit from the TypePrinter's ability
// to skip defaulted template arguments.
Expand All @@ -1343,8 +1406,6 @@ llvm::DIType *CGDebugInfo::CreateType(const TemplateSpecializationType *Ty,
PP.PrintCanonicalTypes = false;
printTemplateArgumentList(OS, Ty->template_arguments(), PP,
TD->getTemplateParameters());

SourceLocation Loc = AliasDecl->getLocation();
return DBuilder.createTypedef(Src, OS.str(), getOrCreateFile(Loc),
getLineNumber(Loc),
getDeclContextDescriptor(AliasDecl));
Expand Down Expand Up @@ -5363,6 +5424,54 @@ static bool IsReconstitutableType(QualType QT) {
return T.Reconstitutable;
}

bool CGDebugInfo::HasReconstitutableArgs(
ArrayRef<TemplateArgument> Args) const {
return llvm::all_of(Args, [&](const TemplateArgument &TA) {
switch (TA.getKind()) {
case TemplateArgument::Template:
// Easy to reconstitute - the value of the parameter in the debug
// info is the string name of the template. The template name
// itself won't benefit from any name rebuilding, but that's a
// representational limitation - maybe DWARF could be
// changed/improved to use some more structural representation.
return true;
case TemplateArgument::Declaration:
// Reference and pointer non-type template parameters point to
// variables, functions, etc and their value is, at best (for
// variables) represented as an address - not a reference to the
// DWARF describing the variable/function/etc. This makes it hard,
// possibly impossible to rebuild the original name - looking up
// the address in the executable file's symbol table would be
// needed.
return false;
case TemplateArgument::NullPtr:
// These could be rebuilt, but figured they're close enough to the
// declaration case, and not worth rebuilding.
return false;
case TemplateArgument::Pack:
// A pack is invalid if any of the elements of the pack are
// invalid.
return HasReconstitutableArgs(TA.getPackAsArray());
case TemplateArgument::Integral:
// Larger integers get encoded as DWARF blocks which are a bit
// harder to parse back into a large integer, etc - so punting on
// this for now. Re-parsing the integers back into APInt is
// probably feasible some day.
return TA.getAsIntegral().getBitWidth() <= 64 &&
IsReconstitutableType(TA.getIntegralType());
case TemplateArgument::StructuralValue:
return false;
case TemplateArgument::Type:
return IsReconstitutableType(TA.getAsType());
case TemplateArgument::Expression:
return IsReconstitutableType(TA.getAsExpr()->getType());
default:
llvm_unreachable("Other, unresolved, template arguments should "
"not be seen here");
}
});
}

std::string CGDebugInfo::GetName(const Decl *D, bool Qualified) const {
std::string Name;
llvm::raw_string_ostream OS(Name);
Expand All @@ -5389,49 +5498,7 @@ std::string CGDebugInfo::GetName(const Decl *D, bool Qualified) const {
} else if (auto *VD = dyn_cast<VarDecl>(ND)) {
Args = GetTemplateArgs(VD);
}
std::function<bool(ArrayRef<TemplateArgument>)> HasReconstitutableArgs =
[&](ArrayRef<TemplateArgument> Args) {
return llvm::all_of(Args, [&](const TemplateArgument &TA) {
switch (TA.getKind()) {
case TemplateArgument::Template:
// Easy to reconstitute - the value of the parameter in the debug
// info is the string name of the template. (so the template name
// itself won't benefit from any name rebuilding, but that's a
// representational limitation - maybe DWARF could be
// changed/improved to use some more structural representation)
return true;
case TemplateArgument::Declaration:
// Reference and pointer non-type template parameters point to
// variables, functions, etc and their value is, at best (for
// variables) represented as an address - not a reference to the
// DWARF describing the variable/function/etc. This makes it hard,
// possibly impossible to rebuild the original name - looking up the
// address in the executable file's symbol table would be needed.
return false;
case TemplateArgument::NullPtr:
// These could be rebuilt, but figured they're close enough to the
// declaration case, and not worth rebuilding.
return false;
case TemplateArgument::Pack:
// A pack is invalid if any of the elements of the pack are invalid.
return HasReconstitutableArgs(TA.getPackAsArray());
case TemplateArgument::Integral:
// Larger integers get encoded as DWARF blocks which are a bit
// harder to parse back into a large integer, etc - so punting on
// this for now. Re-parsing the integers back into APInt is probably
// feasible some day.
return TA.getAsIntegral().getBitWidth() <= 64 &&
IsReconstitutableType(TA.getIntegralType());
case TemplateArgument::StructuralValue:
return false;
case TemplateArgument::Type:
return IsReconstitutableType(TA.getAsType());
default:
llvm_unreachable("Other, unresolved, template arguments should "
"not be seen here");
}
});
};

// A conversion operator presents complications/ambiguity if there's a
// conversion to class template that is itself a template, eg:
// template<typename T>
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/CodeGen/CGDebugInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,8 @@ class CGDebugInfo {
llvm::DIType *WrappedType;
};

std::string GetName(const Decl*, bool Qualified = false) const;
bool HasReconstitutableArgs(ArrayRef<TemplateArgument> Args) const;
std::string GetName(const Decl *, bool Qualified = false) const;

/// Build up structure info for the byref. See \a BuildByRefType.
BlockByRefType EmitTypeForVarWithBlocksAttr(const VarDecl *VD,
Expand Down
15 changes: 15 additions & 0 deletions clang/lib/Driver/ToolChains/Clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4632,6 +4632,21 @@ renderDebugOptions(const ToolChain &TC, const Driver &D, const llvm::Triple &T,
}
}

// Emit DW_TAG_template_alias for template aliases? True by default for SCE.
bool UseDebugTemplateAlias =
DebuggerTuning == llvm::DebuggerKind::SCE && RequestedDWARFVersion >= 5;
if (const auto *DebugTemplateAlias = Args.getLastArg(
options::OPT_gtemplate_alias, options::OPT_gno_template_alias)) {
// DW_TAG_template_alias is only supported from DWARFv5 but if a user
// asks for it we should let them have it (if the target supports it).
if (checkDebugInfoOption(DebugTemplateAlias, Args, D, TC)) {
const auto &Opt = DebugTemplateAlias->getOption();
UseDebugTemplateAlias = Opt.matches(options::OPT_gtemplate_alias);
}
}
if (UseDebugTemplateAlias)
CmdArgs.push_back("-gtemplate-alias");

if (const Arg *A = Args.getLastArg(options::OPT_gsrc_hash_EQ)) {
StringRef v = A->getValue();
CmdArgs.push_back(Args.MakeArgString("-gsrc-hash=" + v));
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1556,6 +1556,9 @@ void CompilerInvocationBase::GenerateCodeGenArgs(const CodeGenOptions &Opts,
llvm::DICompileUnit::DebugNameTableKind::Default))
GenerateArg(Consumer, OPT_gpubnames);

if (Opts.DebugTemplateAlias)
GenerateArg(Consumer, OPT_gtemplate_alias);

auto TNK = Opts.getDebugSimpleTemplateNames();
if (TNK != llvm::codegenoptions::DebugTemplateNamesKind::Full) {
if (TNK == llvm::codegenoptions::DebugTemplateNamesKind::Simple)
Expand Down Expand Up @@ -1827,6 +1830,8 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args,
Opts.BinutilsVersion =
std::string(Args.getLastArgValue(OPT_fbinutils_version_EQ));

Opts.DebugTemplateAlias = Args.hasArg(OPT_gtemplate_alias);

Opts.DebugNameTable = static_cast<unsigned>(
Args.hasArg(OPT_ggnu_pubnames)
? llvm::DICompileUnit::DebugNameTableKind::GNU
Expand Down
2 changes: 1 addition & 1 deletion clang/test/CodeGenCXX/debug-info-alias.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang -g -std=c++11 -S -emit-llvm %s -o - | FileCheck %s
// RUN: %clang -g -gno-template-alias -std=c++11 -S -emit-llvm %s -o - | FileCheck %s

template<typename T>
struct foo {
Expand Down
38 changes: 38 additions & 0 deletions clang/test/CodeGenCXX/defaulted-template-alias.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// RUN: %clang_cc1 -triple x86_64-unk-unk -o - -emit-llvm -debug-info-kind=standalone -gtemplate-alias %s -gsimple-template-names=simple \
// RUN: | FileCheck %s

//// Check that -gtemplate-alias causes DW_TAG_template_alias emission for
//// template aliases with default parameter values. See template-alias.cpp for
//// more template alias tests.
//// FIXME: We currently do not emit defaulted arguments.

template<typename T>
struct X {
char m;
};

template<typename T>
struct Y {
char n;
};

template <typename NonDefault, template <typename C> class T = Y, int I = 5, typename... Ts>
using A = X<NonDefault>;

//// We should be able to emit type alias metadata which describes all the
//// values, including the defaulted parameters and empty parameter pack.
A<int> a;

// CHECK: !DIDerivedType(tag: DW_TAG_template_alias, name: "A", file: ![[#]], line: [[#]], baseType: ![[baseType:[0-9]+]], extraData: ![[extraData:[0-9]+]])
// CHECK: ![[baseType]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "X",
// CHECK: ![[int:[0-9]+]] = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
// CHECK: ![[extraData]] = !{![[NonDefault:[0-9]+]]}
// CHECK: ![[NonDefault]] = !DITemplateTypeParameter(name: "NonDefault", type: ![[int]])

//// FIXME: Ideally, we would describe the deafulted args, like this:
// : ![[extraData]] = !{![[NonDefault:[0-9]+]], ![[T:[0-9]+]], ![[I:[0-9]+]], ![[Ts:[0-9]+]]}
// : ![[NonDefault]] = !DITemplateTypeParameter(name: "NonDefault", type: ![[int]])
// : ![[T]] = !DITemplateValueParameter(tag: DW_TAG_GNU_template_template_param, name: "T", defaulted: true, value: !"Y")
// : ![[I]] = !DITemplateValueParameter(name: "I", type: ![[int]], defaulted: true, value: i32 5)
// : ![[Ts]] = !DITemplateValueParameter(tag: DW_TAG_GNU_template_parameter_pack, name: "Ts", value: ![[types:[0-9]+]])
// : ![[types]] = !{}
47 changes: 47 additions & 0 deletions clang/test/CodeGenCXX/template-alias.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// RUN: %clang_cc1 -triple x86_64-unk-unk -o - -emit-llvm -debug-info-kind=standalone -gtemplate-alias %s -gsimple-template-names=simple \
// RUN: | FileCheck %s --check-prefixes=ALIAS-SIMPLE,ALIAS-ALL

// RUN: %clang_cc1 -triple x86_64-unk-unk -o - -emit-llvm -debug-info-kind=standalone -gtemplate-alias %s -gsimple-template-names=mangled \
// RUN: | FileCheck %s --check-prefixes=ALIAS-MANGLED,ALIAS-ALL

// RUN: %clang_cc1 -triple x86_64-unk-unk -o - -emit-llvm -debug-info-kind=standalone -gtemplate-alias %s \
// RUN: | FileCheck %s --check-prefixes=ALIAS-FULL,ALIAS-ALL

// RUN: %clang_cc1 -triple x86_64-unk-unk -o - -emit-llvm -debug-info-kind=standalone %s \
// RUN: | FileCheck %s --check-prefixes=TYPEDEF


//// Check that -gtemplate-alias causes DW_TAG_template_alias emission for
//// template aliases, and that respects gsimple-template-names.
////
//// Test type and value template parameters.

template<typename Y, int Z>
struct X {
Y m1 = Z;
};

template<typename B, int C>
using A = X<B, C>;

A<int, 5> a;


// ALIAS-SIMPLE: !DIDerivedType(tag: DW_TAG_template_alias, name: "A", file: ![[#]], line: [[#]], baseType: ![[baseType:[0-9]+]], extraData: ![[extraData:[0-9]+]])
// ALIAS-SIMPLE: ![[baseType]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "X",

// FIXME: Mangled name is wrong (not a regression).
// ALIAS-MANGLED: !DIDerivedType(tag: DW_TAG_template_alias, name: "A<int, 5>", file: ![[#]], line: [[#]], baseType: ![[baseType:[0-9]+]], extraData: ![[extraData:[0-9]+]])
// ALIAS-MANGLED: ![[baseType]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "_STN|X|<int, 5>",

// ALIAS-FULL: !DIDerivedType(tag: DW_TAG_template_alias, name: "A<int, 5>", file: ![[#]], line: [[#]], baseType: ![[baseType:[0-9]+]], extraData: ![[extraData:[0-9]+]])
// ALIAS-FULL: ![[baseType]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "X<int, 5>",

// ALIAS-ALL: ![[int:[0-9]+]] = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
// ALIAS-ALL: ![[extraData]] = !{![[B:[0-9]+]], ![[C:[0-9]+]]}
// ALIAS-ALL: ![[B]] = !DITemplateTypeParameter(name: "B", type: ![[int]])
// ALIAS-ALL: ![[C]] = !DITemplateValueParameter(name: "C", type: ![[int]], value: i32 5)

// TYPEDEF: !DIDerivedType(tag: DW_TAG_typedef, name: "A<int, 5>", file: ![[#]], line: [[#]], baseType: ![[baseType:[0-9]+]])
// TYPEDEF: ![[baseType]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "X<int, 5>",
// TYPEDEF: ![[int:[0-9]+]] = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
25 changes: 25 additions & 0 deletions clang/test/CodeGenCXX/variadic-template-alias.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// RUN: %clang_cc1 -triple x86_64-unk-unk -o - -emit-llvm -debug-info-kind=standalone -gtemplate-alias %s -gsimple-template-names=simple \
// RUN: | FileCheck %s

//// Check that -gtemplate-alias causes DW_TAG_template_alias emission for
//// variadic template aliases. See template-alias.cpp for more template alias
//// tests.

template<typename Y, int Z>
struct X {
Y m1 = Z;
};

template<int I, typename... Ts>
using A = X<Ts..., I>;

A<5, int> a;

// CHECK: !DIDerivedType(tag: DW_TAG_template_alias, name: "A", file: ![[#]], line: [[#]], baseType: ![[baseType:[0-9]+]], extraData: ![[extraData:[0-9]+]])
// CHECK: ![[baseType]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "X",
// CHECK: ![[int:[0-9]+]] = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
// CHECK: ![[extraData]] = !{![[I:[0-9]+]], ![[Ts:[0-9]+]]}
// CHECK: ![[I]] = !DITemplateValueParameter(name: "I", type: ![[int]], value: i32 5)
// CHECK: ![[Ts]] = !DITemplateValueParameter(tag: DW_TAG_GNU_template_parameter_pack, name: "Ts", value: ![[types:[0-9]+]])
// CHECK: ![[types]] = !{![[int_template_param:[0-9]+]]}
// CHECK: ![[int_template_param]] = !DITemplateTypeParameter(type: ![[int]])
10 changes: 10 additions & 0 deletions clang/test/Driver/debug-options.c
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,13 @@
// MANGLED_TEMP_NAMES: error: unknown argument '-gsimple-template-names=mangled'; did you mean '-Xclang -gsimple-template-names=mangled'
// RUN: %clang -### -target x86_64 -c -g %s 2>&1 | FileCheck --check-prefix=FULL_TEMP_NAMES --implicit-check-not=debug-forward-template-params %s
// FULL_TEMP_NAMES-NOT: -gsimple-template-names

//// Test -g[no-]template-alias (enabled by default with SCE debugger tuning and DWARFv5).
// RUN: %clang -### -target x86_64 -c -gdwarf-5 -gsce %s 2>&1 | FileCheck %s --check-prefixes=TEMPLATE-ALIAS
// RUN: %clang -### -target x86_64 -c -gdwarf-4 -gsce %s 2>&1 | FileCheck %s --check-prefixes=NO-TEMPLATE-ALIAS
// RUN: %clang -### -target x86_64 -c -gdwarf-5 -gsce -gtemplate-alias %s 2>&1 | FileCheck %s --check-prefixes=TEMPLATE-ALIAS
// RUN: %clang -### -target x86_64 -c -gdwarf-5 -gsce -gno-template-alias %s 2>&1 | FileCheck %s --check-prefixes=NO-TEMPLATE-ALIAS
// RUN: %clang -### -target x86_64 -c -gdwarf-5 -gtemplate-alias %s 2>&1 | FileCheck %s --check-prefixes=TEMPLATE-ALIAS
// RUN: %clang -### -target x86_64 -c -gdwarf-5 -gtemplate-alias -gno-template-alias %s 2>&1 | FileCheck %s --check-prefixes=NO-TEMPLATE-ALIAS
// TEMPLATE-ALIAS: "-gtemplate-alias"
// NO-TEMPLATE-ALIAS-NOT: "-gtemplate-alias"

0 comments on commit e772a26

Please sign in to comment.