xref: /llvm-project/mlir/tools/mlir-pdll/mlir-pdll.cpp (revision e10d551aa482ee185a80216b2670a2947a8bdeb0)
1 //===- mlir-pdll.cpp - MLIR PDLL frontend -----------------------*- 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 
9 #include "mlir/IR/BuiltinOps.h"
10 #include "mlir/Support/FileUtilities.h"
11 #include "mlir/Support/ToolUtilities.h"
12 #include "mlir/Tools/PDLL/AST/Context.h"
13 #include "mlir/Tools/PDLL/AST/Nodes.h"
14 #include "mlir/Tools/PDLL/CodeGen/CPPGen.h"
15 #include "mlir/Tools/PDLL/CodeGen/MLIRGen.h"
16 #include "mlir/Tools/PDLL/ODS/Context.h"
17 #include "mlir/Tools/PDLL/Parser/Parser.h"
18 #include "llvm/Support/CommandLine.h"
19 #include "llvm/Support/InitLLVM.h"
20 #include "llvm/Support/SourceMgr.h"
21 #include "llvm/Support/ToolOutputFile.h"
22 #include <set>
23 
24 using namespace mlir;
25 using namespace mlir::pdll;
26 
27 //===----------------------------------------------------------------------===//
28 // main
29 //===----------------------------------------------------------------------===//
30 
31 /// The desired output type.
32 enum class OutputType {
33   AST,
34   MLIR,
35   CPP,
36 };
37 
38 static LogicalResult
39 processBuffer(raw_ostream &os, std::unique_ptr<llvm::MemoryBuffer> chunkBuffer,
40               OutputType outputType, std::vector<std::string> &includeDirs,
41               bool dumpODS, std::set<std::string> *includedFiles) {
42   llvm::SourceMgr sourceMgr;
43   sourceMgr.setIncludeDirs(includeDirs);
44   sourceMgr.AddNewSourceBuffer(std::move(chunkBuffer), SMLoc());
45 
46   // If we are dumping ODS information, also enable documentation to ensure the
47   // summary and description information is imported as well.
48   bool enableDocumentation = dumpODS;
49 
50   ods::Context odsContext;
51   ast::Context astContext(odsContext);
52   FailureOr<ast::Module *> module =
53       parsePDLLAST(astContext, sourceMgr, enableDocumentation);
54   if (failed(module))
55     return failure();
56 
57   // Add the files that were included to the set.
58   if (includedFiles) {
59     for (unsigned i = 1, e = sourceMgr.getNumBuffers(); i < e; ++i) {
60       includedFiles->insert(
61           sourceMgr.getMemoryBuffer(i + 1)->getBufferIdentifier().str());
62     }
63   }
64 
65   // Print out the ODS information if requested.
66   if (dumpODS)
67     odsContext.print(llvm::errs());
68 
69   // Generate the output.
70   if (outputType == OutputType::AST) {
71     (*module)->print(os);
72     return success();
73   }
74 
75   MLIRContext mlirContext;
76   OwningOpRef<ModuleOp> pdlModule =
77       codegenPDLLToMLIR(&mlirContext, astContext, sourceMgr, **module);
78   if (!pdlModule)
79     return failure();
80 
81   if (outputType == OutputType::MLIR) {
82     pdlModule->print(os, OpPrintingFlags().enableDebugInfo());
83     return success();
84   }
85   codegenPDLLToCPP(**module, *pdlModule, os);
86   return success();
87 }
88 
89 /// Create a dependency file for `-d` option.
90 ///
91 /// This functionality is generally only for the benefit of the build system,
92 /// and is modeled after the same option in TableGen.
93 static LogicalResult
94 createDependencyFile(StringRef outputFilename, StringRef dependencyFile,
95                      std::set<std::string> &includedFiles) {
96   if (outputFilename == "-") {
97     llvm::errs() << "error: the option -d must be used together with -o\n";
98     return failure();
99   }
100 
101   std::string errorMessage;
102   std::unique_ptr<llvm::ToolOutputFile> outputFile =
103       openOutputFile(dependencyFile, &errorMessage);
104   if (!outputFile) {
105     llvm::errs() << errorMessage << "\n";
106     return failure();
107   }
108 
109   outputFile->os() << outputFilename << ":";
110   for (const auto &includeFile : includedFiles)
111     outputFile->os() << ' ' << includeFile;
112   outputFile->os() << "\n";
113   outputFile->keep();
114   return success();
115 }
116 
117 int main(int argc, char **argv) {
118   // FIXME: This is necessary because we link in TableGen, which defines its
119   // options as static variables.. some of which overlap with our options.
120   llvm::cl::ResetCommandLineParser();
121 
122   llvm::cl::opt<std::string> inputFilename(
123       llvm::cl::Positional, llvm::cl::desc("<input file>"), llvm::cl::init("-"),
124       llvm::cl::value_desc("filename"));
125 
126   llvm::cl::opt<std::string> outputFilename(
127       "o", llvm::cl::desc("Output filename"), llvm::cl::value_desc("filename"),
128       llvm::cl::init("-"));
129 
130   llvm::cl::list<std::string> includeDirs(
131       "I", llvm::cl::desc("Directory of include files"),
132       llvm::cl::value_desc("directory"), llvm::cl::Prefix);
133 
134   llvm::cl::opt<bool> dumpODS(
135       "dump-ods",
136       llvm::cl::desc(
137           "Print out the parsed ODS information from the input file"),
138       llvm::cl::init(false));
139   llvm::cl::opt<std::string> inputSplitMarker{
140       "split-input-file", llvm::cl::ValueOptional,
141       llvm::cl::callback([&](const std::string &str) {
142         // Implicit value: use default marker if flag was used without value.
143         if (str.empty())
144           inputSplitMarker.setValue(kDefaultSplitMarker);
145       }),
146       llvm::cl::desc("Split the input file into chunks using the given or "
147                      "default marker and process each chunk independently"),
148       llvm::cl::init("")};
149   llvm::cl::opt<std::string> outputSplitMarker(
150       "output-split-marker",
151       llvm::cl::desc("Split marker to use for merging the ouput"),
152       llvm::cl::init(kDefaultSplitMarker));
153   llvm::cl::opt<enum OutputType> outputType(
154       "x", llvm::cl::init(OutputType::AST),
155       llvm::cl::desc("The type of output desired"),
156       llvm::cl::values(clEnumValN(OutputType::AST, "ast",
157                                   "generate the AST for the input file"),
158                        clEnumValN(OutputType::MLIR, "mlir",
159                                   "generate the PDL MLIR for the input file"),
160                        clEnumValN(OutputType::CPP, "cpp",
161                                   "generate a C++ source file containing the "
162                                   "patterns for the input file")));
163   llvm::cl::opt<std::string> dependencyFilename(
164       "d", llvm::cl::desc("Dependency filename"),
165       llvm::cl::value_desc("filename"), llvm::cl::init(""));
166   llvm::cl::opt<bool> writeIfChanged(
167       "write-if-changed",
168       llvm::cl::desc("Only write to the output file if it changed"));
169 
170   // `ResetCommandLineParser` at the above unregistered the "D" option
171   // of `llvm-tblgen`, which causes tblgen usage to fail due to
172   // "Unknnown command line argument '-D...`" when a macros name is
173   // present. The following is a workaround to re-register it again.
174   llvm::cl::list<std::string> macroNames(
175       "D",
176       llvm::cl::desc("Name of the macro to be defined -- ignored by mlir-pdll"),
177       llvm::cl::value_desc("macro name"), llvm::cl::Prefix);
178 
179   llvm::InitLLVM y(argc, argv);
180   llvm::cl::ParseCommandLineOptions(argc, argv, "PDLL Frontend");
181 
182   // Set up the input file.
183   std::string errorMessage;
184   std::unique_ptr<llvm::MemoryBuffer> inputFile =
185       openInputFile(inputFilename, &errorMessage);
186   if (!inputFile) {
187     llvm::errs() << errorMessage << "\n";
188     return 1;
189   }
190 
191   // If we are creating a dependency file, we'll also need to track what files
192   // get included during processing.
193   std::set<std::string> includedFilesStorage;
194   std::set<std::string> *includedFiles = nullptr;
195   if (!dependencyFilename.empty())
196     includedFiles = &includedFilesStorage;
197 
198   // The split-input-file mode is a very specific mode that slices the file
199   // up into small pieces and checks each independently.
200   std::string outputStr;
201   llvm::raw_string_ostream outputStrOS(outputStr);
202   auto processFn = [&](std::unique_ptr<llvm::MemoryBuffer> chunkBuffer,
203                        raw_ostream &os) {
204     return processBuffer(os, std::move(chunkBuffer), outputType, includeDirs,
205                          dumpODS, includedFiles);
206   };
207   if (failed(splitAndProcessBuffer(std::move(inputFile), processFn, outputStrOS,
208                                    inputSplitMarker, outputSplitMarker)))
209     return 1;
210 
211   // Write the output.
212   bool shouldWriteOutput = true;
213   if (writeIfChanged) {
214     // Only update the real output file if there are any differences. This
215     // prevents recompilation of all the files depending on it if there aren't
216     // any.
217     if (auto existingOrErr =
218             llvm::MemoryBuffer::getFile(outputFilename, /*IsText=*/true))
219       if (std::move(existingOrErr.get())->getBuffer() == outputStr)
220         shouldWriteOutput = false;
221   }
222 
223   // Populate the output file if necessary.
224   if (shouldWriteOutput) {
225     std::unique_ptr<llvm::ToolOutputFile> outputFile =
226         openOutputFile(outputFilename, &errorMessage);
227     if (!outputFile) {
228       llvm::errs() << errorMessage << "\n";
229       return 1;
230     }
231     outputFile->os() << outputStr;
232     outputFile->keep();
233   }
234 
235   // Always write the depfile, even if the main output hasn't changed. If it's
236   // missing, Ninja considers the output dirty.
237   if (!dependencyFilename.empty()) {
238     if (failed(createDependencyFile(outputFilename, dependencyFilename,
239                                     includedFilesStorage)))
240       return 1;
241   }
242 
243   return 0;
244 }
245