[Preprocessor] Reduce the memory overhead of #define directives (Recommit)

Recently we observed high memory pressure caused by clang during some parallel builds.
We discovered that we have several projects that have a large number of #define directives
in their TUs (on the order of millions), which caused huge memory consumption in clang due
to a lot of allocations for MacroInfo. We would like to reduce the memory overhead of
clang for a single #define to reduce the memory overhead for these files, to allow us to
reduce the memory pressure on the system during highly parallel builds. This change achieves
that by removing the SmallVector in MacroInfo and instead storing the tokens in an array
allocated using the bump pointer allocator, after all tokens are lexed.

The added unit test with 1000000 #define directives illustrates the problem. Prior to this
change, on arm64 macOS, clang's PP bump pointer allocator allocated 272007616 bytes, and
used roughly 272 bytes per #define. After this change, clang's PP bump pointer allocator
allocates 120002016 bytes, and uses only roughly 120 bytes per #define.

For an example test file that we have internally with 7.8 million #define directives, this
change produces the following improvement on arm64 macOS: Persistent allocation footprint for
this test case file as it's being compiled to LLVM IR went down 22% from 5.28 GB to 4.07 GB
and the total allocations went down 14% from 8.26 GB to 7.05 GB. Furthermore, this change
reduced the total number of allocations made by the system for this clang invocation from
1454853 to 133663, an order of magnitude improvement.

The recommit fixes the LLDB build failure.

Differential Revision: https://reviews.llvm.org/D117348
This commit is contained in:
Alex Lorenz 2022-02-11 13:50:30 -08:00
parent f2fd1587bc
commit 00cd6c0420
8 changed files with 190 additions and 37 deletions

View file

