2690 lines · cpp
1//===- BytecodeReader.cpp - MLIR Bytecode Reader --------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "mlir/Bytecode/BytecodeReader.h"10#include "mlir/AsmParser/AsmParser.h"11#include "mlir/Bytecode/BytecodeImplementation.h"12#include "mlir/Bytecode/BytecodeOpInterface.h"13#include "mlir/Bytecode/Encoding.h"14#include "mlir/IR/BuiltinOps.h"15#include "mlir/IR/Diagnostics.h"16#include "mlir/IR/OpImplementation.h"17#include "mlir/IR/Verifier.h"18#include "mlir/IR/Visitors.h"19#include "mlir/Support/LLVM.h"20#include "llvm/ADT/ArrayRef.h"21#include "llvm/ADT/ScopeExit.h"22#include "llvm/ADT/StringExtras.h"23#include "llvm/ADT/StringRef.h"24#include "llvm/Support/Endian.h"25#include "llvm/Support/MemoryBufferRef.h"26#include "llvm/Support/SourceMgr.h"27 28#include <cstddef>29#include <cstdint>30#include <list>31#include <memory>32#include <numeric>33#include <optional>34 35#define DEBUG_TYPE "mlir-bytecode-reader"36 37using namespace mlir;38 39/// Stringify the given section ID.40static std::string toString(bytecode::Section::ID sectionID) {41 switch (sectionID) {42 case bytecode::Section::kString:43 return "String (0)";44 case bytecode::Section::kDialect:45 return "Dialect (1)";46 case bytecode::Section::kAttrType:47 return "AttrType (2)";48 case bytecode::Section::kAttrTypeOffset:49 return "AttrTypeOffset (3)";50 case bytecode::Section::kIR:51 return "IR (4)";52 case bytecode::Section::kResource:53 return "Resource (5)";54 case bytecode::Section::kResourceOffset:55 return "ResourceOffset (6)";56 case bytecode::Section::kDialectVersions:57 return "DialectVersions (7)";58 case bytecode::Section::kProperties:59 return "Properties (8)";60 default:61 return ("Unknown (" + Twine(static_cast<unsigned>(sectionID)) + ")").str();62 }63}64 65/// Returns true if the given top-level section ID is optional.66static bool isSectionOptional(bytecode::Section::ID sectionID, int version) {67 switch (sectionID) {68 case bytecode::Section::kString:69 case bytecode::Section::kDialect:70 case bytecode::Section::kAttrType:71 case bytecode::Section::kAttrTypeOffset:72 case bytecode::Section::kIR:73 return false;74 case bytecode::Section::kResource:75 case bytecode::Section::kResourceOffset:76 case bytecode::Section::kDialectVersions:77 return true;78 case bytecode::Section::kProperties:79 return version < bytecode::kNativePropertiesEncoding;80 default:81 llvm_unreachable("unknown section ID");82 }83}84 85//===----------------------------------------------------------------------===//86// EncodingReader87//===----------------------------------------------------------------------===//88 89namespace {90class EncodingReader {91public:92 explicit EncodingReader(ArrayRef<uint8_t> contents, Location fileLoc)93 : buffer(contents), dataIt(buffer.begin()), fileLoc(fileLoc) {}94 explicit EncodingReader(StringRef contents, Location fileLoc)95 : EncodingReader({reinterpret_cast<const uint8_t *>(contents.data()),96 contents.size()},97 fileLoc) {}98 99 /// Returns true if the entire section has been read.100 bool empty() const { return dataIt == buffer.end(); }101 102 /// Returns the remaining size of the bytecode.103 size_t size() const { return buffer.end() - dataIt; }104 105 /// Align the current reader position to the specified alignment.106 LogicalResult alignTo(unsigned alignment) {107 if (!llvm::isPowerOf2_32(alignment))108 return emitError("expected alignment to be a power-of-two");109 110 auto isUnaligned = [&](const uint8_t *ptr) {111 return ((uintptr_t)ptr & (alignment - 1)) != 0;112 };113 114 // Shift the reader position to the next alignment boundary.115 // Note: this assumes the pointer alignment matches the alignment of the116 // data from the start of the buffer. In other words, this code is only117 // valid if `dataIt` is offsetting into an already aligned buffer.118 while (isUnaligned(dataIt)) {119 uint8_t padding;120 if (failed(parseByte(padding)))121 return failure();122 if (padding != bytecode::kAlignmentByte) {123 return emitError("expected alignment byte (0xCB), but got: '0x" +124 llvm::utohexstr(padding) + "'");125 }126 }127 128 // Ensure the data iterator is now aligned. This case is unlikely because we129 // *just* went through the effort to align the data iterator.130 if (LLVM_UNLIKELY(isUnaligned(dataIt))) {131 return emitError("expected data iterator aligned to ", alignment,132 ", but got pointer: '0x" +133 llvm::utohexstr((uintptr_t)dataIt) + "'");134 }135 136 return success();137 }138 139 /// Emit an error using the given arguments.140 template <typename... Args>141 InFlightDiagnostic emitError(Args &&...args) const {142 return ::emitError(fileLoc).append(std::forward<Args>(args)...);143 }144 InFlightDiagnostic emitError() const { return ::emitError(fileLoc); }145 146 /// Parse a single byte from the stream.147 template <typename T>148 LogicalResult parseByte(T &value) {149 if (empty())150 return emitError("attempting to parse a byte at the end of the bytecode");151 value = static_cast<T>(*dataIt++);152 return success();153 }154 /// Parse a range of bytes of 'length' into the given result.155 LogicalResult parseBytes(size_t length, ArrayRef<uint8_t> &result) {156 if (length > size()) {157 return emitError("attempting to parse ", length, " bytes when only ",158 size(), " remain");159 }160 result = {dataIt, length};161 dataIt += length;162 return success();163 }164 /// Parse a range of bytes of 'length' into the given result, which can be165 /// assumed to be large enough to hold `length`.166 LogicalResult parseBytes(size_t length, uint8_t *result) {167 if (length > size()) {168 return emitError("attempting to parse ", length, " bytes when only ",169 size(), " remain");170 }171 memcpy(result, dataIt, length);172 dataIt += length;173 return success();174 }175 176 /// Parse an aligned blob of data, where the alignment was encoded alongside177 /// the data.178 LogicalResult parseBlobAndAlignment(ArrayRef<uint8_t> &data,179 uint64_t &alignment) {180 uint64_t dataSize;181 if (failed(parseVarInt(alignment)) || failed(parseVarInt(dataSize)) ||182 failed(alignTo(alignment)))183 return failure();184 return parseBytes(dataSize, data);185 }186 187 /// Parse a variable length encoded integer from the byte stream. The first188 /// encoded byte contains a prefix in the low bits indicating the encoded189 /// length of the value. This length prefix is a bit sequence of '0's followed190 /// by a '1'. The number of '0' bits indicate the number of _additional_ bytes191 /// (not including the prefix byte). All remaining bits in the first byte,192 /// along with all of the bits in additional bytes, provide the value of the193 /// integer encoded in little-endian order.194 LogicalResult parseVarInt(uint64_t &result) {195 // Parse the first byte of the encoding, which contains the length prefix.196 if (failed(parseByte(result)))197 return failure();198 199 // Handle the overwhelmingly common case where the value is stored in a200 // single byte. In this case, the first bit is the `1` marker bit.201 if (LLVM_LIKELY(result & 1)) {202 result >>= 1;203 return success();204 }205 206 // Handle the overwhelming uncommon case where the value required all 8207 // bytes (i.e. a really really big number). In this case, the marker byte is208 // all zeros: `00000000`.209 if (LLVM_UNLIKELY(result == 0)) {210 llvm::support::ulittle64_t resultLE;211 if (failed(parseBytes(sizeof(resultLE),212 reinterpret_cast<uint8_t *>(&resultLE))))213 return failure();214 result = resultLE;215 return success();216 }217 return parseMultiByteVarInt(result);218 }219 220 /// Parse a signed variable length encoded integer from the byte stream. A221 /// signed varint is encoded as a normal varint with zigzag encoding applied,222 /// i.e. the low bit of the value is used to indicate the sign.223 LogicalResult parseSignedVarInt(uint64_t &result) {224 if (failed(parseVarInt(result)))225 return failure();226 // Essentially (but using unsigned): (x >> 1) ^ -(x & 1)227 result = (result >> 1) ^ (~(result & 1) + 1);228 return success();229 }230 231 /// Parse a variable length encoded integer whose low bit is used to encode an232 /// unrelated flag, i.e: `(integerValue << 1) | (flag ? 1 : 0)`.233 LogicalResult parseVarIntWithFlag(uint64_t &result, bool &flag) {234 if (failed(parseVarInt(result)))235 return failure();236 flag = result & 1;237 result >>= 1;238 return success();239 }240 241 /// Skip the first `length` bytes within the reader.242 LogicalResult skipBytes(size_t length) {243 if (length > size()) {244 return emitError("attempting to skip ", length, " bytes when only ",245 size(), " remain");246 }247 dataIt += length;248 return success();249 }250 251 /// Parse a null-terminated string into `result` (without including the NUL252 /// terminator).253 LogicalResult parseNullTerminatedString(StringRef &result) {254 const char *startIt = (const char *)dataIt;255 const char *nulIt = (const char *)memchr(startIt, 0, size());256 if (!nulIt)257 return emitError(258 "malformed null-terminated string, no null character found");259 260 result = StringRef(startIt, nulIt - startIt);261 dataIt = (const uint8_t *)nulIt + 1;262 return success();263 }264 265 /// Validate that the alignment requested in the section is valid.266 using ValidateAlignmentFn = function_ref<LogicalResult(unsigned alignment)>;267 268 /// Parse a section header, placing the kind of section in `sectionID` and the269 /// contents of the section in `sectionData`.270 LogicalResult parseSection(bytecode::Section::ID §ionID,271 ValidateAlignmentFn alignmentValidator,272 ArrayRef<uint8_t> §ionData) {273 uint8_t sectionIDAndHasAlignment;274 uint64_t length;275 if (failed(parseByte(sectionIDAndHasAlignment)) ||276 failed(parseVarInt(length)))277 return failure();278 279 // Extract the section ID and whether the section is aligned. The high bit280 // of the ID is the alignment flag.281 sectionID = static_cast<bytecode::Section::ID>(sectionIDAndHasAlignment &282 0b01111111);283 bool hasAlignment = sectionIDAndHasAlignment & 0b10000000;284 285 // Check that the section is actually valid before trying to process its286 // data.287 if (sectionID >= bytecode::Section::kNumSections)288 return emitError("invalid section ID: ", unsigned(sectionID));289 290 // Process the section alignment if present.291 if (hasAlignment) {292 // Read the requested alignment from the bytecode parser.293 uint64_t alignment;294 if (failed(parseVarInt(alignment)))295 return failure();296 297 // Check that the requested alignment must not exceed the alignment of298 // the root buffer itself. Otherwise we cannot guarantee that pointers299 // derived from this buffer will actually satisfy the requested alignment300 // globally.301 //302 // Consider a bytecode buffer that is guaranteed to be 8k aligned, but not303 // 16k aligned (e.g. absolute address 40960. If a section inside this304 // buffer declares a 16k alignment requirement, two problems can arise:305 //306 // (a) If we "align forward" the current pointer to the next307 // 16k boundary, the amount of padding we skip depends on the308 // buffer's starting address. For example:309 //310 // buffer_start = 40960311 // next 16k boundary = 49152312 // bytes skipped = 49152 - 40960 = 8192313 //314 // This leaves behind variable padding that could be misinterpreted315 // as part of the next section.316 //317 // (b) If we align relative to the buffer start, we may318 // obtain addresses that are multiples of "buffer_start +319 // section_alignment" rather than truly globally aligned320 // addresses. For example:321 //322 // buffer_start = 40960 (5×8k, 8k aligned but not 16k)323 // offset = 16384 (first multiple of 16k)324 // section_ptr = 40960 + 16384 = 57344325 //326 // 57344 is 8k aligned but not 16k aligned.327 // Any consumer expecting true 16k alignment would see this as a328 // violation.329 if (failed(alignmentValidator(alignment)))330 return emitError("failed to align section ID: ", unsigned(sectionID));331 332 // Align the buffer.333 if (failed(alignTo(alignment)))334 return failure();335 }336 337 // Parse the actual section data.338 return parseBytes(static_cast<size_t>(length), sectionData);339 }340 341 Location getLoc() const { return fileLoc; }342 343private:344 /// Parse a variable length encoded integer from the byte stream. This method345 /// is a fallback when the number of bytes used to encode the value is greater346 /// than 1, but less than the max (9). The provided `result` value can be347 /// assumed to already contain the first byte of the value.348 /// NOTE: This method is marked noinline to avoid pessimizing the common case349 /// of single byte encoding.350 LLVM_ATTRIBUTE_NOINLINE LogicalResult parseMultiByteVarInt(uint64_t &result) {351 // Count the number of trailing zeros in the marker byte, this indicates the352 // number of trailing bytes that are part of the value. We use `uint32_t`353 // here because we only care about the first byte, and so that be actually354 // get ctz intrinsic calls when possible (the `uint8_t` overload uses a loop355 // implementation).356 uint32_t numBytes = llvm::countr_zero<uint32_t>(result);357 assert(numBytes > 0 && numBytes <= 7 &&358 "unexpected number of trailing zeros in varint encoding");359 360 // Parse in the remaining bytes of the value.361 llvm::support::ulittle64_t resultLE(result);362 if (failed(363 parseBytes(numBytes, reinterpret_cast<uint8_t *>(&resultLE) + 1)))364 return failure();365 366 // Shift out the low-order bits that were used to mark how the value was367 // encoded.368 result = resultLE >> (numBytes + 1);369 return success();370 }371 372 /// The bytecode buffer.373 ArrayRef<uint8_t> buffer;374 375 /// The current iterator within the 'buffer'.376 const uint8_t *dataIt;377 378 /// A location for the bytecode used to report errors.379 Location fileLoc;380};381} // namespace382 383/// Resolve an index into the given entry list. `entry` may either be a384/// reference, in which case it is assigned to the corresponding value in385/// `entries`, or a pointer, in which case it is assigned to the address of the386/// element in `entries`.387template <typename RangeT, typename T>388static LogicalResult resolveEntry(EncodingReader &reader, RangeT &entries,389 uint64_t index, T &entry,390 StringRef entryStr) {391 if (index >= entries.size())392 return reader.emitError("invalid ", entryStr, " index: ", index);393 394 // If the provided entry is a pointer, resolve to the address of the entry.395 if constexpr (std::is_convertible_v<llvm::detail::ValueOfRange<RangeT>, T>)396 entry = entries[index];397 else398 entry = &entries[index];399 return success();400}401 402/// Parse and resolve an index into the given entry list.403template <typename RangeT, typename T>404static LogicalResult parseEntry(EncodingReader &reader, RangeT &entries,405 T &entry, StringRef entryStr) {406 uint64_t entryIdx;407 if (failed(reader.parseVarInt(entryIdx)))408 return failure();409 return resolveEntry(reader, entries, entryIdx, entry, entryStr);410}411 412//===----------------------------------------------------------------------===//413// StringSectionReader414//===----------------------------------------------------------------------===//415 416namespace {417/// This class is used to read references to the string section from the418/// bytecode.419class StringSectionReader {420public:421 /// Initialize the string section reader with the given section data.422 LogicalResult initialize(Location fileLoc, ArrayRef<uint8_t> sectionData);423 424 /// Parse a shared string from the string section. The shared string is425 /// encoded using an index to a corresponding string in the string section.426 LogicalResult parseString(EncodingReader &reader, StringRef &result) const {427 return parseEntry(reader, strings, result, "string");428 }429 430 /// Parse a shared string from the string section. The shared string is431 /// encoded using an index to a corresponding string in the string section.432 /// This variant parses a flag compressed with the index.433 LogicalResult parseStringWithFlag(EncodingReader &reader, StringRef &result,434 bool &flag) const {435 uint64_t entryIdx;436 if (failed(reader.parseVarIntWithFlag(entryIdx, flag)))437 return failure();438 return parseStringAtIndex(reader, entryIdx, result);439 }440 441 /// Parse a shared string from the string section. The shared string is442 /// encoded using an index to a corresponding string in the string section.443 LogicalResult parseStringAtIndex(EncodingReader &reader, uint64_t index,444 StringRef &result) const {445 return resolveEntry(reader, strings, index, result, "string");446 }447 448private:449 /// The table of strings referenced within the bytecode file.450 SmallVector<StringRef> strings;451};452} // namespace453 454LogicalResult StringSectionReader::initialize(Location fileLoc,455 ArrayRef<uint8_t> sectionData) {456 EncodingReader stringReader(sectionData, fileLoc);457 458 // Parse the number of strings in the section.459 uint64_t numStrings;460 if (failed(stringReader.parseVarInt(numStrings)))461 return failure();462 strings.resize(numStrings);463 464 // Parse each of the strings. The sizes of the strings are encoded in reverse465 // order, so that's the order we populate the table.466 size_t stringDataEndOffset = sectionData.size();467 for (StringRef &string : llvm::reverse(strings)) {468 uint64_t stringSize;469 if (failed(stringReader.parseVarInt(stringSize)))470 return failure();471 if (stringDataEndOffset < stringSize) {472 return stringReader.emitError(473 "string size exceeds the available data size");474 }475 476 // Extract the string from the data, dropping the null character.477 size_t stringOffset = stringDataEndOffset - stringSize;478 string = StringRef(479 reinterpret_cast<const char *>(sectionData.data() + stringOffset),480 stringSize - 1);481 stringDataEndOffset = stringOffset;482 }483 484 // Check that the only remaining data was for the strings, i.e. the reader485 // should be at the same offset as the first string.486 if ((sectionData.size() - stringReader.size()) != stringDataEndOffset) {487 return stringReader.emitError("unexpected trailing data between the "488 "offsets for strings and their data");489 }490 return success();491}492 493//===----------------------------------------------------------------------===//494// BytecodeDialect495//===----------------------------------------------------------------------===//496 497namespace {498class DialectReader;499 500/// This struct represents a dialect entry within the bytecode.501struct BytecodeDialect {502 /// Load the dialect into the provided context if it hasn't been loaded yet.503 /// Returns failure if the dialect couldn't be loaded *and* the provided504 /// context does not allow unregistered dialects. The provided reader is used505 /// for error emission if necessary.506 LogicalResult load(const DialectReader &reader, MLIRContext *ctx);507 508 /// Return the loaded dialect, or nullptr if the dialect is unknown. This can509 /// only be called after `load`.510 Dialect *getLoadedDialect() const {511 assert(dialect &&512 "expected `load` to be invoked before `getLoadedDialect`");513 return *dialect;514 }515 516 /// The loaded dialect entry. This field is std::nullopt if we haven't517 /// attempted to load, nullptr if we failed to load, otherwise the loaded518 /// dialect.519 std::optional<Dialect *> dialect;520 521 /// The bytecode interface of the dialect, or nullptr if the dialect does not522 /// implement the bytecode interface. This field should only be checked if the523 /// `dialect` field is not std::nullopt.524 const BytecodeDialectInterface *interface = nullptr;525 526 /// The name of the dialect.527 StringRef name;528 529 /// A buffer containing the encoding of the dialect version parsed.530 ArrayRef<uint8_t> versionBuffer;531 532 /// Lazy loaded dialect version from the handle above.533 std::unique_ptr<DialectVersion> loadedVersion;534};535 536/// This struct represents an operation name entry within the bytecode.537struct BytecodeOperationName {538 BytecodeOperationName(BytecodeDialect *dialect, StringRef name,539 std::optional<bool> wasRegistered)540 : dialect(dialect), name(name), wasRegistered(wasRegistered) {}541 542 /// The loaded operation name, or std::nullopt if it hasn't been processed543 /// yet.544 std::optional<OperationName> opName;545 546 /// The dialect that owns this operation name.547 BytecodeDialect *dialect;548 549 /// The name of the operation, without the dialect prefix.550 StringRef name;551 552 /// Whether this operation was registered when the bytecode was produced.553 /// This flag is populated when bytecode version >=kNativePropertiesEncoding.554 std::optional<bool> wasRegistered;555};556} // namespace557 558/// Parse a single dialect group encoded in the byte stream.559static LogicalResult parseDialectGrouping(560 EncodingReader &reader,561 MutableArrayRef<std::unique_ptr<BytecodeDialect>> dialects,562 function_ref<LogicalResult(BytecodeDialect *)> entryCallback) {563 // Parse the dialect and the number of entries in the group.564 std::unique_ptr<BytecodeDialect> *dialect;565 if (failed(parseEntry(reader, dialects, dialect, "dialect")))566 return failure();567 uint64_t numEntries;568 if (failed(reader.parseVarInt(numEntries)))569 return failure();570 571 for (uint64_t i = 0; i < numEntries; ++i)572 if (failed(entryCallback(dialect->get())))573 return failure();574 return success();575}576 577//===----------------------------------------------------------------------===//578// ResourceSectionReader579//===----------------------------------------------------------------------===//580 581namespace {582/// This class is used to read the resource section from the bytecode.583class ResourceSectionReader {584public:585 /// Initialize the resource section reader with the given section data.586 LogicalResult587 initialize(Location fileLoc, const ParserConfig &config,588 MutableArrayRef<std::unique_ptr<BytecodeDialect>> dialects,589 StringSectionReader &stringReader, ArrayRef<uint8_t> sectionData,590 ArrayRef<uint8_t> offsetSectionData, DialectReader &dialectReader,591 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef);592 593 /// Parse a dialect resource handle from the resource section.594 LogicalResult parseResourceHandle(EncodingReader &reader,595 AsmDialectResourceHandle &result) const {596 return parseEntry(reader, dialectResources, result, "resource handle");597 }598 599private:600 /// The table of dialect resources within the bytecode file.601 SmallVector<AsmDialectResourceHandle> dialectResources;602 llvm::StringMap<std::string> dialectResourceHandleRenamingMap;603};604 605class ParsedResourceEntry : public AsmParsedResourceEntry {606public:607 ParsedResourceEntry(StringRef key, AsmResourceEntryKind kind,608 EncodingReader &reader, StringSectionReader &stringReader,609 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef)610 : key(key), kind(kind), reader(reader), stringReader(stringReader),611 bufferOwnerRef(bufferOwnerRef) {}612 ~ParsedResourceEntry() override = default;613 614 StringRef getKey() const final { return key; }615 616 InFlightDiagnostic emitError() const final { return reader.emitError(); }617 618 AsmResourceEntryKind getKind() const final { return kind; }619 620 FailureOr<bool> parseAsBool() const final {621 if (kind != AsmResourceEntryKind::Bool)622 return emitError() << "expected a bool resource entry, but found a "623 << toString(kind) << " entry instead";624 625 bool value;626 if (failed(reader.parseByte(value)))627 return failure();628 return value;629 }630 FailureOr<std::string> parseAsString() const final {631 if (kind != AsmResourceEntryKind::String)632 return emitError() << "expected a string resource entry, but found a "633 << toString(kind) << " entry instead";634 635 StringRef string;636 if (failed(stringReader.parseString(reader, string)))637 return failure();638 return string.str();639 }640 641 FailureOr<AsmResourceBlob>642 parseAsBlob(BlobAllocatorFn allocator) const final {643 if (kind != AsmResourceEntryKind::Blob)644 return emitError() << "expected a blob resource entry, but found a "645 << toString(kind) << " entry instead";646 647 ArrayRef<uint8_t> data;648 uint64_t alignment;649 if (failed(reader.parseBlobAndAlignment(data, alignment)))650 return failure();651 652 // If we have an extendable reference to the buffer owner, we don't need to653 // allocate a new buffer for the data, and can use the data directly.654 if (bufferOwnerRef) {655 ArrayRef<char> charData(reinterpret_cast<const char *>(data.data()),656 data.size());657 658 // Allocate an unmanager buffer which captures a reference to the owner.659 // For now we just mark this as immutable, but in the future we should660 // explore marking this as mutable when desired.661 return UnmanagedAsmResourceBlob::allocateWithAlign(662 charData, alignment,663 [bufferOwnerRef = bufferOwnerRef](void *, size_t, size_t) {});664 }665 666 // Allocate memory for the blob using the provided allocator and copy the667 // data into it.668 AsmResourceBlob blob = allocator(data.size(), alignment);669 assert(llvm::isAddrAligned(llvm::Align(alignment), blob.getData().data()) &&670 blob.isMutable() &&671 "blob allocator did not return a properly aligned address");672 memcpy(blob.getMutableData().data(), data.data(), data.size());673 return blob;674 }675 676private:677 StringRef key;678 AsmResourceEntryKind kind;679 EncodingReader &reader;680 StringSectionReader &stringReader;681 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef;682};683} // namespace684 685template <typename T>686static LogicalResult687parseResourceGroup(Location fileLoc, bool allowEmpty,688 EncodingReader &offsetReader, EncodingReader &resourceReader,689 StringSectionReader &stringReader, T *handler,690 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef,691 function_ref<StringRef(StringRef)> remapKey = {},692 function_ref<LogicalResult(StringRef)> processKeyFn = {}) {693 uint64_t numResources;694 if (failed(offsetReader.parseVarInt(numResources)))695 return failure();696 697 for (uint64_t i = 0; i < numResources; ++i) {698 StringRef key;699 AsmResourceEntryKind kind;700 uint64_t resourceOffset;701 ArrayRef<uint8_t> data;702 if (failed(stringReader.parseString(offsetReader, key)) ||703 failed(offsetReader.parseVarInt(resourceOffset)) ||704 failed(offsetReader.parseByte(kind)) ||705 failed(resourceReader.parseBytes(resourceOffset, data)))706 return failure();707 708 // Process the resource key.709 if ((processKeyFn && failed(processKeyFn(key))))710 return failure();711 712 // If the resource data is empty and we allow it, don't error out when713 // parsing below, just skip it.714 if (allowEmpty && data.empty())715 continue;716 717 // Ignore the entry if we don't have a valid handler.718 if (!handler)719 continue;720 721 // Otherwise, parse the resource value.722 EncodingReader entryReader(data, fileLoc);723 key = remapKey(key);724 ParsedResourceEntry entry(key, kind, entryReader, stringReader,725 bufferOwnerRef);726 if (failed(handler->parseResource(entry)))727 return failure();728 if (!entryReader.empty()) {729 return entryReader.emitError(730 "unexpected trailing bytes in resource entry '", key, "'");731 }732 }733 return success();734}735 736LogicalResult ResourceSectionReader::initialize(737 Location fileLoc, const ParserConfig &config,738 MutableArrayRef<std::unique_ptr<BytecodeDialect>> dialects,739 StringSectionReader &stringReader, ArrayRef<uint8_t> sectionData,740 ArrayRef<uint8_t> offsetSectionData, DialectReader &dialectReader,741 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef) {742 EncodingReader resourceReader(sectionData, fileLoc);743 EncodingReader offsetReader(offsetSectionData, fileLoc);744 745 // Read the number of external resource providers.746 uint64_t numExternalResourceGroups;747 if (failed(offsetReader.parseVarInt(numExternalResourceGroups)))748 return failure();749 750 // Utility functor that dispatches to `parseResourceGroup`, but implicitly751 // provides most of the arguments.752 auto parseGroup = [&](auto *handler, bool allowEmpty = false,753 function_ref<LogicalResult(StringRef)> keyFn = {}) {754 auto resolveKey = [&](StringRef key) -> StringRef {755 auto it = dialectResourceHandleRenamingMap.find(key);756 if (it == dialectResourceHandleRenamingMap.end())757 return key;758 return it->second;759 };760 761 return parseResourceGroup(fileLoc, allowEmpty, offsetReader, resourceReader,762 stringReader, handler, bufferOwnerRef, resolveKey,763 keyFn);764 };765 766 // Read the external resources from the bytecode.767 for (uint64_t i = 0; i < numExternalResourceGroups; ++i) {768 StringRef key;769 if (failed(stringReader.parseString(offsetReader, key)))770 return failure();771 772 // Get the handler for these resources.773 // TODO: Should we require handling external resources in some scenarios?774 AsmResourceParser *handler = config.getResourceParser(key);775 if (!handler) {776 emitWarning(fileLoc) << "ignoring unknown external resources for '" << key777 << "'";778 }779 780 if (failed(parseGroup(handler)))781 return failure();782 }783 784 // Read the dialect resources from the bytecode.785 MLIRContext *ctx = fileLoc->getContext();786 while (!offsetReader.empty()) {787 std::unique_ptr<BytecodeDialect> *dialect;788 if (failed(parseEntry(offsetReader, dialects, dialect, "dialect")) ||789 failed((*dialect)->load(dialectReader, ctx)))790 return failure();791 Dialect *loadedDialect = (*dialect)->getLoadedDialect();792 if (!loadedDialect) {793 return resourceReader.emitError()794 << "dialect '" << (*dialect)->name << "' is unknown";795 }796 const auto *handler = dyn_cast<OpAsmDialectInterface>(loadedDialect);797 if (!handler) {798 return resourceReader.emitError()799 << "unexpected resources for dialect '" << (*dialect)->name << "'";800 }801 802 // Ensure that each resource is declared before being processed.803 auto processResourceKeyFn = [&](StringRef key) -> LogicalResult {804 FailureOr<AsmDialectResourceHandle> handle =805 handler->declareResource(key);806 if (failed(handle)) {807 return resourceReader.emitError()808 << "unknown 'resource' key '" << key << "' for dialect '"809 << (*dialect)->name << "'";810 }811 dialectResourceHandleRenamingMap[key] = handler->getResourceKey(*handle);812 dialectResources.push_back(*handle);813 return success();814 };815 816 // Parse the resources for this dialect. We allow empty resources because we817 // just treat these as declarations.818 if (failed(parseGroup(handler, /*allowEmpty=*/true, processResourceKeyFn)))819 return failure();820 }821 822 return success();823}824 825//===----------------------------------------------------------------------===//826// Attribute/Type Reader827//===----------------------------------------------------------------------===//828 829namespace {830/// This class provides support for reading attribute and type entries from the831/// bytecode. Attribute and Type entries are read lazily on demand, so we use832/// this reader to manage when to actually parse them from the bytecode.833class AttrTypeReader {834 /// This class represents a single attribute or type entry.835 template <typename T>836 struct Entry {837 /// The entry, or null if it hasn't been resolved yet.838 T entry = {};839 /// The parent dialect of this entry.840 BytecodeDialect *dialect = nullptr;841 /// A flag indicating if the entry was encoded using a custom encoding,842 /// instead of using the textual assembly format.843 bool hasCustomEncoding = false;844 /// The raw data of this entry in the bytecode.845 ArrayRef<uint8_t> data;846 };847 using AttrEntry = Entry<Attribute>;848 using TypeEntry = Entry<Type>;849 850public:851 AttrTypeReader(const StringSectionReader &stringReader,852 const ResourceSectionReader &resourceReader,853 const llvm::StringMap<BytecodeDialect *> &dialectsMap,854 uint64_t &bytecodeVersion, Location fileLoc,855 const ParserConfig &config)856 : stringReader(stringReader), resourceReader(resourceReader),857 dialectsMap(dialectsMap), fileLoc(fileLoc),858 bytecodeVersion(bytecodeVersion), parserConfig(config) {}859 860 /// Initialize the attribute and type information within the reader.861 LogicalResult862 initialize(MutableArrayRef<std::unique_ptr<BytecodeDialect>> dialects,863 ArrayRef<uint8_t> sectionData,864 ArrayRef<uint8_t> offsetSectionData);865 866 /// Resolve the attribute or type at the given index. Returns nullptr on867 /// failure.868 Attribute resolveAttribute(size_t index) {869 return resolveEntry(attributes, index, "Attribute");870 }871 Type resolveType(size_t index) { return resolveEntry(types, index, "Type"); }872 873 /// Parse a reference to an attribute or type using the given reader.874 LogicalResult parseAttribute(EncodingReader &reader, Attribute &result) {875 uint64_t attrIdx;876 if (failed(reader.parseVarInt(attrIdx)))877 return failure();878 result = resolveAttribute(attrIdx);879 return success(!!result);880 }881 LogicalResult parseOptionalAttribute(EncodingReader &reader,882 Attribute &result) {883 uint64_t attrIdx;884 bool flag;885 if (failed(reader.parseVarIntWithFlag(attrIdx, flag)))886 return failure();887 if (!flag)888 return success();889 result = resolveAttribute(attrIdx);890 return success(!!result);891 }892 893 LogicalResult parseType(EncodingReader &reader, Type &result) {894 uint64_t typeIdx;895 if (failed(reader.parseVarInt(typeIdx)))896 return failure();897 result = resolveType(typeIdx);898 return success(!!result);899 }900 901 template <typename T>902 LogicalResult parseAttribute(EncodingReader &reader, T &result) {903 Attribute baseResult;904 if (failed(parseAttribute(reader, baseResult)))905 return failure();906 if ((result = dyn_cast<T>(baseResult)))907 return success();908 return reader.emitError("expected attribute of type: ",909 llvm::getTypeName<T>(), ", but got: ", baseResult);910 }911 912private:913 /// Resolve the given entry at `index`.914 template <typename T>915 T resolveEntry(SmallVectorImpl<Entry<T>> &entries, size_t index,916 StringRef entryType);917 918 /// Parse an entry using the given reader that was encoded using the textual919 /// assembly format.920 template <typename T>921 LogicalResult parseAsmEntry(T &result, EncodingReader &reader,922 StringRef entryType);923 924 /// Parse an entry using the given reader that was encoded using a custom925 /// bytecode format.926 template <typename T>927 LogicalResult parseCustomEntry(Entry<T> &entry, EncodingReader &reader,928 StringRef entryType);929 930 /// The string section reader used to resolve string references when parsing931 /// custom encoded attribute/type entries.932 const StringSectionReader &stringReader;933 934 /// The resource section reader used to resolve resource references when935 /// parsing custom encoded attribute/type entries.936 const ResourceSectionReader &resourceReader;937 938 /// The map of the loaded dialects used to retrieve dialect information, such939 /// as the dialect version.940 const llvm::StringMap<BytecodeDialect *> &dialectsMap;941 942 /// The set of attribute and type entries.943 SmallVector<AttrEntry> attributes;944 SmallVector<TypeEntry> types;945 946 /// A location used for error emission.947 Location fileLoc;948 949 /// Current bytecode version being used.950 uint64_t &bytecodeVersion;951 952 /// Reference to the parser configuration.953 const ParserConfig &parserConfig;954};955 956class DialectReader : public DialectBytecodeReader {957public:958 DialectReader(AttrTypeReader &attrTypeReader,959 const StringSectionReader &stringReader,960 const ResourceSectionReader &resourceReader,961 const llvm::StringMap<BytecodeDialect *> &dialectsMap,962 EncodingReader &reader, uint64_t &bytecodeVersion)963 : attrTypeReader(attrTypeReader), stringReader(stringReader),964 resourceReader(resourceReader), dialectsMap(dialectsMap),965 reader(reader), bytecodeVersion(bytecodeVersion) {}966 967 InFlightDiagnostic emitError(const Twine &msg) const override {968 return reader.emitError(msg);969 }970 971 FailureOr<const DialectVersion *>972 getDialectVersion(StringRef dialectName) const override {973 // First check if the dialect is available in the map.974 auto dialectEntry = dialectsMap.find(dialectName);975 if (dialectEntry == dialectsMap.end())976 return failure();977 // If the dialect was found, try to load it. This will trigger reading the978 // bytecode version from the version buffer if it wasn't already processed.979 // Return failure if either of those two actions could not be completed.980 if (failed(dialectEntry->getValue()->load(*this, getLoc().getContext())) ||981 dialectEntry->getValue()->loadedVersion == nullptr)982 return failure();983 return dialectEntry->getValue()->loadedVersion.get();984 }985 986 MLIRContext *getContext() const override { return getLoc().getContext(); }987 988 uint64_t getBytecodeVersion() const override { return bytecodeVersion; }989 990 DialectReader withEncodingReader(EncodingReader &encReader) const {991 return DialectReader(attrTypeReader, stringReader, resourceReader,992 dialectsMap, encReader, bytecodeVersion);993 }994 995 Location getLoc() const { return reader.getLoc(); }996 997 //===--------------------------------------------------------------------===//998 // IR999 //===--------------------------------------------------------------------===//1000 1001 LogicalResult readAttribute(Attribute &result) override {1002 return attrTypeReader.parseAttribute(reader, result);1003 }1004 LogicalResult readOptionalAttribute(Attribute &result) override {1005 return attrTypeReader.parseOptionalAttribute(reader, result);1006 }1007 LogicalResult readType(Type &result) override {1008 return attrTypeReader.parseType(reader, result);1009 }1010 1011 FailureOr<AsmDialectResourceHandle> readResourceHandle() override {1012 AsmDialectResourceHandle handle;1013 if (failed(resourceReader.parseResourceHandle(reader, handle)))1014 return failure();1015 return handle;1016 }1017 1018 //===--------------------------------------------------------------------===//1019 // Primitives1020 //===--------------------------------------------------------------------===//1021 1022 LogicalResult readVarInt(uint64_t &result) override {1023 return reader.parseVarInt(result);1024 }1025 1026 LogicalResult readSignedVarInt(int64_t &result) override {1027 uint64_t unsignedResult;1028 if (failed(reader.parseSignedVarInt(unsignedResult)))1029 return failure();1030 result = static_cast<int64_t>(unsignedResult);1031 return success();1032 }1033 1034 FailureOr<APInt> readAPIntWithKnownWidth(unsigned bitWidth) override {1035 // Small values are encoded using a single byte.1036 if (bitWidth <= 8) {1037 uint8_t value;1038 if (failed(reader.parseByte(value)))1039 return failure();1040 return APInt(bitWidth, value);1041 }1042 1043 // Large values up to 64 bits are encoded using a single varint.1044 if (bitWidth <= 64) {1045 uint64_t value;1046 if (failed(reader.parseSignedVarInt(value)))1047 return failure();1048 return APInt(bitWidth, value);1049 }1050 1051 // Otherwise, for really big values we encode the array of active words in1052 // the value.1053 uint64_t numActiveWords;1054 if (failed(reader.parseVarInt(numActiveWords)))1055 return failure();1056 SmallVector<uint64_t, 4> words(numActiveWords);1057 for (uint64_t i = 0; i < numActiveWords; ++i)1058 if (failed(reader.parseSignedVarInt(words[i])))1059 return failure();1060 return APInt(bitWidth, words);1061 }1062 1063 FailureOr<APFloat>1064 readAPFloatWithKnownSemantics(const llvm::fltSemantics &semantics) override {1065 FailureOr<APInt> intVal =1066 readAPIntWithKnownWidth(APFloat::getSizeInBits(semantics));1067 if (failed(intVal))1068 return failure();1069 return APFloat(semantics, *intVal);1070 }1071 1072 LogicalResult readString(StringRef &result) override {1073 return stringReader.parseString(reader, result);1074 }1075 1076 LogicalResult readBlob(ArrayRef<char> &result) override {1077 uint64_t dataSize;1078 ArrayRef<uint8_t> data;1079 if (failed(reader.parseVarInt(dataSize)) ||1080 failed(reader.parseBytes(dataSize, data)))1081 return failure();1082 result = llvm::ArrayRef(reinterpret_cast<const char *>(data.data()),1083 data.size());1084 return success();1085 }1086 1087 LogicalResult readBool(bool &result) override {1088 return reader.parseByte(result);1089 }1090 1091private:1092 AttrTypeReader &attrTypeReader;1093 const StringSectionReader &stringReader;1094 const ResourceSectionReader &resourceReader;1095 const llvm::StringMap<BytecodeDialect *> &dialectsMap;1096 EncodingReader &reader;1097 uint64_t &bytecodeVersion;1098};1099 1100/// Wraps the properties section and handles reading properties out of it.1101class PropertiesSectionReader {1102public:1103 /// Initialize the properties section reader with the given section data.1104 LogicalResult initialize(Location fileLoc, ArrayRef<uint8_t> sectionData) {1105 if (sectionData.empty())1106 return success();1107 EncodingReader propReader(sectionData, fileLoc);1108 uint64_t count;1109 if (failed(propReader.parseVarInt(count)))1110 return failure();1111 // Parse the raw properties buffer.1112 if (failed(propReader.parseBytes(propReader.size(), propertiesBuffers)))1113 return failure();1114 1115 EncodingReader offsetsReader(propertiesBuffers, fileLoc);1116 offsetTable.reserve(count);1117 for (auto idx : llvm::seq<int64_t>(0, count)) {1118 (void)idx;1119 offsetTable.push_back(propertiesBuffers.size() - offsetsReader.size());1120 ArrayRef<uint8_t> rawProperties;1121 uint64_t dataSize;1122 if (failed(offsetsReader.parseVarInt(dataSize)) ||1123 failed(offsetsReader.parseBytes(dataSize, rawProperties)))1124 return failure();1125 }1126 if (!offsetsReader.empty())1127 return offsetsReader.emitError()1128 << "Broken properties section: didn't exhaust the offsets table";1129 return success();1130 }1131 1132 LogicalResult read(Location fileLoc, DialectReader &dialectReader,1133 OperationName *opName, OperationState &opState) const {1134 uint64_t propertiesIdx;1135 if (failed(dialectReader.readVarInt(propertiesIdx)))1136 return failure();1137 if (propertiesIdx >= offsetTable.size())1138 return dialectReader.emitError("Properties idx out-of-bound for ")1139 << opName->getStringRef();1140 size_t propertiesOffset = offsetTable[propertiesIdx];1141 if (propertiesIdx >= propertiesBuffers.size())1142 return dialectReader.emitError("Properties offset out-of-bound for ")1143 << opName->getStringRef();1144 1145 // Acquire the sub-buffer that represent the requested properties.1146 ArrayRef<char> rawProperties;1147 {1148 // "Seek" to the requested offset by getting a new reader with the right1149 // sub-buffer.1150 EncodingReader reader(propertiesBuffers.drop_front(propertiesOffset),1151 fileLoc);1152 // Properties are stored as a sequence of {size + raw_data}.1153 if (failed(1154 dialectReader.withEncodingReader(reader).readBlob(rawProperties)))1155 return failure();1156 }1157 // Setup a new reader to read from the `rawProperties` sub-buffer.1158 EncodingReader reader(1159 StringRef(rawProperties.begin(), rawProperties.size()), fileLoc);1160 DialectReader propReader = dialectReader.withEncodingReader(reader);1161 1162 auto *iface = opName->getInterface<BytecodeOpInterface>();1163 if (iface)1164 return iface->readProperties(propReader, opState);1165 if (opName->isRegistered())1166 return propReader.emitError(1167 "has properties but missing BytecodeOpInterface for ")1168 << opName->getStringRef();1169 // Unregistered op are storing properties as an attribute.1170 return propReader.readAttribute(opState.propertiesAttr);1171 }1172 1173private:1174 /// The properties buffer referenced within the bytecode file.1175 ArrayRef<uint8_t> propertiesBuffers;1176 1177 /// Table of offset in the buffer above.1178 SmallVector<int64_t> offsetTable;1179};1180} // namespace1181 1182LogicalResult AttrTypeReader::initialize(1183 MutableArrayRef<std::unique_ptr<BytecodeDialect>> dialects,1184 ArrayRef<uint8_t> sectionData, ArrayRef<uint8_t> offsetSectionData) {1185 EncodingReader offsetReader(offsetSectionData, fileLoc);1186 1187 // Parse the number of attribute and type entries.1188 uint64_t numAttributes, numTypes;1189 if (failed(offsetReader.parseVarInt(numAttributes)) ||1190 failed(offsetReader.parseVarInt(numTypes)))1191 return failure();1192 attributes.resize(numAttributes);1193 types.resize(numTypes);1194 1195 // A functor used to accumulate the offsets for the entries in the given1196 // range.1197 uint64_t currentOffset = 0;1198 auto parseEntries = [&](auto &&range) {1199 size_t currentIndex = 0, endIndex = range.size();1200 1201 // Parse an individual entry.1202 auto parseEntryFn = [&](BytecodeDialect *dialect) -> LogicalResult {1203 auto &entry = range[currentIndex++];1204 1205 uint64_t entrySize;1206 if (failed(offsetReader.parseVarIntWithFlag(entrySize,1207 entry.hasCustomEncoding)))1208 return failure();1209 1210 // Verify that the offset is actually valid.1211 if (currentOffset + entrySize > sectionData.size()) {1212 return offsetReader.emitError(1213 "Attribute or Type entry offset points past the end of section");1214 }1215 1216 entry.data = sectionData.slice(currentOffset, entrySize);1217 entry.dialect = dialect;1218 currentOffset += entrySize;1219 return success();1220 };1221 while (currentIndex != endIndex)1222 if (failed(parseDialectGrouping(offsetReader, dialects, parseEntryFn)))1223 return failure();1224 return success();1225 };1226 1227 // Process each of the attributes, and then the types.1228 if (failed(parseEntries(attributes)) || failed(parseEntries(types)))1229 return failure();1230 1231 // Ensure that we read everything from the section.1232 if (!offsetReader.empty()) {1233 return offsetReader.emitError(1234 "unexpected trailing data in the Attribute/Type offset section");1235 }1236 1237 return success();1238}1239 1240template <typename T>1241T AttrTypeReader::resolveEntry(SmallVectorImpl<Entry<T>> &entries, size_t index,1242 StringRef entryType) {1243 if (index >= entries.size()) {1244 emitError(fileLoc) << "invalid " << entryType << " index: " << index;1245 return {};1246 }1247 1248 // If the entry has already been resolved, there is nothing left to do.1249 Entry<T> &entry = entries[index];1250 if (entry.entry)1251 return entry.entry;1252 1253 // Parse the entry.1254 EncodingReader reader(entry.data, fileLoc);1255 1256 // Parse based on how the entry was encoded.1257 if (entry.hasCustomEncoding) {1258 if (failed(parseCustomEntry(entry, reader, entryType)))1259 return T();1260 } else if (failed(parseAsmEntry(entry.entry, reader, entryType))) {1261 return T();1262 }1263 1264 if (!reader.empty()) {1265 reader.emitError("unexpected trailing bytes after " + entryType + " entry");1266 return T();1267 }1268 return entry.entry;1269}1270 1271template <typename T>1272LogicalResult AttrTypeReader::parseAsmEntry(T &result, EncodingReader &reader,1273 StringRef entryType) {1274 StringRef asmStr;1275 if (failed(reader.parseNullTerminatedString(asmStr)))1276 return failure();1277 1278 // Invoke the MLIR assembly parser to parse the entry text.1279 size_t numRead = 0;1280 MLIRContext *context = fileLoc->getContext();1281 if constexpr (std::is_same_v<T, Type>)1282 result =1283 ::parseType(asmStr, context, &numRead, /*isKnownNullTerminated=*/true);1284 else1285 result = ::parseAttribute(asmStr, context, Type(), &numRead,1286 /*isKnownNullTerminated=*/true);1287 if (!result)1288 return failure();1289 1290 // Ensure there weren't dangling characters after the entry.1291 if (numRead != asmStr.size()) {1292 return reader.emitError("trailing characters found after ", entryType,1293 " assembly format: ", asmStr.drop_front(numRead));1294 }1295 return success();1296}1297 1298template <typename T>1299LogicalResult AttrTypeReader::parseCustomEntry(Entry<T> &entry,1300 EncodingReader &reader,1301 StringRef entryType) {1302 DialectReader dialectReader(*this, stringReader, resourceReader, dialectsMap,1303 reader, bytecodeVersion);1304 if (failed(entry.dialect->load(dialectReader, fileLoc.getContext())))1305 return failure();1306 1307 if constexpr (std::is_same_v<T, Type>) {1308 // Try parsing with callbacks first if available.1309 for (const auto &callback :1310 parserConfig.getBytecodeReaderConfig().getTypeCallbacks()) {1311 if (failed(1312 callback->read(dialectReader, entry.dialect->name, entry.entry)))1313 return failure();1314 // Early return if parsing was successful.1315 if (!!entry.entry)1316 return success();1317 1318 // Reset the reader if we failed to parse, so we can fall through the1319 // other parsing functions.1320 reader = EncodingReader(entry.data, reader.getLoc());1321 }1322 } else {1323 // Try parsing with callbacks first if available.1324 for (const auto &callback :1325 parserConfig.getBytecodeReaderConfig().getAttributeCallbacks()) {1326 if (failed(1327 callback->read(dialectReader, entry.dialect->name, entry.entry)))1328 return failure();1329 // Early return if parsing was successful.1330 if (!!entry.entry)1331 return success();1332 1333 // Reset the reader if we failed to parse, so we can fall through the1334 // other parsing functions.1335 reader = EncodingReader(entry.data, reader.getLoc());1336 }1337 }1338 1339 // Ensure that the dialect implements the bytecode interface.1340 if (!entry.dialect->interface) {1341 return reader.emitError("dialect '", entry.dialect->name,1342 "' does not implement the bytecode interface");1343 }1344 1345 if constexpr (std::is_same_v<T, Type>)1346 entry.entry = entry.dialect->interface->readType(dialectReader);1347 else1348 entry.entry = entry.dialect->interface->readAttribute(dialectReader);1349 1350 return success(!!entry.entry);1351}1352 1353//===----------------------------------------------------------------------===//1354// Bytecode Reader1355//===----------------------------------------------------------------------===//1356 1357/// This class is used to read a bytecode buffer and translate it into MLIR.1358class mlir::BytecodeReader::Impl {1359 struct RegionReadState;1360 using LazyLoadableOpsInfo =1361 std::list<std::pair<Operation *, RegionReadState>>;1362 using LazyLoadableOpsMap =1363 DenseMap<Operation *, LazyLoadableOpsInfo::iterator>;1364 1365public:1366 Impl(Location fileLoc, const ParserConfig &config, bool lazyLoading,1367 llvm::MemoryBufferRef buffer,1368 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef)1369 : config(config), fileLoc(fileLoc), lazyLoading(lazyLoading),1370 attrTypeReader(stringReader, resourceReader, dialectsMap, version,1371 fileLoc, config),1372 // Use the builtin unrealized conversion cast operation to represent1373 // forward references to values that aren't yet defined.1374 forwardRefOpState(UnknownLoc::get(config.getContext()),1375 "builtin.unrealized_conversion_cast", ValueRange(),1376 NoneType::get(config.getContext())),1377 buffer(buffer), bufferOwnerRef(bufferOwnerRef) {}1378 1379 /// Read the bytecode defined within `buffer` into the given block.1380 LogicalResult read(Block *block,1381 llvm::function_ref<bool(Operation *)> lazyOps);1382 1383 /// Return the number of ops that haven't been materialized yet.1384 int64_t getNumOpsToMaterialize() const { return lazyLoadableOpsMap.size(); }1385 1386 bool isMaterializable(Operation *op) { return lazyLoadableOpsMap.count(op); }1387 1388 /// Materialize the provided operation, invoke the lazyOpsCallback on every1389 /// newly found lazy operation.1390 LogicalResult1391 materialize(Operation *op,1392 llvm::function_ref<bool(Operation *)> lazyOpsCallback) {1393 this->lazyOpsCallback = lazyOpsCallback;1394 auto resetlazyOpsCallback =1395 llvm::make_scope_exit([&] { this->lazyOpsCallback = nullptr; });1396 auto it = lazyLoadableOpsMap.find(op);1397 assert(it != lazyLoadableOpsMap.end() &&1398 "materialize called on non-materializable op");1399 return materialize(it);1400 }1401 1402 /// Materialize all operations.1403 LogicalResult materializeAll() {1404 while (!lazyLoadableOpsMap.empty()) {1405 if (failed(materialize(lazyLoadableOpsMap.begin())))1406 return failure();1407 }1408 return success();1409 }1410 1411 /// Finalize the lazy-loading by calling back with every op that hasn't been1412 /// materialized to let the client decide if the op should be deleted or1413 /// materialized. The op is materialized if the callback returns true, deleted1414 /// otherwise.1415 LogicalResult finalize(function_ref<bool(Operation *)> shouldMaterialize) {1416 while (!lazyLoadableOps.empty()) {1417 Operation *op = lazyLoadableOps.begin()->first;1418 if (shouldMaterialize(op)) {1419 if (failed(materialize(lazyLoadableOpsMap.find(op))))1420 return failure();1421 continue;1422 }1423 op->dropAllReferences();1424 op->erase();1425 lazyLoadableOps.pop_front();1426 lazyLoadableOpsMap.erase(op);1427 }1428 return success();1429 }1430 1431private:1432 LogicalResult materialize(LazyLoadableOpsMap::iterator it) {1433 assert(it != lazyLoadableOpsMap.end() &&1434 "materialize called on non-materializable op");1435 valueScopes.emplace_back();1436 std::vector<RegionReadState> regionStack;1437 regionStack.push_back(std::move(it->getSecond()->second));1438 lazyLoadableOps.erase(it->getSecond());1439 lazyLoadableOpsMap.erase(it);1440 1441 while (!regionStack.empty())1442 if (failed(parseRegions(regionStack, regionStack.back())))1443 return failure();1444 return success();1445 }1446 1447 LogicalResult checkSectionAlignment(1448 unsigned alignment,1449 function_ref<InFlightDiagnostic(const Twine &error)> emitError) {1450 // Check that the bytecode buffer meets the requested section alignment.1451 //1452 // If it does not, the virtual address of the item in the section will1453 // not be aligned to the requested alignment.1454 //1455 // The typical case where this is necessary is the resource blob1456 // optimization in `parseAsBlob` where we reference the weights from the1457 // provided buffer instead of copying them to a new allocation.1458 const bool isGloballyAligned =1459 ((uintptr_t)buffer.getBufferStart() & (alignment - 1)) == 0;1460 1461 if (!isGloballyAligned)1462 return emitError("expected section alignment ")1463 << alignment << " but bytecode buffer 0x"1464 << Twine::utohexstr((uint64_t)buffer.getBufferStart())1465 << " is not aligned";1466 1467 return success();1468 };1469 1470 /// Return the context for this config.1471 MLIRContext *getContext() const { return config.getContext(); }1472 1473 /// Parse the bytecode version.1474 LogicalResult parseVersion(EncodingReader &reader);1475 1476 //===--------------------------------------------------------------------===//1477 // Dialect Section1478 1479 LogicalResult parseDialectSection(ArrayRef<uint8_t> sectionData);1480 1481 /// Parse an operation name reference using the given reader, and set the1482 /// `wasRegistered` flag that indicates if the bytecode was produced by a1483 /// context where opName was registered.1484 FailureOr<OperationName> parseOpName(EncodingReader &reader,1485 std::optional<bool> &wasRegistered);1486 1487 //===--------------------------------------------------------------------===//1488 // Attribute/Type Section1489 1490 /// Parse an attribute or type using the given reader.1491 template <typename T>1492 LogicalResult parseAttribute(EncodingReader &reader, T &result) {1493 return attrTypeReader.parseAttribute(reader, result);1494 }1495 LogicalResult parseType(EncodingReader &reader, Type &result) {1496 return attrTypeReader.parseType(reader, result);1497 }1498 1499 //===--------------------------------------------------------------------===//1500 // Resource Section1501 1502 LogicalResult1503 parseResourceSection(EncodingReader &reader,1504 std::optional<ArrayRef<uint8_t>> resourceData,1505 std::optional<ArrayRef<uint8_t>> resourceOffsetData);1506 1507 //===--------------------------------------------------------------------===//1508 // IR Section1509 1510 /// This struct represents the current read state of a range of regions. This1511 /// struct is used to enable iterative parsing of regions.1512 struct RegionReadState {1513 RegionReadState(Operation *op, EncodingReader *reader,1514 bool isIsolatedFromAbove)1515 : RegionReadState(op->getRegions(), reader, isIsolatedFromAbove) {}1516 RegionReadState(MutableArrayRef<Region> regions, EncodingReader *reader,1517 bool isIsolatedFromAbove)1518 : curRegion(regions.begin()), endRegion(regions.end()), reader(reader),1519 isIsolatedFromAbove(isIsolatedFromAbove) {}1520 1521 /// The current regions being read.1522 MutableArrayRef<Region>::iterator curRegion, endRegion;1523 /// This is the reader to use for this region, this pointer is pointing to1524 /// the parent region reader unless the current region is IsolatedFromAbove,1525 /// in which case the pointer is pointing to the `owningReader` which is a1526 /// section dedicated to the current region.1527 EncodingReader *reader;1528 std::unique_ptr<EncodingReader> owningReader;1529 1530 /// The number of values defined immediately within this region.1531 unsigned numValues = 0;1532 1533 /// The current blocks of the region being read.1534 SmallVector<Block *> curBlocks;1535 Region::iterator curBlock = {};1536 1537 /// The number of operations remaining to be read from the current block1538 /// being read.1539 uint64_t numOpsRemaining = 0;1540 1541 /// A flag indicating if the regions being read are isolated from above.1542 bool isIsolatedFromAbove = false;1543 };1544 1545 LogicalResult parseIRSection(ArrayRef<uint8_t> sectionData, Block *block);1546 LogicalResult parseRegions(std::vector<RegionReadState> ®ionStack,1547 RegionReadState &readState);1548 FailureOr<Operation *> parseOpWithoutRegions(EncodingReader &reader,1549 RegionReadState &readState,1550 bool &isIsolatedFromAbove);1551 1552 LogicalResult parseRegion(RegionReadState &readState);1553 LogicalResult parseBlockHeader(EncodingReader &reader,1554 RegionReadState &readState);1555 LogicalResult parseBlockArguments(EncodingReader &reader, Block *block);1556 1557 //===--------------------------------------------------------------------===//1558 // Value Processing1559 1560 /// Parse an operand reference using the given reader. Returns nullptr in the1561 /// case of failure.1562 Value parseOperand(EncodingReader &reader);1563 1564 /// Sequentially define the given value range.1565 LogicalResult defineValues(EncodingReader &reader, ValueRange values);1566 1567 /// Create a value to use for a forward reference.1568 Value createForwardRef();1569 1570 //===--------------------------------------------------------------------===//1571 // Use-list order helpers1572 1573 /// This struct is a simple storage that contains information required to1574 /// reorder the use-list of a value with respect to the pre-order traversal1575 /// ordering.1576 struct UseListOrderStorage {1577 UseListOrderStorage(bool isIndexPairEncoding,1578 SmallVector<unsigned, 4> &&indices)1579 : indices(std::move(indices)),1580 isIndexPairEncoding(isIndexPairEncoding) {};1581 /// The vector containing the information required to reorder the1582 /// use-list of a value.1583 SmallVector<unsigned, 4> indices;1584 1585 /// Whether indices represent a pair of type `(src, dst)` or it is a direct1586 /// indexing, such as `dst = order[src]`.1587 bool isIndexPairEncoding;1588 };1589 1590 /// Parse use-list order from bytecode for a range of values if available. The1591 /// range is expected to be either a block argument or an op result range. On1592 /// success, return a map of the position in the range and the use-list order1593 /// encoding. The function assumes to know the size of the range it is1594 /// processing.1595 using UseListMapT = DenseMap<unsigned, UseListOrderStorage>;1596 FailureOr<UseListMapT> parseUseListOrderForRange(EncodingReader &reader,1597 uint64_t rangeSize);1598 1599 /// Shuffle the use-chain according to the order parsed.1600 LogicalResult sortUseListOrder(Value value);1601 1602 /// Recursively visit all the values defined within topLevelOp and sort the1603 /// use-list orders according to the indices parsed.1604 LogicalResult processUseLists(Operation *topLevelOp);1605 1606 //===--------------------------------------------------------------------===//1607 // Fields1608 1609 /// This class represents a single value scope, in which a value scope is1610 /// delimited by isolated from above regions.1611 struct ValueScope {1612 /// Push a new region state onto this scope, reserving enough values for1613 /// those defined within the current region of the provided state.1614 void push(RegionReadState &readState) {1615 nextValueIDs.push_back(values.size());1616 values.resize(values.size() + readState.numValues);1617 }1618 1619 /// Pop the values defined for the current region within the provided region1620 /// state.1621 void pop(RegionReadState &readState) {1622 values.resize(values.size() - readState.numValues);1623 nextValueIDs.pop_back();1624 }1625 1626 /// The set of values defined in this scope.1627 std::vector<Value> values;1628 1629 /// The ID for the next defined value for each region current being1630 /// processed in this scope.1631 SmallVector<unsigned, 4> nextValueIDs;1632 };1633 1634 /// The configuration of the parser.1635 const ParserConfig &config;1636 1637 /// A location to use when emitting errors.1638 Location fileLoc;1639 1640 /// Flag that indicates if lazyloading is enabled.1641 bool lazyLoading;1642 1643 /// Keep track of operations that have been lazy loaded (their regions haven't1644 /// been materialized), along with the `RegionReadState` that allows to1645 /// lazy-load the regions nested under the operation.1646 LazyLoadableOpsInfo lazyLoadableOps;1647 LazyLoadableOpsMap lazyLoadableOpsMap;1648 llvm::function_ref<bool(Operation *)> lazyOpsCallback;1649 1650 /// The reader used to process attribute and types within the bytecode.1651 AttrTypeReader attrTypeReader;1652 1653 /// The version of the bytecode being read.1654 uint64_t version = 0;1655 1656 /// The producer of the bytecode being read.1657 StringRef producer;1658 1659 /// The table of IR units referenced within the bytecode file.1660 SmallVector<std::unique_ptr<BytecodeDialect>> dialects;1661 llvm::StringMap<BytecodeDialect *> dialectsMap;1662 SmallVector<BytecodeOperationName> opNames;1663 1664 /// The reader used to process resources within the bytecode.1665 ResourceSectionReader resourceReader;1666 1667 /// Worklist of values with custom use-list orders to process before the end1668 /// of the parsing.1669 DenseMap<void *, UseListOrderStorage> valueToUseListMap;1670 1671 /// The table of strings referenced within the bytecode file.1672 StringSectionReader stringReader;1673 1674 /// The table of properties referenced by the operation in the bytecode file.1675 PropertiesSectionReader propertiesReader;1676 1677 /// The current set of available IR value scopes.1678 std::vector<ValueScope> valueScopes;1679 1680 /// The global pre-order operation ordering.1681 DenseMap<Operation *, unsigned> operationIDs;1682 1683 /// A block containing the set of operations defined to create forward1684 /// references.1685 Block forwardRefOps;1686 1687 /// A block containing previously created, and no longer used, forward1688 /// reference operations.1689 Block openForwardRefOps;1690 1691 /// An operation state used when instantiating forward references.1692 OperationState forwardRefOpState;1693 1694 /// Reference to the input buffer.1695 llvm::MemoryBufferRef buffer;1696 1697 /// The optional owning source manager, which when present may be used to1698 /// extend the lifetime of the input buffer.1699 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef;1700};1701 1702LogicalResult BytecodeReader::Impl::read(1703 Block *block, llvm::function_ref<bool(Operation *)> lazyOpsCallback) {1704 EncodingReader reader(buffer.getBuffer(), fileLoc);1705 this->lazyOpsCallback = lazyOpsCallback;1706 auto resetlazyOpsCallback =1707 llvm::make_scope_exit([&] { this->lazyOpsCallback = nullptr; });1708 1709 // Skip over the bytecode header, this should have already been checked.1710 if (failed(reader.skipBytes(StringRef("ML\xefR").size())))1711 return failure();1712 // Parse the bytecode version and producer.1713 if (failed(parseVersion(reader)) ||1714 failed(reader.parseNullTerminatedString(producer)))1715 return failure();1716 1717 // Add a diagnostic handler that attaches a note that includes the original1718 // producer of the bytecode.1719 ScopedDiagnosticHandler diagHandler(getContext(), [&](Diagnostic &diag) {1720 diag.attachNote() << "in bytecode version " << version1721 << " produced by: " << producer;1722 return failure();1723 });1724 1725 const auto checkSectionAlignment = [&](unsigned alignment) {1726 return this->checkSectionAlignment(1727 alignment, [&](const auto &msg) { return reader.emitError(msg); });1728 };1729 1730 // Parse the raw data for each of the top-level sections of the bytecode.1731 std::optional<ArrayRef<uint8_t>>1732 sectionDatas[bytecode::Section::kNumSections];1733 while (!reader.empty()) {1734 // Read the next section from the bytecode.1735 bytecode::Section::ID sectionID;1736 ArrayRef<uint8_t> sectionData;1737 if (failed(1738 reader.parseSection(sectionID, checkSectionAlignment, sectionData)))1739 return failure();1740 1741 // Check for duplicate sections, we only expect one instance of each.1742 if (sectionDatas[sectionID]) {1743 return reader.emitError("duplicate top-level section: ",1744 ::toString(sectionID));1745 }1746 sectionDatas[sectionID] = sectionData;1747 }1748 // Check that all of the required sections were found.1749 for (int i = 0; i < bytecode::Section::kNumSections; ++i) {1750 bytecode::Section::ID sectionID = static_cast<bytecode::Section::ID>(i);1751 if (!sectionDatas[i] && !isSectionOptional(sectionID, version)) {1752 return reader.emitError("missing data for top-level section: ",1753 ::toString(sectionID));1754 }1755 }1756 1757 // Process the string section first.1758 if (failed(stringReader.initialize(1759 fileLoc, *sectionDatas[bytecode::Section::kString])))1760 return failure();1761 1762 // Process the properties section.1763 if (sectionDatas[bytecode::Section::kProperties] &&1764 failed(propertiesReader.initialize(1765 fileLoc, *sectionDatas[bytecode::Section::kProperties])))1766 return failure();1767 1768 // Process the dialect section.1769 if (failed(parseDialectSection(*sectionDatas[bytecode::Section::kDialect])))1770 return failure();1771 1772 // Process the resource section if present.1773 if (failed(parseResourceSection(1774 reader, sectionDatas[bytecode::Section::kResource],1775 sectionDatas[bytecode::Section::kResourceOffset])))1776 return failure();1777 1778 // Process the attribute and type section.1779 if (failed(attrTypeReader.initialize(1780 dialects, *sectionDatas[bytecode::Section::kAttrType],1781 *sectionDatas[bytecode::Section::kAttrTypeOffset])))1782 return failure();1783 1784 // Finally, process the IR section.1785 return parseIRSection(*sectionDatas[bytecode::Section::kIR], block);1786}1787 1788LogicalResult BytecodeReader::Impl::parseVersion(EncodingReader &reader) {1789 if (failed(reader.parseVarInt(version)))1790 return failure();1791 1792 // Validate the bytecode version.1793 uint64_t currentVersion = bytecode::kVersion;1794 uint64_t minSupportedVersion = bytecode::kMinSupportedVersion;1795 if (version < minSupportedVersion) {1796 return reader.emitError("bytecode version ", version,1797 " is older than the current version of ",1798 currentVersion, ", and upgrade is not supported");1799 }1800 if (version > currentVersion) {1801 return reader.emitError("bytecode version ", version,1802 " is newer than the current version ",1803 currentVersion);1804 }1805 // Override any request to lazy-load if the bytecode version is too old.1806 if (version < bytecode::kLazyLoading)1807 lazyLoading = false;1808 return success();1809}1810 1811//===----------------------------------------------------------------------===//1812// Dialect Section1813//===----------------------------------------------------------------------===//1814 1815LogicalResult BytecodeDialect::load(const DialectReader &reader,1816 MLIRContext *ctx) {1817 if (dialect)1818 return success();1819 Dialect *loadedDialect = ctx->getOrLoadDialect(name);1820 if (!loadedDialect && !ctx->allowsUnregisteredDialects()) {1821 return reader.emitError("dialect '")1822 << name1823 << "' is unknown. If this is intended, please call "1824 "allowUnregisteredDialects() on the MLIRContext, or use "1825 "-allow-unregistered-dialect with the MLIR tool used.";1826 }1827 dialect = loadedDialect;1828 1829 // If the dialect was actually loaded, check to see if it has a bytecode1830 // interface.1831 if (loadedDialect)1832 interface = dyn_cast<BytecodeDialectInterface>(loadedDialect);1833 if (!versionBuffer.empty()) {1834 if (!interface)1835 return reader.emitError("dialect '")1836 << name1837 << "' does not implement the bytecode interface, "1838 "but found a version entry";1839 EncodingReader encReader(versionBuffer, reader.getLoc());1840 DialectReader versionReader = reader.withEncodingReader(encReader);1841 loadedVersion = interface->readVersion(versionReader);1842 if (!loadedVersion)1843 return failure();1844 }1845 return success();1846}1847 1848LogicalResult1849BytecodeReader::Impl::parseDialectSection(ArrayRef<uint8_t> sectionData) {1850 EncodingReader sectionReader(sectionData, fileLoc);1851 1852 // Parse the number of dialects in the section.1853 uint64_t numDialects;1854 if (failed(sectionReader.parseVarInt(numDialects)))1855 return failure();1856 dialects.resize(numDialects);1857 1858 const auto checkSectionAlignment = [&](unsigned alignment) {1859 return this->checkSectionAlignment(alignment, [&](const auto &msg) {1860 return sectionReader.emitError(msg);1861 });1862 };1863 1864 // Parse each of the dialects.1865 for (uint64_t i = 0; i < numDialects; ++i) {1866 dialects[i] = std::make_unique<BytecodeDialect>();1867 /// Before version kDialectVersioning, there wasn't any versioning available1868 /// for dialects, and the entryIdx represent the string itself.1869 if (version < bytecode::kDialectVersioning) {1870 if (failed(stringReader.parseString(sectionReader, dialects[i]->name)))1871 return failure();1872 continue;1873 }1874 1875 // Parse ID representing dialect and version.1876 uint64_t dialectNameIdx;1877 bool versionAvailable;1878 if (failed(sectionReader.parseVarIntWithFlag(dialectNameIdx,1879 versionAvailable)))1880 return failure();1881 if (failed(stringReader.parseStringAtIndex(sectionReader, dialectNameIdx,1882 dialects[i]->name)))1883 return failure();1884 if (versionAvailable) {1885 bytecode::Section::ID sectionID;1886 if (failed(sectionReader.parseSection(sectionID, checkSectionAlignment,1887 dialects[i]->versionBuffer)))1888 return failure();1889 if (sectionID != bytecode::Section::kDialectVersions) {1890 emitError(fileLoc, "expected dialect version section");1891 return failure();1892 }1893 }1894 dialectsMap[dialects[i]->name] = dialects[i].get();1895 }1896 1897 // Parse the operation names, which are grouped by dialect.1898 auto parseOpName = [&](BytecodeDialect *dialect) {1899 StringRef opName;1900 std::optional<bool> wasRegistered;1901 // Prior to version kNativePropertiesEncoding, the information about wheter1902 // an op was registered or not wasn't encoded.1903 if (version < bytecode::kNativePropertiesEncoding) {1904 if (failed(stringReader.parseString(sectionReader, opName)))1905 return failure();1906 } else {1907 bool wasRegisteredFlag;1908 if (failed(stringReader.parseStringWithFlag(sectionReader, opName,1909 wasRegisteredFlag)))1910 return failure();1911 wasRegistered = wasRegisteredFlag;1912 }1913 opNames.emplace_back(dialect, opName, wasRegistered);1914 return success();1915 };1916 // Avoid re-allocation in bytecode version >=kElideUnknownBlockArgLocation1917 // where the number of ops are known.1918 if (version >= bytecode::kElideUnknownBlockArgLocation) {1919 uint64_t numOps;1920 if (failed(sectionReader.parseVarInt(numOps)))1921 return failure();1922 opNames.reserve(numOps);1923 }1924 while (!sectionReader.empty())1925 if (failed(parseDialectGrouping(sectionReader, dialects, parseOpName)))1926 return failure();1927 return success();1928}1929 1930FailureOr<OperationName>1931BytecodeReader::Impl::parseOpName(EncodingReader &reader,1932 std::optional<bool> &wasRegistered) {1933 BytecodeOperationName *opName = nullptr;1934 if (failed(parseEntry(reader, opNames, opName, "operation name")))1935 return failure();1936 wasRegistered = opName->wasRegistered;1937 // Check to see if this operation name has already been resolved. If we1938 // haven't, load the dialect and build the operation name.1939 if (!opName->opName) {1940 // If the opName is empty, this is because we use to accept names such as1941 // `foo` without any `.` separator. We shouldn't tolerate this in textual1942 // format anymore but for now we'll be backward compatible. This can only1943 // happen with unregistered dialects.1944 if (opName->name.empty()) {1945 opName->opName.emplace(opName->dialect->name, getContext());1946 } else {1947 // Load the dialect and its version.1948 DialectReader dialectReader(attrTypeReader, stringReader, resourceReader,1949 dialectsMap, reader, version);1950 if (failed(opName->dialect->load(dialectReader, getContext())))1951 return failure();1952 opName->opName.emplace((opName->dialect->name + "." + opName->name).str(),1953 getContext());1954 }1955 }1956 return *opName->opName;1957}1958 1959//===----------------------------------------------------------------------===//1960// Resource Section1961//===----------------------------------------------------------------------===//1962 1963LogicalResult BytecodeReader::Impl::parseResourceSection(1964 EncodingReader &reader, std::optional<ArrayRef<uint8_t>> resourceData,1965 std::optional<ArrayRef<uint8_t>> resourceOffsetData) {1966 // Ensure both sections are either present or not.1967 if (resourceData.has_value() != resourceOffsetData.has_value()) {1968 if (resourceOffsetData)1969 return emitError(fileLoc, "unexpected resource offset section when "1970 "resource section is not present");1971 return emitError(1972 fileLoc,1973 "expected resource offset section when resource section is present");1974 }1975 1976 // If the resource sections are absent, there is nothing to do.1977 if (!resourceData)1978 return success();1979 1980 // Initialize the resource reader with the resource sections.1981 DialectReader dialectReader(attrTypeReader, stringReader, resourceReader,1982 dialectsMap, reader, version);1983 return resourceReader.initialize(fileLoc, config, dialects, stringReader,1984 *resourceData, *resourceOffsetData,1985 dialectReader, bufferOwnerRef);1986}1987 1988//===----------------------------------------------------------------------===//1989// UseListOrder Helpers1990//===----------------------------------------------------------------------===//1991 1992FailureOr<BytecodeReader::Impl::UseListMapT>1993BytecodeReader::Impl::parseUseListOrderForRange(EncodingReader &reader,1994 uint64_t numResults) {1995 BytecodeReader::Impl::UseListMapT map;1996 uint64_t numValuesToRead = 1;1997 if (numResults > 1 && failed(reader.parseVarInt(numValuesToRead)))1998 return failure();1999 2000 for (size_t valueIdx = 0; valueIdx < numValuesToRead; valueIdx++) {2001 uint64_t resultIdx = 0;2002 if (numResults > 1 && failed(reader.parseVarInt(resultIdx)))2003 return failure();2004 2005 uint64_t numValues;2006 bool indexPairEncoding;2007 if (failed(reader.parseVarIntWithFlag(numValues, indexPairEncoding)))2008 return failure();2009 2010 SmallVector<unsigned, 4> useListOrders;2011 for (size_t idx = 0; idx < numValues; idx++) {2012 uint64_t index;2013 if (failed(reader.parseVarInt(index)))2014 return failure();2015 useListOrders.push_back(index);2016 }2017 2018 // Store in a map the result index2019 map.try_emplace(resultIdx, UseListOrderStorage(indexPairEncoding,2020 std::move(useListOrders)));2021 }2022 2023 return map;2024}2025 2026/// Sorts each use according to the order specified in the use-list parsed. If2027/// the custom use-list is not found, this means that the order needs to be2028/// consistent with the reverse pre-order walk of the IR. If multiple uses lie2029/// on the same operation, the order will follow the reverse operand number2030/// ordering.2031LogicalResult BytecodeReader::Impl::sortUseListOrder(Value value) {2032 // Early return for trivial use-lists.2033 if (value.use_empty() || value.hasOneUse())2034 return success();2035 2036 bool hasIncomingOrder =2037 valueToUseListMap.contains(value.getAsOpaquePointer());2038 2039 // Compute the current order of the use-list with respect to the global2040 // ordering. Detect if the order is already sorted while doing so.2041 bool alreadySorted = true;2042 auto &firstUse = *value.use_begin();2043 uint64_t prevID =2044 bytecode::getUseID(firstUse, operationIDs.at(firstUse.getOwner()));2045 llvm::SmallVector<std::pair<unsigned, uint64_t>> currentOrder = {{0, prevID}};2046 for (auto item : llvm::drop_begin(llvm::enumerate(value.getUses()))) {2047 uint64_t currentID = bytecode::getUseID(2048 item.value(), operationIDs.at(item.value().getOwner()));2049 alreadySorted &= prevID > currentID;2050 currentOrder.push_back({item.index(), currentID});2051 prevID = currentID;2052 }2053 2054 // If the order is already sorted, and there wasn't a custom order to apply2055 // from the bytecode file, we are done.2056 if (alreadySorted && !hasIncomingOrder)2057 return success();2058 2059 // If not already sorted, sort the indices of the current order by descending2060 // useIDs.2061 if (!alreadySorted)2062 std::sort(2063 currentOrder.begin(), currentOrder.end(),2064 [](auto elem1, auto elem2) { return elem1.second > elem2.second; });2065 2066 if (!hasIncomingOrder) {2067 // If the bytecode file did not contain any custom use-list order, it means2068 // that the order was descending useID. Hence, shuffle by the first index2069 // of the `currentOrder` pair.2070 SmallVector<unsigned> shuffle(llvm::make_first_range(currentOrder));2071 value.shuffleUseList(shuffle);2072 return success();2073 }2074 2075 // Pull the custom order info from the map.2076 UseListOrderStorage customOrder =2077 valueToUseListMap.at(value.getAsOpaquePointer());2078 SmallVector<unsigned, 4> shuffle = std::move(customOrder.indices);2079 uint64_t numUses = value.getNumUses();2080 2081 // If the encoding was a pair of indices `(src, dst)` for every permutation,2082 // reconstruct the shuffle vector for every use. Initialize the shuffle vector2083 // as identity, and then apply the mapping encoded in the indices.2084 if (customOrder.isIndexPairEncoding) {2085 // Return failure if the number of indices was not representing pairs.2086 if (shuffle.size() & 1)2087 return failure();2088 2089 SmallVector<unsigned, 4> newShuffle(numUses);2090 size_t idx = 0;2091 std::iota(newShuffle.begin(), newShuffle.end(), idx);2092 for (idx = 0; idx < shuffle.size(); idx += 2)2093 newShuffle[shuffle[idx]] = shuffle[idx + 1];2094 2095 shuffle = std::move(newShuffle);2096 }2097 2098 // Make sure that the indices represent a valid mapping. That is, the sum of2099 // all the values needs to be equal to (numUses - 1) * numUses / 2, and no2100 // duplicates are allowed in the list.2101 DenseSet<unsigned> set;2102 uint64_t accumulator = 0;2103 for (const auto &elem : shuffle) {2104 if (!set.insert(elem).second)2105 return failure();2106 accumulator += elem;2107 }2108 if (numUses != shuffle.size() ||2109 accumulator != (((numUses - 1) * numUses) >> 1))2110 return failure();2111 2112 // Apply the current ordering map onto the shuffle vector to get the final2113 // use-list sorting indices before shuffling.2114 shuffle = SmallVector<unsigned, 4>(llvm::map_range(2115 currentOrder, [&](auto item) { return shuffle[item.first]; }));2116 value.shuffleUseList(shuffle);2117 return success();2118}2119 2120LogicalResult BytecodeReader::Impl::processUseLists(Operation *topLevelOp) {2121 // Precompute operation IDs according to the pre-order walk of the IR. We2122 // can't do this while parsing since parseRegions ordering is not strictly2123 // equal to the pre-order walk.2124 unsigned operationID = 0;2125 topLevelOp->walk<mlir::WalkOrder::PreOrder>(2126 [&](Operation *op) { operationIDs.try_emplace(op, operationID++); });2127 2128 auto blockWalk = topLevelOp->walk([this](Block *block) {2129 for (auto arg : block->getArguments())2130 if (failed(sortUseListOrder(arg)))2131 return WalkResult::interrupt();2132 return WalkResult::advance();2133 });2134 2135 auto resultWalk = topLevelOp->walk([this](Operation *op) {2136 for (auto result : op->getResults())2137 if (failed(sortUseListOrder(result)))2138 return WalkResult::interrupt();2139 return WalkResult::advance();2140 });2141 2142 return failure(blockWalk.wasInterrupted() || resultWalk.wasInterrupted());2143}2144 2145//===----------------------------------------------------------------------===//2146// IR Section2147//===----------------------------------------------------------------------===//2148 2149LogicalResult2150BytecodeReader::Impl::parseIRSection(ArrayRef<uint8_t> sectionData,2151 Block *block) {2152 EncodingReader reader(sectionData, fileLoc);2153 2154 // A stack of operation regions currently being read from the bytecode.2155 std::vector<RegionReadState> regionStack;2156 2157 // Parse the top-level block using a temporary module operation.2158 OwningOpRef<ModuleOp> moduleOp = ModuleOp::create(fileLoc);2159 regionStack.emplace_back(*moduleOp, &reader, /*isIsolatedFromAbove=*/true);2160 regionStack.back().curBlocks.push_back(moduleOp->getBody());2161 regionStack.back().curBlock = regionStack.back().curRegion->begin();2162 if (failed(parseBlockHeader(reader, regionStack.back())))2163 return failure();2164 valueScopes.emplace_back();2165 valueScopes.back().push(regionStack.back());2166 2167 // Iteratively parse regions until everything has been resolved.2168 while (!regionStack.empty())2169 if (failed(parseRegions(regionStack, regionStack.back())))2170 return failure();2171 if (!forwardRefOps.empty()) {2172 return reader.emitError(2173 "not all forward unresolved forward operand references");2174 }2175 2176 // Sort use-lists according to what specified in bytecode.2177 if (failed(processUseLists(*moduleOp)))2178 return reader.emitError(2179 "parsed use-list orders were invalid and could not be applied");2180 2181 // Resolve dialect version.2182 for (const std::unique_ptr<BytecodeDialect> &byteCodeDialect : dialects) {2183 // Parsing is complete, give an opportunity to each dialect to visit the2184 // IR and perform upgrades.2185 if (!byteCodeDialect->loadedVersion)2186 continue;2187 if (byteCodeDialect->interface &&2188 failed(byteCodeDialect->interface->upgradeFromVersion(2189 *moduleOp, *byteCodeDialect->loadedVersion)))2190 return failure();2191 }2192 2193 // Verify that the parsed operations are valid.2194 if (config.shouldVerifyAfterParse() && failed(verify(*moduleOp)))2195 return failure();2196 2197 // Splice the parsed operations over to the provided top-level block.2198 auto &parsedOps = moduleOp->getBody()->getOperations();2199 auto &destOps = block->getOperations();2200 destOps.splice(destOps.end(), parsedOps, parsedOps.begin(), parsedOps.end());2201 return success();2202}2203 2204LogicalResult2205BytecodeReader::Impl::parseRegions(std::vector<RegionReadState> ®ionStack,2206 RegionReadState &readState) {2207 const auto checkSectionAlignment = [&](unsigned alignment) {2208 return this->checkSectionAlignment(2209 alignment, [&](const auto &msg) { return emitError(fileLoc, msg); });2210 };2211 2212 // Process regions, blocks, and operations until the end or if a nested2213 // region is encountered. In this case we push a new state in regionStack and2214 // return, the processing of the current region will resume afterward.2215 for (; readState.curRegion != readState.endRegion; ++readState.curRegion) {2216 // If the current block hasn't been setup yet, parse the header for this2217 // region. The current block is already setup when this function was2218 // interrupted to recurse down in a nested region and we resume the current2219 // block after processing the nested region.2220 if (readState.curBlock == Region::iterator()) {2221 if (failed(parseRegion(readState)))2222 return failure();2223 2224 // If the region is empty, there is nothing to more to do.2225 if (readState.curRegion->empty())2226 continue;2227 }2228 2229 // Parse the blocks within the region.2230 EncodingReader &reader = *readState.reader;2231 do {2232 while (readState.numOpsRemaining--) {2233 // Read in the next operation. We don't read its regions directly, we2234 // handle those afterwards as necessary.2235 bool isIsolatedFromAbove = false;2236 FailureOr<Operation *> op =2237 parseOpWithoutRegions(reader, readState, isIsolatedFromAbove);2238 if (failed(op))2239 return failure();2240 2241 // If the op has regions, add it to the stack for processing and return:2242 // we stop the processing of the current region and resume it after the2243 // inner one is completed. Unless LazyLoading is activated in which case2244 // nested region parsing is delayed.2245 if ((*op)->getNumRegions()) {2246 RegionReadState childState(*op, &reader, isIsolatedFromAbove);2247 2248 // Isolated regions are encoded as a section in version 2 and above.2249 if (version >= bytecode::kLazyLoading && isIsolatedFromAbove) {2250 bytecode::Section::ID sectionID;2251 ArrayRef<uint8_t> sectionData;2252 if (failed(reader.parseSection(sectionID, checkSectionAlignment,2253 sectionData)))2254 return failure();2255 if (sectionID != bytecode::Section::kIR)2256 return emitError(fileLoc, "expected IR section for region");2257 childState.owningReader =2258 std::make_unique<EncodingReader>(sectionData, fileLoc);2259 childState.reader = childState.owningReader.get();2260 2261 // If the user has a callback set, they have the opportunity to2262 // control lazyloading as we go.2263 if (lazyLoading && (!lazyOpsCallback || !lazyOpsCallback(*op))) {2264 lazyLoadableOps.emplace_back(*op, std::move(childState));2265 lazyLoadableOpsMap.try_emplace(*op,2266 std::prev(lazyLoadableOps.end()));2267 continue;2268 }2269 }2270 regionStack.push_back(std::move(childState));2271 2272 // If the op is isolated from above, push a new value scope.2273 if (isIsolatedFromAbove)2274 valueScopes.emplace_back();2275 return success();2276 }2277 }2278 2279 // Move to the next block of the region.2280 if (++readState.curBlock == readState.curRegion->end())2281 break;2282 if (failed(parseBlockHeader(reader, readState)))2283 return failure();2284 } while (true);2285 2286 // Reset the current block and any values reserved for this region.2287 readState.curBlock = {};2288 valueScopes.back().pop(readState);2289 }2290 2291 // When the regions have been fully parsed, pop them off of the read stack. If2292 // the regions were isolated from above, we also pop the last value scope.2293 if (readState.isIsolatedFromAbove) {2294 assert(!valueScopes.empty() && "Expect a valueScope after reading region");2295 valueScopes.pop_back();2296 }2297 assert(!regionStack.empty() && "Expect a regionStack after reading region");2298 regionStack.pop_back();2299 return success();2300}2301 2302FailureOr<Operation *>2303BytecodeReader::Impl::parseOpWithoutRegions(EncodingReader &reader,2304 RegionReadState &readState,2305 bool &isIsolatedFromAbove) {2306 // Parse the name of the operation.2307 std::optional<bool> wasRegistered;2308 FailureOr<OperationName> opName = parseOpName(reader, wasRegistered);2309 if (failed(opName))2310 return failure();2311 2312 // Parse the operation mask, which indicates which components of the operation2313 // are present.2314 uint8_t opMask;2315 if (failed(reader.parseByte(opMask)))2316 return failure();2317 2318 /// Parse the location.2319 LocationAttr opLoc;2320 if (failed(parseAttribute(reader, opLoc)))2321 return failure();2322 2323 // With the location and name resolved, we can start building the operation2324 // state.2325 OperationState opState(opLoc, *opName);2326 2327 // Parse the attributes of the operation.2328 if (opMask & bytecode::OpEncodingMask::kHasAttrs) {2329 DictionaryAttr dictAttr;2330 if (failed(parseAttribute(reader, dictAttr)))2331 return failure();2332 opState.attributes = dictAttr;2333 }2334 2335 if (opMask & bytecode::OpEncodingMask::kHasProperties) {2336 // kHasProperties wasn't emitted in older bytecode, we should never get2337 // there without also having the `wasRegistered` flag available.2338 if (!wasRegistered)2339 return emitError(fileLoc,2340 "Unexpected missing `wasRegistered` opname flag at "2341 "bytecode version ")2342 << version << " with properties.";2343 // When an operation is emitted without being registered, the properties are2344 // stored as an attribute. Otherwise the op must implement the bytecode2345 // interface and control the serialization.2346 if (wasRegistered) {2347 DialectReader dialectReader(attrTypeReader, stringReader, resourceReader,2348 dialectsMap, reader, version);2349 if (failed(2350 propertiesReader.read(fileLoc, dialectReader, &*opName, opState)))2351 return failure();2352 } else {2353 // If the operation wasn't registered when it was emitted, the properties2354 // was serialized as an attribute.2355 if (failed(parseAttribute(reader, opState.propertiesAttr)))2356 return failure();2357 }2358 }2359 2360 /// Parse the results of the operation.2361 if (opMask & bytecode::OpEncodingMask::kHasResults) {2362 uint64_t numResults;2363 if (failed(reader.parseVarInt(numResults)))2364 return failure();2365 opState.types.resize(numResults);2366 for (int i = 0, e = numResults; i < e; ++i)2367 if (failed(parseType(reader, opState.types[i])))2368 return failure();2369 }2370 2371 /// Parse the operands of the operation.2372 if (opMask & bytecode::OpEncodingMask::kHasOperands) {2373 uint64_t numOperands;2374 if (failed(reader.parseVarInt(numOperands)))2375 return failure();2376 opState.operands.resize(numOperands);2377 for (int i = 0, e = numOperands; i < e; ++i)2378 if (!(opState.operands[i] = parseOperand(reader)))2379 return failure();2380 }2381 2382 /// Parse the successors of the operation.2383 if (opMask & bytecode::OpEncodingMask::kHasSuccessors) {2384 uint64_t numSuccs;2385 if (failed(reader.parseVarInt(numSuccs)))2386 return failure();2387 opState.successors.resize(numSuccs);2388 for (int i = 0, e = numSuccs; i < e; ++i) {2389 if (failed(parseEntry(reader, readState.curBlocks, opState.successors[i],2390 "successor")))2391 return failure();2392 }2393 }2394 2395 /// Parse the use-list orders for the results of the operation. Use-list2396 /// orders are available since version 3 of the bytecode.2397 std::optional<UseListMapT> resultIdxToUseListMap = std::nullopt;2398 if (version >= bytecode::kUseListOrdering &&2399 (opMask & bytecode::OpEncodingMask::kHasUseListOrders)) {2400 size_t numResults = opState.types.size();2401 auto parseResult = parseUseListOrderForRange(reader, numResults);2402 if (failed(parseResult))2403 return failure();2404 resultIdxToUseListMap = std::move(*parseResult);2405 }2406 2407 /// Parse the regions of the operation.2408 if (opMask & bytecode::OpEncodingMask::kHasInlineRegions) {2409 uint64_t numRegions;2410 if (failed(reader.parseVarIntWithFlag(numRegions, isIsolatedFromAbove)))2411 return failure();2412 2413 opState.regions.reserve(numRegions);2414 for (int i = 0, e = numRegions; i < e; ++i)2415 opState.regions.push_back(std::make_unique<Region>());2416 }2417 2418 // Create the operation at the back of the current block.2419 Operation *op = Operation::create(opState);2420 readState.curBlock->push_back(op);2421 2422 // If the operation had results, update the value references. We don't need to2423 // do this if the current value scope is empty. That is, the op was not2424 // encoded within a parent region.2425 if (readState.numValues && op->getNumResults() &&2426 failed(defineValues(reader, op->getResults())))2427 return failure();2428 2429 /// Store a map for every value that received a custom use-list order from the2430 /// bytecode file.2431 if (resultIdxToUseListMap.has_value()) {2432 for (size_t idx = 0; idx < op->getNumResults(); idx++) {2433 if (resultIdxToUseListMap->contains(idx)) {2434 valueToUseListMap.try_emplace(op->getResult(idx).getAsOpaquePointer(),2435 resultIdxToUseListMap->at(idx));2436 }2437 }2438 }2439 return op;2440}2441 2442LogicalResult BytecodeReader::Impl::parseRegion(RegionReadState &readState) {2443 EncodingReader &reader = *readState.reader;2444 2445 // Parse the number of blocks in the region.2446 uint64_t numBlocks;2447 if (failed(reader.parseVarInt(numBlocks)))2448 return failure();2449 2450 // If the region is empty, there is nothing else to do.2451 if (numBlocks == 0)2452 return success();2453 2454 // Parse the number of values defined in this region.2455 uint64_t numValues;2456 if (failed(reader.parseVarInt(numValues)))2457 return failure();2458 readState.numValues = numValues;2459 2460 // Create the blocks within this region. We do this before processing so that2461 // we can rely on the blocks existing when creating operations.2462 readState.curBlocks.clear();2463 readState.curBlocks.reserve(numBlocks);2464 for (uint64_t i = 0; i < numBlocks; ++i) {2465 readState.curBlocks.push_back(new Block());2466 readState.curRegion->push_back(readState.curBlocks.back());2467 }2468 2469 // Prepare the current value scope for this region.2470 valueScopes.back().push(readState);2471 2472 // Parse the entry block of the region.2473 readState.curBlock = readState.curRegion->begin();2474 return parseBlockHeader(reader, readState);2475}2476 2477LogicalResult2478BytecodeReader::Impl::parseBlockHeader(EncodingReader &reader,2479 RegionReadState &readState) {2480 bool hasArgs;2481 if (failed(reader.parseVarIntWithFlag(readState.numOpsRemaining, hasArgs)))2482 return failure();2483 2484 // Parse the arguments of the block.2485 if (hasArgs && failed(parseBlockArguments(reader, &*readState.curBlock)))2486 return failure();2487 2488 // Uselist orders are available since version 3 of the bytecode.2489 if (version < bytecode::kUseListOrdering)2490 return success();2491 2492 uint8_t hasUseListOrders = 0;2493 if (hasArgs && failed(reader.parseByte(hasUseListOrders)))2494 return failure();2495 2496 if (!hasUseListOrders)2497 return success();2498 2499 Block &blk = *readState.curBlock;2500 auto argIdxToUseListMap =2501 parseUseListOrderForRange(reader, blk.getNumArguments());2502 if (failed(argIdxToUseListMap) || argIdxToUseListMap->empty())2503 return failure();2504 2505 for (size_t idx = 0; idx < blk.getNumArguments(); idx++)2506 if (argIdxToUseListMap->contains(idx))2507 valueToUseListMap.try_emplace(blk.getArgument(idx).getAsOpaquePointer(),2508 argIdxToUseListMap->at(idx));2509 2510 // We don't parse the operations of the block here, that's done elsewhere.2511 return success();2512}2513 2514LogicalResult BytecodeReader::Impl::parseBlockArguments(EncodingReader &reader,2515 Block *block) {2516 // Parse the value ID for the first argument, and the number of arguments.2517 uint64_t numArgs;2518 if (failed(reader.parseVarInt(numArgs)))2519 return failure();2520 2521 SmallVector<Type> argTypes;2522 SmallVector<Location> argLocs;2523 argTypes.reserve(numArgs);2524 argLocs.reserve(numArgs);2525 2526 Location unknownLoc = UnknownLoc::get(config.getContext());2527 while (numArgs--) {2528 Type argType;2529 LocationAttr argLoc = unknownLoc;2530 if (version >= bytecode::kElideUnknownBlockArgLocation) {2531 // Parse the type with hasLoc flag to determine if it has type.2532 uint64_t typeIdx;2533 bool hasLoc;2534 if (failed(reader.parseVarIntWithFlag(typeIdx, hasLoc)) ||2535 !(argType = attrTypeReader.resolveType(typeIdx)))2536 return failure();2537 if (hasLoc && failed(parseAttribute(reader, argLoc)))2538 return failure();2539 } else {2540 // All args has type and location.2541 if (failed(parseType(reader, argType)) ||2542 failed(parseAttribute(reader, argLoc)))2543 return failure();2544 }2545 argTypes.push_back(argType);2546 argLocs.push_back(argLoc);2547 }2548 block->addArguments(argTypes, argLocs);2549 return defineValues(reader, block->getArguments());2550}2551 2552//===----------------------------------------------------------------------===//2553// Value Processing2554//===----------------------------------------------------------------------===//2555 2556Value BytecodeReader::Impl::parseOperand(EncodingReader &reader) {2557 std::vector<Value> &values = valueScopes.back().values;2558 Value *value = nullptr;2559 if (failed(parseEntry(reader, values, value, "value")))2560 return Value();2561 2562 // Create a new forward reference if necessary.2563 if (!*value)2564 *value = createForwardRef();2565 return *value;2566}2567 2568LogicalResult BytecodeReader::Impl::defineValues(EncodingReader &reader,2569 ValueRange newValues) {2570 ValueScope &valueScope = valueScopes.back();2571 std::vector<Value> &values = valueScope.values;2572 2573 unsigned &valueID = valueScope.nextValueIDs.back();2574 unsigned valueIDEnd = valueID + newValues.size();2575 if (valueIDEnd > values.size()) {2576 return reader.emitError(2577 "value index range was outside of the expected range for "2578 "the parent region, got [",2579 valueID, ", ", valueIDEnd, "), but the maximum index was ",2580 values.size() - 1);2581 }2582 2583 // Assign the values and update any forward references.2584 for (unsigned i = 0, e = newValues.size(); i != e; ++i, ++valueID) {2585 Value newValue = newValues[i];2586 2587 // Check to see if a definition for this value already exists.2588 if (Value oldValue = std::exchange(values[valueID], newValue)) {2589 Operation *forwardRefOp = oldValue.getDefiningOp();2590 2591 // Assert that this is a forward reference operation. Given how we compute2592 // definition ids (incrementally as we parse), it shouldn't be possible2593 // for the value to be defined any other way.2594 assert(forwardRefOp && forwardRefOp->getBlock() == &forwardRefOps &&2595 "value index was already defined?");2596 2597 oldValue.replaceAllUsesWith(newValue);2598 forwardRefOp->moveBefore(&openForwardRefOps, openForwardRefOps.end());2599 }2600 }2601 return success();2602}2603 2604Value BytecodeReader::Impl::createForwardRef() {2605 // Check for an available existing operation to use. Otherwise, create a new2606 // fake operation to use for the reference.2607 if (!openForwardRefOps.empty()) {2608 Operation *op = &openForwardRefOps.back();2609 op->moveBefore(&forwardRefOps, forwardRefOps.end());2610 } else {2611 forwardRefOps.push_back(Operation::create(forwardRefOpState));2612 }2613 return forwardRefOps.back().getResult(0);2614}2615 2616//===----------------------------------------------------------------------===//2617// Entry Points2618//===----------------------------------------------------------------------===//2619 2620BytecodeReader::~BytecodeReader() { assert(getNumOpsToMaterialize() == 0); }2621 2622BytecodeReader::BytecodeReader(2623 llvm::MemoryBufferRef buffer, const ParserConfig &config, bool lazyLoading,2624 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef) {2625 Location sourceFileLoc =2626 FileLineColLoc::get(config.getContext(), buffer.getBufferIdentifier(),2627 /*line=*/0, /*column=*/0);2628 impl = std::make_unique<Impl>(sourceFileLoc, config, lazyLoading, buffer,2629 bufferOwnerRef);2630}2631 2632LogicalResult BytecodeReader::readTopLevel(2633 Block *block, llvm::function_ref<bool(Operation *)> lazyOpsCallback) {2634 return impl->read(block, lazyOpsCallback);2635}2636 2637int64_t BytecodeReader::getNumOpsToMaterialize() const {2638 return impl->getNumOpsToMaterialize();2639}2640 2641bool BytecodeReader::isMaterializable(Operation *op) {2642 return impl->isMaterializable(op);2643}2644 2645LogicalResult BytecodeReader::materialize(2646 Operation *op, llvm::function_ref<bool(Operation *)> lazyOpsCallback) {2647 return impl->materialize(op, lazyOpsCallback);2648}2649 2650LogicalResult2651BytecodeReader::finalize(function_ref<bool(Operation *)> shouldMaterialize) {2652 return impl->finalize(shouldMaterialize);2653}2654 2655bool mlir::isBytecode(llvm::MemoryBufferRef buffer) {2656 return buffer.getBuffer().starts_with("ML\xefR");2657}2658 2659/// Read the bytecode from the provided memory buffer reference.2660/// `bufferOwnerRef` if provided is the owning source manager for the buffer,2661/// and may be used to extend the lifetime of the buffer.2662static LogicalResult2663readBytecodeFileImpl(llvm::MemoryBufferRef buffer, Block *block,2664 const ParserConfig &config,2665 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef) {2666 Location sourceFileLoc =2667 FileLineColLoc::get(config.getContext(), buffer.getBufferIdentifier(),2668 /*line=*/0, /*column=*/0);2669 if (!isBytecode(buffer)) {2670 return emitError(sourceFileLoc,2671 "input buffer is not an MLIR bytecode file");2672 }2673 2674 BytecodeReader::Impl reader(sourceFileLoc, config, /*lazyLoading=*/false,2675 buffer, bufferOwnerRef);2676 return reader.read(block, /*lazyOpsCallback=*/nullptr);2677}2678 2679LogicalResult mlir::readBytecodeFile(llvm::MemoryBufferRef buffer, Block *block,2680 const ParserConfig &config) {2681 return readBytecodeFileImpl(buffer, block, config, /*bufferOwnerRef=*/{});2682}2683LogicalResult2684mlir::readBytecodeFile(const std::shared_ptr<llvm::SourceMgr> &sourceMgr,2685 Block *block, const ParserConfig &config) {2686 return readBytecodeFileImpl(2687 *sourceMgr->getMemoryBuffer(sourceMgr->getMainFileID()), block, config,2688 sourceMgr);2689}2690