diff --git a/mlir/include/mlir/Dialect/SPIRV/SPIRVModule.h b/mlir/include/mlir/Dialect/SPIRV/SPIRVModule.h --- a/mlir/include/mlir/Dialect/SPIRV/SPIRVModule.h +++ b/mlir/include/mlir/Dialect/SPIRV/SPIRVModule.h @@ -10,7 +10,7 @@ #define MLIR_DIALECT_SPIRV_SPIRVMODULE_H #include "mlir/Dialect/SPIRV/SPIRVOps.h" -#include "mlir/IR/OwningOpRefBase.h" +#include "mlir/IR/OwningOpRef.h" namespace mlir { namespace spirv { @@ -18,9 +18,10 @@ /// This class acts as an owning reference to a SPIR-V module, and will /// automatically destroy the held module on destruction if the held module /// is valid. -class OwningSPIRVModuleRef : public OwningOpRefBase { +// TODO: Remove this class in favor of using OwningOpRef directly. +class OwningSPIRVModuleRef : public OwningOpRef { public: - using OwningOpRefBase::OwningOpRefBase; + using OwningOpRef::OwningOpRef; }; } // end namespace spirv diff --git a/mlir/include/mlir/IR/BuiltinOps.h b/mlir/include/mlir/IR/BuiltinOps.h --- a/mlir/include/mlir/IR/BuiltinOps.h +++ b/mlir/include/mlir/IR/BuiltinOps.h @@ -14,7 +14,7 @@ #define MLIR_IR_BUILTINOPS_H_ #include "mlir/IR/FunctionSupport.h" -#include "mlir/IR/OwningOpRefBase.h" +#include "mlir/IR/OwningOpRef.h" #include "mlir/IR/SymbolTable.h" #include "mlir/Interfaces/CallInterfaces.h" #include "llvm/Support/PointerLikeTypeTraits.h" @@ -33,9 +33,12 @@ namespace mlir { /// This class acts as an owning reference to a module, and will automatically /// destroy the held module on destruction if the held module is valid. -class OwningModuleRef : public OwningOpRefBase { +// TODO: Remove this class in favor of using OwningOpRef directly. +class OwningModuleRef : public OwningOpRef { public: - using OwningOpRefBase::OwningOpRefBase; + using OwningOpRef::OwningOpRef; + OwningModuleRef(OwningOpRef &&other) + : OwningOpRef(std::move(other)) {} }; } // end namespace mlir diff --git a/mlir/include/mlir/IR/OpDefinition.h b/mlir/include/mlir/IR/OpDefinition.h --- a/mlir/include/mlir/IR/OpDefinition.h +++ b/mlir/include/mlir/IR/OpDefinition.h @@ -808,6 +808,9 @@ } public: + /// The type of the operation used as the implicit terminator type. + using ImplicitTerminatorOpT = TerminatorOpType; + static LogicalResult verifyTrait(Operation *op) { for (unsigned i = 0, e = op->getNumRegions(); i < e; ++i) { Region ®ion = op->getRegion(i); diff --git a/mlir/include/mlir/IR/OwningOpRefBase.h b/mlir/include/mlir/IR/OwningOpRef.h rename from mlir/include/mlir/IR/OwningOpRefBase.h rename to mlir/include/mlir/IR/OwningOpRef.h --- a/mlir/include/mlir/IR/OwningOpRefBase.h +++ b/mlir/include/mlir/IR/OwningOpRef.h @@ -1,4 +1,4 @@ -//===- OwningOpRefBase.h - MLIR OwningOpRefBase -----------------*- C++ -*-===// +//===- OwningOpRef.h - MLIR OwningOpRef -------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -10,8 +10,8 @@ // //===----------------------------------------------------------------------===// -#ifndef MLIR_IR_OWNINGOPREFBASE_H -#define MLIR_IR_OWNINGOPREFBASE_H +#ifndef MLIR_IR_OWNINGOPREF_H +#define MLIR_IR_OWNINGOPREF_H #include @@ -24,20 +24,23 @@ /// instead, and this should only be used in situations where existing solutions /// are not viable. template -class OwningOpRefBase { +class OwningOpRef { public: - OwningOpRefBase(std::nullptr_t = nullptr) {} - OwningOpRefBase(OpTy op) : op(op) {} - OwningOpRefBase(OwningOpRefBase &&other) : op(other.release()) {} - ~OwningOpRefBase() { + /// The underlying operation type stored in this reference. + using OperationT = OpTy; + + OwningOpRef(std::nullptr_t = nullptr) {} + OwningOpRef(OpTy op) : op(op) {} + OwningOpRef(OwningOpRef &&other) : op(other.release()) {} + ~OwningOpRef() { if (op) - op.erase(); + op->erase(); } - // Assign from another op reference. - OwningOpRefBase &operator=(OwningOpRefBase &&other) { + /// Assign from another op reference. + OwningOpRef &operator=(OwningOpRef &&other) { if (op) - op.erase(); + op->erase(); op = other.release(); return *this; } @@ -61,4 +64,4 @@ } // end namespace mlir -#endif // MLIR_IR_OWNINGOPREFBASE_H +#endif // MLIR_IR_OWNINGOPREF_H diff --git a/mlir/include/mlir/Parser.h b/mlir/include/mlir/Parser.h --- a/mlir/include/mlir/Parser.h +++ b/mlir/include/mlir/Parser.h @@ -13,6 +13,8 @@ #ifndef MLIR_PARSER_H #define MLIR_PARSER_H +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinOps.h" #include namespace llvm { @@ -22,36 +24,195 @@ } // end namespace llvm namespace mlir { -class Attribute; -class Location; -class MLIRContext; -class OwningModuleRef; -class Type; - -/// This parses the file specified by the indicated SourceMgr and returns an -/// MLIR module if it was valid. If not, the error message is emitted through -/// the error handler registered in the context, and a null pointer is returned. -OwningModuleRef parseSourceFile(const llvm::SourceMgr &sourceMgr, - MLIRContext *context); - -/// This parses the file specified by the indicated filename and returns an -/// MLIR module if it was valid. If not, the error message is emitted through -/// the error handler registered in the context, and a null pointer is returned. -OwningModuleRef parseSourceFile(llvm::StringRef filename, MLIRContext *context); +namespace detail { +/// Given a block containing operations that have just been parsed, if the block +/// contains a single operation of `ContainerOpT` type then remove it from the +/// block and return it. If the block does not contain just that operation, +/// create a new operation instance of `ContainerOpT` and move all of the +/// operations within `parsedBlock` into the first block of the first region. +/// `ContainerOpT` is required to have a single region containing a single +/// block, and must implement the `SingleBlockImplicitTerminator` trait. +template +inline OwningOpRef constructContainerOpForParserIfNecessary( + Block *parsedBlock, MLIRContext *context, Location sourceFileLoc) { + static_assert( + ContainerOpT::template hasTrait() && + std::is_base_of:: + template Impl, + ContainerOpT>::value, + "Expected `ContainerOpT` to have a single region with a single " + "block that has an implicit terminator"); + + // Check to see if we parsed a single instance of this operation. + if (llvm::hasSingleElement(*parsedBlock)) { + if (ContainerOpT op = dyn_cast(parsedBlock->front())) { + op->remove(); + return op; + } + } + + // If not, then build a new one to contain the parsed operations. + OpBuilder builder(context); + ContainerOpT op = builder.create(sourceFileLoc); + OwningOpRef opRef(op); + assert(op->getNumRegions() == 1 && llvm::hasSingleElement(op->getRegion(0)) && + "expected generated operation to have a single region with a single " + "block"); + Block *opBlock = &op->getRegion(0).front(); + opBlock->getOperations().splice(opBlock->begin(), + parsedBlock->getOperations()); + + // After splicing, verify just this operation to ensure it can properly + // contain the operations inside of it. + if (failed(op.verify())) + return OwningOpRef(); + return std::move(opRef); +} +} // end namespace detail + +/// This parses the file specified by the indicated SourceMgr and appends parsed +/// operations to the given block. If the block is non-empty, the operations are +/// placed before the current terminator. If parsing is successful, success is +/// returned. Otherwise, an error message is emitted through the error handler +/// registered in the context, and failure is returned. If `sourceFileLoc` is +/// non-null, it is populated with a file location representing the start of the +/// source file that is being parsed. +LogicalResult parseSourceFile(const llvm::SourceMgr &sourceMgr, Block *block, + MLIRContext *context, + LocationAttr *sourceFileLoc = nullptr); + +/// This parses the file specified by the indicated filename and appends parsed +/// operations to the given block. If the block is non-empty, the operations are +/// placed before the current terminator. If parsing is successful, success is +/// returned. Otherwise, an error message is emitted through the error handler +/// registered in the context, and failure is returned. If `sourceFileLoc` is +/// non-null, it is populated with a file location representing the start of the +/// source file that is being parsed. +LogicalResult parseSourceFile(llvm::StringRef filename, Block *block, + MLIRContext *context, + LocationAttr *sourceFileLoc = nullptr); /// This parses the file specified by the indicated filename using the provided -/// SourceMgr and returns an MLIR module if it was valid. If not, the error -/// message is emitted through the error handler registered in the context, and -/// a null pointer is returned. -OwningModuleRef parseSourceFile(llvm::StringRef filename, - llvm::SourceMgr &sourceMgr, - MLIRContext *context); +/// SourceMgr and appends parsed operations to the given block. If the block is +/// non-empty, the operations are placed before the current terminator. If +/// parsing is successful, success is returned. Otherwise, an error message is +/// emitted through the error handler registered in the context, and failure is +/// returned. If `sourceFileLoc` is non-null, it is populated with a file +/// location representing the start of the source file that is being parsed. +LogicalResult parseSourceFile(llvm::StringRef filename, + llvm::SourceMgr &sourceMgr, Block *block, + MLIRContext *context, + LocationAttr *sourceFileLoc = nullptr); -/// This parses the module string to a MLIR module if it was valid. If not, the +/// This parses the IR string and appends parsed operations to the given block. +/// If the block is non-empty, the operations are placed before the current +/// terminator. If parsing is successful, success is returned. Otherwise, an /// error message is emitted through the error handler registered in the -/// context, and a null pointer is returned. -OwningModuleRef parseSourceString(llvm::StringRef moduleStr, - MLIRContext *context); +/// context, and failure is returned. If `sourceFileLoc` is non-null, it is +/// populated with a file location representing the start of the source file +/// that is being parsed. +LogicalResult parseSourceString(llvm::StringRef sourceStr, Block *block, + MLIRContext *context, + LocationAttr *sourceFileLoc = nullptr); + +/// This parses the file specified by the indicated SourceMgr. If the source IR +/// contained a single instance of `ContainerOpT`, it is returned. Otherwise, a +/// new instance of `ContainerOpT` is constructed containing all of the parsed +/// operations. If parsing was not successful, null is returned and an error +/// message is emitted through the error handler registered in the context, and +/// failure is returned. `ContainerOpT` is required to have a single region +/// containing a single block, and must implement the +/// `SingleBlockImplicitTerminator` trait. +template +inline OwningOpRef +parseSourceFile(const llvm::SourceMgr &sourceMgr, MLIRContext *context) { + LocationAttr sourceFileLoc; + Block block; + if (failed(parseSourceFile(sourceMgr, &block, context, &sourceFileLoc))) + return OwningOpRef(); + return detail::constructContainerOpForParserIfNecessary( + &block, context, sourceFileLoc); +} + +/// This parses the file specified by the indicated filename. If the source IR +/// contained a single instance of `ContainerOpT`, it is returned. Otherwise, a +/// new instance of `ContainerOpT` is constructed containing all of the parsed +/// operations. If parsing was not successful, null is returned and an error +/// message is emitted through the error handler registered in the context, and +/// failure is returned. `ContainerOpT` is required to have a single region +/// containing a single block, and must implement the +/// `SingleBlockImplicitTerminator` trait. +template +inline OwningOpRef parseSourceFile(llvm::StringRef filename, + MLIRContext *context) { + LocationAttr sourceFileLoc; + Block block; + if (failed(parseSourceFile(filename, &block, context, &sourceFileLoc))) + return OwningOpRef(); + return detail::constructContainerOpForParserIfNecessary( + &block, context, sourceFileLoc); +} + +/// This parses the file specified by the indicated filename using the provided +/// SourceMgr. If the source IR contained a single instance of `ContainerOpT`, +/// it is returned. Otherwise, a new instance of `ContainerOpT` is constructed +/// containing all of the parsed operations. If parsing was not successful, null +/// is returned and an error message is emitted through the error handler +/// registered in the context, and failure is returned. `ContainerOpT` is +/// required to have a single region containing a single block, and must +/// implement the `SingleBlockImplicitTerminator` trait. +template +inline OwningOpRef parseSourceFile(llvm::StringRef filename, + llvm::SourceMgr &sourceMgr, + MLIRContext *context) { + LocationAttr sourceFileLoc; + Block block; + if (failed(parseSourceFile(filename, sourceMgr, &block, context, + &sourceFileLoc))) + return OwningOpRef(); + return detail::constructContainerOpForParserIfNecessary( + &block, context, sourceFileLoc); +} + +/// This parses the provided string containing MLIR. If the source IR contained +/// a single instance of `ContainerOpT`, it is returned. Otherwise, a new +/// instance of `ContainerOpT` is constructed containing all of the parsed +/// operations. If parsing was not successful, null is returned and an error +/// message is emitted through the error handler registered in the context, and +/// failure is returned. `ContainerOpT` is required to have a single region +/// containing a single block, and must implement the +/// `SingleBlockImplicitTerminator` trait. +template +inline OwningOpRef parseSourceString(llvm::StringRef sourceStr, + MLIRContext *context) { + LocationAttr sourceFileLoc; + Block block; + if (failed(parseSourceString(sourceStr, &block, context, &sourceFileLoc))) + return OwningOpRef(); + return detail::constructContainerOpForParserIfNecessary( + &block, context, sourceFileLoc); +} + +/// TODO: These methods are deprecated in favor of the above template versions. +/// They should be removed when usages have been updated. +inline OwningModuleRef parseSourceFile(const llvm::SourceMgr &sourceMgr, + MLIRContext *context) { + return parseSourceFile(sourceMgr, context); +} +inline OwningModuleRef parseSourceFile(llvm::StringRef filename, + MLIRContext *context) { + return parseSourceFile(filename, context); +} +inline OwningModuleRef parseSourceFile(llvm::StringRef filename, + llvm::SourceMgr &sourceMgr, + MLIRContext *context) { + return parseSourceFile(filename, sourceMgr, context); +} +inline OwningModuleRef parseSourceString(llvm::StringRef moduleStr, + MLIRContext *context) { + return parseSourceString(moduleStr, context); +} /// This parses a single MLIR attribute to an MLIR context if it was valid. If /// not, an error message is emitted through a new SourceMgrDiagnosticHandler 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 @@ -103,8 +103,11 @@ /// operations. class OperationParser : public Parser { public: - OperationParser(ParserState &state, ModuleOp moduleOp) - : Parser(state), opBuilder(moduleOp.getBodyRegion()), moduleOp(moduleOp) { + OperationParser(ParserState &state, Operation *topLevelOp) + : Parser(state), opBuilder(topLevelOp->getRegion(0)), + topLevelOp(topLevelOp) { + // The top level operation starts a new name scope. + pushSSANameScope(/*isIsolated=*/true); } ~OperationParser(); @@ -310,8 +313,8 @@ /// The builder used when creating parsed operation instances. OpBuilder opBuilder; - /// The top level module operation. - ModuleOp moduleOp; + /// The top level operation that holds all of the parsed operations. + Operation *topLevelOp; }; } // end anonymous namespace @@ -359,7 +362,8 @@ it.first->setLoc(locAttr); } - return success(); + // Pop the top level name scope. + return popSSANameScope(); } //===----------------------------------------------------------------------===// @@ -386,7 +390,7 @@ for (auto entry : forwardRefInCurrentScope) { errors.push_back({entry.second.getPointer(), entry.first}); // Add this block to the top-level region to allow for automatic cleanup. - moduleOp->getRegion(0).push_back(entry.first); + topLevelOp->getRegion(0).push_back(entry.first); } llvm::array_pod_sort(errors.begin(), errors.end()); @@ -800,7 +804,7 @@ if (consumeIf(Token::l_paren)) { do { // Create temporary regions with the top level region as parent. - result.regions.emplace_back(new Region(moduleOp)); + result.regions.emplace_back(new Region(topLevelOp)); if (parseRegion(*result.regions.back(), /*entryArguments=*/{})) return nullptr; } while (consumeIf(Token::comma)); @@ -1920,11 +1924,12 @@ namespace { /// This parser handles entities that are only valid at the top level of the /// file. -class ModuleParser : public Parser { +class TopLevelOperationParser : public Parser { public: - explicit ModuleParser(ParserState &state) : Parser(state) {} + explicit TopLevelOperationParser(ParserState &state) : Parser(state) {} - ParseResult parseModule(ModuleOp module); + /// Parse a set of operations into the end of the given Block. + ParseResult parse(Block *topLevelBlock, Location parserLoc); private: /// Parse an attribute alias declaration. @@ -1939,7 +1944,7 @@ /// /// attribute-alias-def ::= '#' alias-name `=` attribute-value /// -ParseResult ModuleParser::parseAttributeAliasDef() { +ParseResult TopLevelOperationParser::parseAttributeAliasDef() { assert(getToken().is(Token::hash_identifier)); StringRef aliasName = getTokenSpelling().drop_front(); @@ -1971,7 +1976,7 @@ /// /// type-alias-def ::= '!' alias-name `=` 'type' type /// -ParseResult ModuleParser::parseTypeAliasDef() { +ParseResult TopLevelOperationParser::parseTypeAliasDef() { assert(getToken().is(Token::exclamation_identifier)); StringRef aliasName = getTokenSpelling().drop_front(); @@ -2001,13 +2006,11 @@ return success(); } -/// This is the top-level module parser. -ParseResult ModuleParser::parseModule(ModuleOp module) { - OperationParser opParser(getState(), module); - - // Module itself is a name scope. - opParser.pushSSANameScope(/*isIsolated=*/true); - +ParseResult TopLevelOperationParser::parse(Block *topLevelBlock, + Location parserLoc) { + // Create a top-level operation to contain the parsed state. + OwningOpRef topLevelOp(ModuleOp::create(parserLoc)); + OperationParser opParser(getState(), topLevelOp.get()); while (true) { switch (getToken().getKind()) { default: @@ -2021,25 +2024,17 @@ if (opParser.finalize()) return failure(); - // Handle the case where the top level module was explicitly defined. - auto &bodyBlocks = module.getBodyRegion().getBlocks(); - auto &operations = bodyBlocks.front().getOperations(); - assert(!operations.empty() && "expected a valid module terminator"); - - // Check that the first operation is a module, and it is the only - // non-terminator operation. - ModuleOp nested = dyn_cast(operations.front()); - if (nested && std::next(operations.begin(), 2) == operations.end()) { - // Merge the data of the nested module operation into 'module'. - module.setLoc(nested.getLoc()); - module.setAttrs(nested->getMutableAttrDict()); - bodyBlocks.splice(bodyBlocks.end(), nested.getBodyRegion().getBlocks()); - - // Erase the original module body. - bodyBlocks.pop_front(); - } + // Verify that the parsed operations are valid. + if (failed(verify(topLevelOp.get()))) + return failure(); - return opParser.popSSANameScope(); + // Splice the blocks of the parsed operation over to the provided + // top-level block. + auto &parsedOps = (*topLevelOp)->getRegion(0).front().getOperations(); + auto &destOps = topLevelBlock->getOperations(); + destOps.splice(std::prev(destOps.end()), parsedOps, parsedOps.begin(), + std::prev(parsedOps.end())); + return success(); } // If we got an error token, then the lexer already emitted an error, just @@ -2065,73 +2060,55 @@ //===----------------------------------------------------------------------===// -/// This parses the file specified by the indicated SourceMgr and returns an -/// MLIR module if it was valid. If not, it emits diagnostics and returns -/// null. -OwningModuleRef mlir::parseSourceFile(const llvm::SourceMgr &sourceMgr, - MLIRContext *context) { - auto sourceBuf = sourceMgr.getMemoryBuffer(sourceMgr.getMainFileID()); +LogicalResult mlir::parseSourceFile(const llvm::SourceMgr &sourceMgr, + Block *block, MLIRContext *context, + LocationAttr *sourceFileLoc) { + const auto *sourceBuf = sourceMgr.getMemoryBuffer(sourceMgr.getMainFileID()); - // This is the result module we are parsing into. - OwningModuleRef module(ModuleOp::create(FileLineColLoc::get( - sourceBuf->getBufferIdentifier(), /*line=*/0, /*column=*/0, context))); + Location parserLoc = FileLineColLoc::get(sourceBuf->getBufferIdentifier(), + /*line=*/0, /*column=*/0, context); + if (sourceFileLoc) + *sourceFileLoc = parserLoc; SymbolState aliasState; ParserState state(sourceMgr, context, aliasState); - if (ModuleParser(state).parseModule(*module)) - return nullptr; - - // Make sure the parse module has no other structural problems detected by - // the verifier. - if (failed(verify(*module))) - return nullptr; - - return module; + return TopLevelOperationParser(state).parse(block, parserLoc); } -/// This parses the file specified by the indicated filename and returns an -/// MLIR module if it was valid. If not, the error message is emitted through -/// the error handler registered in the context, and a null pointer is returned. -OwningModuleRef mlir::parseSourceFile(StringRef filename, - MLIRContext *context) { +LogicalResult mlir::parseSourceFile(llvm::StringRef filename, Block *block, + MLIRContext *context, + LocationAttr *sourceFileLoc) { llvm::SourceMgr sourceMgr; - return parseSourceFile(filename, sourceMgr, context); + return parseSourceFile(filename, sourceMgr, block, context, sourceFileLoc); } -/// This parses the file specified by the indicated filename using the provided -/// SourceMgr and returns an MLIR module if it was valid. If not, the error -/// message is emitted through the error handler registered in the context, and -/// a null pointer is returned. -OwningModuleRef mlir::parseSourceFile(StringRef filename, - llvm::SourceMgr &sourceMgr, - MLIRContext *context) { +LogicalResult mlir::parseSourceFile(llvm::StringRef filename, + llvm::SourceMgr &sourceMgr, Block *block, + MLIRContext *context, + LocationAttr *sourceFileLoc) { if (sourceMgr.getNumBuffers() != 0) { // TODO: Extend to support multiple buffers. - emitError(mlir::UnknownLoc::get(context), - "only main buffer parsed at the moment"); - return nullptr; + return emitError(mlir::UnknownLoc::get(context), + "only main buffer parsed at the moment"); } auto file_or_err = llvm::MemoryBuffer::getFileOrSTDIN(filename); - if (std::error_code error = file_or_err.getError()) { - emitError(mlir::UnknownLoc::get(context), - "could not open input file " + filename); - return nullptr; - } + if (std::error_code error = file_or_err.getError()) + return emitError(mlir::UnknownLoc::get(context), + "could not open input file " + filename); - // Load the MLIR module. + // Load the MLIR source file. sourceMgr.AddNewSourceBuffer(std::move(*file_or_err), llvm::SMLoc()); - return parseSourceFile(sourceMgr, context); + return parseSourceFile(sourceMgr, block, context, sourceFileLoc); } -/// This parses the program string to a MLIR module if it was valid. If not, -/// it emits diagnostics and returns null. -OwningModuleRef mlir::parseSourceString(StringRef moduleStr, - MLIRContext *context) { - auto memBuffer = MemoryBuffer::getMemBuffer(moduleStr); +LogicalResult mlir::parseSourceString(llvm::StringRef sourceStr, Block *block, + MLIRContext *context, + LocationAttr *sourceFileLoc) { + auto memBuffer = MemoryBuffer::getMemBuffer(sourceStr); if (!memBuffer) - return nullptr; + return failure(); SourceMgr sourceMgr; sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc()); - return parseSourceFile(sourceMgr, context); + return parseSourceFile(sourceMgr, block, context, sourceFileLoc); }