https://github.com/Michael137 updated https://github.com/llvm/llvm-project/pull/114529
>From 9337e170d920eaabe2b59a25622f0c554ca5afcf Mon Sep 17 00:00:00 2001 From: Michael Buch <michaelbuc...@gmail.com> Date: Sun, 20 Oct 2024 11:35:15 +0100 Subject: [PATCH] [WIP][lldb][Expression] More reliable function call resolution Implements all the parts of following RFC: https://discourse.llvm.org/t/rfc-lldb-handling-abi-tagged-constructors-destructors-in-expression-evaluator/82816 Main changes: 1. Instead of relying on linkage names to resolve function symbols, encode the exact function DIE and module in the `AsmLabelAttr` 2. Teach the LLDB symbol resolve about (1) 3. Introduce new Clang attribute to allow specifying multiple `asm` labels for ctors/dtors (one for each variant) 4. Attach the new attribute in (3), where the mangled names use the format from (1). To determine which variant a DIE corresponds to we add a new API to the `ItaniumPartialDemangler` (though could be made into a DWARF attribute for quicker determination). --- clang/include/clang/Basic/Attr.td | 8 ++ clang/include/clang/Basic/AttrDocs.td | 5 ++ clang/lib/AST/Mangle.cpp | 69 +++++++++++++++- clang/lib/Sema/SemaDeclAttr.cpp | 22 +++++ lldb/source/Expression/IRExecutionUnit.cpp | 36 +++++++++ .../SymbolFile/DWARF/DWARFASTParserClang.cpp | 80 +++++++++++++++---- .../TypeSystem/Clang/TypeSystemClang.cpp | 15 +++- .../TypeSystem/Clang/TypeSystemClang.h | 3 +- lldb/test/Shell/Expr/TestAbiTagCtorsDtors.cpp | 28 +++++++ llvm/include/llvm/Demangle/Demangle.h | 3 + llvm/include/llvm/Demangle/ItaniumDemangle.h | 2 + llvm/lib/Demangle/ItaniumDemangle.cpp | 18 +++-- 12 files changed, 264 insertions(+), 25 deletions(-) create mode 100644 lldb/test/Shell/Expr/TestAbiTagCtorsDtors.cpp diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 70fad60d4edbb5..407eece2a728a2 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -784,6 +784,14 @@ def AbiTag : Attr { let Documentation = [AbiTagsDocs]; } +def StructorMangledNames : Attr { + let Spellings = [Clang<"structor_names">]; + let Args = [VariadicStringArgument<"MangledNames">]; + let Subjects = SubjectList<[Function], ErrorDiag>; + let Documentation = [StructorMangledNamesDocs]; +} + + def AddressSpace : TypeAttr { let Spellings = [Clang<"address_space">]; let Args = [IntArgument<"AddressSpace">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 546e5100b79dd9..2b886aecd193de 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -3568,6 +3568,11 @@ manipulating bits of the enumerator when issuing warnings. }]; } +def StructorMangledNamesDocs : Documentation { + let Category = DocCatDecl; + let Content = [{ TODO }]; +} + def AsmLabelDocs : Documentation { let Category = DocCatDecl; let Content = [{ diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp index 4875e8537b3c11..9b304d28113625 100644 --- a/clang/lib/AST/Mangle.cpp +++ b/clang/lib/AST/Mangle.cpp @@ -9,19 +9,20 @@ // Implements generic name mangling support for blocks and Objective-C. // //===----------------------------------------------------------------------===// -#include "clang/AST/Attr.h" +#include "clang/AST/Mangle.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/ExprCXX.h" -#include "clang/AST/Mangle.h" #include "clang/AST/VTableBuilder.h" #include "clang/Basic/ABI.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringSwitch.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/Mangler.h" #include "llvm/Support/ErrorHandling.h" @@ -126,7 +127,7 @@ bool MangleContext::shouldMangleDeclName(const NamedDecl *D) { // Any decl can be declared with __asm("foo") on it, and this takes precedence // over all other naming in the .o file. - if (D->hasAttr<AsmLabelAttr>()) + if (D->hasAttr<AsmLabelAttr>() || D->hasAttr<StructorMangledNamesAttr>()) return true; // Declarations that don't have identifier names always need to be mangled. @@ -140,6 +141,68 @@ void MangleContext::mangleName(GlobalDecl GD, raw_ostream &Out) { const ASTContext &ASTContext = getASTContext(); const NamedDecl *D = cast<NamedDecl>(GD.getDecl()); + if (const StructorMangledNamesAttr *SMA = + D->getAttr<StructorMangledNamesAttr>()) { + CXXConstructorDecl const *Ctor = dyn_cast<CXXConstructorDecl>(D); + CXXDestructorDecl const *Dtor = dyn_cast<CXXDestructorDecl>(D); + assert(Ctor || Dtor); + enum CtorDtor { + None = -1, + Deleting = 0, + Base, + Complete, + Allocating + } CtorDtorVariant = None; + + // Map ctor/dtor variant to a the variant that LLDB encoded in the + // special identifier. I.e., a number between [0-3]. + if (Dtor) { + switch (GD.getDtorType()) { + case Dtor_Complete: + CtorDtorVariant = Complete; + break; + case Dtor_Base: + CtorDtorVariant = Base; + break; + case Dtor_Deleting: + case Dtor_Comdat: + llvm_unreachable(""); + } + } else if (Ctor) { + switch (GD.getCtorType()) { + case Ctor_Complete: + CtorDtorVariant = Complete; + break; + case Ctor_Base: + CtorDtorVariant = Base; + break; + case Ctor_DefaultClosure: + case Ctor_CopyingClosure: + case Ctor_Comdat: + llvm_unreachable(""); + } + } + + // Parse the LLDB [variant -> special name] mappings. + llvm::DenseMap<CtorDtor, llvm::StringRef> names; + for (auto name : SMA->mangledNames()) { + auto [structor_variant, mangled_name] = name.split(':'); + auto variant = llvm::StringSwitch<CtorDtor>(structor_variant) + .Case("0", CtorDtor::Deleting) + .Case("1", CtorDtor::Base) + .Case("2", CtorDtor::Complete) + .Case("3", CtorDtor::Complete) + .Default(CtorDtor::None); + names[variant] = mangled_name; + } + + assert(CtorDtorVariant != CtorDtor::None); + + // Pick the mapping + Out << names[CtorDtorVariant]; + return; + } + // Any decl can be declared with __asm("foo") on it, and this takes precedence // over all other naming in the .o file. if (const AsmLabelAttr *ALA = D->getAttr<AsmLabelAttr>()) { diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 14cc51cf89665a..fdf66be8ac3987 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -5539,6 +5539,25 @@ static void handleMSConstexprAttr(Sema &S, Decl *D, const ParsedAttr &AL) { D->addAttr(::new (S.Context) MSConstexprAttr(S.Context, AL)); } +static void handleStructorNamesAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + SmallVector<StringRef, 4> Tags; + for (unsigned I = 0, E = AL.getNumArgs(); I != E; ++I) { + StringRef Tag; + if (!S.checkStringLiteralArgumentAttr(AL, I, Tag)) + return; + Tags.push_back(Tag); + } + + if (!AL.checkAtLeastNumArgs(S, 1)) + return; + + // Store tags without duplicates. + Tags.erase(std::unique(Tags.begin(), Tags.end()), Tags.end()); + + D->addAttr(::new (S.Context) StructorMangledNamesAttr( + S.Context, AL, Tags.data(), Tags.size())); +} + static void handleAbiTagAttr(Sema &S, Decl *D, const ParsedAttr &AL) { SmallVector<StringRef, 4> Tags; for (unsigned I = 0, E = AL.getNumArgs(); I != E; ++I) { @@ -6983,6 +7002,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, S.HLSL().handleParamModifierAttr(D, AL); break; + case ParsedAttr::AT_StructorMangledNames: + handleStructorNamesAttr(S, D, AL); + break; case ParsedAttr::AT_AbiTag: handleAbiTagAttr(S, D, AL); break; diff --git a/lldb/source/Expression/IRExecutionUnit.cpp b/lldb/source/Expression/IRExecutionUnit.cpp index 15ca2ddbbae046..b11085d921c419 100644 --- a/lldb/source/Expression/IRExecutionUnit.cpp +++ b/lldb/source/Expression/IRExecutionUnit.cpp @@ -16,6 +16,8 @@ #include "llvm/Support/SourceMgr.h" #include "llvm/Support/raw_ostream.h" +#include "Plugins/SymbolFile/DWARF/DWARFBaseDIE.h" +#include "Plugins/SymbolFile/DWARF/SymbolFileDWARF.h" #include "lldb/Core/Debugger.h" #include "lldb/Core/Disassembler.h" #include "lldb/Core/Module.h" @@ -37,6 +39,8 @@ #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" +#include <cstddef> +#include <cstdint> #include <optional> using namespace lldb_private; @@ -781,6 +785,38 @@ IRExecutionUnit::FindInSymbols(const std::vector<ConstString> &names, function_options.include_inlines = false; for (const ConstString &name : names) { + auto ref = name.GetStringRef(); + if (ref.consume_front("$__lldb_func_")) { + uintptr_t module_ptr; + if (ref.consumeInteger(0, module_ptr)) + return LLDB_INVALID_ADDRESS; + + auto *mod = (lldb_private::Module *)module_ptr; + auto *sym = mod->GetSymbolFile(); + assert(mod && sym); + + if (!ref.consume_front(":")) + return LLDB_INVALID_ADDRESS; + + lldb::user_id_t die_id; + if (ref.consumeInteger(10, die_id)) + return LLDB_INVALID_ADDRESS; + + auto *dwarf = llvm::dyn_cast<plugin::dwarf::SymbolFileDWARF>(sym); + if (!dwarf) + return LLDB_INVALID_ADDRESS; + + auto die = dwarf->GetDIE(die_id); + Module::LookupInfo lookup_info( + ConstString(die.GetMangledName()), + lldb::FunctionNameType::eFunctionNameTypeAny, + lldb::LanguageType::eLanguageTypeC_plus_plus); + SymbolContextList sc_list; + dwarf->FindFunctions(lookup_info, {}, false, sc_list); + if (auto load_addr = resolver.Resolve(sc_list)) + return *load_addr; + } + if (sc.module_sp) { SymbolContextList sc_list; sc.module_sp->FindFunctions(name, CompilerDeclContext(), diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp index 70540fe7fada68..ea86ef352800ec 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp @@ -45,6 +45,7 @@ #include "clang/AST/Type.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Demangle/Demangle.h" +#include "llvm/Support/FormatVariadic.h" #include <map> #include <memory> @@ -1040,6 +1041,62 @@ bool DWARFASTParserClang::ParseObjCMethod( return true; } +static bool IsStructorDIE(DWARFDIE const &die, DWARFDIE const &parent_die) { + llvm::StringRef name = die.GetName(); + llvm::StringRef parent_name = parent_die.GetName(); + + name.consume_front("~"); + parent_name = parent_name.substr(0, parent_name.find('<')); + + return name == parent_name; +} + +/// Given a DIE with an external definition (and thus no linkage name) +/// find the definitions by lookup into the DWARF name index. +/// We check the DW_AT_specification for each DIE in the index with +/// the same name as the specified 'die' until we find one that references +/// 'die'. Then return that linkage name. If no such DIE is found in the index, +/// returns nullptr. +static std::vector<std::string> FindStructorNames(DWARFDIE die) { + auto *dwarf = die.GetDWARF(); + assert(dwarf); + + ConstString func_name(die.GetName()); + assert(func_name); + + SymbolContextList sc_list; + Module::LookupInfo lookup_info(func_name, + FunctionNameType::eFunctionNameTypeMethod | + FunctionNameType::eFunctionNameTypeFull, + LanguageType::eLanguageTypeUnknown); + dwarf->FindFunctions(lookup_info, {}, true, sc_list); + + std::vector<std::string> structor_names; + for (auto const &sc : sc_list.SymbolContexts()) { + if (auto *func = sc.function) { + auto func_die = dwarf->GetDIE(func->GetID()); + if (!func_die.IsValid()) + continue; + + auto spec_die = + func_die.GetAttributeValueAsReferenceDIE(DW_AT_specification); + if (spec_die.IsValid() && spec_die == die) { + llvm::ItaniumPartialDemangler D; + const bool success = !D.partialDemangle(func_die.GetMangledName()); + assert(success); + const auto maybe_structor_kind = D.getCtorDtorVariant(); + assert(maybe_structor_kind); + + structor_names.push_back( + llvm::formatv("{0}:$__lldb_func_{1}:{2}", *maybe_structor_kind, + func_die.GetModule().get(), func_die.GetID())); + } + } + } + + return structor_names; +} + std::pair<bool, TypeSP> DWARFASTParserClang::ParseCXXMethod( const DWARFDIE &die, CompilerType clang_type, const ParsedDWARFTypeAttributes &attrs, const DWARFDIE &decl_ctx_die, @@ -1140,11 +1197,15 @@ std::pair<bool, TypeSP> DWARFASTParserClang::ParseCXXMethod( const auto accessibility = attrs.accessibility == eAccessNone ? eAccessPublic : attrs.accessibility; + std::vector<std::string> structor_names; + if (IsStructorDIE(die, decl_ctx_die)) + structor_names = FindStructorNames(die); + clang::CXXMethodDecl *cxx_method_decl = m_ast.AddMethodToCXXRecordType( class_opaque_type.GetOpaqueQualType(), attrs.name.GetCString(), attrs.mangled_name, clang_type, accessibility, attrs.is_virtual, is_static, attrs.is_inline, attrs.is_explicit, is_attr_used, - attrs.is_artificial); + attrs.is_artificial, structor_names); if (cxx_method_decl) { LinkDeclContextToDIE(cxx_method_decl, die); @@ -1330,19 +1391,10 @@ DWARFASTParserClang::ParseSubroutine(const DWARFDIE &die, lldbassert(function_decl); if (function_decl) { - // Attach an asm(<mangled_name>) label to the FunctionDecl. - // This ensures that clang::CodeGen emits function calls - // using symbols that are mangled according to the DW_AT_linkage_name. - // If we didn't do this, the external symbols wouldn't exactly - // match the mangled name LLDB knows about and the IRExecutionUnit - // would have to fall back to searching object files for - // approximately matching function names. The motivating - // example is generating calls to ABI-tagged template functions. - // This is done separately for member functions in - // AddMethodToCXXRecordType. - if (attrs.mangled_name) - function_decl->addAttr(clang::AsmLabelAttr::CreateImplicit( - m_ast.getASTContext(), attrs.mangled_name, /*literal=*/false)); + auto ident = llvm::formatv("$__lldb_func_{0}:{1}", + die.GetModule().get(), die.GetID()); + function_decl->addAttr(clang::AsmLabelAttr::CreateImplicit( + m_ast.getASTContext(), ident.str(), /*literal=*/false)); LinkDeclContextToDIE(function_decl, die); diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp index b0f49ebf2d2cbb..549b810490a590 100644 --- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp +++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp @@ -10,6 +10,7 @@ #include "clang/AST/DeclBase.h" #include "clang/AST/ExprCXX.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" #include "llvm/Support/FormatAdapters.h" #include "llvm/Support/FormatVariadic.h" @@ -7735,7 +7736,8 @@ clang::CXXMethodDecl *TypeSystemClang::AddMethodToCXXRecordType( lldb::opaque_compiler_type_t type, llvm::StringRef name, const char *mangled_name, const CompilerType &method_clang_type, lldb::AccessType access, bool is_virtual, bool is_static, bool is_inline, - bool is_explicit, bool is_attr_used, bool is_artificial) { + bool is_explicit, bool is_attr_used, bool is_artificial, + std::vector<std::string> const &structor_names) { if (!type || !method_clang_type.IsValid() || name.empty()) return nullptr; @@ -7788,6 +7790,11 @@ clang::CXXMethodDecl *TypeSystemClang::AddMethodToCXXRecordType( cxx_dtor_decl->setImplicit(is_artificial); cxx_dtor_decl->setInlineSpecified(is_inline); cxx_dtor_decl->setConstexprKind(ConstexprSpecKind::Unspecified); + if (!structor_names.empty()) { + auto names = llvm::to_vector_of<llvm::StringRef>(structor_names); + cxx_dtor_decl->addAttr(clang::StructorMangledNamesAttr::CreateImplicit( + getASTContext(), names.data(), names.size())); + } cxx_method_decl = cxx_dtor_decl; } else if (decl_name == cxx_record_decl->getDeclName()) { cxx_ctor_decl = clang::CXXConstructorDecl::CreateDeserialized( @@ -7802,6 +7809,12 @@ clang::CXXMethodDecl *TypeSystemClang::AddMethodToCXXRecordType( cxx_ctor_decl->setConstexprKind(ConstexprSpecKind::Unspecified); cxx_ctor_decl->setNumCtorInitializers(0); cxx_ctor_decl->setExplicitSpecifier(explicit_spec); + if (!structor_names.empty()) { + auto names = llvm::to_vector_of<llvm::StringRef>(structor_names); + cxx_ctor_decl->addAttr(clang::StructorMangledNamesAttr::CreateImplicit( + getASTContext(), names.data(), names.size())); + } + cxx_method_decl = cxx_ctor_decl; } else { clang::StorageClass SC = is_static ? clang::SC_Static : clang::SC_None; diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h index e39aedec7e3902..f5ab4dde8ed58c 100644 --- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h +++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h @@ -980,7 +980,8 @@ class TypeSystemClang : public TypeSystem { lldb::opaque_compiler_type_t type, llvm::StringRef name, const char *mangled_name, const CompilerType &method_type, lldb::AccessType access, bool is_virtual, bool is_static, bool is_inline, - bool is_explicit, bool is_attr_used, bool is_artificial); + bool is_explicit, bool is_attr_used, bool is_artificial, + std::vector<std::string> const &structor_names = {}); void AddMethodOverridesForCXXRecordType(lldb::opaque_compiler_type_t type); diff --git a/lldb/test/Shell/Expr/TestAbiTagCtorsDtors.cpp b/lldb/test/Shell/Expr/TestAbiTagCtorsDtors.cpp new file mode 100644 index 00000000000000..49de90ee64dea6 --- /dev/null +++ b/lldb/test/Shell/Expr/TestAbiTagCtorsDtors.cpp @@ -0,0 +1,28 @@ +// Tests that we can call abi-tagged constructors/destructors. + +// RUN: %build %s -o %t +// RUN: %lldb %t -o run \ +// RUN: -o "expression sinkTagged(getTagged())" \ +// RUN: -o "expression Tagged()" \ +// RUN: -o exit | FileCheck %s + +// CHECK: expression sinkTagged(getTagged()) +// CHECK: expression Tagged() + +struct Tagged { + [[gnu::abi_tag("Test", "CtorTag")]] [[gnu::abi_tag("v1")]] Tagged() = default; + [[gnu::abi_tag("Test", "DtorTag")]] [[gnu::abi_tag("v1")]] ~Tagged() {} + + int mem = 15; +}; + +Tagged getTagged() { return Tagged(); } +void sinkTagged(Tagged t) {} + +int main() { + Tagged t; + + // TODO: is there a more reliable way of triggering destructor call? + sinkTagged(getTagged()); + __builtin_debugtrap(); +} diff --git a/llvm/include/llvm/Demangle/Demangle.h b/llvm/include/llvm/Demangle/Demangle.h index fe129603c0785d..103b60d6c343ce 100644 --- a/llvm/include/llvm/Demangle/Demangle.h +++ b/llvm/include/llvm/Demangle/Demangle.h @@ -10,6 +10,7 @@ #define LLVM_DEMANGLE_DEMANGLE_H #include <cstddef> +#include <optional> #include <string> #include <string_view> @@ -108,6 +109,8 @@ struct ItaniumPartialDemangler { /// the function is a non-static member function. bool hasFunctionQualifiers() const; + std::optional<int> getCtorDtorVariant() const; + /// If this symbol describes a constructor or destructor. bool isCtorOrDtor() const; diff --git a/llvm/include/llvm/Demangle/ItaniumDemangle.h b/llvm/include/llvm/Demangle/ItaniumDemangle.h index 0af0224bc83fa8..39389132430ab0 100644 --- a/llvm/include/llvm/Demangle/ItaniumDemangle.h +++ b/llvm/include/llvm/Demangle/ItaniumDemangle.h @@ -1728,6 +1728,8 @@ class CtorDtorName final : public Node { template<typename Fn> void match(Fn F) const { F(Basename, IsDtor, Variant); } + int getVariant() const { return Variant; } + void printLeft(OutputBuffer &OB) const override { if (IsDtor) OB += "~"; diff --git a/llvm/lib/Demangle/ItaniumDemangle.cpp b/llvm/lib/Demangle/ItaniumDemangle.cpp index 5c21b06a1d0955..786985a141b022 100644 --- a/llvm/lib/Demangle/ItaniumDemangle.cpp +++ b/llvm/lib/Demangle/ItaniumDemangle.cpp @@ -20,6 +20,7 @@ #include <cstring> #include <exception> #include <functional> +#include <optional> #include <utility> using namespace llvm; @@ -548,15 +549,16 @@ bool ItaniumPartialDemangler::hasFunctionQualifiers() const { return E->getCVQuals() != QualNone || E->getRefQual() != FrefQualNone; } -bool ItaniumPartialDemangler::isCtorOrDtor() const { +std::optional<int> ItaniumPartialDemangler::getCtorDtorVariant() const { const Node *N = static_cast<const Node *>(RootNode); while (N) { switch (N->getKind()) { default: - return false; - case Node::KCtorDtorName: - return true; - + return std::nullopt; + case Node::KCtorDtorName: { + auto const *StructorNode = static_cast<const CtorDtorName *>(N); + return StructorNode->getVariant(); + } case Node::KAbiTagAttr: N = static_cast<const AbiTagAttr *>(N)->Base; break; @@ -577,7 +579,11 @@ bool ItaniumPartialDemangler::isCtorOrDtor() const { break; } } - return false; + return std::nullopt; +} + +bool ItaniumPartialDemangler::isCtorOrDtor() const { + return getCtorDtorVariant().has_value(); } bool ItaniumPartialDemangler::isFunction() const { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits