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