//===- toyc.cpp - The Toy Compiler ----------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file implements the entry point for the Toy compiler. // //===----------------------------------------------------------------------===// #include "toy/Dialect.h" #include "toy/MLIRGen.h" #include "toy/Parser.h" #include "toy/Passes.h" #include "mlir/IR/AsmState.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/MLIRContext.h" #include "mlir/IR/Verifier.h" #include "mlir/InitAllDialects.h" #include "mlir/Parser.h" #include "mlir/Pass/Pass.h" #include "mlir/Pass/PassManager.h" #include "mlir/Transforms/Passes.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/ErrorOr.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/raw_ostream.h" using namespace toy; namespace cl = llvm::cl; static cl::opt inputFilename(cl::Positional, cl::desc(""), cl::init("-"), cl::value_desc("filename")); namespace { enum InputType { Toy, MLIR }; } static cl::opt inputType( "x", cl::init(Toy), cl::desc("Decided the kind of output desired"), cl::values(clEnumValN(Toy, "toy", "load the input file as a Toy source.")), cl::values(clEnumValN(MLIR, "mlir", "load the input file as an MLIR file"))); namespace { enum Action { None, DumpAST, DumpMLIR, DumpMLIRAffine }; } static cl::opt emitAction( "emit", cl::desc("Select the kind of output desired"), cl::values(clEnumValN(DumpAST, "ast", "output the AST dump")), cl::values(clEnumValN(DumpMLIR, "mlir", "output the MLIR dump")), cl::values(clEnumValN(DumpMLIRAffine, "mlir-affine", "output the MLIR dump after affine lowering"))); static cl::opt enableOpt("opt", cl::desc("Enable optimizations")); /// Returns a Toy AST resulting from parsing the file or a nullptr on error. std::unique_ptr parseInputFile(llvm::StringRef filename) { llvm::ErrorOr> fileOrErr = llvm::MemoryBuffer::getFileOrSTDIN(filename); if (std::error_code ec = fileOrErr.getError()) { llvm::errs() << "Could not open input file: " << ec.message() << "\n"; return nullptr; } auto buffer = fileOrErr.get()->getBuffer(); LexerBuffer lexer(buffer.begin(), buffer.end(), std::string(filename)); Parser parser(lexer); return parser.parseModule(); } int loadMLIR(llvm::SourceMgr &sourceMgr, mlir::MLIRContext &context, mlir::OwningModuleRef &module) { // Handle '.toy' input to the compiler. if (inputType != InputType::MLIR && !llvm::StringRef(inputFilename).endswith(".mlir")) { auto moduleAST = parseInputFile(inputFilename); if (!moduleAST) return 6; module = mlirGen(context, *moduleAST); return !module ? 1 : 0; } // Otherwise, the input is '.mlir'. llvm::ErrorOr> fileOrErr = llvm::MemoryBuffer::getFileOrSTDIN(inputFilename); if (std::error_code EC = fileOrErr.getError()) { llvm::errs() << "Could not open input file: " << EC.message() << "\n"; return -1; } // Parse the input mlir. sourceMgr.AddNewSourceBuffer(std::move(*fileOrErr), llvm::SMLoc()); module = mlir::parseSourceFile(sourceMgr, &context); if (!module) { llvm::errs() << "Error can't load file " << inputFilename << "\n"; return 3; } return 0; } int dumpMLIR() { mlir::MLIRContext context; // Load our Dialect in this MLIR Context. context.getOrLoadDialect(); mlir::OwningModuleRef module; llvm::SourceMgr sourceMgr; mlir::SourceMgrDiagnosticHandler sourceMgrHandler(sourceMgr, &context); if (int error = loadMLIR(sourceMgr, context, module)) return error; mlir::PassManager pm(&context); // Apply any generic pass manager command line options and run the pipeline. applyPassManagerCLOptions(pm); // Check to see what granularity of MLIR we are compiling to. bool isLoweringToAffine = emitAction >= Action::DumpMLIRAffine; if (enableOpt || isLoweringToAffine) { // Inline all functions into main and then delete them. pm.addPass(mlir::createInlinerPass()); // Now that there is only one function, we can infer the shapes of each of // the operations. mlir::OpPassManager &optPM = pm.nest(); optPM.addPass(mlir::toy::createShapeInferencePass()); optPM.addPass(mlir::createCanonicalizerPass()); optPM.addPass(mlir::createCSEPass()); } if (isLoweringToAffine) { mlir::OpPassManager &optPM = pm.nest(); // Partially lower the toy dialect with a few cleanups afterwards. optPM.addPass(mlir::toy::createLowerToAffinePass()); optPM.addPass(mlir::createCanonicalizerPass()); optPM.addPass(mlir::createCSEPass()); // Add optimizations if enabled. if (enableOpt) { optPM.addPass(mlir::createLoopFusionPass()); optPM.addPass(mlir::createMemRefDataFlowOptPass()); } } if (mlir::failed(pm.run(*module))) return 4; module->dump(); return 0; } int dumpAST() { if (inputType == InputType::MLIR) { llvm::errs() << "Can't dump a Toy AST when the input is MLIR\n"; return 5; } auto moduleAST = parseInputFile(inputFilename); if (!moduleAST) return 1; dump(*moduleAST); return 0; } int main(int argc, char **argv) { // Register any command line options. mlir::registerAsmPrinterCLOptions(); mlir::registerMLIRContextCLOptions(); mlir::registerPassManagerCLOptions(); cl::ParseCommandLineOptions(argc, argv, "toy compiler\n"); switch (emitAction) { case Action::DumpAST: return dumpAST(); case Action::DumpMLIR: case Action::DumpMLIRAffine: return dumpMLIR(); default: llvm::errs() << "No action specified (parsing only?), use -emit=\n"; } return 0; }