@ -54,11 +54,14 @@ class MacroInfo {
/// macro, this includes the \c __VA_ARGS__ identifier on the list. /// macro, this includes the \c __VA_ARGS__ identifier on the list.
IdentifierInfo **ParameterList = nullptr; IdentifierInfo **ParameterList = nullptr;
/// This is the list of tokens that the macro is defined to.
const Token *ReplacementTokens = nullptr;
/// \see ParameterList /// \see ParameterList
unsigned NumParameters = 0; unsigned NumParameters = 0;
/// This is the list of tokens that the macro is defined to. /// \see ReplacementTokens
SmallVector<Token, 8> ReplacementTokens; unsigned NumReplacementTokens = 0;
/// Length in characters of the macro definition. /// Length in characters of the macro definition.
mutable unsigned DefinitionLength; mutable unsigned DefinitionLength;
@ -230,26 +233,47 @@ public:
bool isWarnIfUnused() const { return IsWarnIfUnused; } bool isWarnIfUnused() const { return IsWarnIfUnused; }
/// Return the number of tokens that this macro expands to. /// Return the number of tokens that this macro expands to.
unsigned getNumTokens() const { return ReplacementTokens.size(); } unsigned getNumTokens() const { return NumReplacementTokens; }
const Token &getReplacementToken(unsigned Tok) const { const Token &getReplacementToken(unsigned Tok) const {
assert(Tok < ReplacementTokens.size() && "Invalid token #"); assert(Tok < NumReplacementTokens && "Invalid token #");
return ReplacementTokens[Tok]; return ReplacementTokens[Tok];
} }
using tokens_iterator = SmallVectorImpl<Token>::const_iterator; using const_tokens_iterator = const Token *;
tokens_iterator tokens_begin() const { return ReplacementTokens.begin(); } const_tokens_iterator tokens_begin() const { return ReplacementTokens; }
tokens_iterator tokens_end() const { return ReplacementTokens.end(); } const_tokens_iterator tokens_end() const {
bool tokens_empty() const { return ReplacementTokens.empty(); } return ReplacementTokens + NumReplacementTokens;
ArrayRef<Token> tokens() const { return ReplacementTokens; } }
bool tokens_empty() const { return NumReplacementTokens == 0; }
ArrayRef<Token> tokens() const {
return llvm::makeArrayRef(ReplacementTokens, NumReplacementTokens);
}
/// Add the specified token to the replacement text for the macro. llvm::MutableArrayRef<Token>
void AddTokenToBody(const Token &Tok) { allocateTokens(unsigned NumTokens, llvm::BumpPtrAllocator &PPAllocator) {
assert(ReplacementTokens == nullptr && NumReplacementTokens == 0 &&
"Token list already allocated!");
NumReplacementTokens = NumTokens;
Token *NewReplacementTokens = PPAllocator.Allocate<Token>(NumTokens);
ReplacementTokens = NewReplacementTokens;
return llvm::makeMutableArrayRef(NewReplacementTokens, NumTokens);
}
void setTokens(ArrayRef<Token> Tokens, llvm::BumpPtrAllocator &PPAllocator) {
assert( assert(
!IsDefinitionLengthCached && !IsDefinitionLengthCached &&
"Changing replacement tokens after definition length got calculated"); "Changing replacement tokens after definition length got calculated");
ReplacementTokens.push_back(Tok); assert(ReplacementTokens == nullptr && NumReplacementTokens == 0 &&
"Token list already set!");
if (Tokens.empty())
return;
NumReplacementTokens = Tokens.size();
Token *NewReplacementTokens = PPAllocator.Allocate<Token>(Tokens.size());
std::copy(Tokens.begin(), Tokens.end(), NewReplacementTokens);
ReplacementTokens = NewReplacementTokens;
} }
/// Return true if this macro is enabled. /// Return true if this macro is enabled.

View file

@ -28,6 +28,25 @@
using namespace clang; using namespace clang;
namespace {
// MacroInfo is expected to take 40 bytes on platforms with an 8 byte pointer
// and 4 byte SourceLocation.
template <int> class MacroInfoSizeChecker {
public:
constexpr static bool AsExpected = true;
};
template <> class MacroInfoSizeChecker<8> {
public:
constexpr static bool AsExpected =
sizeof(MacroInfo) == (32 + sizeof(SourceLocation) * 2);
};
static_assert(MacroInfoSizeChecker<sizeof(void *)>::AsExpected,
"Unexpected size of MacroInfo");
} // end namespace
MacroInfo::MacroInfo(SourceLocation DefLoc) MacroInfo::MacroInfo(SourceLocation DefLoc)
: Location(DefLoc), IsDefinitionLengthCached(false), IsFunctionLike(false), : Location(DefLoc), IsDefinitionLengthCached(false), IsFunctionLike(false),
IsC99Varargs(false), IsGNUVarargs(false), IsBuiltinMacro(false), IsC99Varargs(false), IsGNUVarargs(false), IsBuiltinMacro(false),
@ -39,6 +58,7 @@ unsigned MacroInfo::getDefinitionLengthSlow(const SourceManager &SM) const {
assert(!IsDefinitionLengthCached); assert(!IsDefinitionLengthCached);
IsDefinitionLengthCached = true; IsDefinitionLengthCached = true;
ArrayRef<Token> ReplacementTokens = tokens();
if (ReplacementTokens.empty()) if (ReplacementTokens.empty())
return (DefinitionLength = 0); return (DefinitionLength = 0);
@ -76,7 +96,7 @@ bool MacroInfo::isIdenticalTo(const MacroInfo &Other, Preprocessor &PP,
bool Lexically = !Syntactically; bool Lexically = !Syntactically;
// Check # tokens in replacement, number of args, and various flags all match. // Check # tokens in replacement, number of args, and various flags all match.
if (ReplacementTokens.size() != Other.ReplacementTokens.size() || if (getNumTokens() != Other.getNumTokens() ||
getNumParams() != Other.getNumParams() || getNumParams() != Other.getNumParams() ||
isFunctionLike() != Other.isFunctionLike() || isFunctionLike() != Other.isFunctionLike() ||
isC99Varargs() != Other.isC99Varargs() || isC99Varargs() != Other.isC99Varargs() ||
@ -92,7 +112,7 @@ bool MacroInfo::isIdenticalTo(const MacroInfo &Other, Preprocessor &PP,
} }
// Check all the tokens. // Check all the tokens.
for (unsigned i = 0, e = ReplacementTokens.size(); i != e; ++i) { for (unsigned i = 0; i != NumReplacementTokens; ++i) {
const Token &A = ReplacementTokens[i]; const Token &A = ReplacementTokens[i];
const Token &B = Other.ReplacementTokens[i]; const Token &B = Other.ReplacementTokens[i];
if (A.getKind() != B.getKind()) if (A.getKind() != B.getKind())
@ -157,7 +177,7 @@ LLVM_DUMP_METHOD void MacroInfo::dump() const {
} }
bool First = true; bool First = true;
for (const Token &Tok : ReplacementTokens) { for (const Token &Tok : tokens()) {
// Leading space is semantically meaningful in a macro definition, // Leading space is semantically meaningful in a macro definition,
// so preserve it in the dump output. // so preserve it in the dump output.
if (First || Tok.hasLeadingSpace()) if (First || Tok.hasLeadingSpace())

View file

@ -2711,12 +2711,14 @@ MacroInfo *Preprocessor::ReadOptionalMacroParameterListAndBody(
if (!Tok.is(tok::eod)) if (!Tok.is(tok::eod))
LastTok = Tok; LastTok = Tok;
SmallVector<Token, 16> Tokens;
// Read the rest of the macro body. // Read the rest of the macro body.
if (MI->isObjectLike()) { if (MI->isObjectLike()) {
// Object-like macros are very simple, just read their body. // Object-like macros are very simple, just read their body.
while (Tok.isNot(tok::eod)) { while (Tok.isNot(tok::eod)) {
LastTok = Tok; LastTok = Tok;
MI->AddTokenToBody(Tok); Tokens.push_back(Tok);
// Get the next token of the macro. // Get the next token of the macro.
LexUnexpandedToken(Tok); LexUnexpandedToken(Tok);
} }
@ -2731,7 +2733,7 @@ MacroInfo *Preprocessor::ReadOptionalMacroParameterListAndBody(
LastTok = Tok; LastTok = Tok;
if (!Tok.isOneOf(tok::hash, tok::hashat, tok::hashhash)) { if (!Tok.isOneOf(tok::hash, tok::hashat, tok::hashhash)) {
MI->AddTokenToBody(Tok); Tokens.push_back(Tok);
if (VAOCtx.isVAOptToken(Tok)) { if (VAOCtx.isVAOptToken(Tok)) {
// If we're already within a VAOPT, emit an error. // If we're already within a VAOPT, emit an error.
@ -2745,7 +2747,7 @@ MacroInfo *Preprocessor::ReadOptionalMacroParameterListAndBody(
Diag(Tok, diag::err_pp_missing_lparen_in_vaopt_use); Diag(Tok, diag::err_pp_missing_lparen_in_vaopt_use);
return nullptr; return nullptr;
} }
MI->AddTokenToBody(Tok); Tokens.push_back(Tok);
VAOCtx.sawVAOptFollowedByOpeningParens(Tok.getLocation()); VAOCtx.sawVAOptFollowedByOpeningParens(Tok.getLocation());
LexUnexpandedToken(Tok); LexUnexpandedToken(Tok);
if (Tok.is(tok::hashhash)) { if (Tok.is(tok::hashhash)) {
@ -2756,10 +2758,10 @@ MacroInfo *Preprocessor::ReadOptionalMacroParameterListAndBody(
} else if (VAOCtx.isInVAOpt()) { } else if (VAOCtx.isInVAOpt()) {
if (Tok.is(tok::r_paren)) { if (Tok.is(tok::r_paren)) {
if (VAOCtx.sawClosingParen()) { if (VAOCtx.sawClosingParen()) {
const unsigned NumTokens = MI->getNumTokens(); assert(Tokens.size() >= 3 &&
assert(NumTokens >= 3 && "Must have seen at least __VA_OPT__( " "Must have seen at least __VA_OPT__( "
"and a subsequent tok::r_paren"); "and a subsequent tok::r_paren");
if (MI->getReplacementToken(NumTokens - 2).is(tok::hashhash)) { if (Tokens[Tokens.size() - 2].is(tok::hashhash)) {
Diag(Tok, diag::err_vaopt_paste_at_end); Diag(Tok, diag::err_vaopt_paste_at_end);
return nullptr; return nullptr;
} }
@ -2778,7 +2780,7 @@ MacroInfo *Preprocessor::ReadOptionalMacroParameterListAndBody(
// things. // things.
if (getLangOpts().TraditionalCPP) { if (getLangOpts().TraditionalCPP) {
Tok.setKind(tok::unknown); Tok.setKind(tok::unknown);
MI->AddTokenToBody(Tok); Tokens.push_back(Tok);
// Get the next token of the macro. // Get the next token of the macro.
LexUnexpandedToken(Tok); LexUnexpandedToken(Tok);
@ -2794,17 +2796,16 @@ MacroInfo *Preprocessor::ReadOptionalMacroParameterListAndBody(
LexUnexpandedToken(Tok); LexUnexpandedToken(Tok);
if (Tok.is(tok::eod)) { if (Tok.is(tok::eod)) {
MI->AddTokenToBody(LastTok); Tokens.push_back(LastTok);
break; break;
} }
unsigned NumTokens = MI->getNumTokens(); if (!Tokens.empty() && Tok.getIdentifierInfo() == Ident__VA_ARGS__ &&
if (NumTokens && Tok.getIdentifierInfo() == Ident__VA_ARGS__ && Tokens[Tokens.size() - 1].is(tok::comma))
MI->getReplacementToken(NumTokens-1).is(tok::comma))
MI->setHasCommaPasting(); MI->setHasCommaPasting();
// Things look ok, add the '##' token to the macro. // Things look ok, add the '##' token to the macro.
MI->AddTokenToBody(LastTok); Tokens.push_back(LastTok);
continue; continue;
} }
@ -2823,7 +2824,7 @@ MacroInfo *Preprocessor::ReadOptionalMacroParameterListAndBody(
// confused. // confused.
if (getLangOpts().AsmPreprocessor && Tok.isNot(tok::eod)) { if (getLangOpts().AsmPreprocessor && Tok.isNot(tok::eod)) {
LastTok.setKind(tok::unknown); LastTok.setKind(tok::unknown);
MI->AddTokenToBody(LastTok); Tokens.push_back(LastTok);
continue; continue;
} else { } else {
Diag(Tok, diag::err_pp_stringize_not_parameter) Diag(Tok, diag::err_pp_stringize_not_parameter)
@ -2833,13 +2834,13 @@ MacroInfo *Preprocessor::ReadOptionalMacroParameterListAndBody(
} }
// Things look ok, add the '#' and param name tokens to the macro. // Things look ok, add the '#' and param name tokens to the macro.
MI->AddTokenToBody(LastTok); Tokens.push_back(LastTok);
// If the token following '#' is VAOPT, let the next iteration handle it // If the token following '#' is VAOPT, let the next iteration handle it
// and check it for correctness, otherwise add the token and prime the // and check it for correctness, otherwise add the token and prime the
// loop with the next one. // loop with the next one.
if (!VAOCtx.isVAOptToken(Tok)) { if (!VAOCtx.isVAOptToken(Tok)) {
MI->AddTokenToBody(Tok); Tokens.push_back(Tok);
LastTok = Tok; LastTok = Tok;
// Get the next token of the macro. // Get the next token of the macro.
@ -2855,6 +2856,8 @@ MacroInfo *Preprocessor::ReadOptionalMacroParameterListAndBody(
} }
} }
MI->setDefinitionEndLoc(LastTok.getLocation()); MI->setDefinitionEndLoc(LastTok.getLocation());
MI->setTokens(Tokens, BP);
return MI; return MI;
} }
/// HandleDefineDirective - Implements \#define. This consumes the entire macro /// HandleDefineDirective - Implements \#define. This consumes the entire macro
@ -3007,7 +3010,7 @@ void Preprocessor::HandleDefineDirective(
Tok.startToken(); Tok.startToken();
Tok.setKind(tok::kw__Static_assert); Tok.setKind(tok::kw__Static_assert);
Tok.setIdentifierInfo(getIdentifierInfo("_Static_assert")); Tok.setIdentifierInfo(getIdentifierInfo("_Static_assert"));
MI->AddTokenToBody(Tok); MI->setTokens({Tok}, BP);
(void)appendDefMacroDirective(getIdentifierInfo("static_assert"), MI); (void)appendDefMacroDirective(getIdentifierInfo("static_assert"), MI);
} }
} }

View file

@ -1694,6 +1694,7 @@ MacroInfo *ASTReader::ReadMacroRecord(ModuleFile &F, uint64_t Offset) {
RecordData Record; RecordData Record;
SmallVector<IdentifierInfo*, 16> MacroParams; SmallVector<IdentifierInfo*, 16> MacroParams;
MacroInfo *Macro = nullptr; MacroInfo *Macro = nullptr;
llvm::MutableArrayRef<Token> MacroTokens;
while (true) { while (true) {
// Advance to the next record, but if we get to the end of the block, don't // Advance to the next record, but if we get to the end of the block, don't
@ -1748,7 +1749,8 @@ MacroInfo *ASTReader::ReadMacroRecord(ModuleFile &F, uint64_t Offset) {
MI->setDefinitionEndLoc(ReadSourceLocation(F, Record, NextIndex)); MI->setDefinitionEndLoc(ReadSourceLocation(F, Record, NextIndex));
MI->setIsUsed(Record[NextIndex++]); MI->setIsUsed(Record[NextIndex++]);
MI->setUsedForHeaderGuard(Record[NextIndex++]); MI->setUsedForHeaderGuard(Record[NextIndex++]);
MacroTokens = MI->allocateTokens(Record[NextIndex++],
PP.getPreprocessorAllocator());
if (RecType == PP_MACRO_FUNCTION_LIKE) { if (RecType == PP_MACRO_FUNCTION_LIKE) {
// Decode function-like macro info. // Decode function-like macro info.
bool isC99VarArgs = Record[NextIndex++]; bool isC99VarArgs = Record[NextIndex++];
@ -1793,10 +1795,14 @@ MacroInfo *ASTReader::ReadMacroRecord(ModuleFile &F, uint64_t Offset) {
// If we see a TOKEN before a PP_MACRO_*, then the file is // If we see a TOKEN before a PP_MACRO_*, then the file is
// erroneous, just pretend we didn't see this. // erroneous, just pretend we didn't see this.
if (!Macro) break; if (!Macro) break;
if (MacroTokens.empty()) {
Error("unexpected number of macro tokens for a macro in AST file");
return Macro;
}
unsigned Idx = 0; unsigned Idx = 0;
Token Tok = ReadToken(F, Record, Idx); MacroTokens[0] = ReadToken(F, Record, Idx);
Macro->AddTokenToBody(Tok); MacroTokens = MacroTokens.drop_front();
break; break;
} }
} }

View file

@ -2431,6 +2431,7 @@ void ASTWriter::WritePreprocessor(const Preprocessor &PP, bool IsModule) {
AddSourceLocation(MI->getDefinitionEndLoc(), Record); AddSourceLocation(MI->getDefinitionEndLoc(), Record);
Record.push_back(MI->isUsed()); Record.push_back(MI->isUsed());
Record.push_back(MI->isUsedForHeaderGuard()); Record.push_back(MI->isUsedForHeaderGuard());
Record.push_back(MI->getNumTokens());
unsigned Code; unsigned Code;
if (MI->isObjectLike()) { if (MI->isObjectLike()) {
Code = PP_MACRO_OBJECT_LIKE; Code = PP_MACRO_OBJECT_LIKE;

View file

@ -9,6 +9,7 @@ add_clang_unittest(LexTests
LexerTest.cpp LexerTest.cpp
PPCallbacksTest.cpp PPCallbacksTest.cpp
PPConditionalDirectiveRecordTest.cpp PPConditionalDirectiveRecordTest.cpp
PPMemoryAllocationsTest.cpp
) )
clang_target_link_libraries(LexTests clang_target_link_libraries(LexTests

View file

@ -0,0 +1,97 @@
//===- unittests/Lex/PPMemoryAllocationsTest.cpp - ----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===--------------------------------------------------------------===//
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/HeaderSearchOptions.h"
#include "clang/Lex/ModuleLoader.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "gtest/gtest.h"
using namespace clang;
namespace {
class PPMemoryAllocationsTest : public ::testing::Test {
protected:
PPMemoryAllocationsTest()
: FileMgr(FileMgrOpts), DiagID(new DiagnosticIDs()),
Diags(DiagID, new DiagnosticOptions, new IgnoringDiagConsumer()),
SourceMgr(Diags, FileMgr), TargetOpts(new TargetOptions) {
TargetOpts->Triple = "x86_64-apple-darwin11.1.0";
Target = TargetInfo::CreateTargetInfo(Diags, TargetOpts);
}
FileSystemOptions FileMgrOpts;
FileManager FileMgr;
IntrusiveRefCntPtr<DiagnosticIDs> DiagID;
DiagnosticsEngine Diags;
SourceManager SourceMgr;
LangOptions LangOpts;
std::shared_ptr<TargetOptions> TargetOpts;
IntrusiveRefCntPtr<TargetInfo> Target;
};
TEST_F(PPMemoryAllocationsTest, PPMacroDefinesAllocations) {
std::string Source;
size_t NumMacros = 1000000;
{
llvm::raw_string_ostream SourceOS(Source);
// Create a combination of 1 or 3 token macros.
for (size_t I = 0; I < NumMacros; ++I) {
SourceOS << "#define MACRO_ID_" << I << " ";
if ((I % 2) == 0)
SourceOS << "(" << I << ")";
else
SourceOS << I;
SourceOS << "\n";
}
}
std::unique_ptr<llvm::MemoryBuffer> Buf =
llvm::MemoryBuffer::getMemBuffer(Source);
SourceMgr.setMainFileID(SourceMgr.createFileID(std::move(Buf)));
TrivialModuleLoader ModLoader;
HeaderSearch HeaderInfo(std::make_shared<HeaderSearchOptions>(), SourceMgr,
Diags, LangOpts, Target.get());
Preprocessor PP(std::make_shared<PreprocessorOptions>(), Diags, LangOpts,
SourceMgr, HeaderInfo, ModLoader,
/*IILookup =*/nullptr,
/*OwnsHeaderSearch =*/false);
PP.Initialize(*Target);
PP.EnterMainSourceFile();
while (1) {
Token tok;
PP.Lex(tok);
if (tok.is(tok::eof))
break;
}
size_t NumAllocated = PP.getPreprocessorAllocator().getBytesAllocated();
float BytesPerDefine = float(NumAllocated) / float(NumMacros);
llvm::errs() << "Num preprocessor allocations for " << NumMacros
<< " #define: " << NumAllocated << "\n";
llvm::errs() << "Bytes per #define: " << BytesPerDefine << "\n";
// On arm64-apple-macos, we get around 120 bytes per define.
// Assume a reasonable upper bound based on that number that we don't want
// to exceed when storing information about a macro #define with 1 or 3
// tokens.
EXPECT_LT(BytesPerDefine, 130.0f);
}
} // anonymous namespace

View file

@ -518,7 +518,8 @@ void ClangModulesDeclVendorImpl::ForEachMacro(
bool first_token = true; bool first_token = true;
for (clang::MacroInfo::tokens_iterator ti = macro_info->tokens_begin(), for (clang::MacroInfo::const_tokens_iterator
ti = macro_info->tokens_begin(),
te = macro_info->tokens_end(); te = macro_info->tokens_end();
ti != te; ++ti) { ti != te; ++ti) {
if (!first_token) if (!first_token)