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