1 //===- StandardInstrumentations.h ------------------------------*- C++ -*--===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 /// \file 9 /// 10 /// This header defines a class that provides bookkeeping for all standard 11 /// (i.e in-tree) pass instrumentations. 12 /// 13 //===----------------------------------------------------------------------===// 14 15 #ifndef LLVM_PASSES_STANDARDINSTRUMENTATIONS_H 16 #define LLVM_PASSES_STANDARDINSTRUMENTATIONS_H 17 18 #include "llvm/ADT/STLExtras.h" 19 #include "llvm/ADT/SmallVector.h" 20 #include "llvm/ADT/StringRef.h" 21 #include "llvm/IR/BasicBlock.h" 22 #include "llvm/IR/OptBisect.h" 23 #include "llvm/IR/PassTimingInfo.h" 24 #include "llvm/IR/ValueHandle.h" 25 #include "llvm/Support/CommandLine.h" 26 #include "llvm/Support/TimeProfiler.h" 27 #include "llvm/Transforms/IPO/SampleProfileProbe.h" 28 29 #include <string> 30 #include <utility> 31 32 namespace llvm { 33 34 class Module; 35 class Function; 36 class PassInstrumentationCallbacks; 37 38 /// Instrumentation to print IR before/after passes. 39 /// 40 /// Needs state to be able to print module after pass that invalidates IR unit 41 /// (typically Loop or SCC). 42 class PrintIRInstrumentation { 43 public: 44 ~PrintIRInstrumentation(); 45 46 void registerCallbacks(PassInstrumentationCallbacks &PIC); 47 48 private: 49 struct PassRunDescriptor { 50 const Module *M; 51 const std::string DumpIRFilename; 52 const std::string IRName; 53 const StringRef PassID; 54 PassRunDescriptorPassRunDescriptor55 PassRunDescriptor(const Module *M, std::string DumpIRFilename, 56 std::string IRName, const StringRef PassID) 57 : M{M}, DumpIRFilename{DumpIRFilename}, IRName{IRName}, PassID(PassID) { 58 } 59 }; 60 61 void printBeforePass(StringRef PassID, Any IR); 62 void printAfterPass(StringRef PassID, Any IR); 63 void printAfterPassInvalidated(StringRef PassID); 64 65 bool shouldPrintBeforePass(StringRef PassID); 66 bool shouldPrintAfterPass(StringRef PassID); 67 bool shouldPrintPassNumbers(); 68 bool shouldPrintBeforePassNumber(); 69 70 void pushPassRunDescriptor(StringRef PassID, Any IR, 71 std::string &DumpIRFilename); 72 PassRunDescriptor popPassRunDescriptor(StringRef PassID); 73 std::string fetchDumpFilename(StringRef PassId, Any IR); 74 75 PassInstrumentationCallbacks *PIC; 76 /// Stack of Pass Run descriptions, enough to print the IR unit after a given 77 /// pass. 78 SmallVector<PassRunDescriptor, 2> PassRunDescriptorStack; 79 80 /// Used for print-at-pass-number 81 unsigned CurrentPassNumber = 0; 82 }; 83 84 class OptNoneInstrumentation { 85 public: OptNoneInstrumentation(bool DebugLogging)86 OptNoneInstrumentation(bool DebugLogging) : DebugLogging(DebugLogging) {} 87 void registerCallbacks(PassInstrumentationCallbacks &PIC); 88 89 private: 90 bool DebugLogging; 91 bool shouldRun(StringRef PassID, Any IR); 92 }; 93 94 class OptPassGateInstrumentation { 95 LLVMContext &Context; 96 bool HasWrittenIR = false; 97 public: OptPassGateInstrumentation(LLVMContext & Context)98 OptPassGateInstrumentation(LLVMContext &Context) : Context(Context) {} 99 bool shouldRun(StringRef PassName, Any IR); 100 void registerCallbacks(PassInstrumentationCallbacks &PIC); 101 }; 102 103 struct PrintPassOptions { 104 /// Print adaptors and pass managers. 105 bool Verbose = false; 106 /// Don't print information for analyses. 107 bool SkipAnalyses = false; 108 /// Indent based on hierarchy. 109 bool Indent = false; 110 }; 111 112 // Debug logging for transformation and analysis passes. 113 class PrintPassInstrumentation { 114 raw_ostream &print(); 115 116 public: PrintPassInstrumentation(bool Enabled,PrintPassOptions Opts)117 PrintPassInstrumentation(bool Enabled, PrintPassOptions Opts) 118 : Enabled(Enabled), Opts(Opts) {} 119 void registerCallbacks(PassInstrumentationCallbacks &PIC); 120 121 private: 122 bool Enabled; 123 PrintPassOptions Opts; 124 int Indent = 0; 125 }; 126 127 class PreservedCFGCheckerInstrumentation { 128 public: 129 // Keeps sticky poisoned flag for the given basic block once it has been 130 // deleted or RAUWed. 131 struct BBGuard final : public CallbackVH { BBGuardfinal132 BBGuard(const BasicBlock *BB) : CallbackVH(BB) {} deletedfinal133 void deleted() override { CallbackVH::deleted(); } allUsesReplacedWithfinal134 void allUsesReplacedWith(Value *) override { CallbackVH::deleted(); } isPoisonedfinal135 bool isPoisoned() const { return !getValPtr(); } 136 }; 137 138 // CFG is a map BB -> {(Succ, Multiplicity)}, where BB is a non-leaf basic 139 // block, {(Succ, Multiplicity)} set of all pairs of the block's successors 140 // and the multiplicity of the edge (BB->Succ). As the mapped sets are 141 // unordered the order of successors is not tracked by the CFG. In other words 142 // this allows basic block successors to be swapped by a pass without 143 // reporting a CFG change. CFG can be guarded by basic block tracking pointers 144 // in the Graph (BBGuard). That is if any of the block is deleted or RAUWed 145 // then the CFG is treated poisoned and no block pointer of the Graph is used. 146 struct CFG { 147 std::optional<DenseMap<intptr_t, BBGuard>> BBGuards; 148 DenseMap<const BasicBlock *, DenseMap<const BasicBlock *, unsigned>> Graph; 149 150 CFG(const Function *F, bool TrackBBLifetime); 151 152 bool operator==(const CFG &G) const { 153 return !isPoisoned() && !G.isPoisoned() && Graph == G.Graph; 154 } 155 isPoisonedCFG156 bool isPoisoned() const { 157 return BBGuards && llvm::any_of(*BBGuards, [](const auto &BB) { 158 return BB.second.isPoisoned(); 159 }); 160 } 161 162 static void printDiff(raw_ostream &out, const CFG &Before, 163 const CFG &After); 164 bool invalidate(Function &F, const PreservedAnalyses &PA, 165 FunctionAnalysisManager::Invalidator &); 166 }; 167 168 #ifdef LLVM_ENABLE_ABI_BREAKING_CHECKS 169 SmallVector<StringRef, 8> PassStack; 170 #endif 171 172 void registerCallbacks(PassInstrumentationCallbacks &PIC, 173 ModuleAnalysisManager &MAM); 174 }; 175 176 // Base class for classes that report changes to the IR. 177 // It presents an interface for such classes and provides calls 178 // on various events as the new pass manager transforms the IR. 179 // It also provides filtering of information based on hidden options 180 // specifying which functions are interesting. 181 // Calls are made for the following events/queries: 182 // 1. The initial IR processed. 183 // 2. To get the representation of the IR (of type \p T). 184 // 3. When a pass does not change the IR. 185 // 4. When a pass changes the IR (given both before and after representations 186 // of type \p T). 187 // 5. When an IR is invalidated. 188 // 6. When a pass is run on an IR that is not interesting (based on options). 189 // 7. When a pass is ignored (pass manager or adapter pass). 190 // 8. To compare two IR representations (of type \p T). 191 template <typename IRUnitT> class ChangeReporter { 192 protected: ChangeReporter(bool RunInVerboseMode)193 ChangeReporter(bool RunInVerboseMode) : VerboseMode(RunInVerboseMode) {} 194 195 public: 196 virtual ~ChangeReporter(); 197 198 // Determine if this pass/IR is interesting and if so, save the IR 199 // otherwise it is left on the stack without data. 200 void saveIRBeforePass(Any IR, StringRef PassID, StringRef PassName); 201 // Compare the IR from before the pass after the pass. 202 void handleIRAfterPass(Any IR, StringRef PassID, StringRef PassName); 203 // Handle the situation where a pass is invalidated. 204 void handleInvalidatedPass(StringRef PassID); 205 206 protected: 207 // Register required callbacks. 208 void registerRequiredCallbacks(PassInstrumentationCallbacks &PIC); 209 210 // Called on the first IR processed. 211 virtual void handleInitialIR(Any IR) = 0; 212 // Called before and after a pass to get the representation of the IR. 213 virtual void generateIRRepresentation(Any IR, StringRef PassID, 214 IRUnitT &Output) = 0; 215 // Called when the pass is not iteresting. 216 virtual void omitAfter(StringRef PassID, std::string &Name) = 0; 217 // Called when an interesting IR has changed. 218 virtual void handleAfter(StringRef PassID, std::string &Name, 219 const IRUnitT &Before, const IRUnitT &After, 220 Any) = 0; 221 // Called when an interesting pass is invalidated. 222 virtual void handleInvalidated(StringRef PassID) = 0; 223 // Called when the IR or pass is not interesting. 224 virtual void handleFiltered(StringRef PassID, std::string &Name) = 0; 225 // Called when an ignored pass is encountered. 226 virtual void handleIgnored(StringRef PassID, std::string &Name) = 0; 227 228 // Stack of IRs before passes. 229 std::vector<IRUnitT> BeforeStack; 230 // Is this the first IR seen? 231 bool InitialIR = true; 232 233 // Run in verbose mode, printing everything? 234 const bool VerboseMode; 235 }; 236 237 // An abstract template base class that handles printing banners and 238 // reporting when things have not changed or are filtered out. 239 template <typename IRUnitT> 240 class TextChangeReporter : public ChangeReporter<IRUnitT> { 241 protected: 242 TextChangeReporter(bool Verbose); 243 244 // Print a module dump of the first IR that is changed. 245 void handleInitialIR(Any IR) override; 246 // Report that the IR was omitted because it did not change. 247 void omitAfter(StringRef PassID, std::string &Name) override; 248 // Report that the pass was invalidated. 249 void handleInvalidated(StringRef PassID) override; 250 // Report that the IR was filtered out. 251 void handleFiltered(StringRef PassID, std::string &Name) override; 252 // Report that the pass was ignored. 253 void handleIgnored(StringRef PassID, std::string &Name) override; 254 // Make substitutions in \p S suitable for reporting changes 255 // after the pass and then print it. 256 257 raw_ostream &Out; 258 }; 259 260 // A change printer based on the string representation of the IR as created 261 // by unwrapAndPrint. The string representation is stored in a std::string 262 // to preserve it as the IR changes in each pass. Note that the banner is 263 // included in this representation but it is massaged before reporting. 264 class IRChangedPrinter : public TextChangeReporter<std::string> { 265 public: IRChangedPrinter(bool VerboseMode)266 IRChangedPrinter(bool VerboseMode) 267 : TextChangeReporter<std::string>(VerboseMode) {} 268 ~IRChangedPrinter() override; 269 void registerCallbacks(PassInstrumentationCallbacks &PIC); 270 271 protected: 272 // Called before and after a pass to get the representation of the IR. 273 void generateIRRepresentation(Any IR, StringRef PassID, 274 std::string &Output) override; 275 // Called when an interesting IR has changed. 276 void handleAfter(StringRef PassID, std::string &Name, 277 const std::string &Before, const std::string &After, 278 Any) override; 279 }; 280 281 class IRChangedTester : public IRChangedPrinter { 282 public: IRChangedTester()283 IRChangedTester() : IRChangedPrinter(true) {} 284 ~IRChangedTester() override; 285 void registerCallbacks(PassInstrumentationCallbacks &PIC); 286 287 protected: 288 void handleIR(const std::string &IR, StringRef PassID); 289 290 // Check initial IR 291 void handleInitialIR(Any IR) override; 292 // Do nothing. 293 void omitAfter(StringRef PassID, std::string &Name) override; 294 // Do nothing. 295 void handleInvalidated(StringRef PassID) override; 296 // Do nothing. 297 void handleFiltered(StringRef PassID, std::string &Name) override; 298 // Do nothing. 299 void handleIgnored(StringRef PassID, std::string &Name) override; 300 301 // Call test as interesting IR has changed. 302 void handleAfter(StringRef PassID, std::string &Name, 303 const std::string &Before, const std::string &After, 304 Any) override; 305 }; 306 307 // Information that needs to be saved for a basic block in order to compare 308 // before and after the pass to determine if it was changed by a pass. 309 template <typename T> class BlockDataT { 310 public: BlockDataT(const BasicBlock & B)311 BlockDataT(const BasicBlock &B) : Label(B.getName().str()), Data(B) { 312 raw_string_ostream SS(Body); 313 B.print(SS, nullptr, true, true); 314 } 315 316 bool operator==(const BlockDataT &That) const { return Body == That.Body; } 317 bool operator!=(const BlockDataT &That) const { return Body != That.Body; } 318 319 // Return the label of the represented basic block. getLabel()320 StringRef getLabel() const { return Label; } 321 // Return the string representation of the basic block. getBody()322 StringRef getBody() const { return Body; } 323 324 // Return the associated data getData()325 const T &getData() const { return Data; } 326 327 protected: 328 std::string Label; 329 std::string Body; 330 331 // Extra data associated with a basic block 332 T Data; 333 }; 334 335 template <typename T> class OrderedChangedData { 336 public: 337 // Return the names in the order they were saved getOrder()338 std::vector<std::string> &getOrder() { return Order; } getOrder()339 const std::vector<std::string> &getOrder() const { return Order; } 340 341 // Return a map of names to saved representations getData()342 StringMap<T> &getData() { return Data; } getData()343 const StringMap<T> &getData() const { return Data; } 344 345 bool operator==(const OrderedChangedData<T> &That) const { 346 return Data == That.getData(); 347 } 348 349 // Call the lambda \p HandlePair on each corresponding pair of data from 350 // \p Before and \p After. The order is based on the order in \p After 351 // with ones that are only in \p Before interspersed based on where they 352 // occur in \p Before. This is used to present the output in an order 353 // based on how the data is ordered in LLVM. 354 static void report(const OrderedChangedData &Before, 355 const OrderedChangedData &After, 356 function_ref<void(const T *, const T *)> HandlePair); 357 358 protected: 359 std::vector<std::string> Order; 360 StringMap<T> Data; 361 }; 362 363 // Do not need extra information for patch-style change reporter. 364 class EmptyData { 365 public: EmptyData(const BasicBlock &)366 EmptyData(const BasicBlock &) {} 367 }; 368 369 // The data saved for comparing functions. 370 template <typename T> 371 class FuncDataT : public OrderedChangedData<BlockDataT<T>> { 372 public: FuncDataT(std::string S)373 FuncDataT(std::string S) : EntryBlockName(S) {} 374 375 // Return the name of the entry block getEntryBlockName()376 std::string getEntryBlockName() const { return EntryBlockName; } 377 378 protected: 379 std::string EntryBlockName; 380 }; 381 382 // The data saved for comparing IRs. 383 template <typename T> 384 class IRDataT : public OrderedChangedData<FuncDataT<T>> {}; 385 386 // Abstract template base class for a class that compares two IRs. The 387 // class is created with the 2 IRs to compare and then compare is called. 388 // The static function analyzeIR is used to build up the IR representation. 389 template <typename T> class IRComparer { 390 public: IRComparer(const IRDataT<T> & Before,const IRDataT<T> & After)391 IRComparer(const IRDataT<T> &Before, const IRDataT<T> &After) 392 : Before(Before), After(After) {} 393 394 // Compare the 2 IRs. \p handleFunctionCompare is called to handle the 395 // compare of a function. When \p InModule is set, 396 // this function is being handled as part of comparing a module. 397 void compare( 398 bool CompareModule, 399 std::function<void(bool InModule, unsigned Minor, 400 const FuncDataT<T> &Before, const FuncDataT<T> &After)> 401 CompareFunc); 402 403 // Analyze \p IR and build the IR representation in \p Data. 404 static void analyzeIR(Any IR, IRDataT<T> &Data); 405 406 protected: 407 // Generate the data for \p F into \p Data. 408 static bool generateFunctionData(IRDataT<T> &Data, const Function &F); 409 410 const IRDataT<T> &Before; 411 const IRDataT<T> &After; 412 }; 413 414 // A change printer that prints out in-line differences in the basic 415 // blocks. It uses an InlineComparer to do the comparison so it shows 416 // the differences prefixed with '-' and '+' for code that is removed 417 // and added, respectively. Changes to the IR that do not affect basic 418 // blocks are not reported as having changed the IR. The option 419 // -print-module-scope does not affect this change reporter. 420 class InLineChangePrinter : public TextChangeReporter<IRDataT<EmptyData>> { 421 public: InLineChangePrinter(bool VerboseMode,bool ColourMode)422 InLineChangePrinter(bool VerboseMode, bool ColourMode) 423 : TextChangeReporter<IRDataT<EmptyData>>(VerboseMode), 424 UseColour(ColourMode) {} 425 ~InLineChangePrinter() override; 426 void registerCallbacks(PassInstrumentationCallbacks &PIC); 427 428 protected: 429 // Create a representation of the IR. 430 void generateIRRepresentation(Any IR, StringRef PassID, 431 IRDataT<EmptyData> &Output) override; 432 433 // Called when an interesting IR has changed. 434 void handleAfter(StringRef PassID, std::string &Name, 435 const IRDataT<EmptyData> &Before, 436 const IRDataT<EmptyData> &After, Any) override; 437 438 void handleFunctionCompare(StringRef Name, StringRef Prefix, StringRef PassID, 439 StringRef Divider, bool InModule, unsigned Minor, 440 const FuncDataT<EmptyData> &Before, 441 const FuncDataT<EmptyData> &After); 442 443 bool UseColour; 444 }; 445 446 class VerifyInstrumentation { 447 bool DebugLogging; 448 449 public: VerifyInstrumentation(bool DebugLogging)450 VerifyInstrumentation(bool DebugLogging) : DebugLogging(DebugLogging) {} 451 void registerCallbacks(PassInstrumentationCallbacks &PIC); 452 }; 453 454 /// This class implements --time-trace functionality for new pass manager. 455 /// It provides the pass-instrumentation callbacks that measure the pass 456 /// execution time. They collect time tracing info by TimeProfiler. 457 class TimeProfilingPassesHandler { 458 public: 459 TimeProfilingPassesHandler(); 460 // We intend this to be unique per-compilation, thus no copies. 461 TimeProfilingPassesHandler(const TimeProfilingPassesHandler &) = delete; 462 void operator=(const TimeProfilingPassesHandler &) = delete; 463 464 void registerCallbacks(PassInstrumentationCallbacks &PIC); 465 466 private: 467 // Implementation of pass instrumentation callbacks. 468 void runBeforePass(StringRef PassID, Any IR); 469 void runAfterPass(); 470 }; 471 472 // Class that holds transitions between basic blocks. The transitions 473 // are contained in a map of values to names of basic blocks. 474 class DCData { 475 public: 476 // Fill the map with the transitions from basic block \p B. 477 DCData(const BasicBlock &B); 478 479 // Return an iterator to the names of the successor blocks. begin()480 StringMap<std::string>::const_iterator begin() const { 481 return Successors.begin(); 482 } end()483 StringMap<std::string>::const_iterator end() const { 484 return Successors.end(); 485 } 486 487 // Return the label of the basic block reached on a transition on \p S. getSuccessorLabel(StringRef S)488 StringRef getSuccessorLabel(StringRef S) const { 489 assert(Successors.count(S) == 1 && "Expected to find successor."); 490 return Successors.find(S)->getValue(); 491 } 492 493 protected: 494 // Add a transition to \p Succ on \p Label addSuccessorLabel(StringRef Succ,StringRef Label)495 void addSuccessorLabel(StringRef Succ, StringRef Label) { 496 std::pair<std::string, std::string> SS{Succ.str(), Label.str()}; 497 Successors.insert(SS); 498 } 499 500 StringMap<std::string> Successors; 501 }; 502 503 // A change reporter that builds a website with links to pdf files showing 504 // dot control flow graphs with changed instructions shown in colour. 505 class DotCfgChangeReporter : public ChangeReporter<IRDataT<DCData>> { 506 public: 507 DotCfgChangeReporter(bool Verbose); 508 ~DotCfgChangeReporter() override; 509 void registerCallbacks(PassInstrumentationCallbacks &PIC); 510 511 protected: 512 // Initialize the HTML file and output the header. 513 bool initializeHTML(); 514 515 // Called on the first IR processed. 516 void handleInitialIR(Any IR) override; 517 // Called before and after a pass to get the representation of the IR. 518 void generateIRRepresentation(Any IR, StringRef PassID, 519 IRDataT<DCData> &Output) override; 520 // Called when the pass is not iteresting. 521 void omitAfter(StringRef PassID, std::string &Name) override; 522 // Called when an interesting IR has changed. 523 void handleAfter(StringRef PassID, std::string &Name, 524 const IRDataT<DCData> &Before, const IRDataT<DCData> &After, 525 Any) override; 526 // Called when an interesting pass is invalidated. 527 void handleInvalidated(StringRef PassID) override; 528 // Called when the IR or pass is not interesting. 529 void handleFiltered(StringRef PassID, std::string &Name) override; 530 // Called when an ignored pass is encountered. 531 void handleIgnored(StringRef PassID, std::string &Name) override; 532 533 // Generate the pdf file into \p Dir / \p PDFFileName using \p DotFile as 534 // input and return the html <a> tag with \Text as the content. 535 static std::string genHTML(StringRef Text, StringRef DotFile, 536 StringRef PDFFileName); 537 538 void handleFunctionCompare(StringRef Name, StringRef Prefix, StringRef PassID, 539 StringRef Divider, bool InModule, unsigned Minor, 540 const FuncDataT<DCData> &Before, 541 const FuncDataT<DCData> &After); 542 543 unsigned N = 0; 544 std::unique_ptr<raw_fd_ostream> HTML; 545 }; 546 547 // Print IR on crash. 548 class PrintCrashIRInstrumentation { 549 public: PrintCrashIRInstrumentation()550 PrintCrashIRInstrumentation() 551 : SavedIR("*** Dump of IR Before Last Pass Unknown ***") {} 552 ~PrintCrashIRInstrumentation(); 553 void registerCallbacks(PassInstrumentationCallbacks &PIC); 554 void reportCrashIR(); 555 556 protected: 557 std::string SavedIR; 558 559 private: 560 // The crash reporter that will report on a crash. 561 static PrintCrashIRInstrumentation *CrashReporter; 562 // Crash handler registered when print-on-crash is specified. 563 static void SignalHandler(void *); 564 }; 565 566 /// This class provides an interface to register all the standard pass 567 /// instrumentations and manages their state (if any). 568 class StandardInstrumentations { 569 PrintIRInstrumentation PrintIR; 570 PrintPassInstrumentation PrintPass; 571 TimePassesHandler TimePasses; 572 TimeProfilingPassesHandler TimeProfilingPasses; 573 OptNoneInstrumentation OptNone; 574 OptPassGateInstrumentation OptPassGate; 575 PreservedCFGCheckerInstrumentation PreservedCFGChecker; 576 IRChangedPrinter PrintChangedIR; 577 PseudoProbeVerifier PseudoProbeVerification; 578 InLineChangePrinter PrintChangedDiff; 579 DotCfgChangeReporter WebsiteChangeReporter; 580 PrintCrashIRInstrumentation PrintCrashIR; 581 IRChangedTester ChangeTester; 582 VerifyInstrumentation Verify; 583 584 bool VerifyEach; 585 586 public: 587 StandardInstrumentations(LLVMContext &Context, bool DebugLogging, 588 bool VerifyEach = false, 589 PrintPassOptions PrintPassOpts = PrintPassOptions()); 590 591 // Register all the standard instrumentation callbacks. If \p FAM is nullptr 592 // then PreservedCFGChecker is not enabled. 593 void registerCallbacks(PassInstrumentationCallbacks &PIC, 594 ModuleAnalysisManager *MAM = nullptr); 595 getTimePasses()596 TimePassesHandler &getTimePasses() { return TimePasses; } 597 }; 598 599 extern template class ChangeReporter<std::string>; 600 extern template class TextChangeReporter<std::string>; 601 602 extern template class BlockDataT<EmptyData>; 603 extern template class FuncDataT<EmptyData>; 604 extern template class IRDataT<EmptyData>; 605 extern template class ChangeReporter<IRDataT<EmptyData>>; 606 extern template class TextChangeReporter<IRDataT<EmptyData>>; 607 extern template class IRComparer<EmptyData>; 608 609 } // namespace llvm 610 611 #endif 612