diff --git a/mlir/include/mlir/IR/OwningOpRef.h b/mlir/include/mlir/IR/OwningOpRef.h --- a/mlir/include/mlir/IR/OwningOpRef.h +++ b/mlir/include/mlir/IR/OwningOpRef.h @@ -29,7 +29,7 @@ /// The underlying operation type stored in this reference. using OperationT = OpTy; - OwningOpRef(std::nullptr_t = nullptr) {} + OwningOpRef(std::nullptr_t = nullptr) : op(nullptr) {} OwningOpRef(OpTy op) : op(op) {} OwningOpRef(OwningOpRef &&other) : op(other.release()) {} ~OwningOpRef() { @@ -53,7 +53,7 @@ /// Release the referenced op. OpTy release() { - OpTy released; + OpTy released(nullptr); std::swap(released, op); return released; } diff --git a/mlir/include/mlir/Parser/Parser.h b/mlir/include/mlir/Parser/Parser.h --- a/mlir/include/mlir/Parser/Parser.h +++ b/mlir/include/mlir/Parser/Parser.h @@ -117,6 +117,51 @@ const ParserConfig &config, LocationAttr *sourceFileLoc = nullptr); +/// This parses the file specified by the indicated SourceMgr. If the source IR +/// contained a single operation, it is returned. If parsing was not successful, +/// null is returned and an error message is emitted through the error handler +/// registered in the context. +/// If insertImplicitModuleOp is true, a top-level builtin.module will be +/// inserted which contains the parsed IR, unless a top-level builtin.module +/// already exists. This argument is deprecated and will be removed soon. +OwningOpRef parseSourceFile(const llvm::SourceMgr &sourceMgr, + const ParserConfig &config, + bool insertImplicitModuleOp = false); + +/// This parses the file specified by the indicated filename. If the source IR +/// contained a single operation, it is returned. If parsing was not successful, +/// null is returned and an error message is emitted through the error handler +/// registered in the context. +/// If insertImplicitModuleOp is true, a top-level builtin.module will be +/// inserted which contains the parsed IR, unless a top-level builtin.module +/// already exists. This argument is deprecated and will be removed soon. +OwningOpRef parseSourceFile(StringRef filename, + const ParserConfig &config, + bool insertImplicitModuleOp = false); + +/// This parses the file specified by the indicated filename using the provided +/// SourceMgr. If the source IR contained a single operation, it is returned. If +/// parsing was not successful, null is returned and an error message is emitted +/// through the error handler registered in the context. +/// If insertImplicitModuleOp is true, a top-level builtin.module will be +/// inserted which contains the parsed IR, unless a top-level builtin.module +/// already exists. This argument is deprecated and will be removed soon. +OwningOpRef parseSourceFile(StringRef filename, + llvm::SourceMgr &sourceMgr, + const ParserConfig &config, + bool insertImplicitModuleOp = false); + +/// This parses the provided string containing MLIR. If the source IR contained +/// a single operation, it is returned. If parsing was not successful, null is +/// returned and an error message is emitted through the error handler +/// registered in the context. +/// If insertImplicitModuleOp is true, a top-level builtin.module will be +/// inserted which contains the parsed IR, unless a top-level builtin.module +/// already exists. This argument is deprecated and will be removed soon. +OwningOpRef parseSourceString(StringRef sourceStr, + const ParserConfig &config, + bool insertImplicitModuleOp = false); + namespace detail { /// The internal implementation of the templated `parseSourceFile` methods /// below, that simply forwards to the non-templated version. diff --git a/mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h b/mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h --- a/mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h +++ b/mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h @@ -51,26 +51,25 @@ /// dialects from the global registry in the MLIRContext. This option is /// deprecated and will be removed soon. /// - emitBytecode will generate bytecode output instead of text. -LogicalResult MlirOptMain(llvm::raw_ostream &outputStream, - std::unique_ptr buffer, - const PassPipelineCLParser &passPipeline, - DialectRegistry ®istry, bool splitInputFile, - bool verifyDiagnostics, bool verifyPasses, - bool allowUnregisteredDialects, - bool preloadDialectsInContext = false, - bool emitBytecode = false); +/// - insertImplicitModuleOp will insert a top-level `builtin.module` op if one +/// does not already exist in the input. This option is deprecated and will be +/// removed soon. +LogicalResult MlirOptMain( + llvm::raw_ostream &outputStream, std::unique_ptr buffer, + const PassPipelineCLParser &passPipeline, DialectRegistry ®istry, + bool splitInputFile, bool verifyDiagnostics, bool verifyPasses, + bool allowUnregisteredDialects, bool preloadDialectsInContext = false, + bool emitBytecode = false, bool insertImplicitModuleOp = false); /// Support a callback to setup the pass manager. /// - passManagerSetupFn is the callback invoked to setup the pass manager to /// apply on the loaded IR. -LogicalResult MlirOptMain(llvm::raw_ostream &outputStream, - std::unique_ptr buffer, - PassPipelineFn passManagerSetupFn, - DialectRegistry ®istry, bool splitInputFile, - bool verifyDiagnostics, bool verifyPasses, - bool allowUnregisteredDialects, - bool preloadDialectsInContext = false, - bool emitBytecode = false); +LogicalResult MlirOptMain( + llvm::raw_ostream &outputStream, std::unique_ptr buffer, + PassPipelineFn passManagerSetupFn, DialectRegistry ®istry, + bool splitInputFile, bool verifyDiagnostics, bool verifyPasses, + bool allowUnregisteredDialects, bool preloadDialectsInContext = false, + bool emitBytecode = false, bool insertImplicitModuleOp = false); /// Implementation for tools like `mlir-opt`. /// - toolName is used for the header displayed by `--help`. diff --git a/mlir/lib/Parser/Parser.cpp b/mlir/lib/Parser/Parser.cpp --- a/mlir/lib/Parser/Parser.cpp +++ b/mlir/lib/Parser/Parser.cpp @@ -13,6 +13,7 @@ #include "mlir/Parser/Parser.h" #include "mlir/AsmParser/AsmParser.h" #include "mlir/Bytecode/BytecodeReader.h" +#include "mlir/IR/BuiltinOps.h" #include "llvm/Support/SourceMgr.h" using namespace mlir; @@ -68,3 +69,63 @@ sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc()); return parseSourceFile(sourceMgr, block, config, sourceFileLoc); } + +static OwningOpRef getTopLevelOp(Block *block, Location loc, + bool insertImplicitModuleOp) { + if (insertImplicitModuleOp) // TODO: Legacy behaviour, should be removed + return detail::constructContainerOpForParserIfNecessary( + block, loc.getContext(), loc) + .release() + .getOperation(); + + if (!llvm::hasSingleElement(*block)) + return emitError(loc) << "parsed source must contain a single " + "top-level operation, found: " + << block->getOperations().size(), + nullptr; + Operation *op = &block->front(); + op->remove(); + return op; +} + +OwningOpRef mlir::parseSourceFile(const llvm::SourceMgr &sourceMgr, + const ParserConfig &config, + bool insertImplicitModuleOp) { + Block block; + LocationAttr sourceFileLoc; + if (failed(parseSourceFile(sourceMgr, &block, config, &sourceFileLoc))) + return nullptr; + return getTopLevelOp(&block, sourceFileLoc, insertImplicitModuleOp); +} + +OwningOpRef mlir::parseSourceFile(StringRef filename, + const ParserConfig &config, + bool insertImplicitModuleOp) { + Block block; + LocationAttr sourceFileLoc; + if (failed(parseSourceFile(filename, &block, config, &sourceFileLoc))) + return nullptr; + return getTopLevelOp(&block, sourceFileLoc, insertImplicitModuleOp); +} + +OwningOpRef mlir::parseSourceFile(StringRef filename, + llvm::SourceMgr &sourceMgr, + const ParserConfig &config, + bool insertImplicitModuleOp) { + Block block; + LocationAttr sourceFileLoc; + if (failed( + parseSourceFile(filename, sourceMgr, &block, config, &sourceFileLoc))) + return nullptr; + return getTopLevelOp(&block, sourceFileLoc, insertImplicitModuleOp); +} + +OwningOpRef mlir::parseSourceString(StringRef sourceStr, + const ParserConfig &config, + bool insertImplicitModuleOp) { + Block block; + LocationAttr sourceFileLoc; + if (failed(parseSourceString(sourceStr, &block, config, &sourceFileLoc))) + return nullptr; + return getTopLevelOp(&block, sourceFileLoc, insertImplicitModuleOp); +} diff --git a/mlir/lib/Tools/mlir-opt/MlirOptMain.cpp b/mlir/lib/Tools/mlir-opt/MlirOptMain.cpp --- a/mlir/lib/Tools/mlir-opt/MlirOptMain.cpp +++ b/mlir/lib/Tools/mlir-opt/MlirOptMain.cpp @@ -49,7 +49,8 @@ bool verifyPasses, SourceMgr &sourceMgr, MLIRContext *context, PassPipelineFn passManagerSetupFn, - bool emitBytecode) { + bool emitBytecode, + bool insertImplicitModuleOp) { DefaultTimingManager tm; applyDefaultTimingManagerCLOptions(tm); TimingScope timing = tm.getRootScope(); @@ -68,15 +69,16 @@ // Parse the input file and reset the context threading state. TimingScope parserTiming = timing.nest("Parser"); - OwningOpRef module(parseSourceFile(sourceMgr, config)); + OwningOpRef op = + parseSourceFile(sourceMgr, config, insertImplicitModuleOp); context->enableMultithreading(wasThreadingEnabled); - if (!module) + if (!op) return failure(); parserTiming.stop(); // Prepare the pass manager, applying command-line and reproducer options. PassManager pm(context, OpPassManager::Nesting::Implicit, - module->getOperationName()); + op.get()->getName().getStringRef()); pm.enableVerifier(verifyPasses); applyPassManagerCLOptions(pm); pm.enableTiming(timing); @@ -86,15 +88,15 @@ return failure(); // Run the pipeline. - if (failed(pm.run(*module))) + if (failed(pm.run(*op))) return failure(); // Print the output. TimingScope outputTiming = timing.nest("Output"); if (emitBytecode) { - writeBytecodeToFile(module->getOperation(), os); + writeBytecodeToFile(*op, os); } else { - module->print(os); + op.get()->print(os); os << '\n'; } return success(); @@ -106,8 +108,9 @@ processBuffer(raw_ostream &os, std::unique_ptr ownedBuffer, bool verifyDiagnostics, bool verifyPasses, bool allowUnregisteredDialects, bool preloadDialectsInContext, - bool emitBytecode, PassPipelineFn passManagerSetupFn, - DialectRegistry ®istry, llvm::ThreadPool *threadPool) { + bool emitBytecode, bool insertImplicitModuleOp, + PassPipelineFn passManagerSetupFn, DialectRegistry ®istry, + llvm::ThreadPool *threadPool) { // Tell sourceMgr about this buffer, which is what the parser will pick up. SourceMgr sourceMgr; sourceMgr.AddNewSourceBuffer(std::move(ownedBuffer), SMLoc()); @@ -131,7 +134,8 @@ if (!verifyDiagnostics) { SourceMgrDiagnosticHandler sourceMgrHandler(sourceMgr, &context); return performActions(os, verifyDiagnostics, verifyPasses, sourceMgr, - &context, passManagerSetupFn, emitBytecode); + &context, passManagerSetupFn, emitBytecode, + insertImplicitModuleOp); } SourceMgrDiagnosticVerifierHandler sourceMgrHandler(sourceMgr, &context); @@ -140,7 +144,8 @@ // these actions succeed or fail, we only care what diagnostics they produce // and whether they match our expectations. (void)performActions(os, verifyDiagnostics, verifyPasses, sourceMgr, &context, - passManagerSetupFn, emitBytecode); + passManagerSetupFn, emitBytecode, + insertImplicitModuleOp); // Verify the diagnostic handler to make sure that each of the diagnostics // matched. @@ -154,7 +159,7 @@ bool verifyDiagnostics, bool verifyPasses, bool allowUnregisteredDialects, bool preloadDialectsInContext, - bool emitBytecode) { + bool emitBytecode, bool insertImplicitModule) { // The split-input-file mode is a very specific mode that slices the file // up into small pieces and checks each independently. // We use an explicit threadpool to avoid creating and joining/destroying @@ -171,10 +176,10 @@ auto chunkFn = [&](std::unique_ptr chunkBuffer, raw_ostream &os) { - return processBuffer(os, std::move(chunkBuffer), verifyDiagnostics, - verifyPasses, allowUnregisteredDialects, - preloadDialectsInContext, emitBytecode, - passManagerSetupFn, registry, threadPool); + return processBuffer( + os, std::move(chunkBuffer), verifyDiagnostics, verifyPasses, + allowUnregisteredDialects, preloadDialectsInContext, emitBytecode, + insertImplicitModule, passManagerSetupFn, registry, threadPool); }; return splitAndProcessBuffer(std::move(buffer), chunkFn, outputStream, splitInputFile, /*insertMarkerInOutput=*/true); @@ -187,7 +192,7 @@ bool verifyDiagnostics, bool verifyPasses, bool allowUnregisteredDialects, bool preloadDialectsInContext, - bool emitBytecode) { + bool emitBytecode, bool insertImplicitModule) { auto passManagerSetupFn = [&](PassManager &pm) { auto errorHandler = [&](const Twine &msg) { emitError(UnknownLoc::get(pm.getContext())) << msg; @@ -198,7 +203,7 @@ return MlirOptMain(outputStream, std::move(buffer), passManagerSetupFn, registry, splitInputFile, verifyDiagnostics, verifyPasses, allowUnregisteredDialects, preloadDialectsInContext, - emitBytecode); + emitBytecode, insertImplicitModule); } LogicalResult mlir::MlirOptMain(int argc, char **argv, llvm::StringRef toolName, @@ -240,6 +245,12 @@ "emit-bytecode", cl::desc("Emit bytecode when generating output"), cl::init(false)); + // TODO: default this option to 'false' + static llvm::cl::opt insertImplicitModuleOp{ + "insert-implicit-module-op", + llvm::cl::desc("Insert an implicit 'builtin.module' op when parsing"), + llvm::cl::init(true)}; + InitLLVM y(argc, argv); // Register any command line options. @@ -285,7 +296,7 @@ if (failed(MlirOptMain(output->os(), std::move(file), passPipeline, registry, splitInputFile, verifyDiagnostics, verifyPasses, allowUnregisteredDialects, preloadDialectsInContext, - emitBytecode))) + emitBytecode, insertImplicitModuleOp))) return failure(); // Keep the output file if the invocation of MlirOptMain was successful. diff --git a/mlir/test/IR/top-level.mlir b/mlir/test/IR/top-level.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/IR/top-level.mlir @@ -0,0 +1,15 @@ +// RUN: mlir-opt --insert-implicit-module-op=false --verify-diagnostics --split-input-file %s | FileCheck %s + +// CHECK-NOT: module +// CHECK: func.func +func.func private @foo() + +// ----- + +// expected-error@-3 {{parsed source must contain a single top-level operation, found: 2}} +func.func private @bar() +func.func private @baz() + +// ----- + +// expected-error@-3 {{parsed source must contain a single top-level operation, found: 0}}