Index: lib/CodeGen/CodeGenModule.cpp =================================================================== --- lib/CodeGen/CodeGenModule.cpp +++ lib/CodeGen/CodeGenModule.cpp @@ -1440,6 +1440,8 @@ } } + getTargetCodeGenInfo().EmitTargetMD(D, F, *this); + // Make sure the result is of the requested type. if (!IsIncompleteFunction) { assert(F->getType()->getElementType() == Ty); @@ -1596,6 +1598,8 @@ isExternallyVisible(D->getLinkageAndVisibility().getLinkage())) GV->setSection(".cp.rodata"); + getTargetCodeGenInfo().EmitTargetMD(D, GV, *this); + return GV; } Index: lib/CodeGen/TargetInfo.h =================================================================== --- lib/CodeGen/TargetInfo.h +++ lib/CodeGen/TargetInfo.h @@ -56,6 +56,11 @@ virtual void SetTargetAttributes(const Decl *D, llvm::GlobalValue *GV, CodeGen::CodeGenModule &M) const { } + /// EmitTargetMD - Provides a convenient hook to handle extra + /// target-specific metadata for the given global. + virtual void EmitTargetMD(const Decl *D, llvm::GlobalValue *GV, + CodeGen::CodeGenModule &M) const { } + /// Determines the size of struct _Unwind_Exception on this platform, /// in 8-bit units. The Itanium ABI defines this as: /// struct _Unwind_Exception { Index: lib/CodeGen/TargetInfo.cpp =================================================================== --- lib/CodeGen/TargetInfo.cpp +++ lib/CodeGen/TargetInfo.cpp @@ -23,6 +23,9 @@ #include "llvm/IR/DataLayout.h" #include "llvm/IR/Type.h" #include "llvm/Support/raw_ostream.h" + +#include // std::sort + using namespace clang; using namespace CodeGen; @@ -5695,7 +5698,55 @@ //===----------------------------------------------------------------------===// // XCore ABI Implementation //===----------------------------------------------------------------------===// + namespace { + +/// A SmallStringEnc instance is used to build up the TypeString by passing +/// it by reference between functions that append to it. +typedef llvm::SmallString<128> SmallStringEnc; + +/// TypeStringCache caches the meta encodings of Types. +/// +/// An Entry can have a Status of: +/// Complete: The encoding is fully formed with no incomplete subtypes; +/// Incomplete: The encoding is for a RecordType and is incomplete; +/// IncompleteUsed: As for Incomplete, also it has been used by a super-type. +/// +/// Whilst encoding a RecordType, AddIncomplete() place an incomplete +/// TypeString into the cache to break recursive inclusion of itself. +/// If the RecordType is a nested sub-type, its Complete Entry (if present) +/// will be swapped out into 'Temp'. On exit from the recursion, +/// RemoveIncomplete() will replace 'Temp' back into the cache. +/// +/// If IncompleteUsedCount is zero, any discovered subtypes will be complete +/// and will be added to the cache. +/// +class TypeStringCache { + enum Status {Complete, Incomplete, IncompleteUsed}; + struct Entry {std::string Str; enum Status State;}; + std::map Map; + unsigned IncompleteUsedCount; +public: + void AddIncomplete(const IdentifierInfo *ID, std::string& Temp); + void RemoveIncomplete(const IdentifierInfo *ID, std::string& Temp); + bool Add(const IdentifierInfo *ID, StringRef Str); + StringRef Str(const IdentifierInfo *ID); +}; + +/// TypeString encodings for union fields must be order. +/// FieldEncoding is a helper for this ordering process. +class FieldEncoding { + bool HasName; + std::string Enc; +public: + FieldEncoding(bool b, SmallStringEnc &e) : HasName(b), Enc(e.c_str()) {}; + StringRef Str() {return Enc.c_str();}; + bool operator<(const FieldEncoding& rhs) const { + if (HasName != rhs.HasName) return HasName; + return Enc < rhs.Enc; + } +}; + class XCoreABIInfo : public DefaultABIInfo { public: XCoreABIInfo(CodeGen::CodeGenTypes &CGT) : DefaultABIInfo(CGT) {} @@ -5704,10 +5755,14 @@ }; class XCoreTargetCodeGenInfo : public TargetCodeGenInfo { + mutable TypeStringCache TSC; public: XCoreTargetCodeGenInfo(CodeGenTypes &CGT) :TargetCodeGenInfo(new XCoreABIInfo(CGT)) {} + virtual void EmitTargetMD(const Decl *D, llvm::GlobalValue *GV, + CodeGen::CodeGenModule &M) const; }; + } // End anonymous namespace. llvm::Value *XCoreABIInfo::EmitVAArg(llvm::Value *VAListAddr, QualType Ty, @@ -5759,6 +5814,372 @@ return Val; } +void TypeStringCache::AddIncomplete(const IdentifierInfo *ID, + std::string& Temp) { + Entry& E = Map[ID]; // find Complete string or create an empty string. + Temp.swap(E.Str); + E.State = Incomplete; +} + +void TypeStringCache::RemoveIncomplete(const IdentifierInfo *ID, + std::string& Temp) { + std::map::iterator I = Map.find(ID); + assert(I != Map.end() && "Entry not present"); + assert(I->second.State != Complete && "Entry must be Incomplete"); + if (I->second.State == IncompleteUsed) + --IncompleteUsedCount; + if (Temp.empty()) + Map.erase(I); + else { + Temp.swap(I->second.Str); + I->second.State = Complete; + } +} + +bool TypeStringCache::Add(const IdentifierInfo *ID, StringRef Str) { + if (IncompleteUsedCount) + return false; // This is an incomplete sub-type so don't add. + Entry& E = Map[ID]; + assert(E.Str.empty() && "Entry already present"); + E.Str = Str.str(); + E.State = Complete; + return true; +} + +StringRef TypeStringCache::Str(const IdentifierInfo *ID) { + std::map::iterator I = Map.find(ID); + if (I == Map.end()) + return StringRef(); // This Type has not be decoded. + if (I->second.State == Incomplete) { + I->second.State = IncompleteUsed; + ++IncompleteUsedCount; + } + return I->second.Str.c_str(); +} + +static bool GetTypeString(SmallStringEnc &Enc, const Decl *D, + CodeGen::CodeGenModule &CGM, TypeStringCache &TSC); + +void XCoreTargetCodeGenInfo::EmitTargetMD(const Decl *D, llvm::GlobalValue *GV, + CodeGen::CodeGenModule &CGM) const { + SmallStringEnc Enc; + if (GetTypeString(Enc, D, CGM, TSC)) { + llvm::LLVMContext &Ctx = CGM.getModule().getContext(); + llvm::SmallVector MDVals; + MDVals.push_back(GV); + MDVals.push_back(llvm::MDString::get(Ctx, Enc.str())); + llvm::NamedMDNode *MD = + CGM.getModule().getOrInsertNamedMetadata("xcore.typestrings"); + MD->addOperand(llvm::MDNode::get(Ctx, MDVals)); + } +} + +static bool AppendType(SmallStringEnc &Enc, QualType QType, + const CodeGen::CodeGenModule &CGM, + TypeStringCache &TSC); + +static bool ExtractFieldType(SmallVectorImpl &FE, + const RecordDecl *RD, + const CodeGen::CodeGenModule &CGM, + TypeStringCache &TSC) { + for (RecordDecl::field_iterator I = RD->field_begin(), E = RD->field_end(); + I != E; ++I) { + SmallStringEnc Enc; + Enc += "m("; + Enc += I->getName(); + Enc += "){"; + if (I->isBitField()) { + Enc += "b("; + llvm::raw_svector_ostream::raw_svector_ostream OS(Enc); + OS.resync(); + OS << I->getBitWidthValue(CGM.getContext()); + OS.flush(); + Enc += ':'; + } + if (!AppendType(Enc, I->getType(), CGM, TSC)) + return false; + if (I->isBitField()) + Enc += ')'; + Enc += '}'; + FE.push_back(FieldEncoding(!I->getName().empty(), Enc)); + } + return true; +} + +static bool AppendRecordType(SmallStringEnc &Enc, const RecordType *RT, + const CodeGen::CodeGenModule &CGM, + TypeStringCache &TSC, bool isUnionType, + const IdentifierInfo *ID) { + // Append the cached TypeString if we have one. + StringRef TypeString = TSC.Str(ID); + if (!TypeString.empty()) { + Enc += TypeString; + return true; + } + + // Start to emit an incomplete TypeString. + size_t Start = Enc.size(); + Enc += (isUnionType? 'u' : 's'); + Enc += '('; + Enc += ID->getName(); + Enc += "){"; + + // We collect all encoded fields and order as necessary. + SmallVector FE; + const RecordDecl *RD = RT->getDecl()->getDefinition(); + if (RD && !RD->field_empty()) { + // An incomplete TypeString is placed in the TSC for this RecordType so + // that recursive calls to this RecordType will use it whilst we build a + // complete TypeString for this RecordType. + std::string Temp(Enc.substr(Start).str()); + Temp += '}'; // Temp now holds a valid incomplete TypeString. + TSC.AddIncomplete(ID, Temp); + bool success = ExtractFieldType(FE, RD, CGM, TSC); + TSC.RemoveIncomplete(ID, Temp); + if (!success) + return false; + if (isUnionType) + std::sort(FE.begin(), FE.end()); + } + + // We can now complete the TypeString. + if (unsigned E = FE.size()) + for (unsigned I = 0; I != E; ++I) { + if (I) + Enc += ','; + Enc += FE[I].Str(); + } + Enc += '}'; + // Add the encoded TypeString for RecordType into the map. + // If RecordType is a sub-type, it may be incomplete and thus rejected. + TSC.Add(ID, Enc.substr(Start)); + return true; +} + +static bool AppendEnumType(SmallStringEnc &Enc, const EnumType *ET, + TypeStringCache &TSC, + const IdentifierInfo *ID) { + // Append the cached TypeString if we have one. + StringRef TypeString = TSC.Str(ID); + if (!TypeString.empty()) { + Enc += TypeString; + return true; + } + + size_t Start = Enc.size(); + Enc += "e("; + Enc += ID->getName(); + Enc += "){"; + if (const EnumDecl *ED = ET->getDecl()->getDefinition()) { + EnumDecl::enumerator_iterator I = ED->enumerator_begin(); + EnumDecl::enumerator_iterator E = ED->enumerator_end(); + while (I != E) { + Enc += "m("; + Enc += I->getName(); + Enc += "){"; + I->getInitVal().toString(Enc); + Enc += '}'; + ++I; + if (I != E) + Enc += ','; + } + } + Enc += '}'; + TSC.Add(ID, Enc.substr(Start)); + return true; +} + +static void AppendQualifier(SmallStringEnc &Enc, QualType QT) { + // Qualifiers are emitted in alphabetical order. + static const char *Table[] = {"","c:","r:","cr:","v:","cv:","rv:","crv:"}; + int Lookup = 0; + if (QT.isConstQualified()) + Lookup += 1<<0; + if (QT.isRestrictQualified()) + Lookup += 1<<1; + if (QT.isVolatileQualified()) + Lookup += 1<<2; + Enc += Table[Lookup]; +} + +static bool AppendBuiltinType(SmallStringEnc &Enc, const BuiltinType *BT) { + const char *EncType; + switch (BT->getKind()) { + case BuiltinType::Void: + EncType = "0"; + break; + case BuiltinType::Bool: + EncType = "b"; + break; + case BuiltinType::Char_U: + EncType = "uc"; + break; + case BuiltinType::UChar: + EncType = "uc"; + break; + case BuiltinType::SChar: + EncType = "sc"; + break; + case BuiltinType::UShort: + EncType = "us"; + break; + case BuiltinType::Short: + EncType = "ss"; + break; + case BuiltinType::UInt: + EncType = "ui"; + break; + case BuiltinType::Int: + EncType = "si"; + break; + case BuiltinType::ULong: + EncType = "ul"; + break; + case BuiltinType::Long: + EncType = "sl"; + break; + case BuiltinType::ULongLong: + EncType = "ull"; + break; + case BuiltinType::LongLong: + EncType = "sll"; + break; + case BuiltinType::Float: + EncType = "ft"; + break; + case BuiltinType::Double: + EncType = "d"; + break; + case BuiltinType::LongDouble: + EncType = "ld"; + break; + default: + return false; + } + Enc += EncType; + return true; +} + +static bool AppendPointerType(SmallStringEnc &Enc, const PointerType *PT, + const CodeGen::CodeGenModule &CGM, + TypeStringCache &TSC) { + Enc += "p("; + if (!AppendType(Enc, PT->getPointeeType(), CGM, TSC)) + return false; + Enc += ')'; + return true; +} + +static bool AppendArrayType(SmallStringEnc &Enc, const ArrayType *AT, + const CodeGen::CodeGenModule &CGM, + TypeStringCache &TSC, StringRef NoSizeEnc) { + if (AT->getSizeModifier() != ArrayType::Normal) + return false; + Enc += "a("; + if (const ConstantArrayType *CAT = dyn_cast(AT)) + CAT->getSize().toStringUnsigned(Enc); + else + Enc += NoSizeEnc; + Enc += ':'; + if (!AppendType(Enc, AT->getElementType(), CGM, TSC)) + return false; + Enc += ')'; + return true; +} + +static bool AppendFunctionType(SmallStringEnc &Enc, const FunctionType *FT, + const CodeGen::CodeGenModule &CGM, + TypeStringCache &TSC) { + Enc += "f{"; + if (!AppendType(Enc, FT->getReturnType(), CGM, TSC)) + return false; + Enc += "}("; + if (const FunctionProtoType *FPT = FT->getAs()) { + // N.B. we are only interested in the adjusted param types. + FunctionProtoType::param_type_iterator I = FPT->param_type_begin(); + FunctionProtoType::param_type_iterator E = FPT->param_type_end(); + if (I != E) { + do { + if (!AppendType(Enc, *I, CGM, TSC)) + return false; + ++I; + if (I != E) + Enc += ','; + } while (I != E); + if (FPT->isVariadic()) + Enc += ",va"; + } else { + if (FPT->isVariadic()) + Enc += "va"; + else + Enc += '0'; + } + } + Enc += ')'; + return true; +} + +static bool AppendType(SmallStringEnc &Enc, QualType QType, + const CodeGen::CodeGenModule &CGM, + TypeStringCache &TSC) { + + QualType QT = QType.getCanonicalType(); + + AppendQualifier(Enc, QT); + + if (const BuiltinType *BT = QT->getAs()) + return AppendBuiltinType(Enc, BT); + + if (const ArrayType *AT = QT->getAsArrayTypeUnsafe()) + return AppendArrayType(Enc, AT, CGM, TSC, ""); + + if (const PointerType *PT = QT->getAs()) + return AppendPointerType(Enc, PT, CGM, TSC); + + if (const EnumType *ET = QT->getAs()) + return AppendEnumType(Enc, ET, TSC, QT.getBaseTypeIdentifier()); + + if (const RecordType *RT = QT->getAsStructureType()) + return AppendRecordType(Enc, RT, CGM, TSC, false, + QT.getBaseTypeIdentifier()); + + if (const RecordType *RT = QT->getAsUnionType()) + return AppendRecordType(Enc, RT, CGM, TSC, true, + QT.getBaseTypeIdentifier()); + + if (const FunctionType *FT = QT->getAs()) + return AppendFunctionType(Enc, FT, CGM, TSC); + + return false; +} + + +static bool GetTypeString(SmallStringEnc &Enc, const Decl *D, + CodeGen::CodeGenModule &CGM, TypeStringCache &TSC) { + if (!D) + return false; + + if (const FunctionDecl *FD = dyn_cast(D)) { + if (FD->getLanguageLinkage() != CLanguageLinkage) + return false; + return AppendType(Enc, FD->getType(), CGM, TSC); + } + + if (const VarDecl *VD = dyn_cast(D)) { + if (VD->getLanguageLinkage() != CLanguageLinkage) + return false; + QualType QT = VD->getType().getCanonicalType(); + if (const ArrayType *AT = QT->getAsArrayTypeUnsafe()) { + // Global ArrayTypes are given a size of '*' if the size is unknown. + AppendQualifier(Enc, QT); + return AppendArrayType(Enc, AT, CGM, TSC, "*"); + } + return AppendType(Enc, QT, CGM, TSC); + } + return false; +} + + //===----------------------------------------------------------------------===// // Driver code //===----------------------------------------------------------------------===// Index: test/CodeGen/xcore-stringtype.c =================================================================== --- /dev/null +++ test/CodeGen/xcore-stringtype.c @@ -0,0 +1,126 @@ +// REQUIRES: xcore-registered-target +// RUN: %clang_cc1 -triple xcore-unknown-unknown -fno-signed-char -fno-common -emit-llvm -o - %s | FileCheck %s + +// CHECK: target triple = "xcore-unknown-unknown" + +// We expect exactly 27 TypeStrings to be created. Thus unsupported Types are implicitly verified. +// CHECK: !xcore.typestrings = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10, !11, !12, !13, !14, !15, !16, !17, !18, !19, !20, !21, !22, !23, !24, !25, !26, !27, !28, !29} + + +// test BuiltinType +// CHECK: !0 = metadata !{void (i1, i8, i8, i8, i16, i16, i16, i32, i32, i32, i32, i32, i32, i64, i64, i64, float, double, double)* @BuiltinType, metadata !"f{0}(b,uc,uc,sc,ss,us,ss,si,ui,si,sl,ul,sl,sll,ull,sll,ft,d,ld)"} +void BuiltinType(_Bool B, char C, unsigned char UC, signed char SC, short S, + unsigned short US, signed short SS, int I, unsigned int UI, + signed int SI, long L, unsigned long UL, signed long SL, + long long LL, unsigned long long ULL, signed long long SLL, + float F, double D, long double LD) {} +double _Complex Complex; // not supported + + +// test FunctionType & Qualifiers +// CHECK: !1 = metadata !{void ()* @GI, metadata !"f{0}()"} +// CHECK: !2 = metadata !{void (...)* @EI, metadata !"f{0}()"} +// CHECK: !3 = metadata !{void ()* @GV, metadata !"f{0}(0)"} +// CHECK: !4 = metadata !{void ()* @EV, metadata !"f{0}(0)"} +// CHECK: !5 = metadata !{void (i32, ...)* @GVA, metadata !"f{0}(si,va)"} +// CHECK: !6 = metadata !{void (i32, ...)* @EVA, metadata !"f{0}(si,va)"} +// CHECK: !7 = metadata !{i32* (i32*)* @GQ, metadata !"f{crv:p(cv:si)}(p(cv:si))"} +// CHECK: !8 = metadata !{i32* (i32*)* @EQ, metadata !"f{crv:p(cv:si)}(p(cv:si))"} +extern void EI(); +void GI() {EI();}; +extern void EV(void); +void GV(void) {EV();} +extern void EVA(int, ...); +void GVA(int i, ...) {EVA(i);} +extern const volatile int* volatile restrict const + EQ(const volatile int * volatile restrict const); +const volatile int* volatile restrict const + GQ(const volatile int * volatile restrict const i) {return EQ(i);} + + +// test PointerType +// CHECK: !9 = metadata !{i32* (i32*, i32* (i32*)*)* @PointerType, metadata !"f{p(si)}(p(si),p(f{p(si)}(p(si))))"} +// CHECK: !10 = metadata !{i32** @EP, metadata !"p(si)"} +// CHECK: !11 = metadata !{i32** @GP, metadata !"p(si)"} +extern int* EP; +int* GP; +int* PointerType(int *I, int * (*FP)(int *)) { + return I? EP : GP; +} + + +// test ArrayType +// CHECK: !12 = metadata !{[2 x i32]* (i32*, i32*, [2 x i32]*, [2 x i32]*, i32*)* @ArrayType, metadata !"f{p(a(2:si))}(p(si),p(si),p(a(2:si)),p(a(2:si)),p(si))"} +// CHECK: !13 = metadata !{[0 x i32]* @EA1, metadata !"a(*:si)"} +// CHECK: !14 = metadata !{[2 x i32]* @EA2, metadata !"a(2:si)"} +// CHECK: !15 = metadata !{[0 x [2 x i32]]* @EA3, metadata !"a(*:a(2:si))"} +// CHECK: !16 = metadata !{[3 x [2 x i32]]* @EA4, metadata !"a(3:a(2:si))"} +// CHECK: !17 = metadata !{[2 x i32]* @GA1, metadata !"a(2:si)"} +// CHECK: !18 = metadata !{void ([2 x i32]*)* @ArrayTypeVariable1, metadata !"f{0}(p(a(2:si)))"} +// CHECK: !19 = metadata !{void (void ([2 x i32]*)*)* @ArrayTypeVariable2, metadata !"f{0}(p(f{0}(p(a(2:si)))))"} +// CHECK: !20 = metadata !{[3 x [2 x i32]]* @GA2, metadata !"a(3:a(2:si))"} +extern int EA1[]; +extern int EA2[2]; +extern int EA3[][2]; +extern int EA4[3][2]; +int GA1[2]; +int GA2[3][2]; +extern void ArrayTypeVariable1(int[*][2]); +extern void ArrayTypeVariable2( void(*fp)(int[*][2]) ); +extern void ArrayTypeVariable3(int[3][*]); // not supported +extern void ArrayTypeVariable4( void(*fp)(int[3][*]) ); // not supported +typedef int RetType[2]; +RetType* ArrayType(int A1[], int A2[2], int A3[][2], int A4[3][2], int A5[const volatile restrict static 2]) { + if (A1) return &EA1; + if (A2) return &EA2; + if (A3) return EA3; + if (A4) return EA4; + if (A5) return &GA1; + ArrayTypeVariable1(EA4); + ArrayTypeVariable2(ArrayTypeVariable1); + ArrayTypeVariable3(EA4); + ArrayTypeVariable4(ArrayTypeVariable3); + return GA2; +} + + +// test StructureType +// CHECK: !21 = metadata !{void (%struct.S1*)* @StructureType1, metadata !"f{0}(s(S1){m(ps2){p(s(S2){m(ps3){p(s(S3){m(s1){s(S1){}}})}})}})"} +// CHECK: !22 = metadata !{void (%struct.S2*)* @StructureType2, metadata !"f{0}(s(S2){m(ps3){p(s(S3){m(s1){s(S1){m(ps2){p(s(S2){m(ps3){p(s(S3){m(s1){s(S1){}}})}})}}}})}})"} +// CHECK: !23 = metadata !{void (%struct.S3*)* @StructureType3, metadata !"f{0}(s(S3){m(s1){s(S1){m(ps2){p(s(S2){m(ps3){p(s(S3){m(s1){s(S1){}}})}})}}}})"} +// CHECK: !24 = metadata !{void (%struct.SB*)* @StructureTypeB, metadata !"f{0}(s(SB){m(){b(4:si)},m(){b(2:si)},m(N4){b(4:si)},m(N2){b(2:si)},m(){b(4:ui)},m(){b(4:si)},m(){b(4:c:si)},m(){b(4:c:si)},m(){b(4:cv:si)}})"} +struct S2; +struct S1{struct S2 *ps2;}; +struct S3; +struct S2{struct S3 *ps3;}; +struct S3{struct S1 s1;}; +void StructureType1(struct S1 s1){} +void StructureType2(struct S2 s2){} +void StructureType3(struct S3 s3){} +struct SB{int:4; int:2; int N4:4; int N2:2; unsigned int:4; signed int:4; + const int:4; int const :4; volatile const int:4;}; +void StructureTypeB(struct SB sb){} + + +// test UnionType +// CHECK: !25 = metadata !{void (%union.U1*)* @UnionType1, metadata !"f{0}(u(U1){m(pu2){p(u(U2){m(pu3){p(u(U3){m(u1){u(U1){}}})}})}})"} +// CHECK: !26 = metadata !{void (%union.U2*)* @UnionType2, metadata !"f{0}(u(U2){m(pu3){p(u(U3){m(u1){u(U1){m(pu2){p(u(U2){m(pu3){p(u(U3){m(u1){u(U1){}}})}})}}}})}})"} +// CHECK: !27 = metadata !{void (%union.U3*)* @UnionType3, metadata !"f{0}(u(U3){m(u1){u(U1){m(pu2){p(u(U2){m(pu3){p(u(U3){m(u1){u(U1){}}})}})}}}})"} +// CHECK: !28 = metadata !{void (%union.UB*)* @UnionTypeB, metadata !"f{0}(u(UB){m(N2){b(2:si)},m(N4){b(4:si)},m(){b(2:si)},m(){b(4:c:si)},m(){b(4:c:si)},m(){b(4:cv:si)},m(){b(4:si)},m(){b(4:si)},m(){b(4:ui)}})"} +union U2; +union U1{union U2 *pu2;}; +union U3; +union U2{union U3 *pu3;}; +union U3{union U1 u1;}; +void UnionType1(union U1 u1) {} +void UnionType2(union U2 u2) {} +void UnionType3(union U3 u3) {} +union UB{int:4; int:2; int N4:4; int N2:2; unsigned int:4; signed int:4; + const int:4; int const :4; volatile const int:4;}; +void UnionTypeB(union UB ub) {} + + +// test EnumType +// CHECK: !29 = metadata !{void (i32)* @EnumType, metadata !"f{0}(e(E){m(A){0},m(B){1},m(C){5},m(D){6}})"} +enum E {A, B, C=5, D}; +void EnumType(enum E e) {}