Index: ELF/Config.h =================================================================== --- ELF/Config.h +++ ELF/Config.h @@ -51,11 +51,13 @@ bool DiscardNone; bool EnableNewDtags; bool ExportDynamic; + bool GnuHash = false; bool Mips64EL = false; bool NoInhibitExec; bool NoUndefined; bool Shared; bool Static = false; + bool SysvHash = true; bool Verbose; bool ZNodelete = false; bool ZNow = false; Index: ELF/Driver.cpp =================================================================== --- ELF/Driver.cpp +++ ELF/Driver.cpp @@ -155,6 +155,17 @@ Config->SoName = getString(Args, OPT_soname); Config->Sysroot = getString(Args, OPT_sysroot); + if (auto *Arg = Args.getLastArg(OPT_hash_style)) { + StringRef S = Arg->getValue(); + if (S == "gnu") { + Config->GnuHash = true; + Config->SysvHash = false; + } else if (S == "both") { + Config->GnuHash = true; + } else if (S != "sysv") + error("Unknown hash style: " + S); + } + for (auto *Arg : Args.filtered(OPT_undefined)) Config->Undefined.push_back(Arg->getValue()); Index: ELF/Options.td =================================================================== --- ELF/Options.td +++ ELF/Options.td @@ -46,6 +46,9 @@ def fini : Separate<["-"], "fini">, MetaVarName<"">, HelpText<"Specify a finalizer function">; +def hash_style : Separate<["--", "-"], "hash-style">, + HelpText<"Specify hash style (sysv, gnu or both)">; + def init : Separate<["-"], "init">, MetaVarName<"">, HelpText<"Specify an initializer function">; @@ -107,6 +110,7 @@ def alias_discard_locals_X: Flag<["-"], "X">, Alias; def alias_entry_e : Separate<["-"], "e">, Alias; def alias_fini_fini : Joined<["-"], "fini=">, Alias; +def alias_hash_style_hash_style : Joined<["--", "-"], "hash-style=">, Alias; def alias_init_init : Joined<["-"], "init=">, Alias; def alias_l__library : Joined<["--"], "library=">, Alias; def alias_o_output : Joined<["--"], "output=">, Alias; @@ -124,7 +128,6 @@ def end_group : Flag<["--"], "end-group">; def fatal_warnings : Flag<["--"], "fatal-warnings">; def gc_sections : Flag<["--"], "gc-sections">; -def hash_style : Joined<["--"], "hash-style=">; def no_add_needed : Flag<["--"], "no-add-needed">; def no_fatal_warnings : Flag<["--"], "no-fatal-warnings">; def no_warn_mismatch : Flag<["--"], "no-warn-mismatch">; Index: ELF/OutputSections.h =================================================================== --- ELF/OutputSections.h +++ ELF/OutputSections.h @@ -57,6 +57,7 @@ template bool includeInSymtab(const SymbolBody &B); bool includeInDynamicSymtab(const SymbolBody &B); +bool includeInGnuHashTable(const SymbolBody &B); template bool shouldKeepInSymtab( @@ -176,8 +177,16 @@ void addSymbol(SymbolBody *Body); StringTableSection &getStrTabSec() const { return StrTabSec; } unsigned getNumSymbols() const { return NumVisible + 1; } + unsigned getNumGnuHashSymbols() const { return NumGnuHashed; } - ArrayRef getSymbols() const { return Symbols; } + struct SymbolData { + SymbolData(SymbolBody *Body, bool HasGnuHash); + SymbolBody *Body; + bool HasGnuHash; + uint32_t GnuHash; + }; + ArrayRef getSymbols() const { return Symbols; } + ArrayRef getGnuHashSymbols() const; private: void writeLocalSymbols(uint8_t *&Buf); @@ -187,9 +196,10 @@ SymbolTable &Table; StringTableSection &StrTabSec; - std::vector Symbols; + std::vector Symbols; unsigned NumVisible = 0; unsigned NumLocals = 0; + unsigned NumGnuHashed = 0; }; template @@ -283,6 +293,33 @@ void writeTo(uint8_t *Buf) override; }; +// Outputs GNU Hash section. For detailed explanation see: +// https://blogs.oracle.com/ali/entry/gnu_hash_elf_sections +template +class GnuHashTableSection final : public OutputSectionBase { + typedef typename llvm::object::ELFFile::Elf_Off Elf_Off; + typedef typename llvm::object::ELFFile::Elf_Word Elf_Word; + typedef typename llvm::object::ELFFile::uintX_t uintX_t; + +public: + GnuHashTableSection(); + void finalize() override; + void writeTo(uint8_t *Buf) override; + + static unsigned calcNBuckets(unsigned NumHashed); + +private: + static unsigned calcMaskWords(unsigned NumHashed); + + void writeHeader(uint8_t *&Buf); + void writeBloomFilter(uint8_t *&Buf); + void writeHashTable(uint8_t *Buf); + + unsigned MaskWords; + unsigned NBuckets; + unsigned Shift2; +}; + template class DynamicSection final : public OutputSectionBase { typedef OutputSectionBase Base; @@ -312,6 +349,7 @@ // until Writer is initialized. template struct Out { static DynamicSection *Dynamic; + static GnuHashTableSection *GnuHashTab; static GotPltSection *GotPlt; static GotSection *Got; static HashTableSection *HashTab; @@ -330,6 +368,7 @@ }; template DynamicSection *Out::Dynamic; +template GnuHashTableSection *Out::GnuHashTab; template GotPltSection *Out::GotPlt; template GotSection *Out::Got; template HashTableSection *Out::HashTab; Index: ELF/OutputSections.cpp =================================================================== --- ELF/OutputSections.cpp +++ ELF/OutputSections.cpp @@ -11,6 +11,7 @@ #include "Config.h" #include "SymbolTable.h" #include "Target.h" +#include "llvm/Support/MathExtras.h" using namespace llvm; using namespace llvm::object; @@ -238,7 +239,7 @@ this->Header.sh_addralign = sizeof(Elf_Word); } -static uint32_t hash(StringRef Name) { +static uint32_t hashSysv(StringRef Name) { uint32_t H = 0; for (char C : Name) { H = (H << 4) + C; @@ -272,15 +273,138 @@ Elf_Word *Buckets = P; Elf_Word *Chains = P + NumSymbols; - for (SymbolBody *Body : Out::DynSymTab->getSymbols()) { + for (const typename SymbolTableSection::SymbolData &Item : + Out::DynSymTab->getSymbols()) { + SymbolBody *Body = Item.Body; StringRef Name = Body->getName(); unsigned I = Body->getDynamicSymbolTableIndex(); - uint32_t Hash = hash(Name) % NumSymbols; + uint32_t Hash = hashSysv(Name) % NumSymbols; Chains[I] = Buckets[Hash]; Buckets[Hash] = I; } } +static uint32_t hashGnu(StringRef Name) { + uint32_t H = 5381; + for (uint8_t C : Name) + H = (H << 5) + H + C; + return H; +} + +template +GnuHashTableSection::GnuHashTableSection() + : OutputSectionBase(".gnu.hash", llvm::ELF::SHT_GNU_HASH, + llvm::ELF::SHF_ALLOC) { + this->Header.sh_entsize = ELFT::Is64Bits ? 0 : 4; + this->Header.sh_addralign = ELFT::Is64Bits ? 8 : 4; +} + +template +unsigned GnuHashTableSection::calcNBuckets(unsigned NumHashed) { + if (!NumHashed) + return 0; + + // These values are prime numbers which are not greater than 2^(N-1) + 1. + // In result, for any particular NumHashed we return a prime number + // which is not greater than NumHashed. + static const unsigned Primes[] = { + 1, 1, 3, 3, 7, 13, 31, 61, 127, 251, + 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071}; + + return Primes[std::min(Log2_32_Ceil(NumHashed), + array_lengthof(Primes) - 1)]; +} + +// Bloom filter estimation: at least 8 bits for each hashed symbol. +// GNU Hash table requirement: it should be a power of 2, +// the minimum value is 1, even for an empty table. +// Expected results for a 32-bit target: +// calcMaskWords(0..4) = 1 +// calcMaskWords(5..8) = 2 +// calcMaskWords(9..16) = 4 +// For a 64-bit target: +// calcMaskWords(0..8) = 1 +// calcMaskWords(9..16) = 2 +// calcMaskWords(17..32) = 4 +template +unsigned GnuHashTableSection::calcMaskWords(unsigned NumHashed) { + if (!NumHashed) + return 1; + return NextPowerOf2((NumHashed - 1) / sizeof(Elf_Off)); +} + +template void GnuHashTableSection::finalize() { + const unsigned NumHashed = Out::DynSymTab->getNumGnuHashSymbols(); + NBuckets = calcNBuckets(NumHashed); + MaskWords = calcMaskWords(NumHashed); + // Second hash shift estimation: just predefined values. + Shift2 = ELFT::Is64Bits ? 6 : 5; + + this->Header.sh_link = Out::DynSymTab->SectionIndex; + this->Header.sh_size = sizeof(Elf_Word) * 4 // Header + + sizeof(Elf_Off) * MaskWords // Bloom Filter + + sizeof(Elf_Word) * NBuckets // Hash Buckets + + sizeof(Elf_Word) * NumHashed; // Hash Values +} + +template void GnuHashTableSection::writeTo(uint8_t *Buf) { + writeHeader(Buf); + if (!NBuckets) // There are no hashed symbols + return; + writeBloomFilter(Buf); + writeHashTable(Buf); +} + +template +void GnuHashTableSection::writeHeader(uint8_t *&Buf) { + auto *P = reinterpret_cast(Buf); + *P++ = NBuckets; + *P++ = Out::DynSymTab->getNumSymbols() - + Out::DynSymTab->getNumGnuHashSymbols(); + *P++ = MaskWords; + *P++ = Shift2; + Buf = reinterpret_cast(P); +} + +template +void GnuHashTableSection::writeBloomFilter(uint8_t *&Buf) { + unsigned C = sizeof(Elf_Off) * 8; + + auto *Masks = reinterpret_cast(Buf); + for (const typename SymbolTableSection::SymbolData &Item : + Out::DynSymTab->getGnuHashSymbols()) { + size_t Pos = (Item.GnuHash / C) & (MaskWords - 1); + uintX_t V = (uintX_t(1) << (Item.GnuHash % C)) | + (uintX_t(1) << ((Item.GnuHash >> Shift2) % C)); + Masks[Pos] |= V; + } + Buf += sizeof(Elf_Off) * MaskWords; +} + +template +void GnuHashTableSection::writeHashTable(uint8_t *Buf) { + Elf_Word *Buckets = reinterpret_cast(Buf); + Elf_Word *Values = Buckets + NBuckets; + + int PrevBucket = -1; + int I = 0; + for (const typename SymbolTableSection::SymbolData &Item : + Out::DynSymTab->getGnuHashSymbols()) { + int Bucket = Item.GnuHash % NBuckets; + assert(PrevBucket <= Bucket); + if (Bucket != PrevBucket) { + Buckets[Bucket] = Item.Body->getDynamicSymbolTableIndex(); + PrevBucket = Bucket; + if (I > 0) + Values[I - 1] |= 1; + } + Values[I] = Item.GnuHash & ~1; + ++I; + } + if (I > 0) + Values[I - 1] |= 1; +} + template DynamicSection::DynamicSection(SymbolTable &SymTab) : OutputSectionBase(".dynamic", llvm::ELF::SHT_DYNAMIC, @@ -315,7 +439,10 @@ ++NumEntries; // DT_SYMENT ++NumEntries; // DT_STRTAB ++NumEntries; // DT_STRSZ - ++NumEntries; // DT_HASH + if (Out::GnuHashTab) + ++NumEntries; // DT_GNU_HASH + if (Out::HashTab) + ++NumEntries; // DT_HASH if (!Config->RPath.empty()) { ++NumEntries; // DT_RUNPATH / DT_RPATH @@ -391,7 +518,10 @@ WritePtr(DT_SYMENT, sizeof(Elf_Sym)); WritePtr(DT_STRTAB, Out::DynStrTab->getVA()); WriteVal(DT_STRSZ, Out::DynStrTab->data().size()); - WritePtr(DT_HASH, Out::HashTab->getVA()); + if (Out::GnuHashTab) + WritePtr(DT_GNU_HASH, Out::GnuHashTab->getVA()); + if (Out::HashTab) + WritePtr(DT_HASH, Out::HashTab->getVA()); if (!Config->RPath.empty()) @@ -652,6 +782,11 @@ return B.isUsedInDynamicReloc(); } +bool lld::elf2::includeInGnuHashTable(const SymbolBody &B) { + // Assume that includeInDynamicSymtab() is already checked. + return !B.isUndefined(); +} + template bool lld::elf2::shouldKeepInSymtab(const ObjectFile &File, StringRef SymName, @@ -671,6 +806,12 @@ } template +SymbolTableSection::SymbolData::SymbolData(SymbolBody *Body, + bool HasGnuHash) + : Body(Body), HasGnuHash(HasGnuHash), + GnuHash(HasGnuHash ? hashGnu(Body->getName()) : 0) {} + +template SymbolTableSection::SymbolTableSection( SymbolTable &Table, StringTableSection &StrTabSec) : OutputSectionBase( @@ -692,15 +833,24 @@ if (!StrTabSec.isDynamic()) { std::stable_sort(Symbols.begin(), Symbols.end(), - [](SymbolBody *L, SymbolBody *R) { - return getSymbolBinding(L) == STB_LOCAL && - getSymbolBinding(R) != STB_LOCAL; + [](const SymbolData &L, const SymbolData &R) { + return getSymbolBinding(L.Body) == STB_LOCAL && + getSymbolBinding(R.Body) != STB_LOCAL; }); return; } + if (NumGnuHashed) { + unsigned NBuckets = GnuHashTableSection::calcNBuckets(NumGnuHashed); + std::stable_sort(Symbols.begin(), Symbols.end(), + [NBuckets](const SymbolData &L, const SymbolData &R) { + if (!L.HasGnuHash || !R.HasGnuHash) + return R.HasGnuHash; + return L.GnuHash % NBuckets < R.GnuHash % NBuckets; + }); + } size_t I = 0; - for (SymbolBody *Body : Symbols) - Body->setDynamicSymbolTableIndex(++I); + for (const SymbolData &Item : Symbols) + Item.Body->setDynamicSymbolTableIndex(++I); } template @@ -713,8 +863,12 @@ template void SymbolTableSection::addSymbol(SymbolBody *Body) { StrTabSec.add(Body->getName()); - Symbols.push_back(Body); + const bool HasGnuHash = StrTabSec.isDynamic() && Out::GnuHashTab && + includeInGnuHashTable(*Body); + Symbols.emplace_back(Body, HasGnuHash); ++NumVisible; + if (HasGnuHash) + ++NumGnuHashed; } template void SymbolTableSection::writeTo(uint8_t *Buf) { @@ -766,7 +920,8 @@ // Write the internal symbol table contents to the output symbol table // pointed by Buf. auto *ESym = reinterpret_cast(Buf); - for (SymbolBody *Body : Symbols) { + for (const SymbolData &Item : Symbols) { + SymbolBody *Body = Item.Body; StringRef Name = Body->getName(); ESym->st_name = StrTabSec.getFileOff(Name); @@ -826,6 +981,12 @@ return Body->isWeak() ? STB_WEAK : STB_GLOBAL; } +template +ArrayRef::SymbolData> +SymbolTableSection::getGnuHashSymbols() const { + return getSymbols().slice(Symbols.size() - NumGnuHashed); +} + namespace lld { namespace elf2 { template class OutputSectionBase; @@ -858,6 +1019,11 @@ template class InterpSection; template class InterpSection; +template class GnuHashTableSection; +template class GnuHashTableSection; +template class GnuHashTableSection; +template class GnuHashTableSection; + template class HashTableSection; template class HashTableSection; template class HashTableSection; Index: ELF/Writer.cpp =================================================================== --- ELF/Writer.cpp +++ ELF/Writer.cpp @@ -114,7 +114,11 @@ SymbolTableSection DynSymTab(*Symtab, *Out::DynStrTab); Out::DynSymTab = &DynSymTab; HashTableSection HashTab; - Out::HashTab = &HashTab; + if (Config->SysvHash) + Out::HashTab = &HashTab; + GnuHashTableSection GnuHashTab; + if (Config->GnuHash) + Out::GnuHashTab = &GnuHashTab; bool IsRela = Symtab->shouldUseRela(); RelocationSection RelaDyn(IsRela ? ".rela.dyn" : ".rel.dyn", IsRela); Out::RelaDyn = &RelaDyn; @@ -521,7 +525,10 @@ OutputSections.push_back(Out::StrTab); if (isOutputDynamic()) { OutputSections.push_back(Out::DynSymTab); - OutputSections.push_back(Out::HashTab); + if (Out::GnuHashTab) + OutputSections.push_back(Out::GnuHashTab); + if (Out::HashTab) + OutputSections.push_back(Out::HashTab); OutputSections.push_back(Out::Dynamic); OutputSections.push_back(Out::DynStrTab); if (Out::RelaDyn->hasRelocs()) Index: test/elf2/gnu-hash-table.s =================================================================== --- /dev/null +++ test/elf2/gnu-hash-table.s @@ -0,0 +1,193 @@ +# RUN: echo ".globl foo" > %te.s +# RUN: llvm-mc -filetype=obj -triple=i386-pc-linux %te.s -o %te-i386.o +# RUN: llvm-mc -filetype=obj -triple=i386-pc-linux %s -o %t-i386.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t-x86_64.o +# RUN: llvm-mc -filetype=obj -triple=powerpc64-pc-linux %s -o %t-ppc64.o + +# RUN: ld.lld2 -shared --hash-style=gnu -o %te-i386.so %te-i386.o +# RUN: ld.lld2 -shared -hash-style=gnu -o %t-i386.so %t-i386.o +# RUN: ld.lld2 -shared -hash-style=gnu -o %t-x86_64.so %t-x86_64.o +# RUN: ld.lld2 -shared --hash-style both -o %t-ppc64.so %t-ppc64.o + +# RUN: llvm-readobj -dyn-symbols -gnu-hash-table %te-i386.so \ +# RUN: | FileCheck %s -check-prefix=EMPTY +# RUN: llvm-readobj -sections -dyn-symbols -gnu-hash-table %t-i386.so \ +# RUN: | FileCheck %s -check-prefix=I386 +# RUN: llvm-readobj -sections -dyn-symbols -gnu-hash-table %t-x86_64.so \ +# RUN: | FileCheck %s -check-prefix=X86_64 +# RUN: llvm-readobj -sections -dyn-symbols -gnu-hash-table %t-ppc64.so \ +# RUN: | FileCheck %s -check-prefix=PPC64 + +# EMPTY: DynamicSymbols [ +# EMPTY: Symbol { +# EMPTY: Name: foo@ +# EMPTY-NEXT: Value: 0x0 +# EMPTY-NEXT: Size: 0 +# EMPTY-NEXT: Binding: Global +# EMPTY-NEXT: Type: None +# EMPTY-NEXT: Other: 0 +# EMPTY-NEXT: Section: Undefined +# EMPTY-NEXT: } +# EMPTY-NEXT: ] +# EMPTY: GnuHashTable { +# EMPTY-NEXT: Num Buckets: 0 +# EMPTY-NEXT: First Hashed Symbol Index: 2 +# EMPTY-NEXT: Num Mask Words: 1 +# EMPTY-NEXT: Shift Count: 5 +# EMPTY-NEXT: Bloom Filter: [0x0] +# EMPTY-NEXT: Buckets: [] +# EMPTY-NEXT: Values: [] +# EMPTY-NEXT: } + +# I386: Format: ELF32-i386 +# I386: Arch: i386 +# I386: AddressSize: 32bit +# I386: Sections [ +# I386: Name: .gnu.hash +# I386-NEXT: Type: SHT_GNU_HASH +# I386-NEXT: Flags [ +# I386-NEXT: SHF_ALLOC +# I386-NEXT: ] +# I386-NEXT: Address: +# I386-NEXT: Offset: +# I386-NEXT: Size: 32 +# I386-NEXT: Link: +# I386-NEXT: Info: 0 +# I386-NEXT: AddressAlignment: 4 +# I386-NEXT: EntrySize: 4 +# I386: ] +# I386: DynamicSymbols [ +# I386: Symbol { +# I386: Name: @ +# I386: Binding: Local +# I386: Section: Undefined +# I386: } +# I386: Symbol { +# I386: Name: baz@ +# I386: Binding: Global +# I386: Section: Undefined +# I386: } +# I386: Symbol { +# I386: Name: bar@ +# I386: Binding: Global +# I386: Section: .text +# I386: } +# I386: Symbol { +# I386: Name: foo@ +# I386: Binding: Global +# I386: Section: .text +# I386: } +# I386: ] +# I386: GnuHashTable { +# I386-NEXT: Num Buckets: 1 +# I386-NEXT: First Hashed Symbol Index: 2 +# I386-NEXT: Num Mask Words: 1 +# I386-NEXT: Shift Count: 5 +# I386-NEXT: Bloom Filter: [0x14000220] +# I386-NEXT: Buckets: [2] +# I386-NEXT: Values: [0xB8860BA, 0xB887389] +# I386-NEXT: } + +# X86_64: Format: ELF64-x86-64 +# X86_64: Arch: x86_64 +# X86_64: AddressSize: 64bit +# X86_64: Sections [ +# X86_64: Name: .gnu.hash +# X86_64-NEXT: Type: SHT_GNU_HASH +# X86_64-NEXT: Flags [ +# X86_64-NEXT: SHF_ALLOC +# X86_64-NEXT: ] +# X86_64-NEXT: Address: +# X86_64-NEXT: Offset: +# X86_64-NEXT: Size: 36 +# X86_64-NEXT: Link: +# X86_64-NEXT: Info: 0 +# X86_64-NEXT: AddressAlignment: 8 +# X86_64-NEXT: EntrySize: 0 +# X86_64-NEXT: } +# X86_64: ] +# X86_64: DynamicSymbols [ +# X86_64: Symbol { +# X86_64: Name: @ +# X86_64: Binding: Local +# X86_64: Section: Undefined +# X86_64: } +# X86_64: Symbol { +# X86_64: Name: baz@ +# X86_64: Binding: Global +# X86_64: Section: Undefined +# X86_64: } +# X86_64: Symbol { +# X86_64: Name: bar@ +# X86_64: Binding: Global +# X86_64: Section: .text +# X86_64: } +# X86_64: Symbol { +# X86_64: Name: foo@ +# X86_64: Binding: Global +# X86_64: Section: .text +# X86_64: } +# X86_64: ] +# X86_64: GnuHashTable { +# X86_64-NEXT: Num Buckets: 1 +# X86_64-NEXT: First Hashed Symbol Index: 2 +# X86_64-NEXT: Num Mask Words: 1 +# X86_64-NEXT: Shift Count: 6 +# X86_64-NEXT: Bloom Filter: [0x400000000004204] +# X86_64-NEXT: Buckets: [2] +# X86_64-NEXT: Values: [0xB8860BA, 0xB887389] +# X86_64-NEXT: } + +# PPC64: Format: ELF64-ppc64 +# PPC64: Arch: powerpc64 +# PPC64: AddressSize: 64bit +# PPC64: Sections [ +# PPC64: Name: .gnu.hash +# PPC64-NEXT: Type: SHT_GNU_HASH +# PPC64-NEXT: Flags [ +# PPC64-NEXT: SHF_ALLOC +# PPC64-NEXT: ] +# PPC64-NEXT: Address: 0x180 +# PPC64-NEXT: Offset: 0x180 +# PPC64-NEXT: Size: 36 +# PPC64-NEXT: Link: 1 +# PPC64-NEXT: Info: 0 +# PPC64-NEXT: AddressAlignment: 8 +# PPC64-NEXT: EntrySize: 0 +# PPC64-NEXT: } +# PPC64: ] +# PPC64: DynamicSymbols [ +# PPC64: Symbol { +# PPC64: Name: @ +# PPC64: Binding: Local +# PPC64: Section: Undefined +# PPC64: } +# PPC64: Symbol { +# PPC64: Name: baz@ +# PPC64: Binding: Global +# PPC64: Section: Undefined +# PPC64: } +# PPC64: Symbol { +# PPC64: Name: bar@ +# PPC64: Binding: Global +# PPC64: Section: .text +# PPC64: } +# PPC64: Symbol { +# PPC64: Name: foo@ +# PPC64: Binding: Global +# PPC64: Section: .text +# PPC64: } +# PPC64: ] +# PPC64: GnuHashTable { +# PPC64-NEXT: Num Buckets: 1 +# PPC64-NEXT: First Hashed Symbol Index: 2 +# PPC64-NEXT: Num Mask Words: 1 +# PPC64-NEXT: Shift Count: 6 +# PPC64-NEXT: Bloom Filter: [0x400000000004204] +# PPC64-NEXT: Buckets: [2] +# PPC64-NEXT: Values: [0xB8860BA, 0xB887389] +# PPC64-NEXT: } + +.globl foo,bar,baz +foo: +bar: