[llvm] r266548 - IR: Use ODR to unique DICompositeType members

Duncan P. N. Exon Smith via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 18 13:33:39 PDT 2016


> On 2016-Apr-18, at 13:26, David Blaikie <dblaikie at gmail.com> wrote:
> 
> 
> 
> On Mon, Apr 18, 2016 at 1:04 PM, Duncan P. N. Exon Smith <dexonsmith at apple.com> wrote:
> 
> > On 2016-Apr-18, at 12:47, David Blaikie <dblaikie at gmail.com> wrote:
> >
> >
> >
> > On Mon, Apr 18, 2016 at 11:29 AM, Duncan P. N. Exon Smith <dexonsmith at apple.com> wrote:
> > >
> > >> On 2016-Apr-18, at 09:10, David Blaikie <dblaikie at gmail.com> wrote:
> > >>
> > >>
> > >>
> > >> On Sun, Apr 17, 2016 at 4:27 PM, Duncan P. N. Exon Smith <dexonsmith at apple.com> wrote:
> > >>
> > >> > On 2016-Apr-17, at 15:34, David Blaikie <dblaikie at gmail.com> wrote:
> > >> >
> > >> >
> > >> >
> > >> > On Sun, Apr 17, 2016 at 3:09 PM, Duncan P. N. Exon Smith <dexonsmith at apple.com> wrote:
> > >> >
> > >> > > On 2016-Apr-17, at 15:08, David Blaikie <dblaikie at gmail.com> wrote:
> > >> > >
> > >> > >
> > >> > >
> > >> > > On Sat, Apr 16, 2016 at 7:30 PM, Duncan P. N. Exon Smith via llvm-commits <llvm-commits at lists.llvm.org> wrote:
> > >> > > Author: dexonsmith
> > >> > > Date: Sat Apr 16 21:30:20 2016
> > >> > > New Revision: 266548
> > >> > >
> > >> > > URL: http://llvm.org/viewvc/llvm-project?rev=266548&view=rev
> > >> > > Log:
> > >> > > IR: Use ODR to unique DICompositeType members
> > >> > >
> > >> > > Merge members that are describing the same member of the same ODR type,
> > >> > > even if other bits differ.  If the file or line differ, we don't care;
> > >> > > if anything else differs, it's an ODR violation (and we still don't
> > >> > > really care).
> > >> > >
> > >> > > For DISubprogram declarations, this looks at the LinkageName and Scope.
> > >> >
> > >> > What we put linkage names on is a bit haphazard... I think I mentioned this in a recent review for Paul. I included some patches to Clang that might fix those - I'd suggest you check them out or I can go & find them. (and/or test this deduplication with some function-local types (in inline functions) and a type in an anonymous namespace)
> > >> >
> > >> > Why look at the scope as well? What does that add/change?
> > >>
> > >> You need to check the scope on LHS to see if it follows to ODR type
> > >> uniquing (has an 'identifier:' field).  When both LHS and RHS follow
> > >> ODR, then I think you're right: if the 'linkageName:' matches, that
> > >> implies that the 'scope' matches.  But I think it's cheaper to just
> > >> compare the values of LHS and RHS than to inspect the scope of RHS.
> > >
> > > I'm lost here - not sure which left/right hand sides you're referring to.
> >
> > From the commit.  LHS has already been splatted out, but this is the
> > code I was talking about:
> >
> > >> > > +  static bool isDeclarationOfODRMember(bool IsDefinition, const Metadata *Scope,
> > >> > > +                                       const MDString *LinkageName,
> > >> > > +                                       const DISubprogram *RHS) {
> > >> > > +    // Check whether the LHS is eligible.
> > >> > > +    if (IsDefinition || !Scope || !LinkageName || !Scope ||
> > >> > > +        !isa<MDString>(Scope))
> > >> > > +      return false;
> > >> > > +
> > >> > > +    // Compare to the RHS.
> > >> > > +    return IsDefinition == RHS->isDefinition() && Scope == RHS->getRawScope() &&
> > >> > > +           LinkageName == RHS->getRawLinkageName();
> > >> > > +  }
> > >> > > +};
> >
> > ^ Note that isa<MDString> implies the type follows ODR.
> >
> > Still confused here - you said "cheaper to just compare the values of LHS and RHS than to inspect the scope of RHS"  - I'm confused why we aren't just inspecting the linkage name? That's not about inspecting the scope... but I'm getting lost here.
> 
>  1. Check whether LHS is allowed to use the special uniquing rules.
> 
>       - It must be a declaration,
>       - have a linkage name to compare against, and
>       - have a special ODR-scope (isa<MDString>).
> 
>  2. Check whether RHS is allowed to use the special uniquing rules.
> 
>      a) We could repeat the same checks as (1), or
>      b) check that the relevant fields are equal.
> 
>  3. Check whether LHS and RHS should evaluate to the same node.
> 
>       - Likely sufficient to check the Linkage name.
>       - Not harmful to check the Scope.
> 
> Maybe the not-so-obvious part is that only *some* subprogram
> declarations are allowed to use the special uniquing rules.  It
> sounds like you're suggesting we jump straight to (3), without
> checking (1) and (2).
> 
> Yeah, I'm not exactly understanding entirely what (1) and (2) are, nor why they are necessary.
> 
> (the phrase "have a special ODR-scope" doesn't seem to mean anything to me right now)

Generally with the debug info IR we check for complete structural
equivalence when looking up a node.

This patch changes the uniquing for
DISubprogram(isDefinition: false) and
DIDerivedType(tag: DW_TAG_member) such that:

  - If (and only if) their scope is a DICompositeType with a non-
    empty 'identifier:' field,

  - then check only the ODR-relevant fields for equivalency.

In the current IR, the 'scope:' field will be an `MDString` iff
the actual scope is a DICompositeType with an 'identifier:'.


>  
> 
> >
> >
> > >> If the linkage names are poor quality, then it's probably best to use
> > >> the scopes anyway.
> > >
> > > I'd like to get more consistency into the linkage names, really. But it's possible we'd end up with competing goals between reusing linkage names for uniquing versus other things that we want to use them for in DWARF, etc.
> > >
> > >> I think it might be necessary to check the scopes when there's no
> > >> DITypeMap in the LLVMContext, since two subprograms might look the
> > >> same from their LinkageName, but be in a different DICompositeType.
> > >
> > > Not sure I follow there.
> >
> > I'm not sure I follow any more either :/.
> >
> > >> > > For DW_TAG_member instances of DIDerivedType, this looks at the Name and
> > >> > > Scope.  In both cases, we know that the Scope follows ODR rules if it
> > >> > > has a non-empty identifier.
> > >> > >
> > >> > > Not quite following this paragraph ^ why does this need to look at subprograms and members, rather than just the 'identifier' of the DICompositeType itself?
> > >> >
> > >> > It's the subprograms and members that are being uniqued.
> > >> >
> > >> > *reads the commit message again* Right.
> > >> >
> > >> > *ponders*
> > >> >
> > >> > If it's any help, I think it should be fine to never touch/merge the "members" list in a DICompositeType (perhaps you're already doing this) - if a member is in the member list, just take the equivalent (linkage name, I guess).
> > >> >
> > >> > But I guess you've got to deduplicate the things that aren't in the member list too anyway (& the order of things, and their simple names aren't enough to identify them, etc) so there's not much point doing something special for the things in the member list, perhaps.
> > >>
> > >> I think merging these should happen implicitly now when the
> > >> DITypeMap is enabled.  Actually the motivation for this was that
> > >> llvm/trunk/test/Linker/type-unique-odr-a.ll started failing when
> > >> wrote my DITypeMap patch (r266549).  This was necessary to make
> > >> it all work.
> > >>
> > >> >
> > >> > >
> > >> > >
> > >> > > Added:
> > >> > >     llvm/trunk/test/Assembler/dicompositetype-members.ll
> > >> > > Modified:
> > >> > >     llvm/trunk/docs/LangRef.rst
> > >> > >     llvm/trunk/lib/IR/LLVMContextImpl.h
> > >> > >     llvm/trunk/test/Linker/type-unique-odr-a.ll
> > >> > >
> > >> > > Modified: llvm/trunk/docs/LangRef.rst
> > >> > > URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/docs/LangRef.rst?rev=266548&r1=266547&r2=266548&view=diff
> > >> > > ==============================================================================
> > >> > > --- llvm/trunk/docs/LangRef.rst (original)
> > >> > > +++ llvm/trunk/docs/LangRef.rst Sat Apr 16 21:30:20 2016
> > >> > > @@ -3976,7 +3976,10 @@ The following ``tag:`` values are valid:
> > >> > >
> > >> > >  ``DW_TAG_member`` is used to define a member of a :ref:`composite type
> > >> > >  <DICompositeType>`. The type of the member is the ``baseType:``. The
> > >> > > -``offset:`` is the member's bit offset.
> > >> > > +``offset:`` is the member's bit offset.  If the composite type has a non-empty
> > >> > > +``identifier:``, then it respects ODR rules.  In that case, the ``scope:``
> > >> > > +reference will be a :ref:`metadata string <metadata-string>`, and the member
> > >> > > +will be uniqued solely based on its ``name:`` and ``scope:``.
> > >> > >
> > >> > >  ``DW_TAG_inheritance`` and ``DW_TAG_friend`` are used in the ``elements:``
> > >> > >  field of :ref:`composite types <DICompositeType>` to describe parents and
> > >> > > @@ -4125,6 +4128,12 @@ metadata. The ``variables:`` field point
> > >> > >  that must be retained, even if their IR counterparts are optimized out of
> > >> > >  the IR. The ``type:`` field must point at an :ref:`DISubroutineType`.
> > >> > >
> > >> > > +When ``isDefinition: false``, subprograms describe a declaration in the type
> > >> > > +tree as opposed to a definition of a funciton.  If the scope is a
> > >> > > +:ref:`metadata string <metadata-string>` then the composite type follows ODR
> > >> > > +rules, and the subprogram declaration is uniqued based only on its
> > >> > > +``linkageName:`` and ``scope:``.
> > >> > > +
> > >> > >  .. code-block:: llvm
> > >> > >
> > >> > >      define void @_Z3foov() !dbg !0 {
> > >> > > @@ -4133,7 +4142,7 @@ the IR. The ``type:`` field must point a
> > >> > >
> > >> > >      !0 = distinct !DISubprogram(name: "foo", linkageName: "_Zfoov", scope: !1,
> > >> > >                                  file: !2, line: 7, type: !3, isLocal: true,
> > >> > > -                                isDefinition: false, scopeLine: 8,
> > >> > > +                                isDefinition: true, scopeLine: 8,
> > >> > >                                  containingType: !4,
> > >> > >                                  virtuality: DW_VIRTUALITY_pure_virtual,
> > >> > >                                  virtualIndex: 10, flags: DIFlagPrototyped,
> > >> > >
> > >> > > Modified: llvm/trunk/lib/IR/LLVMContextImpl.h
> > >> > > URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/IR/LLVMContextImpl.h?rev=266548&r1=266547&r2=266548&view=diff
> > >> > > ==============================================================================
> > >> > > --- llvm/trunk/lib/IR/LLVMContextImpl.h (original)
> > >> > > +++ llvm/trunk/lib/IR/LLVMContextImpl.h Sat Apr 16 21:30:20 2016
> > >> > > @@ -32,6 +32,7 @@
> > >> > >  #include "llvm/IR/LLVMContext.h"
> > >> > >  #include "llvm/IR/Metadata.h"
> > >> > >  #include "llvm/IR/ValueHandle.h"
> > >> > > +#include "llvm/Support/Dwarf.h"
> > >> > >  #include <vector>
> > >> > >
> > >> > >  namespace llvm {
> > >> > > @@ -376,6 +377,12 @@ template <> struct MDNodeKeyImpl<DIDeriv
> > >> > >             ExtraData == RHS->getRawExtraData();
> > >> > >    }
> > >> > >    unsigned getHashValue() const {
> > >> > > +    // If this is a member inside an ODR type, only hash the type and the name.
> > >> > > +    // Otherwise the hash will be stronger than
> > >> > > +    // MDNodeSubsetEqualImpl::isODRMember().
> > >> > > +    if (Tag == dwarf::DW_TAG_member && Name && Scope && isa<MDString>(Scope))
> > >> > > +      return hash_combine(Name, Scope);
> > >> > > +
> > >> > >      // Intentionally computes the hash on a subset of the operands for
> > >> > >      // performance reason. The subset has to be significant enough to avoid
> > >> > >      // collision "most of the time". There is no correctness issue in case of
> > >> > > @@ -384,6 +391,30 @@ template <> struct MDNodeKeyImpl<DIDeriv
> > >> > >    }
> > >> > >  };
> > >> > >
> > >> > > +template <> struct MDNodeSubsetEqualImpl<DIDerivedType> {
> > >> > > +  typedef MDNodeKeyImpl<DIDerivedType> KeyTy;
> > >> > > +  static bool isSubsetEqual(const KeyTy &LHS, const DIDerivedType *RHS) {
> > >> > > +    return isODRMember(LHS.Tag, LHS.Scope, LHS.Name, RHS);
> > >> > > +  }
> > >> > > +  static bool isSubsetEqual(const DIDerivedType *LHS, const DIDerivedType *RHS) {
> > >> > > +    return isODRMember(LHS->getTag(), LHS->getRawScope(), LHS->getRawName(),
> > >> > > +                       RHS);
> > >> > > +  }
> > >> > > +
> > >> > > +  /// Subprograms compare equal if they declare the same function in an ODR
> > >> > > +  /// type.
> > >> > > +  static bool isODRMember(unsigned Tag, const Metadata *Scope,
> > >> > > +                          const MDString *Name, const DIDerivedType *RHS) {
> > >> > > +    // Check whether the LHS is eligible.
> > >> > > +    if (Tag != dwarf::DW_TAG_member || !Name || !Scope || !isa<MDString>(Scope))
> > >> > > +      return false;
> > >> > > +
> > >> > > +    // Compare to the RHS.
> > >> > > +    return Tag == RHS->getTag() && Name == RHS->getRawName() &&
> > >> > > +           Scope == RHS->getRawScope();
> > >> > > +  }
> > >> > > +};
> > >> > > +
> > >> > >  template <> struct MDNodeKeyImpl<DICompositeType> {
> > >> > >    unsigned Tag;
> > >> > >    MDString *Name;
> > >> > > @@ -537,6 +568,12 @@ template <> struct MDNodeKeyImpl<DISubpr
> > >> > >             Variables == RHS->getRawVariables();
> > >> > >    }
> > >> > >    unsigned getHashValue() const {
> > >> > > +    // If this is a declaration inside an ODR type, only hash the type and the
> > >> > > +    // name.  Otherwise the hash will be stronger than
> > >> > > +    // MDNodeSubsetEqualImpl::isDeclarationOfODRMember().
> > >> > > +    if (!IsDefinition && LinkageName && Scope && isa<MDString>(Scope))
> > >> > > +      return hash_combine(LinkageName, Scope);
> > >> > > +
> > >> > >      // Intentionally computes the hash on a subset of the operands for
> > >> > >      // performance reason. The subset has to be significant enough to avoid
> > >> > >      // collision "most of the time". There is no correctness issue in case of
> > >> > > @@ -545,6 +582,33 @@ template <> struct MDNodeKeyImpl<DISubpr
> > >> > >    }
> > >> > >  };
> > >> > >
> > >> > > +template <> struct MDNodeSubsetEqualImpl<DISubprogram> {
> > >> > > +  typedef MDNodeKeyImpl<DISubprogram> KeyTy;
> > >> > > +  static bool isSubsetEqual(const KeyTy &LHS, const DISubprogram *RHS) {
> > >> > > +    return isDeclarationOfODRMember(LHS.IsDefinition, LHS.Scope,
> > >> > > +                                    LHS.LinkageName, RHS);
> > >> > > +  }
> > >> > > +  static bool isSubsetEqual(const DISubprogram *LHS, const DISubprogram *RHS) {
> > >> > > +    return isDeclarationOfODRMember(LHS->isDefinition(), LHS->getRawScope(),
> > >> > > +                                    LHS->getRawLinkageName(), RHS);
> > >> > > +  }
> > >> > > +
> > >> > > +  /// Subprograms compare equal if they declare the same function in an ODR
> > >> > > +  /// type.
> > >> > > +  static bool isDeclarationOfODRMember(bool IsDefinition, const Metadata *Scope,
> > >> > > +                                       const MDString *LinkageName,
> > >> > > +                                       const DISubprogram *RHS) {
> > >> > > +    // Check whether the LHS is eligible.
> > >> > > +    if (IsDefinition || !Scope || !LinkageName || !Scope ||
> > >> > > +        !isa<MDString>(Scope))
> > >> > > +      return false;
> > >> > > +
> > >> > > +    // Compare to the RHS.
> > >> > > +    return IsDefinition == RHS->isDefinition() && Scope == RHS->getRawScope() &&
> > >> > > +           LinkageName == RHS->getRawLinkageName();
> > >> > > +  }
> > >> > > +};
> > >> > > +
> > >> > >  template <> struct MDNodeKeyImpl<DILexicalBlock> {
> > >> > >    Metadata *Scope;
> > >> > >    Metadata *File;
> > >> > >
> > >> > > Added: llvm/trunk/test/Assembler/dicompositetype-members.ll
> > >> > > URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/Assembler/dicompositetype-members.ll?rev=266548&view=auto
> > >> > > ==============================================================================
> > >> > > --- llvm/trunk/test/Assembler/dicompositetype-members.ll (added)
> > >> > > +++ llvm/trunk/test/Assembler/dicompositetype-members.ll Sat Apr 16 21:30:20 2016
> > >> > > @@ -0,0 +1,54 @@
> > >> > > +; RUN: llvm-as < %s | llvm-dis | llvm-as | llvm-dis | FileCheck %s
> > >> > > +; RUN: verify-uselistorder %s
> > >> > > +
> > >> > > +; Anchor the order of the nodes.
> > >> > > +!named = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10, !11, !12, !13, !14, !15, !16, !17}
> > >> > > +
> > >> > > +; Some basic building blocks.
> > >> > > +; CHECK:      !0 = !DIBasicType
> > >> > > +; CHECK-NEXT: !1 = !DIFile
> > >> > > +; CHECK-NEXT: !2 = !DIFile
> > >> > > +!0 = !DIBasicType(tag: DW_TAG_base_type, name: "name", size: 1, align: 2, encoding: DW_ATE_unsigned_char)
> > >> > > +!1 = !DIFile(filename: "path/to/file", directory: "/path/to/dir")
> > >> > > +!2 = !DIFile(filename: "path/to/other", directory: "/path/to/dir")
> > >> > > +
> > >> > > +; Define an identified type with fields and functions.
> > >> > > +; CHECK-NEXT: !3 = !DICompositeType(tag: DW_TAG_structure_type, name: "has-uuid",
> > >> > > +; CHECK-NEXT: !4 = !DIDerivedType(tag: DW_TAG_member, name: "field1", scope: !"has-uuid", file: !1
> > >> > > +; CHECK-NEXT: !5 = !DIDerivedType(tag: DW_TAG_member, name: "field2", scope: !"has-uuid", file: !1
> > >> > > +; CHECK-NEXT: !6 = !DISubprogram(name: "foo", linkageName: "foo1", scope: !"has-uuid", file: !1
> > >> > > +; CHECK-NEXT: !7 = !DISubprogram(name: "foo", linkageName: "foo2", scope: !"has-uuid", file: !1
> > >> > > +!3 = !DICompositeType(tag: DW_TAG_structure_type, name: "has-uuid", file: !1, line: 2, size: 64, align: 32, identifier: "uuid")
> > >> > > +!4 = !DIDerivedType(tag: DW_TAG_member, name: "field1", scope: !"has-uuid", file: !1, line: 4, baseType: !0, size: 32, align: 32, offset: 32)
> > >> > > +!5 = !DIDerivedType(tag: DW_TAG_member, name: "field2", scope: !"has-uuid", file: !1, line: 4, baseType: !0, size: 32, align: 32, offset: 32)
> > >> > > +!6 = !DISubprogram(name: "foo", linkageName: "foo1", scope: !"has-uuid", file: !1, isDefinition: false)
> > >> > > +!7 = !DISubprogram(name: "foo", linkageName: "foo2", scope: !"has-uuid", file: !1, isDefinition: false)
> > >> > > +
> > >> > > +; Define an un-identified type with fields and functions.
> > >> > > +; CHECK-NEXT: !8 = !DICompositeType(tag: DW_TAG_structure_type, name: "no-uuid", file: !1
> > >> > > +; CHECK-NEXT: !9 = !DIDerivedType(tag: DW_TAG_member, name: "field1", scope: !8, file: !1
> > >> > > +; CHECK-NEXT: !10 = !DIDerivedType(tag: DW_TAG_member, name: "field2", scope: !8, file: !1
> > >> > > +; CHECK-NEXT: !11 = !DISubprogram(name: "foo", linkageName: "foo1", scope: !8, file: !1
> > >> > > +; CHECK-NEXT: !12 = !DISubprogram(name: "foo", linkageName: "foo2", scope: !8, file: !1
> > >> > > +!8 = !DICompositeType(tag: DW_TAG_structure_type, name: "no-uuid", file: !1, line: 2, size: 64, align: 32)
> > >> > > +!9 = !DIDerivedType(tag: DW_TAG_member, name: "field1", scope: !8, file: !1, line: 4, baseType: !0, size: 32, align: 32, offset: 32)
> > >> > > +!10 = !DIDerivedType(tag: DW_TAG_member, name: "field2", scope: !8, file: !1, line: 4, baseType: !0, size: 32, align: 32, offset: 32)
> > >> > > +!11 = !DISubprogram(name: "foo", linkageName: "foo1", scope: !8, file: !1, isDefinition: false)
> > >> > > +!12 = !DISubprogram(name: "foo", linkageName: "foo2", scope: !8, file: !1, isDefinition: false)
> > >> > > +
> > >> > > +; Add duplicate fields and members of "no-uuid" in a different file.  These
> > >> > > +; should stick around, since "no-uuid" does not have an "identifier:" field.
> > >> > > +; CHECK-NEXT: !13 = !DIDerivedType(tag: DW_TAG_member, name: "field1", scope: !8, file: !2,
> > >> > > +; CHECK-NEXT: !14 = !DISubprogram(name: "foo", linkageName: "foo1", scope: !8, file: !2,
> > >> > > +!13 = !DIDerivedType(tag: DW_TAG_member, name: "field1", scope: !8, file: !2, line: 4, baseType: !0, size: 32, align: 32, offset: 32)
> > >> > > +!14 = !DISubprogram(name: "foo", linkageName: "foo1", scope: !8, file: !2, isDefinition: false)
> > >> > > +
> > >> > > +; Add duplicate fields and members of "has-uuid" in a different file.  These
> > >> > > +; should be merged.
> > >> > > +!15 = !DIDerivedType(tag: DW_TAG_member, name: "field1", scope: !"has-uuid", file: !2, line: 4, baseType: !0, size: 32, align: 32, offset: 32)
> > >> > > +!16 = !DISubprogram(name: "foo", linkageName: "foo1", scope: !"has-uuid", file: !2, isDefinition: false)
> > >> > > +
> > >> > > +; CHECK-NEXT: !15 = !{!4, !6}
> > >> > > +; CHECK-NOT: !DIDerivedType
> > >> > > +; CHECK-NOT: !DISubprogram
> > >> > > +!17 = !{!15, !16}
> > >> > >
> > >> > > Modified: llvm/trunk/test/Linker/type-unique-odr-a.ll
> > >> > > URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/Linker/type-unique-odr-a.ll?rev=266548&r1=266547&r2=266548&view=diff
> > >> > > ==============================================================================
> > >> > > --- llvm/trunk/test/Linker/type-unique-odr-a.ll (original)
> > >> > > +++ llvm/trunk/test/Linker/type-unique-odr-a.ll Sat Apr 16 21:30:20 2016
> > >> > > @@ -4,6 +4,10 @@
> > >> > >  ; RUN:   | %llc_dwarf -dwarf-linkage-names=Enable -filetype=obj -O0 \
> > >> > >  ; RUN:   | llvm-dwarfdump -debug-dump=info - \
> > >> > >  ; RUN:   | FileCheck %s
> > >> > > +; RUN: llvm-link %p/type-unique-odr-b.ll %s -S -o - \
> > >> > > +; RUN:   | %llc_dwarf -dwarf-linkage-names=Enable -filetype=obj -O0 \
> > >> > > +; RUN:   | llvm-dwarfdump -debug-dump=info - \
> > >> > > +; RUN:   | FileCheck %s
> > >> > >  ;
> > >> > >  ; Test ODR-based type uniquing for C++ class members.
> > >> > >  ; rdar://problem/15851313.
> > >> > >
> > >> > >
> > >> > > _______________________________________________
> > >> > > llvm-commits mailing list
> > >> > > llvm-commits at lists.llvm.org
> > >> > > http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits
> > >> > >
> > >>
> > >>
> > >
> >
> 
> 



More information about the llvm-commits mailing list