diff --git a/llvm/docs/CommandGuide/tblgen.rst b/llvm/docs/CommandGuide/tblgen.rst --- a/llvm/docs/CommandGuide/tblgen.rst +++ b/llvm/docs/CommandGuide/tblgen.rst @@ -99,6 +99,10 @@ Show the version number of the program. +.. option:: -warn-on-unused-entities + + Generate warnings on unused classes and multiclasses. + .. option:: -write-if-changed Write the output file only if it is new or has changed. diff --git a/llvm/include/llvm/TableGen/Record.h b/llvm/include/llvm/TableGen/Record.h --- a/llvm/include/llvm/TableGen/Record.h +++ b/llvm/include/llvm/TableGen/Record.h @@ -1598,6 +1598,7 @@ bool IsAnonymous; bool IsClass; + bool IsUsed = false; void checkName(); @@ -1665,6 +1666,10 @@ bool isClass() const { return IsClass; } + bool isUsed() const { return IsUsed; } + + void setUsed() { IsUsed = true; } + ArrayRef getTemplateArgs() const { return TemplateArgs; } diff --git a/llvm/lib/TableGen/Main.cpp b/llvm/lib/TableGen/Main.cpp --- a/llvm/lib/TableGen/Main.cpp +++ b/llvm/lib/TableGen/Main.cpp @@ -68,6 +68,10 @@ "no-warn-on-unused-template-args", cl::desc("Disable unused template argument warnings.")); +static cl::opt + WarnOnUnusedEntities("warn-on-unused-entities", + cl::desc("Enable unused entity warnings.")); + static int reportError(const char *ProgName, Twine Msg) { errs() << ProgName << ": " << Msg; errs().flush(); @@ -127,6 +131,9 @@ return 1; Records.stopTimer(); + if (WarnOnUnusedEntities) + Parser.CheckUnusedEntities(); + // Write output to memory. Records.startBackendTimer("Backend overall"); std::string OutString; diff --git a/llvm/lib/TableGen/TGParser.h b/llvm/lib/TableGen/TGParser.h --- a/llvm/lib/TableGen/TGParser.h +++ b/llvm/lib/TableGen/TGParser.h @@ -78,6 +78,7 @@ struct MultiClass { Record Rec; // Placeholder for template args and Name. std::vector Entries; + bool IsUsed = false; void dump() const; @@ -219,7 +220,10 @@ CurScope = CurScope->extractParent(); } + void CheckUnusedEntities() const; + private: // Semantic analysis methods. + void AddReference(Record *Rec, SMRange Loc); bool AddValue(Record *TheRec, SMLoc Loc, const RecordVal &RV); /// Set the value of a RecordVal within the given record. If `OverrideDefLoc` /// is set, the provided location overrides any existing location of the diff --git a/llvm/lib/TableGen/TGParser.cpp b/llvm/lib/TableGen/TGParser.cpp --- a/llvm/lib/TableGen/TGParser.cpp +++ b/llvm/lib/TableGen/TGParser.cpp @@ -212,6 +212,13 @@ return nullptr; } +void TGParser::AddReference(Record *Rec, SMRange Loc) { + Rec->setUsed(); + + if (TrackReferenceLocs) + Rec->appendReferenceLoc(Loc); +} + bool TGParser::AddValue(Record *CurRec, SMLoc Loc, const RecordVal &RV) { if (!CurRec) CurRec = &CurMultiClass->Rec; @@ -683,8 +690,8 @@ Lex.getCurStrVal() + "'"); else TokError(Msg); - } else if (TrackReferenceLocs) { - Result->appendReferenceLoc(Lex.getLocRange()); + } else { + AddReference(Result, Lex.getLocRange()); } Lex.Lex(); @@ -706,6 +713,8 @@ if (!Result) TokError("Couldn't find multiclass '" + Lex.getCurStrVal() + "'"); + Result->IsUsed = true; + Lex.Lex(); return Result; } @@ -1119,10 +1128,8 @@ if (Init *I = Records.getGlobal(Name->getValue())) { // Add a reference to the global if it's a record. - if (TrackReferenceLocs) { - if (auto *Def = dyn_cast(I)) - Def->getDef()->appendReferenceLoc(NameLoc); - } + if (auto *Def = dyn_cast(I)) + AddReference(Def->getDef(), NameLoc); return I; } @@ -2608,8 +2615,7 @@ } } - if (TrackReferenceLocs) - Class->appendReferenceLoc(NameLoc); + AddReference(Class, NameLoc); return VarDefInit::get(Class, Args)->Fold(); } case tgtok::l_brace: { // Value ::= '{' ValueList '}' @@ -4295,6 +4301,32 @@ return false; } +static bool isInCommonTableGenHeader(ArrayRef Loc) { + // TODO: Marking common .td files explicitly would be more reliable. + auto BufferID = SrcMgr.FindBufferContainingLoc(Loc[0]); + assert(BufferID && "Invalid location!"); + StringRef FileSpec = + SrcMgr.getBufferInfo(BufferID).Buffer->getBufferIdentifier(); + return FileSpec.contains("/include/llvm/CodeGen/") || + FileSpec.contains("/include/llvm/IR/") || + FileSpec.contains("/include/llvm/TableGen/") || + FileSpec.contains("/include/llvm/Target/"); +} + +void TGParser::CheckUnusedEntities() const { + for (const auto &I : MultiClasses) { + ArrayRef Loc = I.second->Rec.getLoc(); + if (!I.second->IsUsed && !isInCommonTableGenHeader(Loc)) + PrintWarning(Loc, "multiclass '" + I.first + "' is unused"); + } + + for (const auto &I : Records.getClasses()) { + ArrayRef Loc = I.second->getLoc(); + if (!I.second->isUsed() && !isInCommonTableGenHeader(Loc)) + PrintWarning(Loc, "class '" + I.first + "' is unused"); + } +} + #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) LLVM_DUMP_METHOD void RecordsEntry::dump() const { if (Loop) diff --git a/llvm/test/TableGen/warn-unused-class.td b/llvm/test/TableGen/warn-unused-class.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/warn-unused-class.td @@ -0,0 +1,9 @@ +// RUN: llvm-tblgen --warn-on-unused-entities %s 2>&1 | \ +// RUN: FileCheck --implicit-check-not=warning %s + +class Used {} +class Unused {} + +def : Used; + +// CHECK: warning: class 'Unused' is unused diff --git a/llvm/test/TableGen/warn-unused-multiclass.td b/llvm/test/TableGen/warn-unused-multiclass.td new file mode 100644 --- /dev/null +++ b/llvm/test/TableGen/warn-unused-multiclass.td @@ -0,0 +1,9 @@ +// RUN: llvm-tblgen --warn-on-unused-entities %s 2>&1 | \ +// RUN: FileCheck --implicit-check-not=warning %s + +multiclass Used { def foo; } +multiclass Unused { def foo; } + +defm : Used; + +// CHECK: warning: multiclass 'Unused' is unused