Load the selector table lazily from the PCH file.

This results in a 10% speedup on the Cocoa-prefixed "Hello, World!",
all of which is (not surprisingly) user time. There was a tiny
reduction in the size of the PCH file for Cocoa.h, because certain
selectors aren't being written twice.

I'm using two new tricks here that I'd like to replicate elsewhere:
  (1) The selectors not used in the global method pool are packed into
  the blob after the global method pool's on-disk hash table and
  stored as keys, so that all selectors are in the same blob.
  (2) We record the offsets of each selector key when we write it into
  the global method pool (or after it, in the same blob). The offset
  table is written as a blob, so that we don't need to pack/unpack a
  SmallVector with its contents.




git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@70055 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Douglas Gregor 2009-04-25 17:48:32 +00:00
Родитель d3f632eda7
Коммит 83941df274
5 изменённых файлов: 171 добавлений и 165 удалений

Просмотреть файл

@ -68,10 +68,7 @@ namespace clang {
/// \brief The block containing the definitions of all of the
/// declarations stored in the PCH file.
DECLS_BLOCK_ID,
/// \brief The block containing ObjC selectors stored in the PCH file.
SELECTOR_BLOCK_ID
DECLS_BLOCK_ID
};
/// \brief Record types that occur within the PCH block itself.
@ -167,8 +164,9 @@ namespace clang {
/// declarations.
LOCALLY_SCOPED_EXTERNAL_DECLS = 11,
/// \brief Record code for the Objective-C Selector Table.
SELECTOR_TABLE = 12,
/// \brief Record code for the table of offsets into the
/// Objective-C method pool.
SELECTOR_OFFSETS = 12,
/// \brief Record code for the Objective-C method pool,
METHOD_POOL = 13

Просмотреть файл

@ -146,9 +146,6 @@ private:
/// an IdentifierInfo* that has already been resolved.
llvm::SmallVector<uint64_t, 16> IdentifierData;
/// \brief SelectorData, indexed by the selector ID minus one.
llvm::SmallVector<Selector, 16> SelectorData;
/// \brief A pointer to an on-disk hash table of opaque type
/// PCHMethodPoolLookupTable.
///
@ -156,6 +153,29 @@ private:
/// associated with every selector known in the PCH file.
void *MethodPoolLookupTable;
/// \brief A pointer to the character data that comprises the method
/// pool.
///
/// The SelectorOffsets table refers into this memory.
const unsigned char *MethodPoolLookupTableData;
/// \brief The number of selectors stored in the method pool itself.
unsigned TotalSelectorsInMethodPool;
/// \brief Offsets into the method pool lookup table's data array
/// where each selector resides.
const uint32_t *SelectorOffsets;
/// \brief The total number of selectors stored in the PCH file.
unsigned TotalNumSelectors;
/// \brief A vector containing selectors that have already been loaded.
///
/// This vector is indexed by the Selector ID (-1). NULL selector
/// entries indicate that the particular selector ID has not yet
/// been loaded.
llvm::SmallVector<Selector, 16> SelectorsLoaded;
/// \brief The set of external definitions stored in the the PCH
/// file.
llvm::SmallVector<uint64_t, 16> ExternalDefinitions;
@ -197,6 +217,13 @@ private:
/// \brief The number of macros de-serialized from the PCH file.
unsigned NumMacrosRead;
/// \brief The number of method pool entries that have been read.
unsigned NumMethodPoolSelectorsRead;
/// \brief The number of times we have looked into the global method
/// pool and not found anything.
unsigned NumMethodPoolMisses;
/// \brief The total number of macros stored in the PCH file.
unsigned TotalNumMacros;
@ -217,14 +244,12 @@ private:
/// Objective-C protocols.
llvm::SmallVector<Decl *, 16> InterestingDecls;
PCHReadResult ReadPCHBlock(uint64_t &PreprocessorBlockOffset,
uint64_t &SelectorBlockOffset);
PCHReadResult ReadPCHBlock(uint64_t &PreprocessorBlockOffset);
bool CheckPredefinesBuffer(const char *PCHPredef,
unsigned PCHPredefLen,
FileID PCHBufferID);
PCHReadResult ReadSourceManagerBlock();
bool ReadPreprocessorBlock();
bool ReadSelectorBlock();
bool ParseLanguageOptions(const llvm::SmallVectorImpl<uint64_t> &Record);
QualType ReadTypeRecord(uint64_t Offset);
@ -240,7 +265,10 @@ public:
explicit PCHReader(Preprocessor &PP, ASTContext &Context)
: SemaObj(0), PP(PP), Context(Context), Consumer(0),
IdentifierTableData(0), IdentifierLookupTable(0),
MethodPoolLookupTable(0), NumStatementsRead(0), NumMacrosRead(0),
MethodPoolLookupTable(0), MethodPoolLookupTableData(0),
TotalSelectorsInMethodPool(0), SelectorOffsets(0),
TotalNumSelectors(0), NumStatementsRead(0), NumMacrosRead(0),
NumMethodPoolSelectorsRead(0), NumMethodPoolMisses(0),
NumLexicalDeclContextsRead(0), NumVisibleDeclContextsRead(0) { }
~PCHReader() {}

Просмотреть файл

@ -101,6 +101,10 @@ private:
/// \brief Map that provides the ID numbers of each Selector.
llvm::DenseMap<Selector, pch::SelectorID> SelectorIDs;
/// \brief Offset of each selector within the method pool/selector
/// table, indexed by the Selector ID (-1).
llvm::SmallVector<uint32_t, 16> SelectorOffsets;
/// \brief A vector of all Selectors (ordered by ID).
llvm::SmallVector<Selector, 16> SelVector;
@ -161,7 +165,6 @@ private:
void WriteDeclsBlock(ASTContext &Context);
void WriteMethodPool(Sema &SemaRef);
void WriteIdentifierTable(Preprocessor &PP);
void WriteSelectorTable();
void WriteAttributeRecord(const Attr *Attr);
public:
@ -224,6 +227,10 @@ public:
/// within the identifier table.
void SetIdentifierOffset(const IdentifierInfo *II, uint32_t Offset);
/// \brief Note that the selector Sel occurs at the given offset
/// within the method pool/selector table.
void SetSelectorOffset(Selector Sel, uint32_t Offset);
/// \brief Add the given statement or expression to the queue of
/// statements to emit.
///

Просмотреть файл

@ -1141,7 +1141,7 @@ public:
return std::make_pair(KeyLen, DataLen);
}
internal_key_type ReadKey(const unsigned char* d, unsigned n) {
internal_key_type ReadKey(const unsigned char* d, unsigned) {
using namespace clang::io;
SelectorTable &SelTable = Reader.getContext().Selectors;
unsigned N = ReadUnalignedLE16(d);
@ -1725,70 +1725,8 @@ bool PCHReader::ReadPreprocessorBlock() {
}
}
bool PCHReader::ReadSelectorBlock() {
if (Stream.EnterSubBlock(pch::SELECTOR_BLOCK_ID))
return Error("Malformed selector block record");
RecordData Record;
while (true) {
unsigned Code = Stream.ReadCode();
switch (Code) {
case llvm::bitc::END_BLOCK:
if (Stream.ReadBlockEnd())
return Error("Error at end of preprocessor block");
return false;
case llvm::bitc::ENTER_SUBBLOCK:
// No known subblocks, always skip them.
Stream.ReadSubBlockID();
if (Stream.SkipBlock())
return Error("Malformed block record");
continue;
case llvm::bitc::DEFINE_ABBREV:
Stream.ReadAbbrevRecord();
continue;
default: break;
}
// Read a record.
Record.clear();
pch::PCHRecordTypes RecType =
(pch::PCHRecordTypes)Stream.ReadRecord(Code, Record);
switch (RecType) {
default: // Default behavior: ignore unknown records.
break;
case pch::SELECTOR_TABLE:
unsigned Idx = 1; // Record[0] == pch::SELECTOR_TABLE.
unsigned NumSels = Record[Idx++];
llvm::SmallVector<IdentifierInfo *, 8> KeyIdents;
for (unsigned SelIdx = 0; SelIdx < NumSels; SelIdx++) {
unsigned NumArgs = Record[Idx++];
KeyIdents.clear();
if (NumArgs == 0) {
// If the number of arguments is 0, we must have an Identifier.
IdentifierInfo *II = DecodeIdentifierInfo(Record[Idx++]);
assert(II && "DecodeIdentifierInfo returned 0");
KeyIdents.push_back(II);
} else {
// For keyword selectors, the Identifier is optional (::: is legal!).
for (unsigned ArgIdx = 0; ArgIdx < NumArgs; ++ArgIdx) {
IdentifierInfo *II = DecodeIdentifierInfo(Record[Idx++]);
KeyIdents.push_back(II);
}
}
Selector Sel = PP.getSelectorTable().getSelector(NumArgs,&KeyIdents[0]);
SelectorData.push_back(Sel);
}
}
}
return false;
}
PCHReader::PCHReadResult
PCHReader::ReadPCHBlock(uint64_t &PreprocessorBlockOffset,
uint64_t &SelectorBlockOffset) {
PCHReader::ReadPCHBlock(uint64_t &PreprocessorBlockOffset) {
if (Stream.EnterSubBlock(pch::PCH_BLOCK_ID)) {
Error("Malformed block record");
return Failure;
@ -1832,20 +1770,6 @@ PCHReader::ReadPCHBlock(uint64_t &PreprocessorBlockOffset,
}
break;
case pch::SELECTOR_BLOCK_ID:
// Skip the selector block for now, but remember where it is. We
// want to read it in after the identifier table.
if (SelectorBlockOffset) {
Error("Multiple selector blocks found.");
return Failure;
}
SelectorBlockOffset = Stream.GetCurrentBitNo();
if (Stream.SkipBlock()) {
Error("Malformed block record");
return Failure;
}
break;
case pch::SOURCE_MANAGER_BLOCK_ID:
switch (ReadSourceManagerBlock()) {
case Success:
@ -1971,12 +1895,21 @@ PCHReader::ReadPCHBlock(uint64_t &PreprocessorBlockOffset,
LocallyScopedExternalDecls.swap(Record);
break;
case pch::SELECTOR_OFFSETS:
SelectorOffsets = (const uint32_t *)BlobStart;
TotalNumSelectors = Record[0];
SelectorsLoaded.resize(TotalNumSelectors);
break;
case pch::METHOD_POOL:
MethodPoolLookupTable
= PCHMethodPoolLookupTable::Create(
(const unsigned char *)BlobStart + Record[0],
(const unsigned char *)BlobStart,
MethodPoolLookupTableData = (const unsigned char *)BlobStart;
if (Record[0])
MethodPoolLookupTable
= PCHMethodPoolLookupTable::Create(
MethodPoolLookupTableData + Record[0],
MethodPoolLookupTableData,
PCHMethodPoolLookupTrait(*this));
TotalSelectorsInMethodPool = Record[1];
break;
}
}
@ -2012,7 +1945,6 @@ PCHReader::PCHReadResult PCHReader::ReadPCH(const std::string &FileName) {
// We expect a number of well-defined blocks, though we don't necessarily
// need to understand them all.
uint64_t PreprocessorBlockOffset = 0;
uint64_t SelectorBlockOffset = 0;
while (!Stream.AtEndOfStream()) {
unsigned Code = Stream.ReadCode();
@ -2033,7 +1965,7 @@ PCHReader::PCHReadResult PCHReader::ReadPCH(const std::string &FileName) {
}
break;
case pch::PCH_BLOCK_ID:
switch (ReadPCHBlock(PreprocessorBlockOffset, SelectorBlockOffset)) {
switch (ReadPCHBlock(PreprocessorBlockOffset)) {
case Success:
break;
@ -2117,14 +2049,6 @@ PCHReader::PCHReadResult PCHReader::ReadPCH(const std::string &FileName) {
return Failure;
}
}
if (SelectorBlockOffset) {
SavedStreamPosition SavedPos(Stream);
Stream.JumpToBit(SelectorBlockOffset);
if (ReadSelectorBlock()) {
Error("Malformed preprocessor block");
return Failure;
}
}
return Success;
}
@ -2799,30 +2723,53 @@ void PCHReader::PrintStats() {
if ((IdentifierData[I] & 0x01) == 0)
++NumIdentifiersLoaded;
}
unsigned NumSelectorsLoaded = 0;
for (unsigned I = 0; I < SelectorsLoaded.size(); ++I) {
if (SelectorsLoaded[I].getAsOpaquePtr())
++NumSelectorsLoaded;
}
std::fprintf(stderr, " %u/%u types read (%f%%)\n",
NumTypesLoaded, (unsigned)TypeAlreadyLoaded.size(),
((float)NumTypesLoaded/TypeAlreadyLoaded.size() * 100));
std::fprintf(stderr, " %u/%u declarations read (%f%%)\n",
NumDeclsLoaded, (unsigned)DeclAlreadyLoaded.size(),
((float)NumDeclsLoaded/DeclAlreadyLoaded.size() * 100));
std::fprintf(stderr, " %u/%u identifiers read (%f%%)\n",
NumIdentifiersLoaded, (unsigned)IdentifierData.size(),
((float)NumIdentifiersLoaded/IdentifierData.size() * 100));
std::fprintf(stderr, " %u/%u statements read (%f%%)\n",
NumStatementsRead, TotalNumStatements,
((float)NumStatementsRead/TotalNumStatements * 100));
std::fprintf(stderr, " %u/%u macros read (%f%%)\n",
NumMacrosRead, TotalNumMacros,
((float)NumMacrosRead/TotalNumMacros * 100));
std::fprintf(stderr, " %u/%u lexical declcontexts read (%f%%)\n",
NumLexicalDeclContextsRead, TotalLexicalDeclContexts,
((float)NumLexicalDeclContextsRead/TotalLexicalDeclContexts
* 100));
std::fprintf(stderr, " %u/%u visible declcontexts read (%f%%)\n",
NumVisibleDeclContextsRead, TotalVisibleDeclContexts,
((float)NumVisibleDeclContextsRead/TotalVisibleDeclContexts
* 100));
if (!TypeAlreadyLoaded.empty())
std::fprintf(stderr, " %u/%u types read (%f%%)\n",
NumTypesLoaded, (unsigned)TypeAlreadyLoaded.size(),
((float)NumTypesLoaded/TypeAlreadyLoaded.size() * 100));
if (!DeclAlreadyLoaded.empty())
std::fprintf(stderr, " %u/%u declarations read (%f%%)\n",
NumDeclsLoaded, (unsigned)DeclAlreadyLoaded.size(),
((float)NumDeclsLoaded/DeclAlreadyLoaded.size() * 100));
if (!IdentifierData.empty())
std::fprintf(stderr, " %u/%u identifiers read (%f%%)\n",
NumIdentifiersLoaded, (unsigned)IdentifierData.size(),
((float)NumIdentifiersLoaded/IdentifierData.size() * 100));
if (TotalNumSelectors)
std::fprintf(stderr, " %u/%u selectors read (%f%%)\n",
NumSelectorsLoaded, TotalNumSelectors,
((float)NumSelectorsLoaded/TotalNumSelectors * 100));
if (TotalNumStatements)
std::fprintf(stderr, " %u/%u statements read (%f%%)\n",
NumStatementsRead, TotalNumStatements,
((float)NumStatementsRead/TotalNumStatements * 100));
if (TotalNumMacros)
std::fprintf(stderr, " %u/%u macros read (%f%%)\n",
NumMacrosRead, TotalNumMacros,
((float)NumMacrosRead/TotalNumMacros * 100));
if (TotalLexicalDeclContexts)
std::fprintf(stderr, " %u/%u lexical declcontexts read (%f%%)\n",
NumLexicalDeclContextsRead, TotalLexicalDeclContexts,
((float)NumLexicalDeclContextsRead/TotalLexicalDeclContexts
* 100));
if (TotalVisibleDeclContexts)
std::fprintf(stderr, " %u/%u visible declcontexts read (%f%%)\n",
NumVisibleDeclContextsRead, TotalVisibleDeclContexts,
((float)NumVisibleDeclContextsRead/TotalVisibleDeclContexts
* 100));
if (TotalSelectorsInMethodPool) {
std::fprintf(stderr, " %u/%u method pool entries read (%f%%)\n",
NumMethodPoolSelectorsRead, TotalSelectorsInMethodPool,
((float)NumMethodPoolSelectorsRead/TotalSelectorsInMethodPool
* 100));
std::fprintf(stderr, " %u method pool misses\n", NumMethodPoolMisses);
}
std::fprintf(stderr, "\n");
}
@ -2878,9 +2825,12 @@ PCHReader::ReadMethodPool(Selector Sel) {
PCHMethodPoolLookupTable *PoolTable
= (PCHMethodPoolLookupTable*)MethodPoolLookupTable;
PCHMethodPoolLookupTable::iterator Pos = PoolTable->find(Sel);
if (Pos == PoolTable->end())
if (Pos == PoolTable->end()) {
++NumMethodPoolMisses;
return std::pair<ObjCMethodList, ObjCMethodList>();;
}
++NumMethodPoolSelectorsRead;
return *Pos;
}
@ -2911,16 +2861,26 @@ Selector PCHReader::DecodeSelector(unsigned ID) {
if (ID == 0)
return Selector();
if (SelectorData.empty()) {
if (!MethodPoolLookupTableData) {
Error("No selector table in PCH file");
return Selector();
}
if (ID > SelectorData.size()) {
if (ID > TotalNumSelectors) {
Error("Selector ID out of range");
return Selector();
}
return SelectorData[ID-1];
unsigned Index = ID - 1;
if (SelectorsLoaded[Index].getAsOpaquePtr() == 0) {
// Load this selector from the selector table.
// FIXME: endianness portability issues with SelectorOffsets table
PCHMethodPoolLookupTrait Trait(*this);
SelectorsLoaded[Index]
= Trait.ReadKey(MethodPoolLookupTableData + SelectorOffsets[Index], 0);
}
return SelectorsLoaded[Index];
}
DeclarationName

Просмотреть файл

@ -1832,11 +1832,10 @@ public:
return std::make_pair(KeyLen, DataLen);
}
void EmitKey(llvm::raw_ostream& Out, Selector Sel, unsigned KeyLen) {
// FIXME: Keep track of the location of the key data (the
// selector), so we can fold the selector table's storage into
// this hash table.
uint64_t Start = Out.tell(); (void)Start;
void EmitKey(llvm::raw_ostream& Out, Selector Sel, unsigned) {
uint64_t Start = Out.tell();
assert((Start >> 32) == 0 && "Selector key offset too large");
Writer.SetSelectorOffset(Sel, Start);
unsigned N = Sel.getNumArgs();
clang::io::Emit16(Out, N);
if (N == 0)
@ -1844,8 +1843,6 @@ public:
for (unsigned I = 0; I != N; ++I)
clang::io::Emit32(Out,
Writer.getIdentifierRef(Sel.getIdentifierInfoForSlot(I)));
assert(Out.tell() - Start == KeyLen && "Key length is wrong");
}
void EmitData(llvm::raw_ostream& Out, key_type_ref,
@ -1895,6 +1892,7 @@ void PCHWriter::WriteMethodPool(Sema &SemaRef) {
// Create the on-disk hash table representation. Start by
// iterating through the instance method pool.
PCHMethodPoolTrait::key_type Key;
unsigned NumSelectorsInMethodPool = 0;
for (llvm::DenseMap<Selector, ObjCMethodList>::iterator
Instance = SemaRef.InstanceMethodPool.begin(),
InstanceEnd = SemaRef.InstanceMethodPool.end();
@ -1912,6 +1910,7 @@ void PCHWriter::WriteMethodPool(Sema &SemaRef) {
Generator.insert(Instance->first,
std::make_pair(Instance->second, Factory->second));
++NumSelectorsInMethodPool;
Empty = false;
}
@ -1926,41 +1925,68 @@ void PCHWriter::WriteMethodPool(Sema &SemaRef) {
llvm::DenseMap<Selector, ObjCMethodList>::iterator Instance
= SemaRef.InstanceMethodPool.find(Factory->first);
if (Instance == SemaRef.InstanceMethodPool.end())
if (Instance == SemaRef.InstanceMethodPool.end()) {
Generator.insert(Factory->first,
std::make_pair(ObjCMethodList(), Factory->second));
++NumSelectorsInMethodPool;
}
Empty = false;
}
if (Empty)
if (Empty && SelectorOffsets.empty())
return;
// Create the on-disk hash table in a buffer.
llvm::SmallVector<char, 4096> MethodPool;
uint32_t BucketOffset;
SelectorOffsets.resize(SelVector.size());
{
PCHMethodPoolTrait Trait(*this);
llvm::raw_svector_ostream Out(MethodPool);
// Make sure that no bucket is at offset 0
clang::io::Emit32(Out, 0);
BucketOffset = Generator.Emit(Out, Trait);
// For every selector that we have seen but which was not
// written into the hash table, write the selector itself and
// record it's offset.
for (unsigned I = 0, N = SelVector.size(); I != N; ++I)
if (SelectorOffsets[I] == 0)
Trait.EmitKey(Out, SelVector[I], 0);
}
// Create a blob abbreviation
BitCodeAbbrev *Abbrev = new BitCodeAbbrev();
Abbrev->Add(BitCodeAbbrevOp(pch::METHOD_POOL));
Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 32));
Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 32));
Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob));
unsigned MethodPoolAbbrev = Stream.EmitAbbrev(Abbrev);
// Write the identifier table
// Write the method pool
RecordData Record;
Record.push_back(pch::METHOD_POOL);
Record.push_back(BucketOffset);
Record.push_back(NumSelectorsInMethodPool);
Stream.EmitRecordWithBlob(MethodPoolAbbrev, Record,
&MethodPool.front(),
MethodPool.size());
// Create a blob abbreviation for the selector table offsets.
Abbrev = new BitCodeAbbrev();
Abbrev->Add(BitCodeAbbrevOp(pch::SELECTOR_OFFSETS));
Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 32)); // index
Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob));
unsigned SelectorOffsetAbbrev = Stream.EmitAbbrev(Abbrev);
// Write the selector offsets table.
Record.clear();
Record.push_back(pch::SELECTOR_OFFSETS);
Record.push_back(SelectorOffsets.size());
Stream.EmitRecordWithBlob(SelectorOffsetAbbrev, Record,
(const char *)&SelectorOffsets.front(),
SelectorOffsets.size() * 4);
}
}
@ -2096,26 +2122,6 @@ void PCHWriter::WriteIdentifierTable(Preprocessor &PP) {
Stream.EmitRecord(pch::IDENTIFIER_OFFSET, IdentifierOffsets);
}
void PCHWriter::WriteSelectorTable() {
Stream.EnterSubblock(pch::SELECTOR_BLOCK_ID, 2);
RecordData Record;
Record.push_back(pch::SELECTOR_TABLE);
Record.push_back(SelectorIDs.size());
// Create the on-disk representation.
for (unsigned selIdx = 0; selIdx < SelVector.size(); selIdx++) {
assert(SelVector[selIdx].getAsOpaquePtr() && "NULL Selector found");
Record.push_back(SelVector[selIdx].getNumArgs());
if (SelVector[selIdx].getNumArgs())
for (unsigned i = 0; i < SelVector[selIdx].getNumArgs(); i++)
AddIdentifierRef(SelVector[selIdx].getIdentifierInfoForSlot(i), Record);
else
AddIdentifierRef(SelVector[selIdx].getIdentifierInfoForSlot(0), Record);
}
Stream.EmitRecord(pch::SELECTOR_TABLE, Record);
Stream.ExitBlock();
}
/// \brief Write a record containing the given attributes.
void PCHWriter::WriteAttributeRecord(const Attr *Attr) {
RecordData Record;
@ -2250,6 +2256,14 @@ void PCHWriter::SetIdentifierOffset(const IdentifierInfo *II, uint32_t Offset) {
IdentifierOffsets[IdentifierIDs[II] - 1] = (Offset << 1) | 0x01;
}
/// \brief Note that the selector Sel occurs at the given offset
/// within the method pool/selector table.
void PCHWriter::SetSelectorOffset(Selector Sel, uint32_t Offset) {
unsigned ID = SelectorIDs[Sel];
assert(ID && "Unknown selector");
SelectorOffsets[ID - 1] = Offset;
}
PCHWriter::PCHWriter(llvm::BitstreamWriter &Stream)
: Stream(Stream), NextTypeID(pch::NUM_PREDEF_TYPE_IDS),
NumStatements(0), NumMacros(0), NumLexicalDeclContexts(0),
@ -2309,7 +2323,6 @@ void PCHWriter::WritePCH(Sema &SemaRef) {
WriteTypesBlock(Context);
WriteDeclsBlock(Context);
WriteMethodPool(SemaRef);
WriteSelectorTable();
WriteIdentifierTable(PP);
Stream.EmitRecord(pch::TYPE_OFFSET, TypeOffsets);
Stream.EmitRecord(pch::DECL_OFFSET, DeclOffsets);