| //===- Parser.cpp - MLIR Parser Implementation ----------------------------===// | |
| // | |
| // 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 parser for the MLIR textual form. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "Parser.h" | |
| #include "AsmParserImpl.h" | |
| #include "mlir/AsmParser/AsmParser.h" | |
| #include "mlir/AsmParser/AsmParserState.h" | |
| #include "mlir/AsmParser/CodeComplete.h" | |
| #include "mlir/IR/AffineExpr.h" | |
| #include "mlir/IR/AffineMap.h" | |
| #include "mlir/IR/AsmState.h" | |
| #include "mlir/IR/Attributes.h" | |
| #include "mlir/IR/BuiltinAttributes.h" | |
| #include "mlir/IR/BuiltinOps.h" | |
| #include "mlir/IR/BuiltinTypes.h" | |
| #include "mlir/IR/Diagnostics.h" | |
| #include "mlir/IR/Dialect.h" | |
| #include "mlir/IR/Location.h" | |
| #include "mlir/IR/OpDefinition.h" | |
| #include "mlir/IR/OpImplementation.h" | |
| #include "mlir/IR/OperationSupport.h" | |
| #include "mlir/IR/OwningOpRef.h" | |
| #include "mlir/IR/Region.h" | |
| #include "mlir/IR/Value.h" | |
| #include "mlir/IR/Verifier.h" | |
| #include "mlir/IR/Visitors.h" | |
| #include "mlir/Support/LLVM.h" | |
| #include "mlir/Support/TypeID.h" | |
| #include "llvm/ADT/APFloat.h" | |
| #include "llvm/ADT/DenseMap.h" | |
| #include "llvm/ADT/PointerUnion.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/ScopeExit.h" | |
| #include "llvm/ADT/Sequence.h" | |
| #include "llvm/ADT/StringExtras.h" | |
| #include "llvm/ADT/StringMap.h" | |
| #include "llvm/ADT/StringSet.h" | |
| #include "llvm/Support/Alignment.h" | |
| #include "llvm/Support/Casting.h" | |
| #include "llvm/Support/Endian.h" | |
| #include "llvm/Support/ErrorHandling.h" | |
| #include "llvm/Support/MathExtras.h" | |
| #include "llvm/Support/PrettyStackTrace.h" | |
| #include "llvm/Support/SourceMgr.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <tuple> | |
| #include <utility> | |
| #include <vector> | |
| using namespace mlir; | |
| using namespace mlir::detail; | |
| //===----------------------------------------------------------------------===// | |
| // CodeComplete | |
| //===----------------------------------------------------------------------===// | |
| AsmParserCodeCompleteContext::~AsmParserCodeCompleteContext() = default; | |
| //===----------------------------------------------------------------------===// | |
| // Parser | |
| //===----------------------------------------------------------------------===// | |
| /// Parse a list of comma-separated items with an optional delimiter. If a | |
| /// delimiter is provided, then an empty list is allowed. If not, then at | |
| /// least one element will be parsed. | |
| ParseResult | |
| Parser::parseCommaSeparatedList(Delimiter delimiter, | |
| function_ref<ParseResult()> parseElementFn, | |
| StringRef contextMessage) { | |
| switch (delimiter) { | |
| case Delimiter::None: | |
| break; | |
| case Delimiter::OptionalParen: | |
| if (getToken().isNot(Token::l_paren)) | |
| return success(); | |
| [[fallthrough]]; | |
| case Delimiter::Paren: | |
| if (parseToken(Token::l_paren, "expected '('" + contextMessage)) | |
| return failure(); | |
| // Check for empty list. | |
| if (consumeIf(Token::r_paren)) | |
| return success(); | |
| break; | |
| case Delimiter::OptionalLessGreater: | |
| // Check for absent list. | |
| if (getToken().isNot(Token::less)) | |
| return success(); | |
| [[fallthrough]]; | |
| case Delimiter::LessGreater: | |
| if (parseToken(Token::less, "expected '<'" + contextMessage)) | |
| return success(); | |
| // Check for empty list. | |
| if (consumeIf(Token::greater)) | |
| return success(); | |
| break; | |
| case Delimiter::OptionalSquare: | |
| if (getToken().isNot(Token::l_square)) | |
| return success(); | |
| [[fallthrough]]; | |
| case Delimiter::Square: | |
| if (parseToken(Token::l_square, "expected '['" + contextMessage)) | |
| return failure(); | |
| // Check for empty list. | |
| if (consumeIf(Token::r_square)) | |
| return success(); | |
| break; | |
| case Delimiter::OptionalBraces: | |
| if (getToken().isNot(Token::l_brace)) | |
| return success(); | |
| [[fallthrough]]; | |
| case Delimiter::Braces: | |
| if (parseToken(Token::l_brace, "expected '{'" + contextMessage)) | |
| return failure(); | |
| // Check for empty list. | |
| if (consumeIf(Token::r_brace)) | |
| return success(); | |
| break; | |
| } | |
| // Non-empty case starts with an element. | |
| if (parseElementFn()) | |
| return failure(); | |
| // Otherwise we have a list of comma separated elements. | |
| while (consumeIf(Token::comma)) { | |
| if (parseElementFn()) | |
| return failure(); | |
| } | |
| switch (delimiter) { | |
| case Delimiter::None: | |
| return success(); | |
| case Delimiter::OptionalParen: | |
| case Delimiter::Paren: | |
| return parseToken(Token::r_paren, "expected ')'" + contextMessage); | |
| case Delimiter::OptionalLessGreater: | |
| case Delimiter::LessGreater: | |
| return parseToken(Token::greater, "expected '>'" + contextMessage); | |
| case Delimiter::OptionalSquare: | |
| case Delimiter::Square: | |
| return parseToken(Token::r_square, "expected ']'" + contextMessage); | |
| case Delimiter::OptionalBraces: | |
| case Delimiter::Braces: | |
| return parseToken(Token::r_brace, "expected '}'" + contextMessage); | |
| } | |
| llvm_unreachable("Unknown delimiter"); | |
| } | |
| /// Parse a comma-separated list of elements, terminated with an arbitrary | |
| /// token. This allows empty lists if allowEmptyList is true. | |
| /// | |
| /// abstract-list ::= rightToken // if allowEmptyList == true | |
| /// abstract-list ::= element (',' element)* rightToken | |
| /// | |
| ParseResult | |
| Parser::parseCommaSeparatedListUntil(Token::Kind rightToken, | |
| function_ref<ParseResult()> parseElement, | |
| bool allowEmptyList) { | |
| // Handle the empty case. | |
| if (getToken().is(rightToken)) { | |
| if (!allowEmptyList) | |
| return emitWrongTokenError("expected list element"); | |
| consumeToken(rightToken); | |
| return success(); | |
| } | |
| if (parseCommaSeparatedList(parseElement) || | |
| parseToken(rightToken, "expected ',' or '" + | |
| Token::getTokenSpelling(rightToken) + "'")) | |
| return failure(); | |
| return success(); | |
| } | |
| InFlightDiagnostic Parser::emitError(const Twine &message) { | |
| auto loc = state.curToken.getLoc(); | |
| if (state.curToken.isNot(Token::eof)) | |
| return emitError(loc, message); | |
| // If the error is to be emitted at EOF, move it back one character. | |
| return emitError(SMLoc::getFromPointer(loc.getPointer() - 1), message); | |
| } | |
| /// Find the start of a line comment (`//`) in the given string, ignoring | |
| /// occurrences inside string literals. Returns StringRef::npos if no comment | |
| /// is found. | |
| static size_t findCommentStart(StringRef line) { | |
| // Fast path: no comment in line at all. | |
| size_t slashPos = line.find("//"); | |
| if (slashPos == StringRef::npos) | |
| return StringRef::npos; | |
| // Fast path: comment at start of line, or no quote before the '//'. | |
| if (slashPos == 0) | |
| return 0; | |
| size_t quotePos = line.find('"'); | |
| if (quotePos == StringRef::npos || quotePos > slashPos) | |
| return slashPos; | |
| // A quote appears before '//'. Parse carefully to handle string literals. | |
| bool inString = false; | |
| for (size_t i = 0, e = line.size(); i < e; ++i) { | |
| char c = line[i]; | |
| if (inString) { | |
| // Skip escaped characters inside strings. | |
| if (c == '\\') { | |
| ++i; | |
| continue; | |
| } | |
| if (c == '"') | |
| inString = false; | |
| } else { | |
| if (c == '"') { | |
| inString = true; | |
| } else if (c == '/' && i + 1 < e && line[i + 1] == '/') { | |
| return i; | |
| } | |
| } | |
| } | |
| return StringRef::npos; | |
| } | |
| InFlightDiagnostic Parser::emitError(SMLoc loc, const Twine &message) { | |
| auto diag = mlir::emitError(getEncodedSourceLocation(loc), message); | |
| // If we hit a parse error in response to a lexer error, then the lexer | |
| // already reported the error. | |
| if (getToken().is(Token::error)) | |
| diag.abandon(); | |
| return diag; | |
| } | |
| /// Emit an error about a "wrong token". If the current token is at the | |
| /// start of a source line, this will apply heuristics to back up and report | |
| /// the error at the end of the previous line, which is where the expected | |
| /// token is supposed to be. | |
| InFlightDiagnostic Parser::emitWrongTokenError(const Twine &message) { | |
| auto loc = state.curToken.getLoc(); | |
| // If the error is to be emitted at EOF, move it back one character. | |
| if (state.curToken.is(Token::eof)) | |
| loc = SMLoc::getFromPointer(loc.getPointer() - 1); | |
| // This is the location we were originally asked to report the error at. | |
| auto originalLoc = loc; | |
| // Determine if the token is at the start of the current line. | |
| const char *bufferStart = state.lex.getBufferBegin(); | |
| const char *curPtr = loc.getPointer(); | |
| // Use this StringRef to keep track of what we are going to back up through, | |
| // it provides nicer string search functions etc. | |
| StringRef startOfBuffer(bufferStart, curPtr - bufferStart); | |
| // Back up over entirely blank lines. | |
| while (true) { | |
| // Back up until we see a \n, but don't look past the buffer start. | |
| startOfBuffer = startOfBuffer.rtrim(" \t"); | |
| // For tokens with no preceding source line, just emit at the original | |
| // location. | |
| if (startOfBuffer.empty()) | |
| return emitError(originalLoc, message); | |
| // If we found something that isn't the end of line, then we're done. | |
| if (startOfBuffer.back() != '\n' && startOfBuffer.back() != '\r') | |
| return emitError(SMLoc::getFromPointer(startOfBuffer.end()), message); | |
| // Drop the \n so we emit the diagnostic at the end of the line. | |
| startOfBuffer = startOfBuffer.drop_back(); | |
| // Check to see if the preceding line has a comment on it. | |
| auto prevLine = startOfBuffer; | |
| size_t newLineIndex = prevLine.find_last_of("\n\r"); | |
| if (newLineIndex != StringRef::npos) | |
| prevLine = prevLine.drop_front(newLineIndex); | |
| // If we find a // in the current line (outside of string literals), then | |
| // emit the diagnostic before it. | |
| size_t commentStart = findCommentStart(prevLine); | |
| if (commentStart != StringRef::npos) | |
| startOfBuffer = startOfBuffer.drop_back(prevLine.size() - commentStart); | |
| } | |
| } | |
| /// Consume the specified token if present and return success. On failure, | |
| /// output a diagnostic and return failure. | |
| ParseResult Parser::parseToken(Token::Kind expectedToken, | |
| const Twine &message) { | |
| if (consumeIf(expectedToken)) | |
| return success(); | |
| return emitWrongTokenError(message); | |
| } | |
| /// Parses a quoted string token if present. | |
| ParseResult Parser::parseOptionalString(std::string *string) { | |
| if (!getToken().is(Token::string)) | |
| return failure(); | |
| if (string) | |
| *string = getToken().getStringValue(); | |
| consumeToken(); | |
| return success(); | |
| } | |
| /// Parse an optional integer value from the stream. | |
| OptionalParseResult Parser::parseOptionalInteger(APInt &result) { | |
| // Parse `false` and `true` keywords as 0 and 1 respectively. | |
| if (consumeIf(Token::kw_false)) { | |
| result = false; | |
| return success(); | |
| } | |
| if (consumeIf(Token::kw_true)) { | |
| result = true; | |
| return success(); | |
| } | |
| Token curToken = getToken(); | |
| if (curToken.isNot(Token::integer, Token::minus)) | |
| return std::nullopt; | |
| bool negative = consumeIf(Token::minus); | |
| Token curTok = getToken(); | |
| if (parseToken(Token::integer, "expected integer value")) | |
| return failure(); | |
| StringRef spelling = curTok.getSpelling(); | |
| bool isHex = spelling.size() > 1 && spelling[1] == 'x'; | |
| if (spelling.getAsInteger(isHex ? 0 : 10, result)) | |
| return emitError(curTok.getLoc(), "integer value too large"); | |
| // Make sure we have a zero at the top so we return the right signedness. | |
| if (result.isNegative()) | |
| result = result.zext(result.getBitWidth() + 1); | |
| // Process the negative sign if present. | |
| if (negative) | |
| result.negate(); | |
| return success(); | |
| } | |
| /// Parse an optional integer value only in decimal format from the stream. | |
| OptionalParseResult Parser::parseOptionalDecimalInteger(APInt &result) { | |
| Token curToken = getToken(); | |
| if (curToken.isNot(Token::integer, Token::minus)) { | |
| return std::nullopt; | |
| } | |
| bool negative = consumeIf(Token::minus); | |
| Token curTok = getToken(); | |
| if (parseToken(Token::integer, "expected integer value")) { | |
| return failure(); | |
| } | |
| StringRef spelling = curTok.getSpelling(); | |
| // If the integer is in hexadecimal return only the 0. The lexer has already | |
| // moved past the entire hexidecimal encoded integer so we reset the lex | |
| // pointer to just past the 0 we actualy want to consume. | |
| if (spelling[0] == '0' && spelling.size() > 1 && | |
| llvm::toLower(spelling[1]) == 'x') { | |
| result = 0; | |
| state.lex.resetPointer(spelling.data() + 1); | |
| consumeToken(); | |
| return success(); | |
| } | |
| if (spelling.getAsInteger(10, result)) | |
| return emitError(curTok.getLoc(), "integer value too large"); | |
| // Make sure we have a zero at the top so we return the right signedness. | |
| if (result.isNegative()) | |
| result = result.zext(result.getBitWidth() + 1); | |
| // Process the negative sign if present. | |
| if (negative) | |
| result.negate(); | |
| return success(); | |
| } | |
| ParseResult Parser::parseFloatFromLiteral(std::optional<APFloat> &result, | |
| const Token &tok, bool isNegative, | |
| const llvm::fltSemantics &semantics) { | |
| // Check for a floating point value. | |
| if (tok.is(Token::floatliteral)) { | |
| auto val = tok.getFloatingPointValue(); | |
| if (!val) | |
| return emitError(tok.getLoc()) << "floating point value too large"; | |
| result.emplace(isNegative ? -*val : *val); | |
| bool unused; | |
| result->convert(semantics, APFloat::rmNearestTiesToEven, &unused); | |
| return success(); | |
| } | |
| // Check for a hexadecimal float value. | |
| if (tok.is(Token::integer)) | |
| return parseFloatFromIntegerLiteral(result, tok, isNegative, semantics); | |
| return emitError(tok.getLoc()) << "expected floating point literal"; | |
| } | |
| /// Parse a floating point value from an integer literal token. | |
| ParseResult | |
| Parser::parseFloatFromIntegerLiteral(std::optional<APFloat> &result, | |
| const Token &tok, bool isNegative, | |
| const llvm::fltSemantics &semantics) { | |
| StringRef spelling = tok.getSpelling(); | |
| bool isHex = spelling.size() > 1 && spelling[1] == 'x'; | |
| if (!isHex) { | |
| return emitError(tok.getLoc(), "unexpected decimal integer literal for a " | |
| "floating point value") | |
| .attachNote() | |
| << "add a trailing dot to make the literal a float"; | |
| } | |
| if (isNegative) { | |
| return emitError(tok.getLoc(), | |
| "hexadecimal float literal should not have a " | |
| "leading minus"); | |
| } | |
| APInt intValue; | |
| tok.getSpelling().getAsInteger(isHex ? 0 : 10, intValue); | |
| auto typeSizeInBits = APFloat::semanticsSizeInBits(semantics); | |
| if (intValue.getActiveBits() > typeSizeInBits) { | |
| return emitError(tok.getLoc(), | |
| "hexadecimal float constant out of range for type"); | |
| } | |
| APInt truncatedValue(typeSizeInBits, | |
| ArrayRef(intValue.getRawData(), intValue.getNumWords())); | |
| result.emplace(semantics, truncatedValue); | |
| return success(); | |
| } | |
| ParseResult Parser::parseOptionalKeyword(StringRef *keyword) { | |
| // Check that the current token is a keyword. | |
| if (!isCurrentTokenAKeyword()) | |
| return failure(); | |
| *keyword = getTokenSpelling(); | |
| consumeToken(); | |
| return success(); | |
| } | |
| ParseResult Parser::parseOptionalKeywordOrString(std::string *result) { | |
| StringRef keyword; | |
| if (succeeded(parseOptionalKeyword(&keyword))) { | |
| *result = keyword.str(); | |
| return success(); | |
| } | |
| return parseOptionalString(result); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Resource Parsing | |
| //===----------------------------------------------------------------------===// | |
| FailureOr<AsmDialectResourceHandle> | |
| Parser::parseResourceHandle(const OpAsmDialectInterface *dialect, | |
| std::string &name) { | |
| assert(dialect && "expected valid dialect interface"); | |
| SMLoc nameLoc = getToken().getLoc(); | |
| if (failed(parseOptionalKeywordOrString(&name))) | |
| return emitError("expected identifier key for 'resource' entry"); | |
| auto &resources = getState().symbols.dialectResources; | |
| // If this is the first time encountering this handle, ask the dialect to | |
| // resolve a reference to this handle. This allows for us to remap the name of | |
| // the handle if necessary. | |
| std::pair<std::string, AsmDialectResourceHandle> &entry = | |
| resources[dialect][name]; | |
| if (entry.first.empty()) { | |
| FailureOr<AsmDialectResourceHandle> result = dialect->declareResource(name); | |
| if (failed(result)) { | |
| return emitError(nameLoc) | |
| << "unknown 'resource' key '" << name << "' for dialect '" | |
| << dialect->getDialect()->getNamespace() << "'"; | |
| } | |
| entry.first = dialect->getResourceKey(*result); | |
| entry.second = *result; | |
| } | |
| name = entry.first; | |
| return entry.second; | |
| } | |
| FailureOr<AsmDialectResourceHandle> | |
| Parser::parseResourceHandle(Dialect *dialect) { | |
| const auto *interface = dyn_cast<OpAsmDialectInterface>(dialect); | |
| if (!interface) { | |
| return emitError() << "dialect '" << dialect->getNamespace() | |
| << "' does not expect resource handles"; | |
| } | |
| std::string resourceName; | |
| return parseResourceHandle(interface, resourceName); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Code Completion | |
| //===----------------------------------------------------------------------===// | |
| ParseResult Parser::codeCompleteDialectName() { | |
| state.codeCompleteContext->completeDialectName(); | |
| return failure(); | |
| } | |
| ParseResult Parser::codeCompleteOperationName(StringRef dialectName) { | |
| // Perform some simple validation on the dialect name. This doesn't need to be | |
| // extensive, it's more of an optimization (to avoid checking completion | |
| // results when we know they will fail). | |
| if (dialectName.empty() || dialectName.contains('.')) | |
| return failure(); | |
| state.codeCompleteContext->completeOperationName(dialectName); | |
| return failure(); | |
| } | |
| ParseResult Parser::codeCompleteDialectOrElidedOpName(SMLoc loc) { | |
| // Check to see if there is anything else on the current line. This check | |
| // isn't strictly necessary, but it does avoid unnecessarily triggering | |
| // completions for operations and dialects in situations where we don't want | |
| // them (e.g. at the end of an operation). | |
| auto shouldIgnoreOpCompletion = [&]() { | |
| const char *bufBegin = state.lex.getBufferBegin(); | |
| const char *it = loc.getPointer() - 1; | |
| for (; it > bufBegin && *it != '\n'; --it) | |
| if (!StringRef(" \t\r").contains(*it)) | |
| return true; | |
| return false; | |
| }; | |
| if (shouldIgnoreOpCompletion()) | |
| return failure(); | |
| // The completion here is either for a dialect name, or an operation name | |
| // whose dialect prefix was elided. For this we simply invoke both of the | |
| // individual completion methods. | |
| (void)codeCompleteDialectName(); | |
| return codeCompleteOperationName(state.defaultDialectStack.back()); | |
| } | |
| ParseResult Parser::codeCompleteStringDialectOrOperationName(StringRef name) { | |
| // If the name is empty, this is the start of the string and contains the | |
| // dialect. | |
| if (name.empty()) | |
| return codeCompleteDialectName(); | |
| // Otherwise, we treat this as completing an operation name. The current name | |
| // is used as the dialect namespace. | |
| if (name.consume_back(".")) | |
| return codeCompleteOperationName(name); | |
| return failure(); | |
| } | |
| ParseResult Parser::codeCompleteExpectedTokens(ArrayRef<StringRef> tokens) { | |
| state.codeCompleteContext->completeExpectedTokens(tokens, /*optional=*/false); | |
| return failure(); | |
| } | |
| ParseResult Parser::codeCompleteOptionalTokens(ArrayRef<StringRef> tokens) { | |
| state.codeCompleteContext->completeExpectedTokens(tokens, /*optional=*/true); | |
| return failure(); | |
| } | |
| Attribute Parser::codeCompleteAttribute() { | |
| state.codeCompleteContext->completeAttribute( | |
| state.symbols.attributeAliasDefinitions); | |
| return {}; | |
| } | |
| Type Parser::codeCompleteType() { | |
| state.codeCompleteContext->completeType(state.symbols.typeAliasDefinitions); | |
| return {}; | |
| } | |
| Attribute | |
| Parser::codeCompleteDialectSymbol(const llvm::StringMap<Attribute> &aliases) { | |
| state.codeCompleteContext->completeDialectAttributeOrAlias(aliases); | |
| return {}; | |
| } | |
| Type Parser::codeCompleteDialectSymbol(const llvm::StringMap<Type> &aliases) { | |
| state.codeCompleteContext->completeDialectTypeOrAlias(aliases); | |
| return {}; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // OperationParser | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| /// This class provides support for parsing operations and regions of | |
| /// operations. | |
| class OperationParser : public Parser { | |
| public: | |
| OperationParser(ParserState &state, ModuleOp topLevelOp); | |
| ~OperationParser(); | |
| /// After parsing is finished, this function must be called to see if there | |
| /// are any remaining issues. | |
| ParseResult finalize(); | |
| //===--------------------------------------------------------------------===// | |
| // SSA Value Handling | |
| //===--------------------------------------------------------------------===// | |
| using UnresolvedOperand = OpAsmParser::UnresolvedOperand; | |
| using Argument = OpAsmParser::Argument; | |
| struct DeferredLocInfo { | |
| SMLoc loc; | |
| StringRef identifier; | |
| }; | |
| /// Push a new SSA name scope to the parser. | |
| void pushSSANameScope(bool isIsolated); | |
| /// Pop the last SSA name scope from the parser. | |
| ParseResult popSSANameScope(); | |
| /// Register a definition of a value with the symbol table. | |
| ParseResult addDefinition(UnresolvedOperand useInfo, Value value); | |
| /// Parse an optional list of SSA uses into 'results'. | |
| ParseResult | |
| parseOptionalSSAUseList(SmallVectorImpl<UnresolvedOperand> &results); | |
| /// Parse a single SSA use into 'result'. If 'allowResultNumber' is true then | |
| /// we allow #42 syntax. | |
| ParseResult parseSSAUse(UnresolvedOperand &result, | |
| bool allowResultNumber = true); | |
| /// Given a reference to an SSA value and its type, return a reference. This | |
| /// returns null on failure. | |
| Value resolveSSAUse(UnresolvedOperand useInfo, Type type); | |
| ParseResult parseSSADefOrUseAndType( | |
| function_ref<ParseResult(UnresolvedOperand, Type)> action); | |
| ParseResult parseOptionalSSAUseAndTypeList(SmallVectorImpl<Value> &results); | |
| /// Return the location of the value identified by its name and number if it | |
| /// has been already reference. | |
| std::optional<SMLoc> getReferenceLoc(StringRef name, unsigned number) { | |
| auto &values = isolatedNameScopes.back().values; | |
| if (!values.count(name) || number >= values[name].size()) | |
| return {}; | |
| if (values[name][number].value) | |
| return values[name][number].loc; | |
| return {}; | |
| } | |
| //===--------------------------------------------------------------------===// | |
| // Operation Parsing | |
| //===--------------------------------------------------------------------===// | |
| /// Parse an operation instance. | |
| ParseResult parseOperation(); | |
| /// Parse a single operation successor. | |
| ParseResult parseSuccessor(Block *&dest); | |
| /// Parse a comma-separated list of operation successors in brackets. | |
| ParseResult parseSuccessors(SmallVectorImpl<Block *> &destinations); | |
| /// Parse an operation instance that is in the generic form. | |
| Operation *parseGenericOperation(); | |
| /// Parse different components, viz., use-info of operand(s), successor(s), | |
| /// region(s), attribute(s) and function-type, of the generic form of an | |
| /// operation instance and populate the input operation-state 'result' with | |
| /// those components. If any of the components is explicitly provided, then | |
| /// skip parsing that component. | |
| ParseResult parseGenericOperationAfterOpName( | |
| OperationState &result, | |
| std::optional<ArrayRef<UnresolvedOperand>> parsedOperandUseInfo = | |
| std::nullopt, | |
| std::optional<ArrayRef<Block *>> parsedSuccessors = std::nullopt, | |
| std::optional<MutableArrayRef<std::unique_ptr<Region>>> parsedRegions = | |
| std::nullopt, | |
| std::optional<ArrayRef<NamedAttribute>> parsedAttributes = std::nullopt, | |
| std::optional<Attribute> propertiesAttribute = std::nullopt, | |
| std::optional<FunctionType> parsedFnType = std::nullopt); | |
| /// Parse an operation instance that is in the generic form and insert it at | |
| /// the provided insertion point. | |
| Operation *parseGenericOperation(Block *insertBlock, | |
| Block::iterator insertPt); | |
| /// This type is used to keep track of things that are either an Operation or | |
| /// a BlockArgument. We cannot use Value for this, because not all Operations | |
| /// have results. | |
| using OpOrArgument = llvm::PointerUnion<Operation *, BlockArgument>; | |
| /// Parse an optional trailing location and add it to the specifier Operation | |
| /// or `UnresolvedOperand` if present. | |
| /// | |
| /// trailing-location ::= (`loc` (`(` location `)` | attribute-alias))? | |
| /// | |
| ParseResult parseTrailingLocationSpecifier(OpOrArgument opOrArgument); | |
| /// Parse a location alias, that is a sequence looking like: #loc42 | |
| /// The alias may have already be defined or may be defined later, in which | |
| /// case an OpaqueLoc is used a placeholder. The caller must ensure that the | |
| /// token is actually an alias, which means it must not contain a dot. | |
| ParseResult parseLocationAlias(LocationAttr &loc); | |
| /// This is the structure of a result specifier in the assembly syntax, | |
| /// including the name, number of results, and location. | |
| using ResultRecord = std::tuple<StringRef, unsigned, SMLoc>; | |
| /// Parse an operation instance that is in the op-defined custom form. | |
| /// resultInfo specifies information about the "%name =" specifiers. | |
| Operation *parseCustomOperation(ArrayRef<ResultRecord> resultIDs); | |
| /// Parse the name of an operation, in the custom form. On success, return a | |
| /// an object of type 'OperationName'. Otherwise, failure is returned. | |
| FailureOr<OperationName> parseCustomOperationName(); | |
| //===--------------------------------------------------------------------===// | |
| // Region Parsing | |
| //===--------------------------------------------------------------------===// | |
| /// Parse a region into 'region' with the provided entry block arguments. | |
| /// 'isIsolatedNameScope' indicates if the naming scope of this region is | |
| /// isolated from those above. | |
| ParseResult parseRegion(Region ®ion, ArrayRef<Argument> entryArguments, | |
| bool isIsolatedNameScope = false); | |
| /// Parse a region body into 'region'. | |
| ParseResult parseRegionBody(Region ®ion, SMLoc startLoc, | |
| ArrayRef<Argument> entryArguments, | |
| bool isIsolatedNameScope); | |
| //===--------------------------------------------------------------------===// | |
| // Block Parsing | |
| //===--------------------------------------------------------------------===// | |
| /// Parse a new block into 'block'. | |
| ParseResult parseBlock(Block *&block); | |
| /// Parse a list of operations into 'block'. | |
| ParseResult parseBlockBody(Block *block); | |
| /// Parse a (possibly empty) list of block arguments. | |
| ParseResult parseOptionalBlockArgList(Block *owner); | |
| /// Get the block with the specified name, creating it if it doesn't | |
| /// already exist. The location specified is the point of use, which allows | |
| /// us to diagnose references to blocks that are not defined precisely. | |
| Block *getBlockNamed(StringRef name, SMLoc loc); | |
| //===--------------------------------------------------------------------===// | |
| // Code Completion | |
| //===--------------------------------------------------------------------===// | |
| /// The set of various code completion methods. Every completion method | |
| /// returns `failure` to stop the parsing process after providing completion | |
| /// results. | |
| ParseResult codeCompleteSSAUse(); | |
| ParseResult codeCompleteBlock(); | |
| private: | |
| /// This class represents a definition of a Block. | |
| struct BlockDefinition { | |
| /// A pointer to the defined Block. | |
| Block *block; | |
| /// The location that the Block was defined at. | |
| SMLoc loc; | |
| }; | |
| /// This class represents a definition of a Value. | |
| struct ValueDefinition { | |
| /// A pointer to the defined Value. | |
| Value value; | |
| /// The location that the Value was defined at. | |
| SMLoc loc; | |
| }; | |
| /// Returns the info for a block at the current scope for the given name. | |
| BlockDefinition &getBlockInfoByName(StringRef name) { | |
| return blocksByName.back()[name]; | |
| } | |
| /// Insert a new forward reference to the given block. | |
| void insertForwardRef(Block *block, SMLoc loc) { | |
| forwardRef.back().try_emplace(block, loc); | |
| } | |
| /// Erase any forward reference to the given block. | |
| bool eraseForwardRef(Block *block) { return forwardRef.back().erase(block); } | |
| /// Record that a definition was added at the current scope. | |
| void recordDefinition(StringRef def); | |
| /// Get the value entry for the given SSA name. | |
| SmallVectorImpl<ValueDefinition> &getSSAValueEntry(StringRef name); | |
| /// Create a forward reference placeholder value with the given location and | |
| /// result type. | |
| Value createForwardRefPlaceholder(SMLoc loc, Type type); | |
| /// Return true if this is a forward reference. | |
| bool isForwardRefPlaceholder(Value value) { | |
| return forwardRefPlaceholders.count(value); | |
| } | |
| /// This struct represents an isolated SSA name scope. This scope may contain | |
| /// other nested non-isolated scopes. These scopes are used for operations | |
| /// that are known to be isolated to allow for reusing names within their | |
| /// regions, even if those names are used above. | |
| struct IsolatedSSANameScope { | |
| /// Record that a definition was added at the current scope. | |
| void recordDefinition(StringRef def) { | |
| definitionsPerScope.back().insert(def); | |
| } | |
| /// Push a nested name scope. | |
| void pushSSANameScope() { definitionsPerScope.push_back({}); } | |
| /// Pop a nested name scope. | |
| void popSSANameScope() { | |
| for (auto &def : definitionsPerScope.pop_back_val()) | |
| values.erase(def.getKey()); | |
| } | |
| /// This keeps track of all of the SSA values we are tracking for each name | |
| /// scope, indexed by their name. This has one entry per result number. | |
| llvm::StringMap<SmallVector<ValueDefinition, 1>> values; | |
| /// This keeps track of all of the values defined by a specific name scope. | |
| SmallVector<llvm::StringSet<>, 2> definitionsPerScope; | |
| }; | |
| /// A list of isolated name scopes. | |
| SmallVector<IsolatedSSANameScope, 2> isolatedNameScopes; | |
| /// This keeps track of the block names as well as the location of the first | |
| /// reference for each nested name scope. This is used to diagnose invalid | |
| /// block references and memorize them. | |
| SmallVector<DenseMap<StringRef, BlockDefinition>, 2> blocksByName; | |
| SmallVector<DenseMap<Block *, SMLoc>, 2> forwardRef; | |
| /// These are all of the placeholders we've made along with the location of | |
| /// their first reference, to allow checking for use of undefined values. | |
| DenseMap<Value, SMLoc> forwardRefPlaceholders; | |
| /// Operations that define the placeholders. These are kept until the end of | |
| /// of the lifetime of the parser because some custom parsers may store | |
| /// references to them in local state and use them after forward references | |
| /// have been resolved. | |
| DenseSet<Operation *> forwardRefOps; | |
| /// Deffered locations: when parsing `loc(#loc42)` we add an entry to this | |
| /// map. After parsing the definition `#loc42 = ...` we'll patch back users | |
| /// of this location. | |
| std::vector<DeferredLocInfo> deferredLocsReferences; | |
| /// The builder used when creating parsed operation instances. | |
| OpBuilder opBuilder; | |
| /// The top level operation that holds all of the parsed operations. | |
| Operation *topLevelOp; | |
| }; | |
| } // namespace | |
| MLIR_DECLARE_EXPLICIT_SELF_OWNING_TYPE_ID(OperationParser::DeferredLocInfo *) | |
| MLIR_DEFINE_EXPLICIT_SELF_OWNING_TYPE_ID(OperationParser::DeferredLocInfo *) | |
| OperationParser::OperationParser(ParserState &state, ModuleOp topLevelOp) | |
| : Parser(state), opBuilder(topLevelOp.getRegion()), topLevelOp(topLevelOp) { | |
| // The top level operation starts a new name scope. | |
| pushSSANameScope(/*isIsolated=*/true); | |
| // If we are populating the parser state, prepare it for parsing. | |
| if (state.asmState) | |
| state.asmState->initialize(topLevelOp); | |
| } | |
| OperationParser::~OperationParser() { | |
| for (Operation *op : forwardRefOps) { | |
| // Drop all uses of undefined forward declared reference and destroy | |
| // defining operation. | |
| op->dropAllUses(); | |
| op->destroy(); | |
| } | |
| for (const auto &scope : forwardRef) { | |
| for (const auto &fwd : scope) { | |
| // Delete all blocks that were created as forward references but never | |
| // included into a region. | |
| fwd.first->dropAllUses(); | |
| delete fwd.first; | |
| } | |
| } | |
| } | |
| /// After parsing is finished, this function must be called to see if there are | |
| /// any remaining issues. | |
| ParseResult OperationParser::finalize() { | |
| // Check for any forward references that are left. If we find any, error | |
| // out. | |
| if (!forwardRefPlaceholders.empty()) { | |
| SmallVector<const char *, 4> errors; | |
| // Iteration over the map isn't deterministic, so sort by source location. | |
| for (auto entry : forwardRefPlaceholders) | |
| errors.push_back(entry.second.getPointer()); | |
| llvm::array_pod_sort(errors.begin(), errors.end()); | |
| for (const char *entry : errors) { | |
| auto loc = SMLoc::getFromPointer(entry); | |
| emitError(loc, "use of undeclared SSA value name"); | |
| } | |
| return failure(); | |
| } | |
| // Resolve the locations of any deferred operations. | |
| auto &attributeAliases = state.symbols.attributeAliasDefinitions; | |
| auto locID = TypeID::get<DeferredLocInfo *>(); | |
| auto resolveLocation = [&, this](auto &opOrArgument) -> LogicalResult { | |
| auto fwdLoc = dyn_cast<OpaqueLoc>(opOrArgument.getLoc()); | |
| if (!fwdLoc || fwdLoc.getUnderlyingTypeID() != locID) | |
| return success(); | |
| auto locInfo = deferredLocsReferences[fwdLoc.getUnderlyingLocation()]; | |
| Attribute attr = attributeAliases.lookup(locInfo.identifier); | |
| if (!attr) | |
| return this->emitError(locInfo.loc) | |
| << "operation location alias was never defined"; | |
| auto locAttr = dyn_cast<LocationAttr>(attr); | |
| if (!locAttr) | |
| return this->emitError(locInfo.loc) | |
| << "expected location, but found '" << attr << "'"; | |
| opOrArgument.setLoc(locAttr); | |
| return success(); | |
| }; | |
| auto walkRes = topLevelOp->walk([&](Operation *op) { | |
| if (failed(resolveLocation(*op))) | |
| return WalkResult::interrupt(); | |
| for (Region ®ion : op->getRegions()) | |
| for (Block &block : region.getBlocks()) | |
| for (BlockArgument arg : block.getArguments()) | |
| if (failed(resolveLocation(arg))) | |
| return WalkResult::interrupt(); | |
| return WalkResult::advance(); | |
| }); | |
| if (walkRes.wasInterrupted()) | |
| return failure(); | |
| // Pop the top level name scope. | |
| if (failed(popSSANameScope())) | |
| return failure(); | |
| // Verify that the parsed operations are valid. | |
| if (state.config.shouldVerifyAfterParse() && failed(verify(topLevelOp))) | |
| return failure(); | |
| // If we are populating the parser state, finalize the top-level operation. | |
| if (state.asmState) | |
| state.asmState->finalize(topLevelOp); | |
| return success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // SSA Value Handling | |
| //===----------------------------------------------------------------------===// | |
| void OperationParser::pushSSANameScope(bool isIsolated) { | |
| blocksByName.push_back(DenseMap<StringRef, BlockDefinition>()); | |
| forwardRef.push_back(DenseMap<Block *, SMLoc>()); | |
| // Push back a new name definition scope. | |
| if (isIsolated) | |
| isolatedNameScopes.push_back({}); | |
| isolatedNameScopes.back().pushSSANameScope(); | |
| } | |
| ParseResult OperationParser::popSSANameScope() { | |
| auto forwardRefInCurrentScope = forwardRef.pop_back_val(); | |
| // Verify that all referenced blocks were defined. | |
| if (!forwardRefInCurrentScope.empty()) { | |
| SmallVector<std::pair<const char *, Block *>, 4> errors; | |
| // Iteration over the map isn't deterministic, so sort by source location. | |
| 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. | |
| topLevelOp->getRegion(0).push_back(entry.first); | |
| } | |
| llvm::array_pod_sort(errors.begin(), errors.end()); | |
| for (auto entry : errors) { | |
| auto loc = SMLoc::getFromPointer(entry.first); | |
| emitError(loc, "reference to an undefined block"); | |
| } | |
| return failure(); | |
| } | |
| // Pop the next nested namescope. If there is only one internal namescope, | |
| // just pop the isolated scope. | |
| auto ¤tNameScope = isolatedNameScopes.back(); | |
| if (currentNameScope.definitionsPerScope.size() == 1) | |
| isolatedNameScopes.pop_back(); | |
| else | |
| currentNameScope.popSSANameScope(); | |
| blocksByName.pop_back(); | |
| return success(); | |
| } | |
| /// Register a definition of a value with the symbol table. | |
| ParseResult OperationParser::addDefinition(UnresolvedOperand useInfo, | |
| Value value) { | |
| auto &entries = getSSAValueEntry(useInfo.name); | |
| // Make sure there is a slot for this value. | |
| if (entries.size() <= useInfo.number) | |
| entries.resize(useInfo.number + 1); | |
| // If we already have an entry for this, check to see if it was a definition | |
| // or a forward reference. | |
| if (auto existing = entries[useInfo.number].value) { | |
| if (!isForwardRefPlaceholder(existing)) { | |
| return emitError(useInfo.location) | |
| .append("redefinition of SSA value '", useInfo.name, "'") | |
| .attachNote(getEncodedSourceLocation(entries[useInfo.number].loc)) | |
| .append("previously defined here"); | |
| } | |
| if (existing.getType() != value.getType()) { | |
| return emitError(useInfo.location) | |
| .append("definition of SSA value '", useInfo.name, "#", | |
| useInfo.number, "' has type ", value.getType()) | |
| .attachNote(getEncodedSourceLocation(entries[useInfo.number].loc)) | |
| .append("previously used here with type ", existing.getType()); | |
| } | |
| // If it was a forward reference, update everything that used it to use | |
| // the actual definition instead, delete the forward ref, and remove it | |
| // from our set of forward references we track. | |
| existing.replaceAllUsesWith(value); | |
| forwardRefPlaceholders.erase(existing); | |
| // If a definition of the value already exists, replace it in the assembly | |
| // state. | |
| if (state.asmState) | |
| state.asmState->refineDefinition(existing, value); | |
| } | |
| /// Record this definition for the current scope. | |
| entries[useInfo.number] = {value, useInfo.location}; | |
| recordDefinition(useInfo.name); | |
| return success(); | |
| } | |
| /// Parse a (possibly empty) list of SSA operands. | |
| /// | |
| /// ssa-use-list ::= ssa-use (`,` ssa-use)* | |
| /// ssa-use-list-opt ::= ssa-use-list? | |
| /// | |
| ParseResult OperationParser::parseOptionalSSAUseList( | |
| SmallVectorImpl<UnresolvedOperand> &results) { | |
| if (!getToken().isOrIsCodeCompletionFor(Token::percent_identifier)) | |
| return success(); | |
| return parseCommaSeparatedList([&]() -> ParseResult { | |
| UnresolvedOperand result; | |
| if (parseSSAUse(result)) | |
| return failure(); | |
| results.push_back(result); | |
| return success(); | |
| }); | |
| } | |
| /// Parse a SSA operand for an operation. | |
| /// | |
| /// ssa-use ::= ssa-id | |
| /// | |
| ParseResult OperationParser::parseSSAUse(UnresolvedOperand &result, | |
| bool allowResultNumber) { | |
| if (getToken().isCodeCompletion()) | |
| return codeCompleteSSAUse(); | |
| result.name = getTokenSpelling(); | |
| result.number = 0; | |
| result.location = getToken().getLoc(); | |
| if (parseToken(Token::percent_identifier, "expected SSA operand")) | |
| return failure(); | |
| // If we have an attribute ID, it is a result number. | |
| if (getToken().is(Token::hash_identifier)) { | |
| if (!allowResultNumber) | |
| return emitError("result number not allowed in argument list"); | |
| if (auto value = getToken().getHashIdentifierNumber()) | |
| result.number = *value; | |
| else | |
| return emitError("invalid SSA value result number"); | |
| consumeToken(Token::hash_identifier); | |
| } | |
| return success(); | |
| } | |
| /// Given an unbound reference to an SSA value and its type, return the value | |
| /// it specifies. This returns null on failure. | |
| Value OperationParser::resolveSSAUse(UnresolvedOperand useInfo, Type type) { | |
| auto &entries = getSSAValueEntry(useInfo.name); | |
| // Functor used to record the use of the given value if the assembly state | |
| // field is populated. | |
| auto maybeRecordUse = [&](Value value) { | |
| if (state.asmState) | |
| state.asmState->addUses(value, useInfo.location); | |
| return value; | |
| }; | |
| // If we have already seen a value of this name, return it. | |
| if (useInfo.number < entries.size() && entries[useInfo.number].value) { | |
| Value result = entries[useInfo.number].value; | |
| // Check that the type matches the other uses. | |
| if (result.getType() == type) | |
| return maybeRecordUse(result); | |
| emitError(useInfo.location, "use of value '") | |
| .append(useInfo.name, | |
| "' expects different type than prior uses: ", type, " vs ", | |
| result.getType()) | |
| .attachNote(getEncodedSourceLocation(entries[useInfo.number].loc)) | |
| .append("prior use here"); | |
| return nullptr; | |
| } | |
| // Make sure we have enough slots for this. | |
| if (entries.size() <= useInfo.number) | |
| entries.resize(useInfo.number + 1); | |
| // If the value has already been defined and this is an overly large result | |
| // number, diagnose that. | |
| if (entries[0].value && !isForwardRefPlaceholder(entries[0].value)) | |
| return (emitError(useInfo.location, "reference to invalid result number"), | |
| nullptr); | |
| // Otherwise, this is a forward reference. Create a placeholder and remember | |
| // that we did so. | |
| Value result = createForwardRefPlaceholder(useInfo.location, type); | |
| entries[useInfo.number] = {result, useInfo.location}; | |
| return maybeRecordUse(result); | |
| } | |
| /// Parse an SSA use with an associated type. | |
| /// | |
| /// ssa-use-and-type ::= ssa-use `:` type | |
| ParseResult OperationParser::parseSSADefOrUseAndType( | |
| function_ref<ParseResult(UnresolvedOperand, Type)> action) { | |
| UnresolvedOperand useInfo; | |
| if (parseSSAUse(useInfo) || | |
| parseToken(Token::colon, "expected ':' and type for SSA operand")) | |
| return failure(); | |
| auto type = parseType(); | |
| if (!type) | |
| return failure(); | |
| return action(useInfo, type); | |
| } | |
| /// Parse a (possibly empty) list of SSA operands, followed by a colon, then | |
| /// followed by a type list. | |
| /// | |
| /// ssa-use-and-type-list | |
| /// ::= ssa-use-list ':' type-list-no-parens | |
| /// | |
| ParseResult OperationParser::parseOptionalSSAUseAndTypeList( | |
| SmallVectorImpl<Value> &results) { | |
| SmallVector<UnresolvedOperand, 4> valueIDs; | |
| if (parseOptionalSSAUseList(valueIDs)) | |
| return failure(); | |
| // If there were no operands, then there is no colon or type lists. | |
| if (valueIDs.empty()) | |
| return success(); | |
| SmallVector<Type, 4> types; | |
| if (parseToken(Token::colon, "expected ':' in operand list") || | |
| parseTypeListNoParens(types)) | |
| return failure(); | |
| if (valueIDs.size() != types.size()) | |
| return emitError("expected ") | |
| << valueIDs.size() << " types to match operand list"; | |
| results.reserve(valueIDs.size()); | |
| for (unsigned i = 0, e = valueIDs.size(); i != e; ++i) { | |
| if (auto value = resolveSSAUse(valueIDs[i], types[i])) | |
| results.push_back(value); | |
| else | |
| return failure(); | |
| } | |
| return success(); | |
| } | |
| /// Record that a definition was added at the current scope. | |
| void OperationParser::recordDefinition(StringRef def) { | |
| isolatedNameScopes.back().recordDefinition(def); | |
| } | |
| /// Get the value entry for the given SSA name. | |
| auto OperationParser::getSSAValueEntry(StringRef name) | |
| -> SmallVectorImpl<ValueDefinition> & { | |
| return isolatedNameScopes.back().values[name]; | |
| } | |
| /// Create and remember a new placeholder for a forward reference. | |
| Value OperationParser::createForwardRefPlaceholder(SMLoc loc, Type type) { | |
| // Forward references are always created as operations, because we just need | |
| // something with a def/use chain. | |
| // | |
| // We create these placeholders as having an empty name, which we know | |
| // cannot be created through normal user input, allowing us to distinguish | |
| // them. | |
| auto name = OperationName("builtin.unrealized_conversion_cast", getContext()); | |
| auto *op = Operation::create( | |
| getEncodedSourceLocation(loc), name, type, /*operands=*/{}, | |
| /*attributes=*/NamedAttrList(), /*properties=*/PropertyRef(), | |
| /*successors=*/{}, /*numRegions=*/0); | |
| forwardRefPlaceholders[op->getResult(0)] = loc; | |
| forwardRefOps.insert(op); | |
| return op->getResult(0); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Operation Parsing | |
| //===----------------------------------------------------------------------===// | |
| /// Parse an operation. | |
| /// | |
| /// operation ::= op-result-list? | |
| /// (generic-operation | custom-operation) | |
| /// trailing-location? | |
| /// generic-operation ::= string-literal `(` ssa-use-list? `)` | |
| /// successor-list? (`(` region-list `)`)? | |
| /// attribute-dict? `:` function-type | |
| /// custom-operation ::= bare-id custom-operation-format | |
| /// op-result-list ::= op-result (`,` op-result)* `=` | |
| /// op-result ::= ssa-id (`:` integer-literal) | |
| /// | |
| ParseResult OperationParser::parseOperation() { | |
| auto loc = getToken().getLoc(); | |
| SmallVector<ResultRecord, 1> resultIDs; | |
| size_t numExpectedResults = 0; | |
| if (getToken().is(Token::percent_identifier)) { | |
| // Parse the group of result ids. | |
| auto parseNextResult = [&]() -> ParseResult { | |
| // Parse the next result id. | |
| Token nameTok = getToken(); | |
| if (parseToken(Token::percent_identifier, | |
| "expected valid ssa identifier")) | |
| return failure(); | |
| // If the next token is a ':', we parse the expected result count. | |
| size_t expectedSubResults = 1; | |
| if (consumeIf(Token::colon)) { | |
| // Check that the next token is an integer. | |
| if (!getToken().is(Token::integer)) | |
| return emitWrongTokenError("expected integer number of results"); | |
| // Check that number of results is > 0. | |
| auto val = getToken().getUInt64IntegerValue(); | |
| if (!val || *val < 1) | |
| return emitError( | |
| "expected named operation to have at least 1 result"); | |
| consumeToken(Token::integer); | |
| expectedSubResults = *val; | |
| } | |
| resultIDs.emplace_back(nameTok.getSpelling(), expectedSubResults, | |
| nameTok.getLoc()); | |
| numExpectedResults += expectedSubResults; | |
| return success(); | |
| }; | |
| if (parseCommaSeparatedList(parseNextResult)) | |
| return failure(); | |
| if (parseToken(Token::equal, "expected '=' after SSA name")) | |
| return failure(); | |
| } | |
| Operation *op; | |
| Token nameTok = getToken(); | |
| if (nameTok.is(Token::bare_identifier) || nameTok.isKeyword()) | |
| op = parseCustomOperation(resultIDs); | |
| else if (nameTok.is(Token::string)) | |
| op = parseGenericOperation(); | |
| else if (nameTok.isCodeCompletionFor(Token::string)) | |
| return codeCompleteStringDialectOrOperationName(nameTok.getStringValue()); | |
| else if (nameTok.isCodeCompletion()) | |
| return codeCompleteDialectOrElidedOpName(loc); | |
| else | |
| return emitWrongTokenError("expected operation name in quotes"); | |
| // If parsing of the basic operation failed, then this whole thing fails. | |
| if (!op) | |
| return failure(); | |
| // If the operation had a name, register it. | |
| if (!resultIDs.empty()) { | |
| if (op->getNumResults() == 0) | |
| return emitError(loc, "cannot name an operation with no results"); | |
| if (numExpectedResults != op->getNumResults()) | |
| return emitError(loc, "operation defines ") | |
| << op->getNumResults() << " results but was provided " | |
| << numExpectedResults << " to bind"; | |
| // Add this operation to the assembly state if it was provided to populate. | |
| if (state.asmState) { | |
| unsigned resultIt = 0; | |
| SmallVector<std::pair<unsigned, SMLoc>> asmResultGroups; | |
| asmResultGroups.reserve(resultIDs.size()); | |
| for (ResultRecord &record : resultIDs) { | |
| asmResultGroups.emplace_back(resultIt, std::get<2>(record)); | |
| resultIt += std::get<1>(record); | |
| } | |
| state.asmState->finalizeOperationDefinition( | |
| op, nameTok.getLocRange(), /*endLoc=*/getLastToken().getEndLoc(), | |
| asmResultGroups); | |
| } | |
| // Add definitions for each of the result groups. | |
| unsigned opResI = 0; | |
| for (ResultRecord &resIt : resultIDs) { | |
| for (unsigned subRes : llvm::seq<unsigned>(0, std::get<1>(resIt))) { | |
| if (addDefinition({std::get<2>(resIt), std::get<0>(resIt), subRes}, | |
| op->getResult(opResI++))) | |
| return failure(); | |
| } | |
| } | |
| // Add this operation to the assembly state if it was provided to populate. | |
| } else if (state.asmState) { | |
| state.asmState->finalizeOperationDefinition( | |
| op, nameTok.getLocRange(), | |
| /*endLoc=*/getLastToken().getEndLoc()); | |
| } | |
| return success(); | |
| } | |
| /// Parse a single operation successor. | |
| /// | |
| /// successor ::= block-id | |
| /// | |
| ParseResult OperationParser::parseSuccessor(Block *&dest) { | |
| if (getToken().isCodeCompletion()) | |
| return codeCompleteBlock(); | |
| // Verify branch is identifier and get the matching block. | |
| if (!getToken().is(Token::caret_identifier)) | |
| return emitWrongTokenError("expected block name"); | |
| dest = getBlockNamed(getTokenSpelling(), getToken().getLoc()); | |
| consumeToken(); | |
| return success(); | |
| } | |
| /// Parse a comma-separated list of operation successors in brackets. | |
| /// | |
| /// successor-list ::= `[` successor (`,` successor )* `]` | |
| /// | |
| ParseResult | |
| OperationParser::parseSuccessors(SmallVectorImpl<Block *> &destinations) { | |
| if (parseToken(Token::l_square, "expected '['")) | |
| return failure(); | |
| auto parseElt = [this, &destinations] { | |
| Block *dest; | |
| ParseResult res = parseSuccessor(dest); | |
| destinations.push_back(dest); | |
| return res; | |
| }; | |
| return parseCommaSeparatedListUntil(Token::r_square, parseElt, | |
| /*allowEmptyList=*/false); | |
| } | |
| namespace { | |
| // RAII-style guard for cleaning up the regions in the operation state before | |
| // deleting them. Within the parser, regions may get deleted if parsing failed, | |
| // and other errors may be present, in particular undominated uses. This makes | |
| // sure such uses are deleted. | |
| struct CleanupOpStateRegions { | |
| ~CleanupOpStateRegions() { | |
| SmallVector<Region *, 4> regionsToClean; | |
| regionsToClean.reserve(state.regions.size()); | |
| for (auto ®ion : state.regions) | |
| if (region) | |
| for (auto &block : *region) | |
| block.dropAllDefinedValueUses(); | |
| } | |
| OperationState &state; | |
| }; | |
| } // namespace | |
| ParseResult OperationParser::parseGenericOperationAfterOpName( | |
| OperationState &result, | |
| std::optional<ArrayRef<UnresolvedOperand>> parsedOperandUseInfo, | |
| std::optional<ArrayRef<Block *>> parsedSuccessors, | |
| std::optional<MutableArrayRef<std::unique_ptr<Region>>> parsedRegions, | |
| std::optional<ArrayRef<NamedAttribute>> parsedAttributes, | |
| std::optional<Attribute> propertiesAttribute, | |
| std::optional<FunctionType> parsedFnType) { | |
| // Parse the operand list, if not explicitly provided. | |
| SmallVector<UnresolvedOperand, 8> opInfo; | |
| if (!parsedOperandUseInfo) { | |
| if (parseToken(Token::l_paren, "expected '(' to start operand list") || | |
| parseOptionalSSAUseList(opInfo) || | |
| parseToken(Token::r_paren, "expected ')' to end operand list")) { | |
| return failure(); | |
| } | |
| parsedOperandUseInfo = opInfo; | |
| } | |
| // Parse the successor list, if not explicitly provided. | |
| if (!parsedSuccessors) { | |
| if (getToken().is(Token::l_square)) { | |
| // Check if the operation is not a known terminator. | |
| if (!result.name.mightHaveTrait<OpTrait::IsTerminator>()) | |
| return emitError("successors in non-terminator"); | |
| SmallVector<Block *, 2> successors; | |
| if (parseSuccessors(successors)) | |
| return failure(); | |
| result.addSuccessors(successors); | |
| } | |
| } else { | |
| result.addSuccessors(*parsedSuccessors); | |
| } | |
| // Parse the properties, if not explicitly provided. | |
| if (propertiesAttribute) { | |
| result.propertiesAttr = *propertiesAttribute; | |
| } else if (consumeIf(Token::less)) { | |
| result.propertiesAttr = parseAttribute(); | |
| if (!result.propertiesAttr) | |
| return failure(); | |
| if (parseToken(Token::greater, "expected '>' to close properties")) | |
| return failure(); | |
| } | |
| // Parse the region list, if not explicitly provided. | |
| if (!parsedRegions) { | |
| if (consumeIf(Token::l_paren)) { | |
| do { | |
| // Create temporary regions with the top level region as parent. | |
| result.regions.emplace_back(new Region(topLevelOp)); | |
| if (parseRegion(*result.regions.back(), /*entryArguments=*/{})) | |
| return failure(); | |
| } while (consumeIf(Token::comma)); | |
| if (parseToken(Token::r_paren, "expected ')' to end region list")) | |
| return failure(); | |
| } | |
| } else { | |
| result.addRegions(*parsedRegions); | |
| } | |
| // Parse the attributes, if not explicitly provided. | |
| if (!parsedAttributes) { | |
| if (getToken().is(Token::l_brace)) { | |
| if (parseAttributeDict(result.attributes)) | |
| return failure(); | |
| } | |
| } else { | |
| result.addAttributes(*parsedAttributes); | |
| } | |
| // Parse the operation type, if not explicitly provided. | |
| Location typeLoc = result.location; | |
| if (!parsedFnType) { | |
| if (parseToken(Token::colon, "expected ':' followed by operation type")) | |
| return failure(); | |
| typeLoc = getEncodedSourceLocation(getToken().getLoc()); | |
| auto type = parseType(); | |
| if (!type) | |
| return failure(); | |
| auto fnType = dyn_cast<FunctionType>(type); | |
| if (!fnType) | |
| return mlir::emitError(typeLoc, "expected function type"); | |
| parsedFnType = fnType; | |
| } | |
| result.addTypes(parsedFnType->getResults()); | |
| // Check that we have the right number of types for the operands. | |
| ArrayRef<Type> operandTypes = parsedFnType->getInputs(); | |
| if (operandTypes.size() != parsedOperandUseInfo->size()) { | |
| auto plural = "s"[parsedOperandUseInfo->size() == 1]; | |
| return mlir::emitError(typeLoc, "expected ") | |
| << parsedOperandUseInfo->size() << " operand type" << plural | |
| << " but had " << operandTypes.size(); | |
| } | |
| // Resolve all of the operands. | |
| for (unsigned i = 0, e = parsedOperandUseInfo->size(); i != e; ++i) { | |
| result.operands.push_back( | |
| resolveSSAUse((*parsedOperandUseInfo)[i], operandTypes[i])); | |
| if (!result.operands.back()) | |
| return failure(); | |
| } | |
| return success(); | |
| } | |
| Operation *OperationParser::parseGenericOperation() { | |
| // Get location information for the operation. | |
| auto srcLocation = getEncodedSourceLocation(getToken().getLoc()); | |
| std::string name = getToken().getStringValue(); | |
| if (name.empty()) | |
| return (emitError("empty operation name is invalid"), nullptr); | |
| if (name.find('\0') != StringRef::npos) | |
| return (emitError("null character not allowed in operation name"), nullptr); | |
| consumeToken(Token::string); | |
| OperationState result(srcLocation, name); | |
| CleanupOpStateRegions guard{result}; | |
| // Lazy load dialects in the context as needed. | |
| if (!result.name.isRegistered()) { | |
| StringRef dialectName = StringRef(name).split('.').first; | |
| if (!getContext()->getLoadedDialect(dialectName) && | |
| !getContext()->getOrLoadDialect(dialectName)) { | |
| if (!getContext()->allowsUnregisteredDialects()) { | |
| // Emit an error if the dialect couldn't be loaded (i.e., it was not | |
| // registered) and unregistered dialects aren't allowed. | |
| emitError("operation being parsed with an unregistered dialect. If " | |
| "this is intended, please use -allow-unregistered-dialect " | |
| "with the MLIR tool used"); | |
| return nullptr; | |
| } | |
| } else { | |
| // Reload the OperationName now that the dialect is loaded. | |
| result.name = OperationName(name, getContext()); | |
| } | |
| } | |
| // If we are populating the parser state, start a new operation definition. | |
| if (state.asmState) | |
| state.asmState->startOperationDefinition(result.name); | |
| if (parseGenericOperationAfterOpName(result)) | |
| return nullptr; | |
| // Operation::create() is not allowed to fail, however setting the properties | |
| // from an attribute is a failable operation. So we save the attribute here | |
| // and set it on the operation post-parsing. | |
| Attribute properties; | |
| std::swap(properties, result.propertiesAttr); | |
| // If we don't have properties in the textual IR, but the operation now has | |
| // support for properties, we support some backward-compatible generic syntax | |
| // for the operation and as such we accept inherent attributes mixed in the | |
| // dictionary of discardable attributes. We pre-validate these here because | |
| // invalid attributes can't be casted to the properties storage and will be | |
| // silently dropped. For example an attribute { foo = 0 : i32 } that is | |
| // declared as F32Attr in ODS would have a C++ type of FloatAttr in the | |
| // properties array. When setting it we would do something like: | |
| // | |
| // properties.foo = dyn_cast<FloatAttr>(fooAttr); | |
| // | |
| // which would end up with a null Attribute. The diagnostic from the verifier | |
| // would be "missing foo attribute" instead of something like "expects a 32 | |
| // bits float attribute but got a 32 bits integer attribute". | |
| if (!properties && !result.getRawProperties()) { | |
| std::optional<RegisteredOperationName> info = | |
| result.name.getRegisteredInfo(); | |
| if (info) { | |
| if (failed(info->verifyInherentAttrs(result.attributes, [&]() { | |
| return mlir::emitError(srcLocation) << "'" << name << "' op "; | |
| }))) | |
| return nullptr; | |
| } | |
| } | |
| // Create the operation and try to parse a location for it. | |
| Operation *op = opBuilder.create(result); | |
| if (parseTrailingLocationSpecifier(op)) | |
| return nullptr; | |
| // Try setting the properties for the operation, using a diagnostic to print | |
| // errors. | |
| if (properties) { | |
| auto emitError = [&]() { | |
| return mlir::emitError(srcLocation, "invalid properties ") | |
| << properties << " for op " << name << ": "; | |
| }; | |
| if (failed(op->setPropertiesFromAttribute(properties, emitError))) | |
| return nullptr; | |
| } | |
| return op; | |
| } | |
| Operation *OperationParser::parseGenericOperation(Block *insertBlock, | |
| Block::iterator insertPt) { | |
| Token nameToken = getToken(); | |
| OpBuilder::InsertionGuard restoreInsertionPoint(opBuilder); | |
| opBuilder.setInsertionPoint(insertBlock, insertPt); | |
| Operation *op = parseGenericOperation(); | |
| if (!op) | |
| return nullptr; | |
| // If we are populating the parser asm state, finalize this operation | |
| // definition. | |
| if (state.asmState) | |
| state.asmState->finalizeOperationDefinition( | |
| op, nameToken.getLocRange(), | |
| /*endLoc=*/getLastToken().getEndLoc()); | |
| return op; | |
| } | |
| namespace { | |
| class CustomOpAsmParser : public AsmParserImpl<OpAsmParser> { | |
| public: | |
| CustomOpAsmParser( | |
| SMLoc nameLoc, ArrayRef<OperationParser::ResultRecord> resultIDs, | |
| function_ref<ParseResult(OpAsmParser &, OperationState &)> parseAssembly, | |
| bool isIsolatedFromAbove, StringRef opName, OperationParser &parser) | |
| : AsmParserImpl<OpAsmParser>(nameLoc, parser), resultIDs(resultIDs), | |
| parseAssembly(parseAssembly), isIsolatedFromAbove(isIsolatedFromAbove), | |
| opName(opName), parser(parser) { | |
| (void)isIsolatedFromAbove; // Only used in assert, silence unused warning. | |
| } | |
| /// Parse an instance of the operation described by 'opDefinition' into the | |
| /// provided operation state. | |
| ParseResult parseOperation(OperationState &opState) { | |
| if (parseAssembly(*this, opState)) | |
| return failure(); | |
| // Verify that the parsed attributes does not have duplicate attributes. | |
| // This can happen if an attribute set during parsing is also specified in | |
| // the attribute dictionary in the assembly, or the attribute is set | |
| // multiple during parsing. | |
| std::optional<NamedAttribute> duplicate = | |
| opState.attributes.findDuplicate(); | |
| if (duplicate) | |
| return emitError(getNameLoc(), "attribute '") | |
| << duplicate->getName().getValue() | |
| << "' occurs more than once in the attribute list"; | |
| return success(); | |
| } | |
| Operation *parseGenericOperation(Block *insertBlock, | |
| Block::iterator insertPt) final { | |
| return parser.parseGenericOperation(insertBlock, insertPt); | |
| } | |
| FailureOr<OperationName> parseCustomOperationName() final { | |
| return parser.parseCustomOperationName(); | |
| } | |
| ParseResult parseGenericOperationAfterOpName( | |
| OperationState &result, | |
| std::optional<ArrayRef<UnresolvedOperand>> parsedUnresolvedOperands, | |
| std::optional<ArrayRef<Block *>> parsedSuccessors, | |
| std::optional<MutableArrayRef<std::unique_ptr<Region>>> parsedRegions, | |
| std::optional<ArrayRef<NamedAttribute>> parsedAttributes, | |
| std::optional<Attribute> parsedPropertiesAttribute, | |
| std::optional<FunctionType> parsedFnType) final { | |
| return parser.parseGenericOperationAfterOpName( | |
| result, parsedUnresolvedOperands, parsedSuccessors, parsedRegions, | |
| parsedAttributes, parsedPropertiesAttribute, parsedFnType); | |
| } | |
| //===--------------------------------------------------------------------===// | |
| // Utilities | |
| //===--------------------------------------------------------------------===// | |
| /// Return the name of the specified result in the specified syntax, as well | |
| /// as the subelement in the name. For example, in this operation: | |
| /// | |
| /// %x, %y:2, %z = foo.op | |
| /// | |
| /// getResultName(0) == {"x", 0 } | |
| /// getResultName(1) == {"y", 0 } | |
| /// getResultName(2) == {"y", 1 } | |
| /// getResultName(3) == {"z", 0 } | |
| std::pair<StringRef, unsigned> | |
| getResultName(unsigned resultNo) const override { | |
| // Scan for the resultID that contains this result number. | |
| for (const auto &entry : resultIDs) { | |
| if (resultNo < std::get<1>(entry)) { | |
| // Don't pass on the leading %. | |
| StringRef name = std::get<0>(entry).drop_front(); | |
| return {name, resultNo}; | |
| } | |
| resultNo -= std::get<1>(entry); | |
| } | |
| // Invalid result number. | |
| return {"", ~0U}; | |
| } | |
| /// Return the number of declared SSA results. This returns 4 for the foo.op | |
| /// example in the comment for getResultName. | |
| size_t getNumResults() const override { | |
| size_t count = 0; | |
| for (auto &entry : resultIDs) | |
| count += std::get<1>(entry); | |
| return count; | |
| } | |
| /// Emit a diagnostic at the specified location and return failure. | |
| InFlightDiagnostic emitError(SMLoc loc, const Twine &message) override { | |
| return AsmParserImpl<OpAsmParser>::emitError(loc, "custom op '" + opName + | |
| "' " + message); | |
| } | |
| //===--------------------------------------------------------------------===// | |
| // Operand Parsing | |
| //===--------------------------------------------------------------------===// | |
| /// Parse a single operand. | |
| ParseResult parseOperand(UnresolvedOperand &result, | |
| bool allowResultNumber = true) override { | |
| OperationParser::UnresolvedOperand useInfo; | |
| if (parser.parseSSAUse(useInfo, allowResultNumber)) | |
| return failure(); | |
| result = {useInfo.location, useInfo.name, useInfo.number}; | |
| return success(); | |
| } | |
| /// Parse a single operand if present. | |
| OptionalParseResult | |
| parseOptionalOperand(UnresolvedOperand &result, | |
| bool allowResultNumber = true) override { | |
| if (parser.getToken().isOrIsCodeCompletionFor(Token::percent_identifier)) | |
| return parseOperand(result, allowResultNumber); | |
| return std::nullopt; | |
| } | |
| /// Parse zero or more SSA comma-separated operand references with a specified | |
| /// surrounding delimiter, and an optional required operand count. | |
| ParseResult parseOperandList(SmallVectorImpl<UnresolvedOperand> &result, | |
| Delimiter delimiter = Delimiter::None, | |
| bool allowResultNumber = true, | |
| int requiredOperandCount = -1) override { | |
| // The no-delimiter case has some special handling for better diagnostics. | |
| if (delimiter == Delimiter::None) { | |
| // parseCommaSeparatedList doesn't handle the missing case for "none", | |
| // so we handle it custom here. | |
| Token tok = parser.getToken(); | |
| if (!tok.isOrIsCodeCompletionFor(Token::percent_identifier)) { | |
| // If we didn't require any operands or required exactly zero (weird) | |
| // then this is success. | |
| if (requiredOperandCount == -1 || requiredOperandCount == 0) | |
| return success(); | |
| // Otherwise, try to produce a nice error message. | |
| if (tok.isAny(Token::l_paren, Token::l_square)) | |
| return parser.emitError("unexpected delimiter"); | |
| return parser.emitWrongTokenError("expected operand"); | |
| } | |
| } | |
| auto parseOneOperand = [&]() -> ParseResult { | |
| return parseOperand(result.emplace_back(), allowResultNumber); | |
| }; | |
| auto startLoc = parser.getToken().getLoc(); | |
| if (parseCommaSeparatedList(delimiter, parseOneOperand, " in operand list")) | |
| return failure(); | |
| // Check that we got the expected # of elements. | |
| if (requiredOperandCount != -1 && | |
| result.size() != static_cast<size_t>(requiredOperandCount)) | |
| return emitError(startLoc, "expected ") | |
| << requiredOperandCount << " operands"; | |
| return success(); | |
| } | |
| /// Resolve an operand to an SSA value, emitting an error on failure. | |
| ParseResult resolveOperand(const UnresolvedOperand &operand, Type type, | |
| SmallVectorImpl<Value> &result) override { | |
| if (auto value = parser.resolveSSAUse(operand, type)) { | |
| result.push_back(value); | |
| return success(); | |
| } | |
| return failure(); | |
| } | |
| /// Parse an AffineMap of SSA ids. | |
| ParseResult | |
| parseAffineMapOfSSAIds(SmallVectorImpl<UnresolvedOperand> &operands, | |
| Attribute &mapAttr, StringRef attrName, | |
| NamedAttrList &attrs, Delimiter delimiter) override { | |
| SmallVector<UnresolvedOperand, 2> dimOperands; | |
| SmallVector<UnresolvedOperand, 1> symOperands; | |
| auto parseElement = [&](bool isSymbol) -> ParseResult { | |
| UnresolvedOperand operand; | |
| if (parseOperand(operand)) | |
| return failure(); | |
| if (isSymbol) | |
| symOperands.push_back(operand); | |
| else | |
| dimOperands.push_back(operand); | |
| return success(); | |
| }; | |
| AffineMap map; | |
| if (parser.parseAffineMapOfSSAIds(map, parseElement, delimiter)) | |
| return failure(); | |
| // Add AffineMap attribute. | |
| if (map) { | |
| mapAttr = AffineMapAttr::get(map); | |
| attrs.push_back(parser.builder.getNamedAttr(attrName, mapAttr)); | |
| } | |
| // Add dim operands before symbol operands in 'operands'. | |
| operands.assign(dimOperands.begin(), dimOperands.end()); | |
| operands.append(symOperands.begin(), symOperands.end()); | |
| return success(); | |
| } | |
| /// Parse an AffineExpr of SSA ids. | |
| ParseResult | |
| parseAffineExprOfSSAIds(SmallVectorImpl<UnresolvedOperand> &dimOperands, | |
| SmallVectorImpl<UnresolvedOperand> &symbOperands, | |
| AffineExpr &expr) override { | |
| auto parseElement = [&](bool isSymbol) -> ParseResult { | |
| UnresolvedOperand operand; | |
| if (parseOperand(operand)) | |
| return failure(); | |
| if (isSymbol) | |
| symbOperands.push_back(operand); | |
| else | |
| dimOperands.push_back(operand); | |
| return success(); | |
| }; | |
| return parser.parseAffineExprOfSSAIds(expr, parseElement); | |
| } | |
| //===--------------------------------------------------------------------===// | |
| // Argument Parsing | |
| //===--------------------------------------------------------------------===// | |
| /// Parse a single argument with the following syntax: | |
| /// | |
| /// `%ssaname : !type { optionalAttrDict} loc(optionalSourceLoc)` | |
| /// | |
| /// If `allowType` is false or `allowAttrs` are false then the respective | |
| /// parts of the grammar are not parsed. | |
| ParseResult parseArgument(Argument &result, bool allowType = false, | |
| bool allowAttrs = false) override { | |
| NamedAttrList attrs; | |
| if (parseOperand(result.ssaName, /*allowResultNumber=*/false) || | |
| (allowType && parseColonType(result.type)) || | |
| (allowAttrs && parseOptionalAttrDict(attrs)) || | |
| parseOptionalLocationSpecifier(result.sourceLoc)) | |
| return failure(); | |
| result.attrs = attrs.getDictionary(getContext()); | |
| return success(); | |
| } | |
| /// Parse a single argument if present. | |
| OptionalParseResult parseOptionalArgument(Argument &result, bool allowType, | |
| bool allowAttrs) override { | |
| if (parser.getToken().is(Token::percent_identifier)) | |
| return parseArgument(result, allowType, allowAttrs); | |
| return std::nullopt; | |
| } | |
| ParseResult parseArgumentList(SmallVectorImpl<Argument> &result, | |
| Delimiter delimiter, bool allowType, | |
| bool allowAttrs) override { | |
| // The no-delimiter case has some special handling for the empty case. | |
| if (delimiter == Delimiter::None && | |
| parser.getToken().isNot(Token::percent_identifier)) | |
| return success(); | |
| auto parseOneArgument = [&]() -> ParseResult { | |
| return parseArgument(result.emplace_back(), allowType, allowAttrs); | |
| }; | |
| return parseCommaSeparatedList(delimiter, parseOneArgument, | |
| " in argument list"); | |
| } | |
| //===--------------------------------------------------------------------===// | |
| // Region Parsing | |
| //===--------------------------------------------------------------------===// | |
| /// Parse a region that takes `arguments` of `argTypes` types. This | |
| /// effectively defines the SSA values of `arguments` and assigns their type. | |
| ParseResult parseRegion(Region ®ion, ArrayRef<Argument> arguments, | |
| bool enableNameShadowing) override { | |
| // Try to parse the region. | |
| (void)isIsolatedFromAbove; | |
| assert((!enableNameShadowing || isIsolatedFromAbove) && | |
| "name shadowing is only allowed on isolated regions"); | |
| if (parser.parseRegion(region, arguments, enableNameShadowing)) | |
| return failure(); | |
| return success(); | |
| } | |
| /// Parses a region if present. | |
| OptionalParseResult parseOptionalRegion(Region ®ion, | |
| ArrayRef<Argument> arguments, | |
| bool enableNameShadowing) override { | |
| if (parser.getToken().isNot(Token::l_brace)) | |
| return std::nullopt; | |
| return parseRegion(region, arguments, enableNameShadowing); | |
| } | |
| /// Parses a region if present. If the region is present, a new region is | |
| /// allocated and placed in `region`. If no region is present, `region` | |
| /// remains untouched. | |
| OptionalParseResult | |
| parseOptionalRegion(std::unique_ptr<Region> ®ion, | |
| ArrayRef<Argument> arguments, | |
| bool enableNameShadowing = false) override { | |
| if (parser.getToken().isNot(Token::l_brace)) | |
| return std::nullopt; | |
| std::unique_ptr<Region> newRegion = std::make_unique<Region>(); | |
| if (parseRegion(*newRegion, arguments, enableNameShadowing)) | |
| return failure(); | |
| region = std::move(newRegion); | |
| return success(); | |
| } | |
| //===--------------------------------------------------------------------===// | |
| // Successor Parsing | |
| //===--------------------------------------------------------------------===// | |
| /// Parse a single operation successor. | |
| ParseResult parseSuccessor(Block *&dest) override { | |
| return parser.parseSuccessor(dest); | |
| } | |
| /// Parse an optional operation successor and its operand list. | |
| OptionalParseResult parseOptionalSuccessor(Block *&dest) override { | |
| if (!parser.getToken().isOrIsCodeCompletionFor(Token::caret_identifier)) | |
| return std::nullopt; | |
| return parseSuccessor(dest); | |
| } | |
| /// Parse a single operation successor and its operand list. | |
| ParseResult | |
| parseSuccessorAndUseList(Block *&dest, | |
| SmallVectorImpl<Value> &operands) override { | |
| if (parseSuccessor(dest)) | |
| return failure(); | |
| // Handle optional arguments. | |
| if (succeeded(parseOptionalLParen()) && | |
| (parser.parseOptionalSSAUseAndTypeList(operands) || parseRParen())) { | |
| return failure(); | |
| } | |
| return success(); | |
| } | |
| //===--------------------------------------------------------------------===// | |
| // Type Parsing | |
| //===--------------------------------------------------------------------===// | |
| /// Parse a list of assignments of the form | |
| /// (%x1 = %y1, %x2 = %y2, ...). | |
| OptionalParseResult parseOptionalAssignmentList( | |
| SmallVectorImpl<Argument> &lhs, | |
| SmallVectorImpl<UnresolvedOperand> &rhs) override { | |
| if (failed(parseOptionalLParen())) | |
| return std::nullopt; | |
| auto parseElt = [&]() -> ParseResult { | |
| if (parseArgument(lhs.emplace_back()) || parseEqual() || | |
| parseOperand(rhs.emplace_back())) | |
| return failure(); | |
| return success(); | |
| }; | |
| return parser.parseCommaSeparatedListUntil(Token::r_paren, parseElt); | |
| } | |
| /// Parse a loc(...) specifier if present, filling in result if so. | |
| ParseResult | |
| parseOptionalLocationSpecifier(std::optional<Location> &result) override { | |
| // If there is a 'loc' we parse a trailing location. | |
| if (!parser.consumeIf(Token::kw_loc)) | |
| return success(); | |
| LocationAttr directLoc; | |
| if (parser.parseToken(Token::l_paren, "expected '(' in location")) | |
| return failure(); | |
| Token tok = parser.getToken(); | |
| // Check to see if we are parsing a location alias. We are parsing a | |
| // location alias if the token is a hash identifier *without* a dot in it - | |
| // the dot signifies a dialect attribute. Otherwise, we parse the location | |
| // directly. | |
| if (tok.is(Token::hash_identifier) && !tok.getSpelling().contains('.')) { | |
| if (parser.parseLocationAlias(directLoc)) | |
| return failure(); | |
| } else if (parser.parseLocationInstance(directLoc)) { | |
| return failure(); | |
| } | |
| if (parser.parseToken(Token::r_paren, "expected ')' in location")) | |
| return failure(); | |
| result = directLoc; | |
| return success(); | |
| } | |
| private: | |
| /// Information about the result name specifiers. | |
| ArrayRef<OperationParser::ResultRecord> resultIDs; | |
| /// The abstract information of the operation. | |
| function_ref<ParseResult(OpAsmParser &, OperationState &)> parseAssembly; | |
| bool isIsolatedFromAbove; | |
| StringRef opName; | |
| /// The backing operation parser. | |
| OperationParser &parser; | |
| }; | |
| } // namespace | |
| FailureOr<OperationName> OperationParser::parseCustomOperationName() { | |
| Token nameTok = getToken(); | |
| // Accept keywords here as they may be interpreted as a shortened operation | |
| // name, e.g., `dialect.keyword` can be spelled as just `keyword` within a | |
| // region of an operation from `dialect`. | |
| if (nameTok.getKind() != Token::bare_identifier && !nameTok.isKeyword()) | |
| return emitError("expected bare identifier or keyword"); | |
| StringRef opName = nameTok.getSpelling(); | |
| if (opName.empty()) | |
| return (emitError("empty operation name is invalid"), failure()); | |
| consumeToken(); | |
| // Check to see if this operation name is already registered. | |
| std::optional<RegisteredOperationName> opInfo = | |
| RegisteredOperationName::lookup(opName, getContext()); | |
| if (opInfo) | |
| return *opInfo; | |
| // If the operation doesn't have a dialect prefix try using the default | |
| // dialect. | |
| auto opNameSplit = opName.split('.'); | |
| StringRef dialectName = opNameSplit.first; | |
| std::string opNameStorage; | |
| if (opNameSplit.second.empty()) { | |
| // If the name didn't have a prefix, check for a code completion request. | |
| if (getToken().isCodeCompletion() && opName.back() == '.') | |
| return codeCompleteOperationName(dialectName); | |
| dialectName = getState().defaultDialectStack.back(); | |
| opNameStorage = (dialectName + "." + opName).str(); | |
| opName = opNameStorage; | |
| } | |
| // Try to load the dialect before returning the operation name to make sure | |
| // the operation has a chance to be registered. | |
| getContext()->getOrLoadDialect(dialectName); | |
| return OperationName(opName, getContext()); | |
| } | |
| Operation * | |
| OperationParser::parseCustomOperation(ArrayRef<ResultRecord> resultIDs) { | |
| SMLoc opLoc = getToken().getLoc(); | |
| StringRef originalOpName = getTokenSpelling(); | |
| FailureOr<OperationName> opNameInfo = parseCustomOperationName(); | |
| if (failed(opNameInfo)) | |
| return nullptr; | |
| StringRef opName = opNameInfo->getStringRef(); | |
| // This is the actual hook for the custom op parsing, usually implemented by | |
| // the op itself (`Op::parse()`). We retrieve it either from the | |
| // RegisteredOperationName or from the Dialect. | |
| OperationName::ParseAssemblyFn parseAssemblyFn; | |
| bool isIsolatedFromAbove = false; | |
| StringRef defaultDialect = ""; | |
| if (auto opInfo = opNameInfo->getRegisteredInfo()) { | |
| parseAssemblyFn = opInfo->getParseAssemblyFn(); | |
| isIsolatedFromAbove = opInfo->hasTrait<OpTrait::IsIsolatedFromAbove>(); | |
| auto *iface = opInfo->getInterface<OpAsmOpInterface>(); | |
| if (iface && !iface->getDefaultDialect().empty()) | |
| defaultDialect = iface->getDefaultDialect(); | |
| } else { | |
| std::optional<Dialect::ParseOpHook> dialectHook; | |
| Dialect *dialect = opNameInfo->getDialect(); | |
| if (!dialect) { | |
| InFlightDiagnostic diag = | |
| emitError(opLoc) << "Dialect `" << opNameInfo->getDialectNamespace() | |
| << "' not found for custom op '" << originalOpName | |
| << "' "; | |
| if (originalOpName != opName) | |
| diag << " (tried '" << opName << "' as well)"; | |
| auto ¬e = diag.attachNote(); | |
| note << "Available dialects: "; | |
| std::vector<StringRef> registered = getContext()->getAvailableDialects(); | |
| auto loaded = getContext()->getLoadedDialects(); | |
| // Merge the sorted lists of registered and loaded dialects. | |
| SmallVector<std::pair<StringRef, bool>> mergedDialects; | |
| auto regIt = registered.begin(), regEnd = registered.end(); | |
| auto loadIt = loaded.rbegin(), loadEnd = loaded.rend(); | |
| bool isRegistered = false; | |
| bool isOnlyLoaded = true; | |
| while (regIt != regEnd && loadIt != loadEnd) { | |
| StringRef reg = *regIt; | |
| StringRef load = (*loadIt)->getNamespace(); | |
| if (load < reg) { | |
| mergedDialects.emplace_back(load, isOnlyLoaded); | |
| ++loadIt; | |
| } else { | |
| mergedDialects.emplace_back(reg, isRegistered); | |
| ++regIt; | |
| if (reg == load) | |
| ++loadIt; | |
| } | |
| } | |
| for (; regIt != regEnd; ++regIt) | |
| mergedDialects.emplace_back(*regIt, isRegistered); | |
| for (; loadIt != loadEnd; ++loadIt) | |
| mergedDialects.emplace_back((*loadIt)->getNamespace(), isOnlyLoaded); | |
| bool loadedUnregistered = false; | |
| llvm::interleaveComma(mergedDialects, note, [&](auto &pair) { | |
| note << pair.first; | |
| if (pair.second) { | |
| loadedUnregistered = true; | |
| note << " (*)"; | |
| } | |
| }); | |
| note << " "; | |
| if (loadedUnregistered) | |
| note << "(* corresponding to loaded but unregistered dialects)"; | |
| note << "; for more info on dialect registration see " | |
| "https://mlir.llvm.org/getting_started/Faq/" | |
| "#registered-loaded-dependent-whats-up-with-dialects-management"; | |
| return nullptr; | |
| } | |
| dialectHook = dialect->getParseOperationHook(opName); | |
| if (!dialectHook) { | |
| InFlightDiagnostic diag = | |
| emitError(opLoc) << "custom op '" << originalOpName << "' is unknown"; | |
| if (originalOpName != opName) | |
| diag << " (tried '" << opName << "' as well)"; | |
| return nullptr; | |
| } | |
| parseAssemblyFn = *dialectHook; | |
| } | |
| getState().defaultDialectStack.push_back(defaultDialect); | |
| llvm::scope_exit restoreDefaultDialect( | |
| [&]() { getState().defaultDialectStack.pop_back(); }); | |
| // If the custom op parser crashes, produce some indication to help | |
| // debugging. | |
| llvm::PrettyStackTraceFormat fmt("MLIR Parser: custom op parser '%s'", | |
| opNameInfo->getIdentifier().data()); | |
| // Get location information for the operation. | |
| auto srcLocation = getEncodedSourceLocation(opLoc); | |
| OperationState opState(srcLocation, *opNameInfo); | |
| // If we are populating the parser state, start a new operation definition. | |
| if (state.asmState) | |
| state.asmState->startOperationDefinition(opState.name); | |
| // Have the op implementation take a crack and parsing this. | |
| CleanupOpStateRegions guard{opState}; | |
| CustomOpAsmParser opAsmParser(opLoc, resultIDs, parseAssemblyFn, | |
| isIsolatedFromAbove, opName, *this); | |
| if (opAsmParser.parseOperation(opState)) | |
| return nullptr; | |
| // If it emitted an error, we failed. | |
| if (opAsmParser.didEmitError()) | |
| return nullptr; | |
| Attribute properties = opState.propertiesAttr; | |
| opState.propertiesAttr = Attribute{}; | |
| // Otherwise, create the operation and try to parse a location for it. | |
| Operation *op = opBuilder.create(opState); | |
| if (parseTrailingLocationSpecifier(op)) | |
| return nullptr; | |
| // Try setting the properties for the operation. | |
| if (properties) { | |
| auto emitError = [&]() { | |
| return mlir::emitError(srcLocation, "invalid properties ") | |
| << properties << " for op " << op->getName().getStringRef() | |
| << ": "; | |
| }; | |
| if (failed(op->setPropertiesFromAttribute(properties, emitError))) | |
| return nullptr; | |
| } | |
| return op; | |
| } | |
| ParseResult OperationParser::parseLocationAlias(LocationAttr &loc) { | |
| Token tok = getToken(); | |
| consumeToken(Token::hash_identifier); | |
| StringRef identifier = tok.getSpelling().drop_front(); | |
| assert(!identifier.contains('.') && | |
| "unexpected dialect attribute token, expected alias"); | |
| if (state.asmState) | |
| state.asmState->addAttrAliasUses(identifier, tok.getLocRange()); | |
| // If this alias can be resolved, do it now. | |
| Attribute attr = state.symbols.attributeAliasDefinitions.lookup(identifier); | |
| if (attr) { | |
| if (!(loc = dyn_cast<LocationAttr>(attr))) | |
| return emitError(tok.getLoc()) | |
| << "expected location, but found '" << attr << "'"; | |
| } else { | |
| // Otherwise, remember this operation and resolve its location later. | |
| // In the meantime, use a special OpaqueLoc as a marker. | |
| loc = OpaqueLoc::get(deferredLocsReferences.size(), | |
| TypeID::get<DeferredLocInfo *>(), | |
| UnknownLoc::get(getContext())); | |
| deferredLocsReferences.push_back(DeferredLocInfo{tok.getLoc(), identifier}); | |
| } | |
| return success(); | |
| } | |
| ParseResult | |
| OperationParser::parseTrailingLocationSpecifier(OpOrArgument opOrArgument) { | |
| // If there is a 'loc' we parse a trailing location. | |
| if (!consumeIf(Token::kw_loc)) | |
| return success(); | |
| if (parseToken(Token::l_paren, "expected '(' in location")) | |
| return failure(); | |
| Token tok = getToken(); | |
| // Check to see if we are parsing a location alias. We are parsing a location | |
| // alias if the token is a hash identifier *without* a dot in it - the dot | |
| // signifies a dialect attribute. Otherwise, we parse the location directly. | |
| LocationAttr directLoc; | |
| if (tok.is(Token::hash_identifier) && !tok.getSpelling().contains('.')) { | |
| if (parseLocationAlias(directLoc)) | |
| return failure(); | |
| } else if (parseLocationInstance(directLoc)) { | |
| return failure(); | |
| } | |
| if (parseToken(Token::r_paren, "expected ')' in location")) | |
| return failure(); | |
| if (auto *op = llvm::dyn_cast_if_present<Operation *>(opOrArgument)) | |
| op->setLoc(directLoc); | |
| else | |
| cast<BlockArgument>(opOrArgument).setLoc(directLoc); | |
| return success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Region Parsing | |
| //===----------------------------------------------------------------------===// | |
| ParseResult OperationParser::parseRegion(Region ®ion, | |
| ArrayRef<Argument> entryArguments, | |
| bool isIsolatedNameScope) { | |
| // Parse the '{'. | |
| Token lBraceTok = getToken(); | |
| if (parseToken(Token::l_brace, "expected '{' to begin a region")) | |
| return failure(); | |
| // If we are populating the parser state, start a new region definition. | |
| if (state.asmState) | |
| state.asmState->startRegionDefinition(); | |
| // Parse the region body. | |
| if ((!entryArguments.empty() || getToken().isNot(Token::r_brace)) && | |
| parseRegionBody(region, lBraceTok.getLoc(), entryArguments, | |
| isIsolatedNameScope)) { | |
| return failure(); | |
| } | |
| consumeToken(Token::r_brace); | |
| // If we are populating the parser state, finalize this region. | |
| if (state.asmState) | |
| state.asmState->finalizeRegionDefinition(); | |
| return success(); | |
| } | |
| ParseResult OperationParser::parseRegionBody(Region ®ion, SMLoc startLoc, | |
| ArrayRef<Argument> entryArguments, | |
| bool isIsolatedNameScope) { | |
| auto currentPt = opBuilder.saveInsertionPoint(); | |
| // Push a new named value scope. | |
| pushSSANameScope(isIsolatedNameScope); | |
| // Parse the first block directly to allow for it to be unnamed. | |
| auto owningBlock = std::make_unique<Block>(); | |
| llvm::scope_exit failureCleanup([&] { | |
| if (owningBlock) { | |
| // If parsing failed, as indicated by the fact that `owningBlock` still | |
| // owns the block, drop all forward references from preceding operations | |
| // to definitions within the parsed block. | |
| owningBlock->dropAllDefinedValueUses(); | |
| } | |
| }); | |
| Block *block = owningBlock.get(); | |
| // If this block is not defined in the source file, add a definition for it | |
| // now in the assembly state. Blocks with a name will be defined when the name | |
| // is parsed. | |
| if (state.asmState && getToken().isNot(Token::caret_identifier)) | |
| state.asmState->addDefinition(block, startLoc); | |
| // Add arguments to the entry block if we had the form with explicit names. | |
| if (!entryArguments.empty() && !entryArguments[0].ssaName.name.empty()) { | |
| // If we had named arguments, then don't allow a block name. | |
| if (getToken().is(Token::caret_identifier)) | |
| return emitError("invalid block name in region with named arguments"); | |
| for (auto &entryArg : entryArguments) { | |
| auto &argInfo = entryArg.ssaName; | |
| // Ensure that the argument was not already defined. | |
| if (auto defLoc = getReferenceLoc(argInfo.name, argInfo.number)) { | |
| return emitError(argInfo.location, "region entry argument '" + | |
| argInfo.name + | |
| "' is already in use") | |
| .attachNote(getEncodedSourceLocation(*defLoc)) | |
| << "previously referenced here"; | |
| } | |
| Location loc = entryArg.sourceLoc.has_value() | |
| ? *entryArg.sourceLoc | |
| : getEncodedSourceLocation(argInfo.location); | |
| BlockArgument arg = block->addArgument(entryArg.type, loc); | |
| // Add a definition of this arg to the assembly state if provided. | |
| if (state.asmState) | |
| state.asmState->addDefinition(arg, argInfo.location); | |
| // Record the definition for this argument. | |
| if (addDefinition(argInfo, arg)) | |
| return failure(); | |
| } | |
| } | |
| if (parseBlock(block)) | |
| return failure(); | |
| // Verify that no other arguments were parsed. | |
| if (!entryArguments.empty() && | |
| block->getNumArguments() > entryArguments.size()) { | |
| return emitError("entry block arguments were already defined"); | |
| } | |
| // Parse the rest of the region. | |
| region.push_back(owningBlock.release()); | |
| while (getToken().isNot(Token::r_brace)) { | |
| Block *newBlock = nullptr; | |
| if (parseBlock(newBlock)) | |
| return failure(); | |
| region.push_back(newBlock); | |
| } | |
| // Pop the SSA value scope for this region. | |
| if (popSSANameScope()) | |
| return failure(); | |
| // Reset the original insertion point. | |
| opBuilder.restoreInsertionPoint(currentPt); | |
| return success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Block Parsing | |
| //===----------------------------------------------------------------------===// | |
| /// Block declaration. | |
| /// | |
| /// block ::= block-label? operation* | |
| /// block-label ::= block-id block-arg-list? `:` | |
| /// block-id ::= caret-id | |
| /// block-arg-list ::= `(` ssa-id-and-type-list? `)` | |
| /// | |
| ParseResult OperationParser::parseBlock(Block *&block) { | |
| // The first block of a region may already exist, if it does the caret | |
| // identifier is optional. | |
| if (block && getToken().isNot(Token::caret_identifier)) | |
| return parseBlockBody(block); | |
| SMLoc nameLoc = getToken().getLoc(); | |
| auto name = getTokenSpelling(); | |
| if (parseToken(Token::caret_identifier, "expected block name")) | |
| return failure(); | |
| // Define the block with the specified name. | |
| auto &blockAndLoc = getBlockInfoByName(name); | |
| blockAndLoc.loc = nameLoc; | |
| // Use a unique pointer for in-flight block being parsed. Release ownership | |
| // only in the case of a successful parse. This ensures that the Block | |
| // allocated is released if the parse fails and control returns early. | |
| std::unique_ptr<Block> inflightBlock; | |
| llvm::scope_exit cleanupOnFailure([&] { | |
| if (inflightBlock) | |
| inflightBlock->dropAllDefinedValueUses(); | |
| }); | |
| // If a block has yet to be set, this is a new definition. If the caller | |
| // provided a block, use it. Otherwise create a new one. | |
| if (!blockAndLoc.block) { | |
| if (block) { | |
| blockAndLoc.block = block; | |
| } else { | |
| inflightBlock = std::make_unique<Block>(); | |
| blockAndLoc.block = inflightBlock.get(); | |
| } | |
| // Otherwise, the block has a forward declaration. Forward declarations are | |
| // removed once defined, so if we are defining a existing block and it is | |
| // not a forward declaration, then it is a redeclaration. Fail if the block | |
| // was already defined. | |
| } else if (!eraseForwardRef(blockAndLoc.block)) { | |
| return emitError(nameLoc, "redefinition of block '") << name << "'"; | |
| } else { | |
| // This was a forward reference block that is now floating. Keep track of it | |
| // as inflight in case of error, so that it gets cleaned up properly. | |
| inflightBlock.reset(blockAndLoc.block); | |
| } | |
| // Populate the high level assembly state if necessary. | |
| if (state.asmState) | |
| state.asmState->addDefinition(blockAndLoc.block, nameLoc); | |
| block = blockAndLoc.block; | |
| // If an argument list is present, parse it. | |
| if (getToken().is(Token::l_paren)) | |
| if (parseOptionalBlockArgList(block)) | |
| return failure(); | |
| if (parseToken(Token::colon, "expected ':' after block name")) | |
| return failure(); | |
| // Parse the body of the block. | |
| ParseResult res = parseBlockBody(block); | |
| // If parsing was successful, drop the inflight block. We relinquish ownership | |
| // back up to the caller. | |
| if (succeeded(res)) | |
| (void)inflightBlock.release(); | |
| return res; | |
| } | |
| ParseResult OperationParser::parseBlockBody(Block *block) { | |
| // Set the insertion point to the end of the block to parse. | |
| opBuilder.setInsertionPointToEnd(block); | |
| // Parse the list of operations that make up the body of the block. | |
| while (getToken().isNot(Token::caret_identifier, Token::r_brace)) | |
| if (parseOperation()) | |
| return failure(); | |
| return success(); | |
| } | |
| /// Get the block with the specified name, creating it if it doesn't already | |
| /// exist. The location specified is the point of use, which allows | |
| /// us to diagnose references to blocks that are not defined precisely. | |
| Block *OperationParser::getBlockNamed(StringRef name, SMLoc loc) { | |
| BlockDefinition &blockDef = getBlockInfoByName(name); | |
| if (!blockDef.block) { | |
| blockDef = {new Block(), loc}; | |
| insertForwardRef(blockDef.block, blockDef.loc); | |
| } | |
| // Populate the high level assembly state if necessary. | |
| if (state.asmState) | |
| state.asmState->addUses(blockDef.block, loc); | |
| return blockDef.block; | |
| } | |
| /// Parse a (possibly empty) list of SSA operands with types as block arguments | |
| /// enclosed in parentheses. | |
| /// | |
| /// value-id-and-type-list ::= value-id-and-type (`,` ssa-id-and-type)* | |
| /// block-arg-list ::= `(` value-id-and-type-list? `)` | |
| /// | |
| ParseResult OperationParser::parseOptionalBlockArgList(Block *owner) { | |
| if (getToken().is(Token::r_brace)) | |
| return success(); | |
| // If the block already has arguments, then we're handling the entry block. | |
| // Parse and register the names for the arguments, but do not add them. | |
| bool definingExistingArgs = owner->getNumArguments() != 0; | |
| unsigned nextArgument = 0; | |
| return parseCommaSeparatedList(Delimiter::Paren, [&]() -> ParseResult { | |
| return parseSSADefOrUseAndType( | |
| [&](UnresolvedOperand useInfo, Type type) -> ParseResult { | |
| BlockArgument arg; | |
| // If we are defining existing arguments, ensure that the argument | |
| // has already been created with the right type. | |
| if (definingExistingArgs) { | |
| // Otherwise, ensure that this argument has already been created. | |
| if (nextArgument >= owner->getNumArguments()) | |
| return emitError("too many arguments specified in argument list"); | |
| // Finally, make sure the existing argument has the correct type. | |
| arg = owner->getArgument(nextArgument++); | |
| if (arg.getType() != type) | |
| return emitError("argument and block argument type mismatch"); | |
| } else { | |
| auto loc = getEncodedSourceLocation(useInfo.location); | |
| arg = owner->addArgument(type, loc); | |
| } | |
| // If the argument has an explicit loc(...) specifier, parse and apply | |
| // it. | |
| if (parseTrailingLocationSpecifier(arg)) | |
| return failure(); | |
| // Mark this block argument definition in the parser state if it was | |
| // provided. | |
| if (state.asmState) | |
| state.asmState->addDefinition(arg, useInfo.location); | |
| return addDefinition(useInfo, arg); | |
| }); | |
| }); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Code Completion | |
| //===----------------------------------------------------------------------===// | |
| ParseResult OperationParser::codeCompleteSSAUse() { | |
| for (IsolatedSSANameScope &scope : isolatedNameScopes) { | |
| // Collect and sort SSA value names for deterministic completion ordering. | |
| SmallVector<StringRef> sortedNames; | |
| for (auto &it : scope.values) | |
| if (!it.second.empty()) | |
| sortedNames.push_back(it.getKey()); | |
| llvm::sort(sortedNames); | |
| for (StringRef name : sortedNames) { | |
| Value frontValue = scope.values[name].front().value; | |
| std::string detailData; | |
| llvm::raw_string_ostream detailOS(detailData); | |
| // If the value isn't a forward reference, we also add the name of the op | |
| // to the detail. | |
| if (auto result = dyn_cast<OpResult>(frontValue)) { | |
| if (!forwardRefPlaceholders.count(result)) | |
| detailOS << result.getOwner()->getName() << ": "; | |
| } else { | |
| detailOS << "arg #" << cast<BlockArgument>(frontValue).getArgNumber() | |
| << ": "; | |
| } | |
| // Emit the type of the values to aid with completion selection. | |
| detailOS << frontValue.getType(); | |
| // FIXME: We should define a policy for packed values, e.g. with a limit | |
| // on the detail size, but it isn't clear what would be useful right now. | |
| // For now we just only emit the first type. | |
| if (scope.values[name].size() > 1) | |
| detailOS << ", ..."; | |
| state.codeCompleteContext->appendSSAValueCompletion( | |
| name, std::move(detailData)); | |
| } | |
| } | |
| return failure(); | |
| } | |
| ParseResult OperationParser::codeCompleteBlock() { | |
| // Don't provide completions if the token isn't empty, e.g. this avoids | |
| // weirdness when we encounter a `.` within the identifier. | |
| StringRef spelling = getTokenSpelling(); | |
| if (!(spelling.empty() || spelling == "^")) | |
| return failure(); | |
| for (const auto &it : blocksByName.back()) | |
| state.codeCompleteContext->appendBlockCompletion(it.getFirst()); | |
| return failure(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Top-level entity parsing. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| /// This parser handles entities that are only valid at the top level of the | |
| /// file. | |
| class TopLevelOperationParser : public Parser { | |
| public: | |
| explicit TopLevelOperationParser(ParserState &state) : Parser(state) {} | |
| /// Parse a set of operations into the end of the given Block. | |
| ParseResult parse(Block *topLevelBlock, Location parserLoc); | |
| private: | |
| /// Parse an attribute alias declaration. | |
| /// | |
| /// attribute-alias-def ::= '#' alias-name `=` attribute-value | |
| /// | |
| ParseResult parseAttributeAliasDef(); | |
| /// Parse a type alias declaration. | |
| /// | |
| /// type-alias-def ::= '!' alias-name `=` type | |
| /// | |
| ParseResult parseTypeAliasDef(); | |
| /// Parse a top-level file metadata dictionary. | |
| /// | |
| /// file-metadata-dict ::= '{-#' file-metadata-entry* `#-}' | |
| /// | |
| ParseResult parseFileMetadataDictionary(); | |
| /// Parse a resource metadata dictionary. | |
| ParseResult parseResourceFileMetadata( | |
| function_ref<ParseResult(StringRef, SMLoc)> parseBody); | |
| ParseResult parseDialectResourceFileMetadata(); | |
| ParseResult parseExternalResourceFileMetadata(); | |
| }; | |
| /// This class represents an implementation of a resource entry for the MLIR | |
| /// textual format. | |
| class ParsedResourceEntry : public AsmParsedResourceEntry { | |
| public: | |
| ParsedResourceEntry(std::string key, SMLoc keyLoc, Token value, Parser &p) | |
| : key(std::move(key)), keyLoc(keyLoc), value(value), p(p) {} | |
| ~ParsedResourceEntry() override = default; | |
| StringRef getKey() const final { return key; } | |
| InFlightDiagnostic emitError() const final { return p.emitError(keyLoc); } | |
| AsmResourceEntryKind getKind() const final { | |
| if (value.isAny(Token::kw_true, Token::kw_false)) | |
| return AsmResourceEntryKind::Bool; | |
| return value.getSpelling().starts_with("\"0x") | |
| ? AsmResourceEntryKind::Blob | |
| : AsmResourceEntryKind::String; | |
| } | |
| FailureOr<bool> parseAsBool() const final { | |
| if (value.is(Token::kw_true)) | |
| return true; | |
| if (value.is(Token::kw_false)) | |
| return false; | |
| return p.emitError(value.getLoc(), | |
| "expected 'true' or 'false' value for key '" + key + | |
| "'"); | |
| } | |
| FailureOr<std::string> parseAsString() const final { | |
| if (value.isNot(Token::string)) | |
| return p.emitError(value.getLoc(), | |
| "expected string value for key '" + key + "'"); | |
| return value.getStringValue(); | |
| } | |
| FailureOr<AsmResourceBlob> | |
| parseAsBlob(BlobAllocatorFn allocator) const final { | |
| // Blob data within then textual format is represented as a hex string. | |
| // TODO: We could avoid an additional alloc+copy here if we pre-allocated | |
| // the buffer to use during hex processing. | |
| std::optional<std::string> blobData = | |
| value.is(Token::string) ? value.getHexStringValue() : std::nullopt; | |
| if (!blobData) | |
| return p.emitError(value.getLoc(), | |
| "expected hex string blob for key '" + key + "'"); | |
| // Extract the alignment of the blob data, which gets stored at the | |
| // beginning of the string. | |
| if (blobData->size() < sizeof(uint32_t)) { | |
| return p.emitError(value.getLoc(), | |
| "expected hex string blob for key '" + key + | |
| "' to encode alignment in first 4 bytes"); | |
| } | |
| llvm::support::ulittle32_t align; | |
| memcpy(&align, blobData->data(), sizeof(uint32_t)); | |
| if (align && !llvm::isPowerOf2_32(align)) { | |
| return p.emitError(value.getLoc(), | |
| "expected hex string blob for key '" + key + | |
| "' to encode alignment in first 4 bytes, but got " | |
| "non-power-of-2 value: " + | |
| Twine(align)); | |
| } | |
| // Get the data portion of the blob. | |
| StringRef data = StringRef(*blobData).drop_front(sizeof(uint32_t)); | |
| if (data.empty()) | |
| return AsmResourceBlob(); | |
| // Allocate memory for the blob using the provided allocator and copy the | |
| // data into it. | |
| AsmResourceBlob blob = allocator(data.size(), align); | |
| assert(llvm::isAddrAligned(llvm::Align(align), blob.getData().data()) && | |
| blob.isMutable() && | |
| "blob allocator did not return a properly aligned address"); | |
| memcpy(blob.getMutableData().data(), data.data(), data.size()); | |
| return blob; | |
| } | |
| private: | |
| std::string key; | |
| SMLoc keyLoc; | |
| Token value; | |
| Parser &p; | |
| }; | |
| } // namespace | |
| ParseResult TopLevelOperationParser::parseAttributeAliasDef() { | |
| assert(getToken().is(Token::hash_identifier)); | |
| StringRef aliasName = getTokenSpelling().drop_front(); | |
| // Check for redefinitions. | |
| if (state.symbols.attributeAliasDefinitions.count(aliasName) > 0) | |
| return emitError("redefinition of attribute alias id '" + aliasName + "'"); | |
| // Make sure this isn't invading the dialect attribute namespace. | |
| if (aliasName.contains('.')) | |
| return emitError("attribute names with a '.' are reserved for " | |
| "dialect-defined names"); | |
| SMRange location = getToken().getLocRange(); | |
| consumeToken(Token::hash_identifier); | |
| // Parse the '='. | |
| if (parseToken(Token::equal, "expected '=' in attribute alias definition")) | |
| return failure(); | |
| // Parse the attribute value. | |
| Attribute attr = parseAttribute(); | |
| if (!attr) | |
| return failure(); | |
| // Register this alias with the parser state. | |
| if (state.asmState) | |
| state.asmState->addAttrAliasDefinition(aliasName, location, attr); | |
| state.symbols.attributeAliasDefinitions[aliasName] = attr; | |
| return success(); | |
| } | |
| ParseResult TopLevelOperationParser::parseTypeAliasDef() { | |
| assert(getToken().is(Token::exclamation_identifier)); | |
| StringRef aliasName = getTokenSpelling().drop_front(); | |
| // Check for redefinitions. | |
| if (state.symbols.typeAliasDefinitions.count(aliasName) > 0) | |
| return emitError("redefinition of type alias id '" + aliasName + "'"); | |
| // Make sure this isn't invading the dialect type namespace. | |
| if (aliasName.contains('.')) | |
| return emitError("type names with a '.' are reserved for " | |
| "dialect-defined names"); | |
| SMRange location = getToken().getLocRange(); | |
| consumeToken(Token::exclamation_identifier); | |
| // Parse the '='. | |
| if (parseToken(Token::equal, "expected '=' in type alias definition")) | |
| return failure(); | |
| // Parse the type. | |
| Type aliasedType = parseType(); | |
| if (!aliasedType) | |
| return failure(); | |
| // Register this alias with the parser state. | |
| if (state.asmState) | |
| state.asmState->addTypeAliasDefinition(aliasName, location, aliasedType); | |
| state.symbols.typeAliasDefinitions.try_emplace(aliasName, aliasedType); | |
| return success(); | |
| } | |
| ParseResult TopLevelOperationParser::parseFileMetadataDictionary() { | |
| consumeToken(Token::file_metadata_begin); | |
| return parseCommaSeparatedListUntil( | |
| Token::file_metadata_end, [&]() -> ParseResult { | |
| // Parse the key of the metadata dictionary. | |
| SMLoc keyLoc = getToken().getLoc(); | |
| StringRef key; | |
| if (failed(parseOptionalKeyword(&key))) | |
| return emitError("expected identifier key in file " | |
| "metadata dictionary"); | |
| if (parseToken(Token::colon, "expected ':'")) | |
| return failure(); | |
| // Process the metadata entry. | |
| if (key == "dialect_resources") | |
| return parseDialectResourceFileMetadata(); | |
| if (key == "external_resources") | |
| return parseExternalResourceFileMetadata(); | |
| return emitError(keyLoc, "unknown key '" + key + | |
| "' in file metadata dictionary"); | |
| }); | |
| } | |
| ParseResult TopLevelOperationParser::parseResourceFileMetadata( | |
| function_ref<ParseResult(StringRef, SMLoc)> parseBody) { | |
| if (parseToken(Token::l_brace, "expected '{'")) | |
| return failure(); | |
| return parseCommaSeparatedListUntil(Token::r_brace, [&]() -> ParseResult { | |
| // Parse the top-level name entry. | |
| SMLoc nameLoc = getToken().getLoc(); | |
| StringRef name; | |
| if (failed(parseOptionalKeyword(&name))) | |
| return emitError("expected identifier key for 'resource' entry"); | |
| if (parseToken(Token::colon, "expected ':'") || | |
| parseToken(Token::l_brace, "expected '{'")) | |
| return failure(); | |
| return parseBody(name, nameLoc); | |
| }); | |
| } | |
| ParseResult TopLevelOperationParser::parseDialectResourceFileMetadata() { | |
| return parseResourceFileMetadata([&](StringRef name, | |
| SMLoc nameLoc) -> ParseResult { | |
| // Lookup the dialect and check that it can handle a resource entry. | |
| Dialect *dialect = getContext()->getOrLoadDialect(name); | |
| if (!dialect) | |
| return emitError(nameLoc, "dialect '" + name + "' is unknown"); | |
| const auto *handler = dyn_cast<OpAsmDialectInterface>(dialect); | |
| if (!handler) { | |
| return emitError() << "unexpected 'resource' section for dialect '" | |
| << dialect->getNamespace() << "'"; | |
| } | |
| return parseCommaSeparatedListUntil(Token::r_brace, [&]() -> ParseResult { | |
| // Parse the name of the resource entry. | |
| SMLoc keyLoc = getToken().getLoc(); | |
| std::string key; | |
| if (failed(parseResourceHandle(handler, key)) || | |
| parseToken(Token::colon, "expected ':'")) | |
| return failure(); | |
| Token valueTok = getToken(); | |
| consumeToken(); | |
| ParsedResourceEntry entry(key, keyLoc, valueTok, *this); | |
| return handler->parseResource(entry); | |
| }); | |
| }); | |
| } | |
| ParseResult TopLevelOperationParser::parseExternalResourceFileMetadata() { | |
| return parseResourceFileMetadata([&](StringRef name, | |
| SMLoc nameLoc) -> ParseResult { | |
| AsmResourceParser *handler = state.config.getResourceParser(name); | |
| // TODO: Should we require handling external resources in some scenarios? | |
| if (!handler) { | |
| emitWarning(getEncodedSourceLocation(nameLoc)) | |
| << "ignoring unknown external resources for '" << name << "'"; | |
| } | |
| return parseCommaSeparatedListUntil(Token::r_brace, [&]() -> ParseResult { | |
| // Parse the name of the resource entry. | |
| SMLoc keyLoc = getToken().getLoc(); | |
| std::string key; | |
| if (failed(parseOptionalKeywordOrString(&key))) | |
| return emitError( | |
| "expected identifier key for 'external_resources' entry"); | |
| if (parseToken(Token::colon, "expected ':'")) | |
| return failure(); | |
| Token valueTok = getToken(); | |
| consumeToken(); | |
| if (!handler) | |
| return success(); | |
| ParsedResourceEntry entry(key, keyLoc, valueTok, *this); | |
| return handler->parseResource(entry); | |
| }); | |
| }); | |
| } | |
| ParseResult TopLevelOperationParser::parse(Block *topLevelBlock, | |
| Location parserLoc) { | |
| // Create a top-level operation to contain the parsed state. | |
| OwningOpRef<ModuleOp> topLevelOp(ModuleOp::create(parserLoc)); | |
| OperationParser opParser(state, topLevelOp.get()); | |
| while (true) { | |
| switch (getToken().getKind()) { | |
| default: | |
| // Parse a top-level operation. | |
| if (opParser.parseOperation()) | |
| return failure(); | |
| break; | |
| // If we got to the end of the file, then we're done. | |
| case Token::eof: { | |
| if (opParser.finalize()) | |
| return failure(); | |
| // Splice the blocks of the parsed operation over to the provided | |
| // top-level block. | |
| auto &parsedOps = topLevelOp->getBody()->getOperations(); | |
| auto &destOps = topLevelBlock->getOperations(); | |
| destOps.splice(destOps.end(), parsedOps, parsedOps.begin(), | |
| parsedOps.end()); | |
| return success(); | |
| } | |
| // If we got an error token, then the lexer already emitted an error, just | |
| // stop. Someday we could introduce error recovery if there was demand | |
| // for it. | |
| case Token::error: | |
| return failure(); | |
| // Parse an attribute alias. | |
| case Token::hash_identifier: | |
| if (parseAttributeAliasDef()) | |
| return failure(); | |
| break; | |
| // Parse a type alias. | |
| case Token::exclamation_identifier: | |
| if (parseTypeAliasDef()) | |
| return failure(); | |
| break; | |
| // Parse a file-level metadata dictionary. | |
| case Token::file_metadata_begin: | |
| if (parseFileMetadataDictionary()) | |
| return failure(); | |
| break; | |
| } | |
| } | |
| } | |
| //===----------------------------------------------------------------------===// | |
| LogicalResult | |
| mlir::parseAsmSourceFile(const llvm::SourceMgr &sourceMgr, Block *block, | |
| const ParserConfig &config, AsmParserState *asmState, | |
| AsmParserCodeCompleteContext *codeCompleteContext) { | |
| const auto *sourceBuf = sourceMgr.getMemoryBuffer(sourceMgr.getMainFileID()); | |
| Location parserLoc = | |
| FileLineColLoc::get(config.getContext(), sourceBuf->getBufferIdentifier(), | |
| /*line=*/0, /*column=*/0); | |
| SymbolState aliasState; | |
| ParserState state(sourceMgr, config, aliasState, asmState, | |
| codeCompleteContext); | |
| return TopLevelOperationParser(state).parse(block, parserLoc); | |
| } |