[clang-doc] Switch to string internment (#190044)

This is the first step in migrating all the Info types to be POD. We
introduced a shared string saver that can be used safely across threads,
and updated the internal represntation of various data types to use
these over owned strings, like SmallString or std::string.

This also required changes to YAMLGenerator to keep the single quoted
string formatting and to update the YAML traits.

This change gives an almost 50% reduction in peak memory when building
documentation for clang, at about a 10% performance loss. Future patches
can mitigate the performance penalties, and further reduce memory use.

| Metric | Baseline | Prev | This | Culm% | Seq% |
| :--- | :--- | :--- | :--- | :--- | :--- |
| Time | 920.5s | 920.5s | 1011.0s | +9.8% | +9.8% |
| Memory | 86.0G | 86.0G | 44.9G | -47.8% | -47.8% |

| Benchmark | Baseline | Prev | This | Culm% | Seq% |
| :--- | :--- | :--- | :--- | :--- | :--- |
| BM_BitcodeReader_Scale/10 | 67.9us | 67.9us | 70.0us | +3.0% | +3.0% |
| BM_BitcodeReader_Scale/10000 | 70.5ms | 70.5ms | 21.3ms | -69.8% |
-69.8% |
| BM_BitcodeReader_Scale/4096 | 23.2ms | 23.2ms | 4.5ms | -80.7% |
-80.7% |
| BM_BitcodeReader_Scale/512 | 509.4us | 509.4us | 538.8us | +5.8% |
+5.8% |
| BM_BitcodeReader_Scale/64 | 114.8us | 114.8us | 118.0us | +2.8% |
+2.8% |
| BM_Index_Insertion/10 | 2.3us | 2.3us | 4.0us | +71.6% | +71.6% |
| BM_Index_Insertion/10000 | 3.1ms | 3.1ms | 5.0ms | +60.6% | +60.6% |
| BM_Index_Insertion/4096 | 1.3ms | 1.3ms | 2.0ms | +57.1% | +57.1% |
| BM_Index_Insertion/512 | 153.6us | 153.6us | 245.0us | +59.6% | +59.6%
|
| BM_Index_Insertion/64 | 18.1us | 18.1us | 28.9us | +60.0% | +60.0% |
| BM_JSONGenerator_Scale/10 | 36.8us | 36.8us | 36.4us | -1.3% | -1.3% |
| BM_Mapper_Scale/10000 | 104.3ms | 104.3ms | 105.4ms | +1.0% | +1.0% |
| BM_Mapper_Scale/512 | 7.6ms | 7.6ms | 7.7ms | +1.9% | +1.9% |
| BM_MergeInfos_Scale/10000 | 12.2ms | 12.2ms | 1.4ms | -88.2% | -88.2%
|
| BM_MergeInfos_Scale/2 | 1.9us | 1.9us | 1.7us | -10.3% | -10.3% |
| BM_MergeInfos_Scale/4096 | 2.8ms | 2.8ms | 495.6us | -82.2% | -82.2% |
| BM_MergeInfos_Scale/512 | 68.9us | 68.9us | 34.6us | -49.7% | -49.7% |
| BM_MergeInfos_Scale/64 | 10.3us | 10.3us | 6.0us | -41.6% | -41.6% |
| BM_MergeInfos_Scale/8 | 2.8us | 2.8us | 2.1us | -24.4% | -24.4% |
| BM_SerializeFunctionInfo | 25.5us | 25.5us | 26.8us | +4.9% | +4.9% |

note: I used an LLM to help generate the test code adjustments and the
YAML traits.
This commit is contained in:
Paul Kirth 2026-04-02 16:25:21 -07:00 committed by GitHub
parent a76750e6de
commit 2600533a66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 353 additions and 183 deletions

View File

@ -28,6 +28,12 @@ static llvm::Error decodeRecord(const Record &R,
return llvm::Error::success();
}
static llvm::Error decodeRecord(const Record &R, llvm::StringRef &Field,
llvm::StringRef Blob) {
Field = internString(Blob);
return llvm::Error::success();
}
static llvm::Error decodeRecord(const Record &R, SymbolID &Field,
llvm::StringRef Blob) {
if (R[0] != BitCodeConstants::USRHashSize)
@ -123,11 +129,10 @@ static llvm::Error decodeRecord(const Record &R, FieldId &Field,
"invalid value for FieldId");
}
static llvm::Error
decodeRecord(const Record &R,
llvm::SmallVectorImpl<llvm::SmallString<16>> &Field,
llvm::StringRef Blob) {
Field.push_back(Blob);
static llvm::Error decodeRecord(const Record &R,
llvm::SmallVectorImpl<llvm::StringRef> &Field,
llvm::StringRef Blob) {
Field.push_back(internString(Blob));
return llvm::Error::success();
}

View File

@ -837,11 +837,11 @@ static OwningVec<Index> preprocessCDCtxIndex(Index CDCtxIndex) {
Processed.reserve(CDCtxIndex.Children.size());
for (const auto *Idx : CDCtxIndex.getSortedChildren()) {
Index NewIdx = *Idx;
auto NewPath = NewIdx.getRelativeFilePath("");
SmallString<128> NewPath(NewIdx.getRelativeFilePath(""));
sys::path::native(NewPath, sys::path::Style::posix);
sys::path::append(NewPath, sys::path::Style::posix,
NewIdx.getFileBaseName() + ".md");
NewIdx.Path = NewPath;
NewIdx.Path = internString(NewPath);
Processed.push_back(NewIdx);
}
@ -968,7 +968,7 @@ Error JSONGenerator::generateDocumentation(
if (FileToInfos.contains(Path))
continue;
FileToInfos[Path].push_back(Info);
Info->DocumentationFileName = FileName;
Info->DocumentationFileName = internString(FileName);
}
if (CDCtx.Format == OutputFormatTy::md_mustache) {

View File

@ -27,6 +27,11 @@
namespace clang {
namespace doc {
ConcurrentStringPool &getGlobalStringPool() {
static ConcurrentStringPool GlobalPool;
return GlobalPool;
}
CommentKind stringToCommentKind(llvm::StringRef KindStr) {
static const llvm::StringMap<CommentKind> KindMap = {
{"FullComment", CommentKind::CK_FullComment},
@ -207,26 +212,26 @@ calculateRelativeFilePath(const InfoType &Type, const StringRef &Path,
return llvm::sys::path::relative_path(FilePath);
}
llvm::SmallString<64>
Reference::getRelativeFilePath(const StringRef &CurrentPath) const {
return calculateRelativeFilePath(RefType, Path, Name, CurrentPath);
StringRef Reference::getRelativeFilePath(const StringRef &CurrentPath) const {
return internString(
calculateRelativeFilePath(RefType, Path, Name, CurrentPath));
}
llvm::SmallString<16> Reference::getFileBaseName() const {
StringRef Reference::getFileBaseName() const {
if (RefType == InfoType::IT_namespace)
return llvm::SmallString<16>("index");
return "index";
return Name;
}
llvm::SmallString<64>
Info::getRelativeFilePath(const StringRef &CurrentPath) const {
return calculateRelativeFilePath(IT, Path, extractName(), CurrentPath);
StringRef Info::getRelativeFilePath(const StringRef &CurrentPath) const {
return internString(
calculateRelativeFilePath(IT, Path, extractName(), CurrentPath));
}
llvm::SmallString<16> Info::getFileBaseName() const {
StringRef Info::getFileBaseName() const {
if (IT == InfoType::IT_namespace)
return llvm::SmallString<16>("index");
return "index";
return extractName();
}
@ -406,7 +411,7 @@ BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path,
: RecordInfo(USR, Name, Path), Access(Access), IsVirtual(IsVirtual),
IsParent(IsParent) {}
llvm::SmallString<16> Info::extractName() const {
StringRef Info::extractName() const {
if (!Name.empty())
return Name;
@ -417,37 +422,30 @@ llvm::SmallString<16> Info::extractName() const {
// namespace, which would conflict with the hard-coded global namespace name
// below.)
if (Name == "GlobalNamespace" && Namespace.empty())
return llvm::SmallString<16>("@GlobalNamespace");
return "@GlobalNamespace";
// The case of anonymous namespaces is taken care of in serialization,
// so here we can safely assume an unnamed namespace is the global
// one.
return llvm::SmallString<16>("GlobalNamespace");
return "GlobalNamespace";
case InfoType::IT_record:
return llvm::SmallString<16>("@nonymous_record_" +
toHex(llvm::toStringRef(USR)));
return internString("@nonymous_record_" + toHex(llvm::toStringRef(USR)));
case InfoType::IT_enum:
return llvm::SmallString<16>("@nonymous_enum_" +
toHex(llvm::toStringRef(USR)));
return internString("@nonymous_enum_" + toHex(llvm::toStringRef(USR)));
case InfoType::IT_typedef:
return llvm::SmallString<16>("@nonymous_typedef_" +
toHex(llvm::toStringRef(USR)));
return internString("@nonymous_typedef_" + toHex(llvm::toStringRef(USR)));
case InfoType::IT_function:
return llvm::SmallString<16>("@nonymous_function_" +
toHex(llvm::toStringRef(USR)));
return internString("@nonymous_function_" + toHex(llvm::toStringRef(USR)));
case InfoType::IT_concept:
return llvm::SmallString<16>("@nonymous_concept_" +
toHex(llvm::toStringRef(USR)));
return internString("@nonymous_concept_" + toHex(llvm::toStringRef(USR)));
case InfoType::IT_variable:
return llvm::SmallString<16>("@nonymous_variable_" +
toHex(llvm::toStringRef(USR)));
return internString("@nonymous_variable_" + toHex(llvm::toStringRef(USR)));
case InfoType::IT_friend:
return llvm::SmallString<16>("@nonymous_friend_" +
toHex(llvm::toStringRef(USR)));
return internString("@nonymous_friend_" + toHex(llvm::toStringRef(USR)));
case InfoType::IT_default:
return llvm::SmallString<16>("@nonymous_" + toHex(llvm::toStringRef(USR)));
return internString("@nonymous_" + toHex(llvm::toStringRef(USR)));
}
llvm_unreachable("Invalid InfoType.");
return llvm::SmallString<16>("");
return "";
}
// Order is based on the Name attribute: case insensitive order

View File

@ -20,6 +20,9 @@
#include "clang/Tooling/Execution.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/Mutex.h"
#include "llvm/Support/StringSaver.h"
#include <array>
#include <memory>
#include <optional>
@ -28,6 +31,42 @@
namespace clang {
namespace doc {
class ConcurrentStringPool {
public:
StringRef intern(StringRef Name) {
if (Name.empty())
return StringRef();
llvm::sys::SmartScopedLock<true> Lock(PoolMutex);
return Saver.save(Name);
}
private:
llvm::sys::SmartMutex<true> PoolMutex;
llvm::BumpPtrAllocator Alloc;
llvm::UniqueStringSaver Saver{Alloc};
};
ConcurrentStringPool &getGlobalStringPool();
inline StringRef internString(const Twine &T) {
if (T.isTriviallyEmpty())
return StringRef();
if (T.isSingleStringRef()) {
StringRef S = T.getSingleStringRef();
if (S.empty())
return StringRef();
return getGlobalStringPool().intern(S);
}
SmallString<128> Buffer;
StringRef S = T.toStringRef(Buffer);
if (S.empty())
return StringRef();
return getGlobalStringPool().intern(S);
}
// An abstraction for owned pointers. Initially mapped to OwnedPtr,
// to be eventually transitioned to bare pointers in an arena.
template <typename T> using OwnedPtr = std::unique_ptr<T>;
@ -123,16 +162,16 @@ struct CommentInfo {
OwningPtrVec<CommentInfo>
Children; // List of child comments for this CommentInfo.
SmallString<8> Direction; // Parameter direction (for (T)ParamCommand).
SmallString<16> Name; // Name of the comment (for Verbatim and HTML).
SmallString<16> ParamName; // Parameter name (for (T)ParamCommand).
SmallString<16> CloseName; // Closing tag name (for VerbatimBlock).
SmallString<64> Text; // Text of the comment.
llvm::SmallVector<SmallString<16>, 4>
StringRef Direction; // Parameter direction (for (T)ParamCommand).
StringRef Name; // Name of the comment (for Verbatim and HTML).
StringRef ParamName; // Parameter name (for (T)ParamCommand).
StringRef CloseName; // Closing tag name (for VerbatimBlock).
StringRef Text; // Text of the comment.
llvm::SmallVector<StringRef, 4>
AttrKeys; // List of attribute keys (for HTML).
llvm::SmallVector<SmallString<16>, 4>
llvm::SmallVector<StringRef, 4>
AttrValues; // List of attribute values for each key (for HTML).
llvm::SmallVector<SmallString<16>, 4>
llvm::SmallVector<StringRef, 4>
Args; // List of arguments to commands (for InlineCommand).
CommentKind Kind = CommentKind::
CK_Unknown; // Kind of comment (FullComment, ParagraphComment,
@ -154,14 +193,17 @@ struct Reference {
// "GlobalNamespace" as the name, but an empty QualName).
Reference(SymbolID USR = SymbolID(), StringRef Name = StringRef(),
InfoType IT = InfoType::IT_default)
: USR(USR), RefType(IT), Name(Name), QualName(Name) {}
: USR(USR), RefType(IT), Name(internString(Name)),
QualName(internString(Name)) {}
Reference(SymbolID USR, StringRef Name, InfoType IT, StringRef QualName,
StringRef Path = StringRef())
: USR(USR), RefType(IT), Name(Name), QualName(QualName), Path(Path) {}
: USR(USR), RefType(IT), Name(internString(Name)),
QualName(internString(QualName)), Path(internString(Path)) {}
Reference(SymbolID USR, StringRef Name, InfoType IT, StringRef QualName,
StringRef Path, SmallString<16> DocumentationFileName)
: USR(USR), RefType(IT), Name(Name), QualName(QualName), Path(Path),
DocumentationFileName(DocumentationFileName) {}
StringRef Path, StringRef DocumentationFileName)
: USR(USR), RefType(IT), Name(internString(Name)),
QualName(internString(QualName)), Path(internString(Path)),
DocumentationFileName(internString(DocumentationFileName)) {}
bool operator==(const Reference &Other) const {
return std::tie(USR, Name, QualName, RefType) ==
@ -173,10 +215,10 @@ struct Reference {
bool operator<(const Reference &Other) const { return Name < Other.Name; }
/// Returns the path for this Reference relative to CurrentPath.
llvm::SmallString<64> getRelativeFilePath(const StringRef &CurrentPath) const;
StringRef getRelativeFilePath(const StringRef &CurrentPath) const;
/// Returns the basename that should be used for this Reference.
llvm::SmallString<16> getFileBaseName() const;
StringRef getFileBaseName() const;
SymbolID USR = SymbolID(); // Unique identifier for referenced decl
@ -187,27 +229,27 @@ struct Reference {
// Name of type (possibly unresolved). Not including namespaces or template
// parameters (so for a std::vector<int> this would be "vector"). See also
// QualName.
SmallString<16> Name;
StringRef Name;
// Full qualified name of this type, including namespaces and template
// parameter (for example this could be "std::vector<int>"). Contrast to
// Name.
SmallString<16> QualName;
StringRef QualName;
// Path of directory where the clang-doc generated file will be saved
// (possibly unresolved)
llvm::SmallString<128> Path;
SmallString<16> DocumentationFileName;
StringRef Path;
StringRef DocumentationFileName;
};
// A Context is a reference that holds a relative path from a certain Info's
// location.
struct Context : public Reference {
Context(SymbolID USR, StringRef Name, InfoType IT, StringRef QualName,
StringRef Path, SmallString<16> DocumentationFileName)
StringRef Path, StringRef DocumentationFileName)
: Reference(USR, Name, IT, QualName, Path, DocumentationFileName) {}
explicit Context(const Info &I);
SmallString<128> RelativePath;
StringRef RelativePath;
};
// Holds the children of a record or namespace.
@ -255,12 +297,13 @@ struct TypeInfo {
// name and default values in the future if needed.
struct TemplateParamInfo {
TemplateParamInfo() = default;
explicit TemplateParamInfo(StringRef Contents) : Contents(Contents) {}
explicit TemplateParamInfo(StringRef Contents)
: Contents(internString(Contents)) {}
// The literal contents of the code for that specifies this template parameter
// for this declaration. Typical values will be "class T" and
// "typename T = int".
SmallString<16> Contents;
StringRef Contents;
};
struct TemplateSpecializationInfo {
@ -277,7 +320,7 @@ struct ConstraintInfo {
: ConceptRef(USR, Name, InfoType::IT_concept) {}
Reference ConceptRef;
SmallString<16> ConstraintExpr;
StringRef ConstraintExpr;
};
// Records the template information for a struct or function that is a template
@ -296,18 +339,19 @@ struct FieldTypeInfo : public TypeInfo {
FieldTypeInfo() = default;
FieldTypeInfo(const TypeInfo &TI, StringRef Name = StringRef(),
StringRef DefaultValue = StringRef())
: TypeInfo(TI), Name(Name), DefaultValue(DefaultValue) {}
: TypeInfo(TI), Name(internString(Name)),
DefaultValue(internString(DefaultValue)) {}
bool operator==(const FieldTypeInfo &Other) const {
return std::tie(Type, Name, DefaultValue) ==
std::tie(Other.Type, Other.Name, Other.DefaultValue);
}
SmallString<16> Name; // Name associated with this info.
StringRef Name; // Name associated with this info.
// When used for function parameters, contains the string representing the
// expression of the default value, if any.
SmallString<16> DefaultValue;
StringRef DefaultValue;
};
// Info for member types.
@ -336,7 +380,7 @@ struct MemberTypeInfo : public FieldTypeInfo {
struct Location {
Location(int StartLineNumber = 0, int EndLineNumber = 0,
StringRef Filename = StringRef(), bool IsFileInRootDir = false)
: Filename(Filename), StartLineNumber(StartLineNumber),
: Filename(internString(Filename)), StartLineNumber(StartLineNumber),
EndLineNumber(EndLineNumber), IsFileInRootDir(IsFileInRootDir) {}
bool operator==(const Location &Other) const {
@ -355,7 +399,7 @@ struct Location {
std::tie(Other.StartLineNumber, Other.EndLineNumber, Other.Filename);
}
SmallString<32> Filename;
StringRef Filename;
int StartLineNumber = 0;
int EndLineNumber = 0;
bool IsFileInRootDir = false;
@ -365,7 +409,7 @@ struct Location {
struct Info {
Info(InfoType IT = InfoType::IT_default, SymbolID USR = SymbolID(),
StringRef Name = StringRef(), StringRef Path = StringRef())
: Path(Path), Name(Name), USR(USR), IT(IT) {}
: Path(internString(Path)), Name(internString(Name)), USR(USR), IT(IT) {}
Info(const Info &Other) = delete;
Info(Info &&Other) = default;
@ -376,24 +420,24 @@ struct Info {
void mergeBase(Info &&I);
bool mergeable(const Info &Other);
llvm::SmallString<16> extractName() const;
StringRef extractName() const;
/// Returns the file path for this Info relative to CurrentPath.
llvm::SmallString<64> getRelativeFilePath(const StringRef &CurrentPath) const;
StringRef getRelativeFilePath(const StringRef &CurrentPath) const;
/// Returns the basename that should be used for this Info.
llvm::SmallString<16> getFileBaseName() const;
StringRef getFileBaseName() const;
// Path of directory where the clang-doc generated file will be saved.
llvm::SmallString<128> Path;
StringRef Path;
// Unqualified name of the decl.
SmallString<16> Name;
StringRef Name;
// The name used for the file that this info is documented in.
// In the JSON generator, infos are documented in files with mangled names.
// Thus, we keep track of the physical filename for linking purposes.
SmallString<16> DocumentationFileName;
StringRef DocumentationFileName;
// List of parent namespaces for this decl.
llvm::SmallVector<Reference, 4> Namespace;
@ -450,7 +494,7 @@ struct SymbolInfo : public Info {
std::optional<Location> DefLoc; // Location where this decl is defined.
llvm::SmallVector<Location, 2> Loc; // Locations where this decl is declared.
SmallString<16> MangledName;
StringRef MangledName;
bool IsStatic = false;
};
@ -490,7 +534,7 @@ struct FunctionInfo : public SymbolInfo {
Reference Parent;
TypeInfo ReturnType;
llvm::SmallVector<FieldTypeInfo, 4> Params;
SmallString<256> Prototype;
StringRef Prototype;
// When present, this function is a template or specialization.
std::optional<TemplateInfo> Template;
@ -554,7 +598,7 @@ struct TypedefInfo : public SymbolInfo {
std::optional<TemplateInfo> Template;
// Underlying type declaration
SmallString<16> TypeDeclaration;
StringRef TypeDeclaration;
// Indicates if this is a new C++ "using"-style typedef:
// using MyVector = std::vector<int>
@ -581,23 +625,24 @@ struct EnumValueInfo {
explicit EnumValueInfo(StringRef Name = StringRef(),
StringRef Value = StringRef("0"),
StringRef ValueExpr = StringRef())
: Name(Name), Value(Value), ValueExpr(ValueExpr) {}
: Name(internString(Name)), Value(internString(Value)),
ValueExpr(internString(ValueExpr)) {}
bool operator==(const EnumValueInfo &Other) const {
return std::tie(Name, Value, ValueExpr) ==
std::tie(Other.Name, Other.Value, Other.ValueExpr);
}
SmallString<16> Name;
StringRef Name;
// The computed value of the enumeration constant. This could be the result of
// evaluating the ValueExpr, or it could be automatically generated according
// to C rules.
SmallString<16> Value;
StringRef Value;
// Stores the user-supplied initialization expression for this enumeration
// constant. This will be empty for implicit enumeration values.
SmallString<16> ValueExpr;
StringRef ValueExpr;
/// Comment description of this field.
OwningVec<CommentInfo> Description;
@ -630,7 +675,7 @@ struct ConceptInfo : public SymbolInfo {
bool IsType;
TemplateInfo Template;
SmallString<16> ConstraintExpression;
StringRef ConstraintExpression;
};
struct Index : public Reference {
@ -644,7 +689,7 @@ struct Index : public Reference {
bool operator==(const SymbolID &Other) const { return USR == Other; }
bool operator<(const Index &Other) const;
std::optional<SmallString<16>> JumpToSection;
std::optional<StringRef> JumpToSection;
llvm::StringMap<Index> Children;
OwningVec<const Index *> getSortedChildren() const;

View File

@ -27,13 +27,13 @@ namespace doc {
namespace serialize {
namespace {
static SmallString<16> exprToString(const clang::Expr *E) {
static StringRef exprToString(const clang::Expr *E) {
clang::LangOptions Opts;
clang::PrintingPolicy Policy(Opts);
SmallString<16> Result;
llvm::raw_svector_ostream OS(Result);
E->printPretty(OS, nullptr, Policy);
return Result;
return internString(Result);
}
} // namespace
@ -86,8 +86,7 @@ void Serializer::getTemplateParameters(
// Extract the full function prototype from a FunctionDecl including
// Full Decl
llvm::SmallString<256>
Serializer::getFunctionPrototype(const FunctionDecl *FuncDecl) {
StringRef Serializer::getFunctionPrototype(const FunctionDecl *FuncDecl) {
llvm::SmallString<256> Result;
llvm::raw_svector_ostream Stream(Result);
const ASTContext &Ctx = FuncDecl->getASTContext();
@ -154,10 +153,10 @@ Serializer::getFunctionPrototype(const FunctionDecl *FuncDecl) {
if (auto ExceptionSpecType = FuncDecl->getExceptionSpecType())
Stream << " " << ExceptionSpecType;
return Result; // Convert SmallString to std::string for return
return internString(Result);
}
llvm::SmallString<16> Serializer::getTypeAlias(const TypeAliasDecl *Alias) {
StringRef Serializer::getTypeAlias(const TypeAliasDecl *Alias) {
llvm::SmallString<16> Result;
llvm::raw_svector_ostream Stream(Result);
const ASTContext &Ctx = Alias->getASTContext();
@ -167,7 +166,7 @@ llvm::SmallString<16> Serializer::getTypeAlias(const TypeAliasDecl *Alias) {
QualType Q = Alias->getUnderlyingType();
Q.print(Stream, Ctx.getPrintingPolicy());
return Result;
return internString(Result);
}
// A function to extract the appropriate relative path for a given info's
@ -183,15 +182,15 @@ llvm::SmallString<16> Serializer::getTypeAlias(const TypeAliasDecl *Alias) {
//
// }
// }
llvm::SmallString<128> Serializer::getInfoRelativePath(
StringRef Serializer::getInfoRelativePath(
const llvm::SmallVectorImpl<doc::Reference> &Namespaces) {
llvm::SmallString<128> Path;
for (auto R = Namespaces.rbegin(), E = Namespaces.rend(); R != E; ++R)
llvm::sys::path::append(Path, R->Name);
return Path;
return internString(Path);
}
llvm::SmallString<128> Serializer::getInfoRelativePath(const Decl *D) {
StringRef Serializer::getInfoRelativePath(const Decl *D) {
llvm::SmallVector<Reference, 4> Namespaces;
// The third arg in populateParentNamespaces is a boolean passed by reference,
// its value is not relevant in here so it's not used anywhere besides the
@ -220,7 +219,7 @@ public:
void visitVerbatimLineComment(const VerbatimLineComment *C);
private:
std::string getCommandName(unsigned CommandID) const;
StringRef getCommandName(unsigned CommandID) const;
bool isWhitespaceOnly(StringRef S) const;
CommentInfo &CurrentCI;
@ -244,87 +243,86 @@ void ClangDocCommentVisitor::visitTextComment(const TextComment *C) {
void ClangDocCommentVisitor::visitInlineCommandComment(
const InlineCommandComment *C) {
CurrentCI.Name = getCommandName(C->getCommandID());
CurrentCI.Name = internString(getCommandName(C->getCommandID()));
for (unsigned I = 0, E = C->getNumArgs(); I != E; ++I)
CurrentCI.Args.push_back(C->getArgText(I).trim());
CurrentCI.Args.push_back(internString(C->getArgText(I).trim()));
}
void ClangDocCommentVisitor::visitHTMLStartTagComment(
const HTMLStartTagComment *C) {
CurrentCI.Name = C->getTagName();
CurrentCI.Name = internString(C->getTagName());
CurrentCI.SelfClosing = C->isSelfClosing();
for (unsigned I = 0, E = C->getNumAttrs(); I < E; ++I) {
const HTMLStartTagComment::Attribute &Attr = C->getAttr(I);
CurrentCI.AttrKeys.push_back(Attr.Name);
CurrentCI.AttrValues.push_back(Attr.Value);
CurrentCI.AttrKeys.push_back(internString(Attr.Name));
CurrentCI.AttrValues.push_back(internString(Attr.Value));
}
}
void ClangDocCommentVisitor::visitHTMLEndTagComment(
const HTMLEndTagComment *C) {
CurrentCI.Name = C->getTagName();
CurrentCI.Name = internString(C->getTagName());
CurrentCI.SelfClosing = true;
}
void ClangDocCommentVisitor::visitBlockCommandComment(
const BlockCommandComment *C) {
CurrentCI.Name = getCommandName(C->getCommandID());
CurrentCI.Name = internString(getCommandName(C->getCommandID()));
for (unsigned I = 0, E = C->getNumArgs(); I < E; ++I)
CurrentCI.Args.push_back(C->getArgText(I).trim());
CurrentCI.Args.push_back(internString(C->getArgText(I).trim()));
}
void ClangDocCommentVisitor::visitParamCommandComment(
const ParamCommandComment *C) {
CurrentCI.Direction =
ParamCommandComment::getDirectionAsString(C->getDirection());
CurrentCI.Direction = internString(
ParamCommandComment::getDirectionAsString(C->getDirection()));
CurrentCI.Explicit = C->isDirectionExplicit();
if (C->hasParamName())
CurrentCI.ParamName = C->getParamNameAsWritten();
CurrentCI.ParamName = internString(C->getParamNameAsWritten());
}
void ClangDocCommentVisitor::visitTParamCommandComment(
const TParamCommandComment *C) {
if (C->hasParamName())
CurrentCI.ParamName = C->getParamNameAsWritten();
CurrentCI.ParamName = internString(C->getParamNameAsWritten());
}
void ClangDocCommentVisitor::visitVerbatimBlockComment(
const VerbatimBlockComment *C) {
CurrentCI.Name = getCommandName(C->getCommandID());
CurrentCI.CloseName = C->getCloseName();
CurrentCI.Name = internString(getCommandName(C->getCommandID()));
CurrentCI.CloseName = internString(C->getCloseName());
}
void ClangDocCommentVisitor::visitVerbatimBlockLineComment(
const VerbatimBlockLineComment *C) {
if (!isWhitespaceOnly(C->getText()))
CurrentCI.Text = C->getText();
CurrentCI.Text = internString(C->getText());
}
void ClangDocCommentVisitor::visitVerbatimLineComment(
const VerbatimLineComment *C) {
if (!isWhitespaceOnly(C->getText()))
CurrentCI.Text = C->getText();
CurrentCI.Text = internString(C->getText());
}
bool ClangDocCommentVisitor::isWhitespaceOnly(llvm::StringRef S) const {
return llvm::all_of(S, isspace);
}
std::string ClangDocCommentVisitor::getCommandName(unsigned CommandID) const {
StringRef ClangDocCommentVisitor::getCommandName(unsigned CommandID) const {
const CommandInfo *Info = CommandTraits::getBuiltinCommandInfo(CommandID);
if (Info)
return Info->Name;
return internString(Info->Name);
// TODO: Add parsing for \file command.
return "<not a builtin command>";
}
// Serializing functions.
std::string Serializer::getSourceCode(const Decl *D, const SourceRange &R) {
return Lexer::getSourceText(CharSourceRange::getTokenRange(R),
D->getASTContext().getSourceManager(),
D->getASTContext().getLangOpts())
.str();
StringRef Serializer::getSourceCode(const Decl *D, const SourceRange &R) {
return internString(Lexer::getSourceText(
CharSourceRange::getTokenRange(R), D->getASTContext().getSourceManager(),
D->getASTContext().getLangOpts()));
}
template <typename T>
@ -590,7 +588,9 @@ void Serializer::parseParameters(FunctionInfo &I, const FunctionDecl *D) {
for (const ParmVarDecl *P : D->parameters()) {
FieldTypeInfo &FieldInfo = I.Params.emplace_back(
getTypeInfoForType(P->getOriginalType(), LO), P->getNameAsString());
FieldInfo.DefaultValue = getSourceCode(D, P->getDefaultArgRange());
if (std::optional<StringRef> DefaultValue =
getSourceCode(D, P->getDefaultArgRange()))
FieldInfo.DefaultValue = *DefaultValue;
}
}
@ -611,7 +611,7 @@ void Serializer::parseBases(RecordInfo &I, const CXXRecordDecl *D) {
} else if (const RecordDecl *P = getRecordDeclForType(B.getType()))
I.Parents.emplace_back(getUSRForDecl(P), P->getNameAsString(),
InfoType::IT_record, P->getQualifiedNameAsString(),
getInfoRelativePath(P));
internString(getInfoRelativePath(P)));
else
I.Parents.emplace_back(SymbolID(), B.getType().getAsString());
}
@ -619,7 +619,7 @@ void Serializer::parseBases(RecordInfo &I, const CXXRecordDecl *D) {
if (const RecordDecl *P = getRecordDeclForType(B.getType()))
I.VirtualParents.emplace_back(
getUSRForDecl(P), P->getNameAsString(), InfoType::IT_record,
P->getQualifiedNameAsString(), getInfoRelativePath(P));
P->getQualifiedNameAsString(), internString(getInfoRelativePath(P)));
else
I.VirtualParents.emplace_back(SymbolID(), B.getType().getAsString());
}
@ -732,9 +732,10 @@ void Serializer::populateInfo(Info &I, const T *D, const FullComment *C,
ConversionDecl && ConversionDecl->getConversionType()
.getTypePtr()
->isTemplateTypeParmType())
I.Name = "operator " + ConversionDecl->getConversionType().getAsString();
I.Name = internString("operator " +
ConversionDecl->getConversionType().getAsString());
else
I.Name = D->getNameAsString();
I.Name = internString(D->getNameAsString());
populateParentNamespaces(I.Namespace, D, IsInAnonymousNamespace);
if (C) {
I.Description.emplace_back();
@ -764,9 +765,10 @@ void Serializer::populateSymbolInfo(SymbolInfo &I, const T *D,
// different filesystems, with a 5 character buffer for file extensions.
if (MangledName.size() > 250) {
auto SymbolID = llvm::toStringRef(llvm::toHex(I.USR)).str();
I.MangledName = MangledName.substr(0, 250 - SymbolID.size()) + SymbolID;
I.MangledName =
internString(MangledName.substr(0, 250 - SymbolID.size()) + SymbolID);
} else
I.MangledName = MangledName;
I.MangledName = internString(MangledName);
delete Mangler;
}
@ -784,7 +786,7 @@ void Serializer::handleCompoundConstraints(
auto *Concept = dyn_cast<ConceptSpecializationExpr>(Constraint);
ConstraintInfo CI(getUSRForDecl(Concept->getNamedConcept()),
Concept->getNamedConcept()->getNameAsString());
CI.ConstraintExpr = exprToString(Concept);
CI.ConstraintExpr = internString(exprToString(Concept));
ConstraintInfos.push_back(CI);
}
}
@ -805,7 +807,7 @@ void Serializer::populateConstraints(TemplateInfo &I, const TemplateDecl *D) {
Constraint.ConstraintExpr)) {
ConstraintInfo CI(getUSRForDecl(ConstraintExpr->getNamedConcept()),
ConstraintExpr->getNamedConcept()->getNameAsString());
CI.ConstraintExpr = exprToString(ConstraintExpr);
CI.ConstraintExpr = internString(exprToString(ConstraintExpr));
I.Constraints.push_back(std::move(CI));
} else {
handleCompoundConstraints(Constraint.ConstraintExpr, I.Constraints);
@ -890,16 +892,16 @@ void Serializer::parseBases(RecordInfo &I, const CXXRecordDecl *D,
// Initialized without USR and name, this will be set in the following
// if-else stmt.
BaseRecordInfo BI(
{}, "", getInfoRelativePath(Base), B.isVirtual(),
{}, "", internString(getInfoRelativePath(Base)), B.isVirtual(),
getFinalAccessSpecifier(ParentAccess, B.getAccessSpecifier()),
IsParent);
if (const auto *Ty = B.getType()->getAs<TemplateSpecializationType>()) {
const TemplateDecl *D = Ty->getTemplateName().getAsTemplateDecl();
BI.USR = getUSRForDecl(D);
BI.Name = B.getType().getAsString();
BI.Name = internString(B.getType().getAsString());
} else {
BI.USR = getUSRForDecl(Base);
BI.Name = Base->getNameAsString();
BI.Name = internString(Base->getNameAsString());
}
parseFields(BI, Base, PublicOnly, BI.Access);
for (const auto &Decl : Base->decls())
@ -941,9 +943,7 @@ Serializer::emitInfo(const NamespaceDecl *D, const FullComment *FC,
if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D))
return {};
NSI->Name = D->isAnonymousNamespace()
? llvm::SmallString<16>("@nonymous_namespace")
: NSI->Name;
NSI->Name = D->isAnonymousNamespace() ? "@nonymous_namespace" : NSI->Name;
NSI->Path = getInfoRelativePath(NSI->Namespace);
if (NSI->Namespace.empty() && NSI->USR == SymbolID())
return {OwnedPtr<Info>{std::move(NSI)}, nullptr};
@ -1019,7 +1019,7 @@ Serializer::emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc,
if (const auto *C = dyn_cast<CXXRecordDecl>(D)) {
if (const TypedefNameDecl *TD = C->getTypedefNameForAnonDecl()) {
RI->Name = TD->getNameAsString();
RI->Name = internString(TD->getNameAsString());
RI->IsTypeDef = true;
}
// TODO: remove first call to parseBases, that function should be deleted
@ -1027,7 +1027,7 @@ Serializer::emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc,
parseBases(*RI, C, /*IsFileInRootDir=*/true, PublicOnly, /*IsParent=*/true);
parseFriends(*RI, C);
}
RI->Path = getInfoRelativePath(RI->Namespace);
RI->Path = internString(getInfoRelativePath(RI->Namespace));
populateTemplateParameters(RI->Template, D);
if (RI->Template)

View File

@ -90,16 +90,16 @@ private:
void getTemplateParameters(const TemplateParameterList *TemplateParams,
llvm::raw_ostream &Stream);
llvm::SmallString<256> getFunctionPrototype(const FunctionDecl *FuncDecl);
StringRef getFunctionPrototype(const FunctionDecl *FuncDecl);
llvm::SmallString<16> getTypeAlias(const TypeAliasDecl *Alias);
StringRef getTypeAlias(const TypeAliasDecl *Alias);
llvm::SmallString<128>
StringRef
getInfoRelativePath(const llvm::SmallVectorImpl<doc::Reference> &Namespaces);
llvm::SmallString<128> getInfoRelativePath(const Decl *D);
StringRef getInfoRelativePath(const Decl *D);
std::string getSourceCode(const Decl *D, const SourceRange &R);
llvm::StringRef getSourceCode(const Decl *D, const SourceRange &R);
void parseFullComment(const FullComment *C, CommentInfo &CI);

View File

@ -29,7 +29,6 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(TemplateParamInfo)
LLVM_YAML_IS_SEQUENCE_VECTOR(TypedefInfo)
LLVM_YAML_IS_SEQUENCE_VECTOR(BaseRecordInfo)
LLVM_YAML_IS_SEQUENCE_VECTOR(OwnedPtr<CommentInfo>)
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::SmallString<16>)
namespace llvm {
namespace yaml {
@ -94,20 +93,6 @@ template <> struct ScalarEnumerationTraits<clang::doc::CommentKind> {
};
// Scalars to YAML output.
template <unsigned U> struct ScalarTraits<SmallString<U>> {
static void output(const SmallString<U> &S, void *, llvm::raw_ostream &OS) {
for (const auto &C : S)
OS << C;
}
static StringRef input(StringRef Scalar, void *, SmallString<U> &Value) {
Value.assign(Scalar.begin(), Scalar.end());
return StringRef();
}
static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
};
template <> struct ScalarTraits<SymbolID> {
@ -132,6 +117,33 @@ template <> struct ScalarTraits<SymbolID> {
static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
};
/// A wrapper for StringRef to force YAML traits to single-quote the string.
struct QuotedString {
StringRef Ref;
QuotedString() = default;
QuotedString(StringRef R) : Ref(R) {}
operator StringRef() const { return Ref; }
bool operator==(const QuotedString &Other) const { return Ref == Other.Ref; }
};
template <> struct ScalarTraits<QuotedString> {
static void output(const QuotedString &S, void *, llvm::raw_ostream &OS) {
OS << S.Ref;
}
static StringRef input(StringRef Scalar, void *, QuotedString &Value) {
Value.Ref = Scalar;
return StringRef();
}
static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
};
} // end namespace yaml
} // end namespace llvm
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::yaml::QuotedString)
namespace llvm {
namespace yaml {
// Helper functions to map infos to YAML.
static void typeInfoMapping(IO &IO, TypeInfo &I) {
@ -140,14 +152,31 @@ static void typeInfoMapping(IO &IO, TypeInfo &I) {
static void fieldTypeInfoMapping(IO &IO, FieldTypeInfo &I) {
typeInfoMapping(IO, I);
IO.mapOptional("Name", I.Name, SmallString<16>());
IO.mapOptional("DefaultValue", I.DefaultValue, SmallString<16>());
QuotedString QName(I.Name);
IO.mapOptional("Name", QName, QuotedString(StringRef()));
if (!IO.outputting())
I.Name = QName.Ref;
QuotedString QDefault(I.DefaultValue);
IO.mapOptional("DefaultValue", QDefault, QuotedString(StringRef()));
if (!IO.outputting())
I.DefaultValue = QDefault.Ref;
}
static void infoMapping(IO &IO, Info &I) {
IO.mapRequired("USR", I.USR);
IO.mapOptional("Name", I.Name, SmallString<16>());
IO.mapOptional("Path", I.Path, SmallString<128>());
QuotedString QName(I.Name);
IO.mapOptional("Name", QName, QuotedString(StringRef()));
if (!IO.outputting())
I.Name = QName.Ref;
QuotedString QPath(I.Path);
IO.mapOptional("Path", QPath, QuotedString(StringRef()));
if (!IO.outputting())
I.Path = QPath.Ref;
IO.mapOptional("Namespace", I.Namespace, llvm::SmallVector<Reference, 4>());
IO.mapOptional("Description", I.Description);
}
@ -176,18 +205,71 @@ static void recordInfoMapping(IO &IO, RecordInfo &I) {
static void commentInfoMapping(IO &IO, CommentInfo &I) {
IO.mapOptional("Kind", I.Kind, CommentKind::CK_Unknown);
IO.mapOptional("Text", I.Text, SmallString<64>());
IO.mapOptional("Name", I.Name, SmallString<16>());
IO.mapOptional("Direction", I.Direction, SmallString<8>());
IO.mapOptional("ParamName", I.ParamName, SmallString<16>());
IO.mapOptional("CloseName", I.CloseName, SmallString<16>());
QuotedString QText(I.Text);
IO.mapOptional("Text", QText, QuotedString(StringRef()));
if (!IO.outputting())
I.Text = QText.Ref;
QuotedString QName(I.Name);
IO.mapOptional("Name", QName, QuotedString(StringRef()));
if (!IO.outputting())
I.Name = QName.Ref;
QuotedString QDirection(I.Direction);
IO.mapOptional("Direction", QDirection, QuotedString(StringRef()));
if (!IO.outputting())
I.Direction = QDirection.Ref;
QuotedString QParamName(I.ParamName);
IO.mapOptional("ParamName", QParamName, QuotedString(StringRef()));
if (!IO.outputting())
I.ParamName = QParamName.Ref;
QuotedString QCloseName(I.CloseName);
IO.mapOptional("CloseName", QCloseName, QuotedString(StringRef()));
if (!IO.outputting())
I.CloseName = QCloseName.Ref;
IO.mapOptional("SelfClosing", I.SelfClosing, false);
IO.mapOptional("Explicit", I.Explicit, false);
IO.mapOptional("Args", I.Args, llvm::SmallVector<SmallString<16>, 4>());
IO.mapOptional("AttrKeys", I.AttrKeys,
llvm::SmallVector<SmallString<16>, 4>());
IO.mapOptional("AttrValues", I.AttrValues,
llvm::SmallVector<SmallString<16>, 4>());
std::vector<QuotedString> QArgs;
if (IO.outputting()) {
for (auto &S : I.Args)
QArgs.push_back(QuotedString(S));
}
IO.mapOptional("Args", QArgs, std::vector<QuotedString>());
if (!IO.outputting()) {
I.Args.clear();
for (auto &Q : QArgs)
I.Args.push_back(Q.Ref);
}
std::vector<QuotedString> QAttrKeys;
if (IO.outputting()) {
for (auto &S : I.AttrKeys)
QAttrKeys.push_back(QuotedString(S));
}
IO.mapOptional("AttrKeys", QAttrKeys, std::vector<QuotedString>());
if (!IO.outputting()) {
I.AttrKeys.clear();
for (auto &Q : QAttrKeys)
I.AttrKeys.push_back(Q.Ref);
}
std::vector<QuotedString> QAttrValues;
if (IO.outputting()) {
for (auto &S : I.AttrValues)
QAttrValues.push_back(QuotedString(S));
}
IO.mapOptional("AttrValues", QAttrValues, std::vector<QuotedString>());
if (!IO.outputting()) {
I.AttrValues.clear();
for (auto &Q : QAttrValues)
I.AttrValues.push_back(Q.Ref);
}
IO.mapOptional("Children", I.Children);
}
@ -196,17 +278,34 @@ static void commentInfoMapping(IO &IO, CommentInfo &I) {
template <> struct MappingTraits<Location> {
static void mapping(IO &IO, Location &Loc) {
IO.mapOptional("LineNumber", Loc.StartLineNumber, 0);
IO.mapOptional("Filename", Loc.Filename, SmallString<32>());
QuotedString QFilename(Loc.Filename);
IO.mapOptional("Filename", QFilename, QuotedString(StringRef()));
if (!IO.outputting())
Loc.Filename = QFilename.Ref;
}
};
template <> struct MappingTraits<Reference> {
static void mapping(IO &IO, Reference &Ref) {
IO.mapOptional("Type", Ref.RefType, InfoType::IT_default);
IO.mapOptional("Name", Ref.Name, SmallString<16>());
IO.mapOptional("QualName", Ref.QualName, SmallString<16>());
QuotedString QName(Ref.Name);
IO.mapOptional("Name", QName, QuotedString(StringRef()));
if (!IO.outputting())
Ref.Name = QName.Ref;
QuotedString QQualName(Ref.QualName);
IO.mapOptional("QualName", QQualName, QuotedString(StringRef()));
if (!IO.outputting())
Ref.QualName = QQualName.Ref;
IO.mapOptional("USR", Ref.USR, SymbolID());
IO.mapOptional("Path", Ref.Path, SmallString<128>());
QuotedString QPath(Ref.Path);
IO.mapOptional("Path", QPath, QuotedString(StringRef()));
if (!IO.outputting())
Ref.Path = QPath.Ref;
}
};
@ -217,8 +316,16 @@ template <> struct MappingTraits<TypeInfo> {
template <> struct MappingTraits<FieldTypeInfo> {
static void mapping(IO &IO, FieldTypeInfo &I) {
typeInfoMapping(IO, I);
IO.mapOptional("Name", I.Name, SmallString<16>());
IO.mapOptional("DefaultValue", I.DefaultValue, SmallString<16>());
QuotedString QName(I.Name);
IO.mapOptional("Name", QName, QuotedString(StringRef()));
if (!IO.outputting())
I.Name = QName.Ref;
QuotedString QDefault(I.DefaultValue);
IO.mapOptional("DefaultValue", QDefault, QuotedString(StringRef()));
if (!IO.outputting())
I.DefaultValue = QDefault.Ref;
}
};
@ -263,9 +370,20 @@ template <> struct MappingTraits<BaseRecordInfo> {
template <> struct MappingTraits<EnumValueInfo> {
static void mapping(IO &IO, EnumValueInfo &I) {
IO.mapOptional("Name", I.Name);
IO.mapOptional("Value", I.Value);
IO.mapOptional("Expr", I.ValueExpr, SmallString<16>());
QuotedString QName(I.Name);
IO.mapOptional("Name", QName, QuotedString(StringRef()));
if (!IO.outputting())
I.Name = QName.Ref;
QuotedString QValue(I.Value);
IO.mapOptional("Value", QValue, QuotedString(StringRef()));
if (!IO.outputting())
I.Value = QValue.Ref;
QuotedString QExpr(I.ValueExpr);
IO.mapOptional("Expr", QExpr, QuotedString(StringRef()));
if (!IO.outputting())
I.ValueExpr = QExpr.Ref;
}
};
@ -303,7 +421,10 @@ template <> struct MappingTraits<FunctionInfo> {
template <> struct MappingTraits<TemplateParamInfo> {
static void mapping(IO &IO, TemplateParamInfo &I) {
IO.mapOptional("Contents", I.Contents);
QuotedString QContents(I.Contents);
IO.mapOptional("Contents", QContents, QuotedString(StringRef()));
if (!IO.outputting())
I.Contents = QContents.Ref;
}
};

View File

@ -150,7 +150,7 @@ static void BM_BitcodeReader_Scale(benchmark::State &State) {
ClangDocBitcodeWriter Writer(Stream, Diags);
for (int i = 0; i < NumRecords; ++i) {
RecordInfo RI;
RI.Name = "Record" + std::to_string(i);
RI.Name = internString("Record" + std::to_string(i));
RI.USR = {(uint8_t)(i & 0xFF)};
Writer.emitBlock(RI);
}
@ -180,7 +180,6 @@ static void BM_JSONGenerator_Scale(benchmark::State &State) {
llvm::consumeError(G.takeError());
return;
}
int NumRecords = State.range(0);
auto NI = allocatePtr<NamespaceInfo>();
NI->Name = "GlobalNamespace";
@ -218,10 +217,10 @@ static void BM_Index_Insertion(benchmark::State &State) {
Index Idx;
for (int i = 0; i < State.range(0); ++i) {
RecordInfo I;
I.Name = "Record" + std::to_string(i);
I.Name = internString("Record" + std::to_string(i));
// Vary USR to ensure unique entries
I.USR = {(uint8_t)(i & 0xFF), (uint8_t)((i >> 8) & 0xFF)};
I.Path = "path/to/record";
I.Path = internString("path/to/record");
Generator::addInfoToIndex(Idx, &I);
}
benchmark::DoNotOptimize(Idx);

View File

@ -15,7 +15,9 @@
namespace clang {
namespace doc {
TEST(GeneratorTest, emitIndex) {
class GeneratorTest : public ClangDocContextTest {};
TEST_F(GeneratorTest, emitIndex) {
Index Idx;
auto InfoA = std::make_unique<Info>();
InfoA->Name = "A";
@ -80,7 +82,7 @@ TEST(GeneratorTest, emitIndex) {
CheckIndex(ExpectedIdx, Idx);
}
TEST(GeneratorTest, sortIndex) {
TEST_F(GeneratorTest, sortIndex) {
Index Idx;
Index IndexA;
IndexA.Name = "a";