MLIR 22.0.0git
BytecodeReader.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
8
14#include "mlir/IR/BuiltinOps.h"
15#include "mlir/IR/Diagnostics.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 <deque>
31#include <list>
32#include <memory>
33#include <numeric>
34#include <optional>
35
36#define DEBUG_TYPE "mlir-bytecode-reader"
37
38using namespace mlir;
39
40/// Stringify the given section ID.
41static std::string toString(bytecode::Section::ID sectionID) {
42 switch (sectionID) {
44 return "String (0)";
46 return "Dialect (1)";
48 return "AttrType (2)";
50 return "AttrTypeOffset (3)";
52 return "IR (4)";
54 return "Resource (5)";
56 return "ResourceOffset (6)";
58 return "DialectVersions (7)";
60 return "Properties (8)";
61 default:
62 return ("Unknown (" + Twine(static_cast<unsigned>(sectionID)) + ")").str();
63 }
64}
65
66/// Returns true if the given top-level section ID is optional.
67static bool isSectionOptional(bytecode::Section::ID sectionID, int version) {
68 switch (sectionID) {
74 return false;
78 return true;
81 default:
82 llvm_unreachable("unknown section ID");
83 }
84}
85
86//===----------------------------------------------------------------------===//
87// EncodingReader
88//===----------------------------------------------------------------------===//
89
90namespace {
91class EncodingReader {
92public:
93 explicit EncodingReader(ArrayRef<uint8_t> contents, Location fileLoc)
94 : buffer(contents), dataIt(buffer.begin()), fileLoc(fileLoc) {}
95 explicit EncodingReader(StringRef contents, Location fileLoc)
96 : EncodingReader({reinterpret_cast<const uint8_t *>(contents.data()),
97 contents.size()},
98 fileLoc) {}
99
100 /// Returns true if the entire section has been read.
101 bool empty() const { return dataIt == buffer.end(); }
102
103 /// Returns the remaining size of the bytecode.
104 size_t size() const { return buffer.end() - dataIt; }
105
106 /// Align the current reader position to the specified alignment.
107 LogicalResult alignTo(unsigned alignment) {
108 if (!llvm::isPowerOf2_32(alignment))
109 return emitError("expected alignment to be a power-of-two");
110
111 auto isUnaligned = [&](const uint8_t *ptr) {
112 return ((uintptr_t)ptr & (alignment - 1)) != 0;
113 };
114
115 // Shift the reader position to the next alignment boundary.
116 // Note: this assumes the pointer alignment matches the alignment of the
117 // data from the start of the buffer. In other words, this code is only
118 // valid if `dataIt` is offsetting into an already aligned buffer.
119 while (isUnaligned(dataIt)) {
120 uint8_t padding;
121 if (failed(parseByte(padding)))
122 return failure();
123 if (padding != bytecode::kAlignmentByte) {
124 return emitError("expected alignment byte (0xCB), but got: '0x" +
125 llvm::utohexstr(padding) + "'");
126 }
127 }
128
129 // Ensure the data iterator is now aligned. This case is unlikely because we
130 // *just* went through the effort to align the data iterator.
131 if (LLVM_UNLIKELY(isUnaligned(dataIt))) {
132 return emitError("expected data iterator aligned to ", alignment,
133 ", but got pointer: '0x" +
134 llvm::utohexstr((uintptr_t)dataIt) + "'");
135 }
136
137 return success();
138 }
139
140 /// Emit an error using the given arguments.
141 template <typename... Args>
142 InFlightDiagnostic emitError(Args &&...args) const {
143 return ::emitError(fileLoc).append(std::forward<Args>(args)...);
144 }
145 InFlightDiagnostic emitError() const { return ::emitError(fileLoc); }
146
147 /// Parse a single byte from the stream.
148 template <typename T>
149 LogicalResult parseByte(T &value) {
150 if (empty())
151 return emitError("attempting to parse a byte at the end of the bytecode");
152 value = static_cast<T>(*dataIt++);
153 return success();
154 }
155 /// Parse a range of bytes of 'length' into the given result.
156 LogicalResult parseBytes(size_t length, ArrayRef<uint8_t> &result) {
157 if (length > size()) {
158 return emitError("attempting to parse ", length, " bytes when only ",
159 size(), " remain");
160 }
161 result = {dataIt, length};
162 dataIt += length;
163 return success();
164 }
165 /// Parse a range of bytes of 'length' into the given result, which can be
166 /// assumed to be large enough to hold `length`.
167 LogicalResult parseBytes(size_t length, uint8_t *result) {
168 if (length > size()) {
169 return emitError("attempting to parse ", length, " bytes when only ",
170 size(), " remain");
171 }
172 memcpy(result, dataIt, length);
173 dataIt += length;
174 return success();
175 }
176
177 /// Parse an aligned blob of data, where the alignment was encoded alongside
178 /// the data.
179 LogicalResult parseBlobAndAlignment(ArrayRef<uint8_t> &data,
180 uint64_t &alignment) {
181 uint64_t dataSize;
182 if (failed(parseVarInt(alignment)) || failed(parseVarInt(dataSize)) ||
183 failed(alignTo(alignment)))
184 return failure();
185 return parseBytes(dataSize, data);
186 }
187
188 /// Parse a variable length encoded integer from the byte stream. The first
189 /// encoded byte contains a prefix in the low bits indicating the encoded
190 /// length of the value. This length prefix is a bit sequence of '0's followed
191 /// by a '1'. The number of '0' bits indicate the number of _additional_ bytes
192 /// (not including the prefix byte). All remaining bits in the first byte,
193 /// along with all of the bits in additional bytes, provide the value of the
194 /// integer encoded in little-endian order.
195 LogicalResult parseVarInt(uint64_t &result) {
196 // Parse the first byte of the encoding, which contains the length prefix.
197 if (failed(parseByte(result)))
198 return failure();
199
200 // Handle the overwhelmingly common case where the value is stored in a
201 // single byte. In this case, the first bit is the `1` marker bit.
202 if (LLVM_LIKELY(result & 1)) {
203 result >>= 1;
204 return success();
205 }
206
207 // Handle the overwhelming uncommon case where the value required all 8
208 // bytes (i.e. a really really big number). In this case, the marker byte is
209 // all zeros: `00000000`.
210 if (LLVM_UNLIKELY(result == 0)) {
211 llvm::support::ulittle64_t resultLE;
212 if (failed(parseBytes(sizeof(resultLE),
213 reinterpret_cast<uint8_t *>(&resultLE))))
214 return failure();
215 result = resultLE;
216 return success();
217 }
218 return parseMultiByteVarInt(result);
219 }
220
221 /// Parse a signed variable length encoded integer from the byte stream. A
222 /// signed varint is encoded as a normal varint with zigzag encoding applied,
223 /// i.e. the low bit of the value is used to indicate the sign.
224 LogicalResult parseSignedVarInt(uint64_t &result) {
225 if (failed(parseVarInt(result)))
226 return failure();
227 // Essentially (but using unsigned): (x >> 1) ^ -(x & 1)
228 result = (result >> 1) ^ (~(result & 1) + 1);
229 return success();
230 }
231
232 /// Parse a variable length encoded integer whose low bit is used to encode an
233 /// unrelated flag, i.e: `(integerValue << 1) | (flag ? 1 : 0)`.
234 LogicalResult parseVarIntWithFlag(uint64_t &result, bool &flag) {
235 if (failed(parseVarInt(result)))
236 return failure();
237 flag = result & 1;
238 result >>= 1;
239 return success();
240 }
241
242 /// Skip the first `length` bytes within the reader.
243 LogicalResult skipBytes(size_t length) {
244 if (length > size()) {
245 return emitError("attempting to skip ", length, " bytes when only ",
246 size(), " remain");
247 }
248 dataIt += length;
249 return success();
250 }
251
252 /// Parse a null-terminated string into `result` (without including the NUL
253 /// terminator).
254 LogicalResult parseNullTerminatedString(StringRef &result) {
255 const char *startIt = (const char *)dataIt;
256 const char *nulIt = (const char *)memchr(startIt, 0, size());
257 if (!nulIt)
258 return emitError(
259 "malformed null-terminated string, no null character found");
260
261 result = StringRef(startIt, nulIt - startIt);
262 dataIt = (const uint8_t *)nulIt + 1;
263 return success();
264 }
265
266 /// Validate that the alignment requested in the section is valid.
267 using ValidateAlignmentFn = function_ref<LogicalResult(unsigned alignment)>;
268
269 /// Parse a section header, placing the kind of section in `sectionID` and the
270 /// contents of the section in `sectionData`.
271 LogicalResult parseSection(bytecode::Section::ID &sectionID,
272 ValidateAlignmentFn alignmentValidator,
273 ArrayRef<uint8_t> &sectionData) {
274 uint8_t sectionIDAndHasAlignment;
275 uint64_t length;
276 if (failed(parseByte(sectionIDAndHasAlignment)) ||
277 failed(parseVarInt(length)))
278 return failure();
279
280 // Extract the section ID and whether the section is aligned. The high bit
281 // of the ID is the alignment flag.
282 sectionID = static_cast<bytecode::Section::ID>(sectionIDAndHasAlignment &
283 0b01111111);
284 bool hasAlignment = sectionIDAndHasAlignment & 0b10000000;
285
286 // Check that the section is actually valid before trying to process its
287 // data.
288 if (sectionID >= bytecode::Section::kNumSections)
289 return emitError("invalid section ID: ", unsigned(sectionID));
290
291 // Process the section alignment if present.
292 if (hasAlignment) {
293 // Read the requested alignment from the bytecode parser.
294 uint64_t alignment;
295 if (failed(parseVarInt(alignment)))
296 return failure();
297
298 // Check that the requested alignment must not exceed the alignment of
299 // the root buffer itself. Otherwise we cannot guarantee that pointers
300 // derived from this buffer will actually satisfy the requested alignment
301 // globally.
302 //
303 // Consider a bytecode buffer that is guaranteed to be 8k aligned, but not
304 // 16k aligned (e.g. absolute address 40960. If a section inside this
305 // buffer declares a 16k alignment requirement, two problems can arise:
306 //
307 // (a) If we "align forward" the current pointer to the next
308 // 16k boundary, the amount of padding we skip depends on the
309 // buffer's starting address. For example:
310 //
311 // buffer_start = 40960
312 // next 16k boundary = 49152
313 // bytes skipped = 49152 - 40960 = 8192
314 //
315 // This leaves behind variable padding that could be misinterpreted
316 // as part of the next section.
317 //
318 // (b) If we align relative to the buffer start, we may
319 // obtain addresses that are multiples of "buffer_start +
320 // section_alignment" rather than truly globally aligned
321 // addresses. For example:
322 //
323 // buffer_start = 40960 (5×8k, 8k aligned but not 16k)
324 // offset = 16384 (first multiple of 16k)
325 // section_ptr = 40960 + 16384 = 57344
326 //
327 // 57344 is 8k aligned but not 16k aligned.
328 // Any consumer expecting true 16k alignment would see this as a
329 // violation.
330 if (failed(alignmentValidator(alignment)))
331 return emitError("failed to align section ID: ", unsigned(sectionID));
332
333 // Align the buffer.
334 if (failed(alignTo(alignment)))
335 return failure();
336 }
337
338 // Parse the actual section data.
339 return parseBytes(static_cast<size_t>(length), sectionData);
340 }
341
342 Location getLoc() const { return fileLoc; }
343
344private:
345 /// Parse a variable length encoded integer from the byte stream. This method
346 /// is a fallback when the number of bytes used to encode the value is greater
347 /// than 1, but less than the max (9). The provided `result` value can be
348 /// assumed to already contain the first byte of the value.
349 /// NOTE: This method is marked noinline to avoid pessimizing the common case
350 /// of single byte encoding.
351 LLVM_ATTRIBUTE_NOINLINE LogicalResult parseMultiByteVarInt(uint64_t &result) {
352 // Count the number of trailing zeros in the marker byte, this indicates the
353 // number of trailing bytes that are part of the value. We use `uint32_t`
354 // here because we only care about the first byte, and so that be actually
355 // get ctz intrinsic calls when possible (the `uint8_t` overload uses a loop
356 // implementation).
357 uint32_t numBytes = llvm::countr_zero<uint32_t>(result);
358 assert(numBytes > 0 && numBytes <= 7 &&
359 "unexpected number of trailing zeros in varint encoding");
360
361 // Parse in the remaining bytes of the value.
362 llvm::support::ulittle64_t resultLE(result);
363 if (failed(
364 parseBytes(numBytes, reinterpret_cast<uint8_t *>(&resultLE) + 1)))
365 return failure();
366
367 // Shift out the low-order bits that were used to mark how the value was
368 // encoded.
369 result = resultLE >> (numBytes + 1);
370 return success();
371 }
372
373 /// The bytecode buffer.
374 ArrayRef<uint8_t> buffer;
375
376 /// The current iterator within the 'buffer'.
377 const uint8_t *dataIt;
378
379 /// A location for the bytecode used to report errors.
380 Location fileLoc;
381};
382} // namespace
383
384/// Resolve an index into the given entry list. `entry` may either be a
385/// reference, in which case it is assigned to the corresponding value in
386/// `entries`, or a pointer, in which case it is assigned to the address of the
387/// element in `entries`.
388template <typename RangeT, typename T>
389static LogicalResult resolveEntry(EncodingReader &reader, RangeT &entries,
390 uint64_t index, T &entry,
391 StringRef entryStr) {
392 if (index >= entries.size())
393 return reader.emitError("invalid ", entryStr, " index: ", index);
394
395 // If the provided entry is a pointer, resolve to the address of the entry.
396 if constexpr (std::is_convertible_v<llvm::detail::ValueOfRange<RangeT>, T>)
397 entry = entries[index];
398 else
399 entry = &entries[index];
400 return success();
401}
402
403/// Parse and resolve an index into the given entry list.
404template <typename RangeT, typename T>
405static LogicalResult parseEntry(EncodingReader &reader, RangeT &entries,
406 T &entry, StringRef entryStr) {
407 uint64_t entryIdx;
408 if (failed(reader.parseVarInt(entryIdx)))
409 return failure();
410 return resolveEntry(reader, entries, entryIdx, entry, entryStr);
411}
412
413//===----------------------------------------------------------------------===//
414// StringSectionReader
415//===----------------------------------------------------------------------===//
416
417namespace {
418/// This class is used to read references to the string section from the
419/// bytecode.
420class StringSectionReader {
421public:
422 /// Initialize the string section reader with the given section data.
423 LogicalResult initialize(Location fileLoc, ArrayRef<uint8_t> sectionData);
424
425 /// Parse a shared string from the string section. The shared string is
426 /// encoded using an index to a corresponding string in the string section.
427 LogicalResult parseString(EncodingReader &reader, StringRef &result) const {
428 return parseEntry(reader, strings, result, "string");
429 }
430
431 /// Parse a shared string from the string section. The shared string is
432 /// encoded using an index to a corresponding string in the string section.
433 /// This variant parses a flag compressed with the index.
434 LogicalResult parseStringWithFlag(EncodingReader &reader, StringRef &result,
435 bool &flag) const {
436 uint64_t entryIdx;
437 if (failed(reader.parseVarIntWithFlag(entryIdx, flag)))
438 return failure();
439 return parseStringAtIndex(reader, entryIdx, result);
440 }
441
442 /// Parse a shared string from the string section. The shared string is
443 /// encoded using an index to a corresponding string in the string section.
444 LogicalResult parseStringAtIndex(EncodingReader &reader, uint64_t index,
445 StringRef &result) const {
446 return resolveEntry(reader, strings, index, result, "string");
447 }
448
449private:
450 /// The table of strings referenced within the bytecode file.
451 SmallVector<StringRef> strings;
452};
453} // namespace
454
455LogicalResult StringSectionReader::initialize(Location fileLoc,
456 ArrayRef<uint8_t> sectionData) {
457 EncodingReader stringReader(sectionData, fileLoc);
458
459 // Parse the number of strings in the section.
460 uint64_t numStrings;
461 if (failed(stringReader.parseVarInt(numStrings)))
462 return failure();
463 strings.resize(numStrings);
464
465 // Parse each of the strings. The sizes of the strings are encoded in reverse
466 // order, so that's the order we populate the table.
467 size_t stringDataEndOffset = sectionData.size();
468 for (StringRef &string : llvm::reverse(strings)) {
469 uint64_t stringSize;
470 if (failed(stringReader.parseVarInt(stringSize)))
471 return failure();
472 if (stringDataEndOffset < stringSize) {
473 return stringReader.emitError(
474 "string size exceeds the available data size");
475 }
476
477 // Extract the string from the data, dropping the null character.
478 size_t stringOffset = stringDataEndOffset - stringSize;
479 string = StringRef(
480 reinterpret_cast<const char *>(sectionData.data() + stringOffset),
481 stringSize - 1);
482 stringDataEndOffset = stringOffset;
483 }
484
485 // Check that the only remaining data was for the strings, i.e. the reader
486 // should be at the same offset as the first string.
487 if ((sectionData.size() - stringReader.size()) != stringDataEndOffset) {
488 return stringReader.emitError("unexpected trailing data between the "
489 "offsets for strings and their data");
490 }
491 return success();
492}
493
494//===----------------------------------------------------------------------===//
495// BytecodeDialect
496//===----------------------------------------------------------------------===//
497
498namespace {
499class DialectReader;
500
501/// This struct represents a dialect entry within the bytecode.
502struct BytecodeDialect {
503 /// Load the dialect into the provided context if it hasn't been loaded yet.
504 /// Returns failure if the dialect couldn't be loaded *and* the provided
505 /// context does not allow unregistered dialects. The provided reader is used
506 /// for error emission if necessary.
507 LogicalResult load(const DialectReader &reader, MLIRContext *ctx);
508
509 /// Return the loaded dialect, or nullptr if the dialect is unknown. This can
510 /// only be called after `load`.
511 Dialect *getLoadedDialect() const {
512 assert(dialect &&
513 "expected `load` to be invoked before `getLoadedDialect`");
514 return *dialect;
515 }
516
517 /// The loaded dialect entry. This field is std::nullopt if we haven't
518 /// attempted to load, nullptr if we failed to load, otherwise the loaded
519 /// dialect.
520 std::optional<Dialect *> dialect;
521
522 /// The bytecode interface of the dialect, or nullptr if the dialect does not
523 /// implement the bytecode interface. This field should only be checked if the
524 /// `dialect` field is not std::nullopt.
525 const BytecodeDialectInterface *interface = nullptr;
526
527 /// The name of the dialect.
528 StringRef name;
529
530 /// A buffer containing the encoding of the dialect version parsed.
531 ArrayRef<uint8_t> versionBuffer;
532
533 /// Lazy loaded dialect version from the handle above.
534 std::unique_ptr<DialectVersion> loadedVersion;
535};
536
537/// This struct represents an operation name entry within the bytecode.
538struct BytecodeOperationName {
539 BytecodeOperationName(BytecodeDialect *dialect, StringRef name,
540 std::optional<bool> wasRegistered)
541 : dialect(dialect), name(name), wasRegistered(wasRegistered) {}
542
543 /// The loaded operation name, or std::nullopt if it hasn't been processed
544 /// yet.
545 std::optional<OperationName> opName;
546
547 /// The dialect that owns this operation name.
548 BytecodeDialect *dialect;
549
550 /// The name of the operation, without the dialect prefix.
551 StringRef name;
552
553 /// Whether this operation was registered when the bytecode was produced.
554 /// This flag is populated when bytecode version >=kNativePropertiesEncoding.
555 std::optional<bool> wasRegistered;
556};
557} // namespace
558
559/// Parse a single dialect group encoded in the byte stream.
560static LogicalResult parseDialectGrouping(
561 EncodingReader &reader,
562 MutableArrayRef<std::unique_ptr<BytecodeDialect>> dialects,
563 function_ref<LogicalResult(BytecodeDialect *)> entryCallback) {
564 // Parse the dialect and the number of entries in the group.
565 std::unique_ptr<BytecodeDialect> *dialect;
566 if (failed(parseEntry(reader, dialects, dialect, "dialect")))
567 return failure();
568 uint64_t numEntries;
569 if (failed(reader.parseVarInt(numEntries)))
570 return failure();
571
572 for (uint64_t i = 0; i < numEntries; ++i)
573 if (failed(entryCallback(dialect->get())))
574 return failure();
575 return success();
576}
577
578//===----------------------------------------------------------------------===//
579// ResourceSectionReader
580//===----------------------------------------------------------------------===//
581
582namespace {
583/// This class is used to read the resource section from the bytecode.
584class ResourceSectionReader {
585public:
586 /// Initialize the resource section reader with the given section data.
587 LogicalResult
588 initialize(Location fileLoc, const ParserConfig &config,
589 MutableArrayRef<std::unique_ptr<BytecodeDialect>> dialects,
590 StringSectionReader &stringReader, ArrayRef<uint8_t> sectionData,
591 ArrayRef<uint8_t> offsetSectionData, DialectReader &dialectReader,
592 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef);
593
594 /// Parse a dialect resource handle from the resource section.
595 LogicalResult parseResourceHandle(EncodingReader &reader,
596 AsmDialectResourceHandle &result) const {
597 return parseEntry(reader, dialectResources, result, "resource handle");
598 }
599
600private:
601 /// The table of dialect resources within the bytecode file.
602 SmallVector<AsmDialectResourceHandle> dialectResources;
603 llvm::StringMap<std::string> dialectResourceHandleRenamingMap;
604};
605
606class ParsedResourceEntry : public AsmParsedResourceEntry {
607public:
608 ParsedResourceEntry(StringRef key, AsmResourceEntryKind kind,
609 EncodingReader &reader, StringSectionReader &stringReader,
610 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef)
611 : key(key), kind(kind), reader(reader), stringReader(stringReader),
612 bufferOwnerRef(bufferOwnerRef) {}
613 ~ParsedResourceEntry() override = default;
614
615 StringRef getKey() const final { return key; }
616
617 InFlightDiagnostic emitError() const final { return reader.emitError(); }
618
619 AsmResourceEntryKind getKind() const final { return kind; }
620
621 FailureOr<bool> parseAsBool() const final {
622 if (kind != AsmResourceEntryKind::Bool)
623 return emitError() << "expected a bool resource entry, but found a "
624 << toString(kind) << " entry instead";
625
626 bool value;
627 if (failed(reader.parseByte(value)))
628 return failure();
629 return value;
630 }
631 FailureOr<std::string> parseAsString() const final {
632 if (kind != AsmResourceEntryKind::String)
633 return emitError() << "expected a string resource entry, but found a "
634 << toString(kind) << " entry instead";
635
636 StringRef string;
637 if (failed(stringReader.parseString(reader, string)))
638 return failure();
639 return string.str();
640 }
641
642 FailureOr<AsmResourceBlob>
643 parseAsBlob(BlobAllocatorFn allocator) const final {
644 if (kind != AsmResourceEntryKind::Blob)
645 return emitError() << "expected a blob resource entry, but found a "
646 << toString(kind) << " entry instead";
647
648 ArrayRef<uint8_t> data;
649 uint64_t alignment;
650 if (failed(reader.parseBlobAndAlignment(data, alignment)))
651 return failure();
652
653 // If we have an extendable reference to the buffer owner, we don't need to
654 // allocate a new buffer for the data, and can use the data directly.
655 if (bufferOwnerRef) {
656 ArrayRef<char> charData(reinterpret_cast<const char *>(data.data()),
657 data.size());
658
659 // Allocate an unmanager buffer which captures a reference to the owner.
660 // For now we just mark this as immutable, but in the future we should
661 // explore marking this as mutable when desired.
663 charData, alignment,
664 [bufferOwnerRef = bufferOwnerRef](void *, size_t, size_t) {});
665 }
666
667 // Allocate memory for the blob using the provided allocator and copy the
668 // data into it.
669 AsmResourceBlob blob = allocator(data.size(), alignment);
670 assert(llvm::isAddrAligned(llvm::Align(alignment), blob.getData().data()) &&
671 blob.isMutable() &&
672 "blob allocator did not return a properly aligned address");
673 memcpy(blob.getMutableData().data(), data.data(), data.size());
674 return blob;
675 }
676
677private:
678 StringRef key;
680 EncodingReader &reader;
681 StringSectionReader &stringReader;
682 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef;
683};
684} // namespace
685
686template <typename T>
687static LogicalResult
688parseResourceGroup(Location fileLoc, bool allowEmpty,
689 EncodingReader &offsetReader, EncodingReader &resourceReader,
690 StringSectionReader &stringReader, T *handler,
691 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef,
692 function_ref<StringRef(StringRef)> remapKey = {},
693 function_ref<LogicalResult(StringRef)> processKeyFn = {}) {
694 uint64_t numResources;
695 if (failed(offsetReader.parseVarInt(numResources)))
696 return failure();
697
698 for (uint64_t i = 0; i < numResources; ++i) {
699 StringRef key;
701 uint64_t resourceOffset;
702 ArrayRef<uint8_t> data;
703 if (failed(stringReader.parseString(offsetReader, key)) ||
704 failed(offsetReader.parseVarInt(resourceOffset)) ||
705 failed(offsetReader.parseByte(kind)) ||
706 failed(resourceReader.parseBytes(resourceOffset, data)))
707 return failure();
708
709 // Process the resource key.
710 if ((processKeyFn && failed(processKeyFn(key))))
711 return failure();
712
713 // If the resource data is empty and we allow it, don't error out when
714 // parsing below, just skip it.
715 if (allowEmpty && data.empty())
716 continue;
717
718 // Ignore the entry if we don't have a valid handler.
719 if (!handler)
720 continue;
721
722 // Otherwise, parse the resource value.
723 EncodingReader entryReader(data, fileLoc);
724 key = remapKey(key);
725 ParsedResourceEntry entry(key, kind, entryReader, stringReader,
726 bufferOwnerRef);
727 if (failed(handler->parseResource(entry)))
728 return failure();
729 if (!entryReader.empty()) {
730 return entryReader.emitError(
731 "unexpected trailing bytes in resource entry '", key, "'");
732 }
733 }
734 return success();
735}
736
737LogicalResult ResourceSectionReader::initialize(
738 Location fileLoc, const ParserConfig &config,
739 MutableArrayRef<std::unique_ptr<BytecodeDialect>> dialects,
740 StringSectionReader &stringReader, ArrayRef<uint8_t> sectionData,
741 ArrayRef<uint8_t> offsetSectionData, DialectReader &dialectReader,
742 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef) {
743 EncodingReader resourceReader(sectionData, fileLoc);
744 EncodingReader offsetReader(offsetSectionData, fileLoc);
745
746 // Read the number of external resource providers.
747 uint64_t numExternalResourceGroups;
748 if (failed(offsetReader.parseVarInt(numExternalResourceGroups)))
749 return failure();
750
751 // Utility functor that dispatches to `parseResourceGroup`, but implicitly
752 // provides most of the arguments.
753 auto parseGroup = [&](auto *handler, bool allowEmpty = false,
754 function_ref<LogicalResult(StringRef)> keyFn = {}) {
755 auto resolveKey = [&](StringRef key) -> StringRef {
756 auto it = dialectResourceHandleRenamingMap.find(key);
757 if (it == dialectResourceHandleRenamingMap.end())
758 return key;
759 return it->second;
760 };
761
762 return parseResourceGroup(fileLoc, allowEmpty, offsetReader, resourceReader,
763 stringReader, handler, bufferOwnerRef, resolveKey,
764 keyFn);
765 };
766
767 // Read the external resources from the bytecode.
768 for (uint64_t i = 0; i < numExternalResourceGroups; ++i) {
769 StringRef key;
770 if (failed(stringReader.parseString(offsetReader, key)))
771 return failure();
772
773 // Get the handler for these resources.
774 // TODO: Should we require handling external resources in some scenarios?
775 AsmResourceParser *handler = config.getResourceParser(key);
776 if (!handler) {
777 emitWarning(fileLoc) << "ignoring unknown external resources for '" << key
778 << "'";
779 }
780
781 if (failed(parseGroup(handler)))
782 return failure();
783 }
784
785 // Read the dialect resources from the bytecode.
786 MLIRContext *ctx = fileLoc->getContext();
787 while (!offsetReader.empty()) {
788 std::unique_ptr<BytecodeDialect> *dialect;
789 if (failed(parseEntry(offsetReader, dialects, dialect, "dialect")) ||
790 failed((*dialect)->load(dialectReader, ctx)))
791 return failure();
792 Dialect *loadedDialect = (*dialect)->getLoadedDialect();
793 if (!loadedDialect) {
794 return resourceReader.emitError()
795 << "dialect '" << (*dialect)->name << "' is unknown";
796 }
797 const auto *handler = dyn_cast<OpAsmDialectInterface>(loadedDialect);
798 if (!handler) {
799 return resourceReader.emitError()
800 << "unexpected resources for dialect '" << (*dialect)->name << "'";
801 }
802
803 // Ensure that each resource is declared before being processed.
804 auto processResourceKeyFn = [&](StringRef key) -> LogicalResult {
805 FailureOr<AsmDialectResourceHandle> handle =
806 handler->declareResource(key);
807 if (failed(handle)) {
808 return resourceReader.emitError()
809 << "unknown 'resource' key '" << key << "' for dialect '"
810 << (*dialect)->name << "'";
811 }
812 dialectResourceHandleRenamingMap[key] = handler->getResourceKey(*handle);
813 dialectResources.push_back(*handle);
814 return success();
815 };
816
817 // Parse the resources for this dialect. We allow empty resources because we
818 // just treat these as declarations.
819 if (failed(parseGroup(handler, /*allowEmpty=*/true, processResourceKeyFn)))
820 return failure();
821 }
822
823 return success();
824}
825
826//===----------------------------------------------------------------------===//
827// Attribute/Type Reader
828//===----------------------------------------------------------------------===//
829
830namespace {
831/// This class provides support for reading attribute and type entries from the
832/// bytecode. Attribute and Type entries are read lazily on demand, so we use
833/// this reader to manage when to actually parse them from the bytecode.
834///
835/// The parsing of attributes & types are generally recursive, this can lead to
836/// stack overflows for deeply nested structures, so we track a few extra pieces
837/// of information to avoid this:
838///
839/// - `depth`: The current depth while parsing nested attributes. We defer on
840/// parsing deeply nested attributes to avoid potential stack overflows. The
841/// deferred parsing is achieved by reporting a failure when parsing a nested
842/// attribute/type and registering the index of the encountered attribute/type
843/// in the deferred parsing worklist. Hence, a failure with deffered entry
844/// does not constitute a failure, it also requires that folks return on
845/// first failure rather than attempting additional parses.
846/// - `deferredWorklist`: A list of attribute/type indices that we could not
847/// parse due to hitting the depth limit. The worklist is used to capture the
848/// indices of attributes/types that need to be parsed/reparsed when we hit
849/// the depth limit. This enables moving the tracking of what needs to be
850/// parsed to the heap.
851class AttrTypeReader {
852 /// This class represents a single attribute or type entry.
853 template <typename T>
854 struct Entry {
855 /// The entry, or null if it hasn't been resolved yet.
856 T entry = {};
857 /// The parent dialect of this entry.
858 BytecodeDialect *dialect = nullptr;
859 /// A flag indicating if the entry was encoded using a custom encoding,
860 /// instead of using the textual assembly format.
861 bool hasCustomEncoding = false;
862 /// The raw data of this entry in the bytecode.
863 ArrayRef<uint8_t> data;
864 };
865 using AttrEntry = Entry<Attribute>;
866 using TypeEntry = Entry<Type>;
867
868public:
869 AttrTypeReader(const StringSectionReader &stringReader,
870 const ResourceSectionReader &resourceReader,
871 const llvm::StringMap<BytecodeDialect *> &dialectsMap,
872 uint64_t &bytecodeVersion, Location fileLoc,
873 const ParserConfig &config)
874 : stringReader(stringReader), resourceReader(resourceReader),
875 dialectsMap(dialectsMap), fileLoc(fileLoc),
876 bytecodeVersion(bytecodeVersion), parserConfig(config) {}
877
878 /// Initialize the attribute and type information within the reader.
879 LogicalResult
880 initialize(MutableArrayRef<std::unique_ptr<BytecodeDialect>> dialects,
881 ArrayRef<uint8_t> sectionData,
882 ArrayRef<uint8_t> offsetSectionData);
883
884 LogicalResult readAttribute(uint64_t index, Attribute &result,
885 uint64_t depth = 0) {
886 return readEntry(attributes, index, result, "attribute", depth);
887 }
888
889 LogicalResult readType(uint64_t index, Type &result, uint64_t depth = 0) {
890 return readEntry(types, index, result, "type", depth);
891 }
892
893 /// Resolve the attribute or type at the given index. Returns nullptr on
894 /// failure.
895 Attribute resolveAttribute(size_t index, uint64_t depth = 0) {
896 return resolveEntry(attributes, index, "Attribute", depth);
897 }
898 Type resolveType(size_t index, uint64_t depth = 0) {
899 return resolveEntry(types, index, "Type", depth);
900 }
901
902 Attribute getAttributeOrSentinel(size_t index) {
903 if (index >= attributes.size())
904 return nullptr;
905 return attributes[index].entry;
906 }
907 Type getTypeOrSentinel(size_t index) {
908 if (index >= types.size())
909 return nullptr;
910 return types[index].entry;
911 }
912
913 /// Parse a reference to an attribute or type using the given reader.
914 LogicalResult parseAttribute(EncodingReader &reader, Attribute &result) {
915 uint64_t attrIdx;
916 if (failed(reader.parseVarInt(attrIdx)))
917 return failure();
918 result = resolveAttribute(attrIdx);
919 return success(!!result);
920 }
921 LogicalResult parseOptionalAttribute(EncodingReader &reader,
922 Attribute &result) {
923 uint64_t attrIdx;
924 bool flag;
925 if (failed(reader.parseVarIntWithFlag(attrIdx, flag)))
926 return failure();
927 if (!flag)
928 return success();
929 result = resolveAttribute(attrIdx);
930 return success(!!result);
931 }
932
933 LogicalResult parseType(EncodingReader &reader, Type &result) {
934 uint64_t typeIdx;
935 if (failed(reader.parseVarInt(typeIdx)))
936 return failure();
937 result = resolveType(typeIdx);
938 return success(!!result);
939 }
940
941 template <typename T>
942 LogicalResult parseAttribute(EncodingReader &reader, T &result) {
943 Attribute baseResult;
944 if (failed(parseAttribute(reader, baseResult)))
945 return failure();
946 if ((result = dyn_cast<T>(baseResult)))
947 return success();
948 return reader.emitError("expected attribute of type: ",
949 llvm::getTypeName<T>(), ", but got: ", baseResult);
950 }
951
952 /// Add an index to the deferred worklist for re-parsing.
953 void addDeferredParsing(uint64_t index) { deferredWorklist.push_back(index); }
954
955 /// Whether currently resolving.
956 bool isResolving() const { return resolving; }
957
958private:
959 /// Resolve the given entry at `index`.
960 template <typename T>
961 T resolveEntry(SmallVectorImpl<Entry<T>> &entries, uint64_t index,
962 StringRef entryType, uint64_t depth = 0);
963
964 /// Read the entry at the given index, returning failure if the entry is not
965 /// yet resolved.
966 template <typename T>
967 LogicalResult readEntry(SmallVectorImpl<Entry<T>> &entries, uint64_t index,
968 T &result, StringRef entryType, uint64_t depth);
969
970 /// Parse an entry using the given reader that was encoded using a custom
971 /// bytecode format.
972 template <typename T>
973 LogicalResult parseCustomEntry(Entry<T> &entry, EncodingReader &reader,
974 StringRef entryType, uint64_t index,
975 uint64_t depth);
976
977 /// Parse an entry using the given reader that was encoded using the textual
978 /// assembly format.
979 template <typename T>
980 LogicalResult parseAsmEntry(T &result, EncodingReader &reader,
981 StringRef entryType);
982
983 /// The string section reader used to resolve string references when parsing
984 /// custom encoded attribute/type entries.
985 const StringSectionReader &stringReader;
986
987 /// The resource section reader used to resolve resource references when
988 /// parsing custom encoded attribute/type entries.
989 const ResourceSectionReader &resourceReader;
990
991 /// The map of the loaded dialects used to retrieve dialect information, such
992 /// as the dialect version.
993 const llvm::StringMap<BytecodeDialect *> &dialectsMap;
994
995 /// The set of attribute and type entries.
996 SmallVector<AttrEntry> attributes;
997 SmallVector<TypeEntry> types;
998
999 /// A location used for error emission.
1000 Location fileLoc;
1001
1002 /// Current bytecode version being used.
1003 uint64_t &bytecodeVersion;
1004
1005 /// Reference to the parser configuration.
1006 const ParserConfig &parserConfig;
1007
1008 /// Worklist for deferred attribute/type parsing. This is used to handle
1009 /// deeply nested structures like CallSiteLoc iteratively.
1010 std::vector<uint64_t> deferredWorklist;
1011
1012 /// Flag indicating if we are currently resolving an attribute or type.
1013 bool resolving = false;
1014};
1015
1016class DialectReader : public DialectBytecodeReader {
1017public:
1018 DialectReader(AttrTypeReader &attrTypeReader,
1019 const StringSectionReader &stringReader,
1020 const ResourceSectionReader &resourceReader,
1021 const llvm::StringMap<BytecodeDialect *> &dialectsMap,
1022 EncodingReader &reader, uint64_t &bytecodeVersion,
1023 uint64_t depth = 0)
1024 : attrTypeReader(attrTypeReader), stringReader(stringReader),
1025 resourceReader(resourceReader), dialectsMap(dialectsMap),
1026 reader(reader), bytecodeVersion(bytecodeVersion), depth(depth) {}
1027
1028 InFlightDiagnostic emitError(const Twine &msg) const override {
1029 return reader.emitError(msg);
1030 }
1031
1032 FailureOr<const DialectVersion *>
1033 getDialectVersion(StringRef dialectName) const override {
1034 // First check if the dialect is available in the map.
1035 auto dialectEntry = dialectsMap.find(dialectName);
1036 if (dialectEntry == dialectsMap.end())
1037 return failure();
1038 // If the dialect was found, try to load it. This will trigger reading the
1039 // bytecode version from the version buffer if it wasn't already processed.
1040 // Return failure if either of those two actions could not be completed.
1041 if (failed(dialectEntry->getValue()->load(*this, getLoc().getContext())) ||
1042 dialectEntry->getValue()->loadedVersion == nullptr)
1043 return failure();
1044 return dialectEntry->getValue()->loadedVersion.get();
1045 }
1046
1047 MLIRContext *getContext() const override { return getLoc().getContext(); }
1048
1049 uint64_t getBytecodeVersion() const override { return bytecodeVersion; }
1050
1051 DialectReader withEncodingReader(EncodingReader &encReader) const {
1052 return DialectReader(attrTypeReader, stringReader, resourceReader,
1053 dialectsMap, encReader, bytecodeVersion);
1054 }
1055
1056 Location getLoc() const { return reader.getLoc(); }
1057
1058 //===--------------------------------------------------------------------===//
1059 // IR
1060 //===--------------------------------------------------------------------===//
1061
1062 /// The maximum depth to eagerly parse nested attributes/types before
1063 /// deferring.
1064 static constexpr uint64_t maxAttrTypeDepth = 5;
1065
1066 LogicalResult readAttribute(Attribute &result) override {
1067 uint64_t index;
1068 if (failed(reader.parseVarInt(index)))
1069 return failure();
1070
1071 // If we aren't currently resolving an attribute/type, we resolve this
1072 // attribute eagerly. This is the case when we are parsing properties, which
1073 // aren't processed via the worklist.
1074 if (!attrTypeReader.isResolving()) {
1075 if (Attribute attr = attrTypeReader.resolveAttribute(index)) {
1076 result = attr;
1077 return success();
1078 }
1079 return failure();
1080 }
1081
1082 if (depth > maxAttrTypeDepth) {
1083 if (Attribute attr = attrTypeReader.getAttributeOrSentinel(index)) {
1084 result = attr;
1085 return success();
1086 }
1087 attrTypeReader.addDeferredParsing(index);
1088 return failure();
1089 }
1090 return attrTypeReader.readAttribute(index, result, depth + 1);
1091 }
1092 LogicalResult readOptionalAttribute(Attribute &result) override {
1093 return attrTypeReader.parseOptionalAttribute(reader, result);
1094 }
1095 LogicalResult readType(Type &result) override {
1096 uint64_t index;
1097 if (failed(reader.parseVarInt(index)))
1098 return failure();
1099
1100 // If we aren't currently resolving an attribute/type, we resolve this
1101 // type eagerly. This is the case when we are parsing properties, which
1102 // aren't processed via the worklist.
1103 if (!attrTypeReader.isResolving()) {
1104 if (Type type = attrTypeReader.resolveType(index)) {
1105 result = type;
1106 return success();
1107 }
1108 return failure();
1109 }
1110
1111 if (depth > maxAttrTypeDepth) {
1112 if (Type type = attrTypeReader.getTypeOrSentinel(index)) {
1113 result = type;
1114 return success();
1115 }
1116 attrTypeReader.addDeferredParsing(index);
1117 return failure();
1118 }
1119 return attrTypeReader.readType(index, result, depth + 1);
1120 }
1121
1122 FailureOr<AsmDialectResourceHandle> readResourceHandle() override {
1123 AsmDialectResourceHandle handle;
1124 if (failed(resourceReader.parseResourceHandle(reader, handle)))
1125 return failure();
1126 return handle;
1127 }
1128
1129 //===--------------------------------------------------------------------===//
1130 // Primitives
1131 //===--------------------------------------------------------------------===//
1132
1133 LogicalResult readVarInt(uint64_t &result) override {
1134 return reader.parseVarInt(result);
1135 }
1136
1137 LogicalResult readSignedVarInt(int64_t &result) override {
1138 uint64_t unsignedResult;
1139 if (failed(reader.parseSignedVarInt(unsignedResult)))
1140 return failure();
1141 result = static_cast<int64_t>(unsignedResult);
1142 return success();
1143 }
1144
1145 FailureOr<APInt> readAPIntWithKnownWidth(unsigned bitWidth) override {
1146 // Small values are encoded using a single byte.
1147 if (bitWidth <= 8) {
1148 uint8_t value;
1149 if (failed(reader.parseByte(value)))
1150 return failure();
1151 return APInt(bitWidth, value);
1152 }
1153
1154 // Large values up to 64 bits are encoded using a single varint.
1155 if (bitWidth <= 64) {
1156 uint64_t value;
1157 if (failed(reader.parseSignedVarInt(value)))
1158 return failure();
1159 return APInt(bitWidth, value);
1160 }
1161
1162 // Otherwise, for really big values we encode the array of active words in
1163 // the value.
1164 uint64_t numActiveWords;
1165 if (failed(reader.parseVarInt(numActiveWords)))
1166 return failure();
1167 SmallVector<uint64_t, 4> words(numActiveWords);
1168 for (uint64_t i = 0; i < numActiveWords; ++i)
1169 if (failed(reader.parseSignedVarInt(words[i])))
1170 return failure();
1171 return APInt(bitWidth, words);
1172 }
1173
1174 FailureOr<APFloat>
1175 readAPFloatWithKnownSemantics(const llvm::fltSemantics &semantics) override {
1176 FailureOr<APInt> intVal =
1177 readAPIntWithKnownWidth(APFloat::getSizeInBits(semantics));
1178 if (failed(intVal))
1179 return failure();
1180 return APFloat(semantics, *intVal);
1181 }
1182
1183 LogicalResult readString(StringRef &result) override {
1184 return stringReader.parseString(reader, result);
1185 }
1186
1187 LogicalResult readBlob(ArrayRef<char> &result) override {
1188 uint64_t dataSize;
1189 ArrayRef<uint8_t> data;
1190 if (failed(reader.parseVarInt(dataSize)) ||
1191 failed(reader.parseBytes(dataSize, data)))
1192 return failure();
1193 result = llvm::ArrayRef(reinterpret_cast<const char *>(data.data()),
1194 data.size());
1195 return success();
1196 }
1197
1198 LogicalResult readBool(bool &result) override {
1199 return reader.parseByte(result);
1200 }
1201
1202private:
1203 AttrTypeReader &attrTypeReader;
1204 const StringSectionReader &stringReader;
1205 const ResourceSectionReader &resourceReader;
1206 const llvm::StringMap<BytecodeDialect *> &dialectsMap;
1207 EncodingReader &reader;
1208 uint64_t &bytecodeVersion;
1209 uint64_t depth;
1210};
1211
1212/// Wraps the properties section and handles reading properties out of it.
1213class PropertiesSectionReader {
1214public:
1215 /// Initialize the properties section reader with the given section data.
1216 LogicalResult initialize(Location fileLoc, ArrayRef<uint8_t> sectionData) {
1217 if (sectionData.empty())
1218 return success();
1219 EncodingReader propReader(sectionData, fileLoc);
1220 uint64_t count;
1221 if (failed(propReader.parseVarInt(count)))
1222 return failure();
1223 // Parse the raw properties buffer.
1224 if (failed(propReader.parseBytes(propReader.size(), propertiesBuffers)))
1225 return failure();
1226
1227 EncodingReader offsetsReader(propertiesBuffers, fileLoc);
1228 offsetTable.reserve(count);
1229 for (auto idx : llvm::seq<int64_t>(0, count)) {
1230 (void)idx;
1231 offsetTable.push_back(propertiesBuffers.size() - offsetsReader.size());
1232 ArrayRef<uint8_t> rawProperties;
1233 uint64_t dataSize;
1234 if (failed(offsetsReader.parseVarInt(dataSize)) ||
1235 failed(offsetsReader.parseBytes(dataSize, rawProperties)))
1236 return failure();
1237 }
1238 if (!offsetsReader.empty())
1239 return offsetsReader.emitError()
1240 << "Broken properties section: didn't exhaust the offsets table";
1241 return success();
1242 }
1243
1244 LogicalResult read(Location fileLoc, DialectReader &dialectReader,
1245 OperationName *opName, OperationState &opState) const {
1246 uint64_t propertiesIdx;
1247 if (failed(dialectReader.readVarInt(propertiesIdx)))
1248 return failure();
1249 if (propertiesIdx >= offsetTable.size())
1250 return dialectReader.emitError("Properties idx out-of-bound for ")
1251 << opName->getStringRef();
1252 size_t propertiesOffset = offsetTable[propertiesIdx];
1253 if (propertiesIdx >= propertiesBuffers.size())
1254 return dialectReader.emitError("Properties offset out-of-bound for ")
1255 << opName->getStringRef();
1256
1257 // Acquire the sub-buffer that represent the requested properties.
1258 ArrayRef<char> rawProperties;
1259 {
1260 // "Seek" to the requested offset by getting a new reader with the right
1261 // sub-buffer.
1262 EncodingReader reader(propertiesBuffers.drop_front(propertiesOffset),
1263 fileLoc);
1264 // Properties are stored as a sequence of {size + raw_data}.
1265 if (failed(
1266 dialectReader.withEncodingReader(reader).readBlob(rawProperties)))
1267 return failure();
1268 }
1269 // Setup a new reader to read from the `rawProperties` sub-buffer.
1270 EncodingReader reader(
1271 StringRef(rawProperties.begin(), rawProperties.size()), fileLoc);
1272 DialectReader propReader = dialectReader.withEncodingReader(reader);
1273
1274 auto *iface = opName->getInterface<BytecodeOpInterface>();
1275 if (iface)
1276 return iface->readProperties(propReader, opState);
1277 if (opName->isRegistered())
1278 return propReader.emitError(
1279 "has properties but missing BytecodeOpInterface for ")
1280 << opName->getStringRef();
1281 // Unregistered op are storing properties as an attribute.
1282 return propReader.readAttribute(opState.propertiesAttr);
1283 }
1284
1285private:
1286 /// The properties buffer referenced within the bytecode file.
1287 ArrayRef<uint8_t> propertiesBuffers;
1288
1289 /// Table of offset in the buffer above.
1290 SmallVector<int64_t> offsetTable;
1291};
1292} // namespace
1293
1294LogicalResult AttrTypeReader::initialize(
1295 MutableArrayRef<std::unique_ptr<BytecodeDialect>> dialects,
1296 ArrayRef<uint8_t> sectionData, ArrayRef<uint8_t> offsetSectionData) {
1297 EncodingReader offsetReader(offsetSectionData, fileLoc);
1298
1299 // Parse the number of attribute and type entries.
1300 uint64_t numAttributes, numTypes;
1301 if (failed(offsetReader.parseVarInt(numAttributes)) ||
1302 failed(offsetReader.parseVarInt(numTypes)))
1303 return failure();
1304 attributes.resize(numAttributes);
1305 types.resize(numTypes);
1306
1307 // A functor used to accumulate the offsets for the entries in the given
1308 // range.
1309 uint64_t currentOffset = 0;
1310 auto parseEntries = [&](auto &&range) {
1311 size_t currentIndex = 0, endIndex = range.size();
1312
1313 // Parse an individual entry.
1314 auto parseEntryFn = [&](BytecodeDialect *dialect) -> LogicalResult {
1315 auto &entry = range[currentIndex++];
1316
1317 uint64_t entrySize;
1318 if (failed(offsetReader.parseVarIntWithFlag(entrySize,
1319 entry.hasCustomEncoding)))
1320 return failure();
1321
1322 // Verify that the offset is actually valid.
1323 if (currentOffset + entrySize > sectionData.size()) {
1324 return offsetReader.emitError(
1325 "Attribute or Type entry offset points past the end of section");
1326 }
1327
1328 entry.data = sectionData.slice(currentOffset, entrySize);
1329 entry.dialect = dialect;
1330 currentOffset += entrySize;
1331 return success();
1332 };
1333 while (currentIndex != endIndex)
1334 if (failed(parseDialectGrouping(offsetReader, dialects, parseEntryFn)))
1335 return failure();
1336 return success();
1337 };
1338
1339 // Process each of the attributes, and then the types.
1340 if (failed(parseEntries(attributes)) || failed(parseEntries(types)))
1341 return failure();
1342
1343 // Ensure that we read everything from the section.
1344 if (!offsetReader.empty()) {
1345 return offsetReader.emitError(
1346 "unexpected trailing data in the Attribute/Type offset section");
1347 }
1348
1349 return success();
1350}
1351
1352template <typename T>
1353T AttrTypeReader::resolveEntry(SmallVectorImpl<Entry<T>> &entries,
1354 uint64_t index, StringRef entryType,
1355 uint64_t depth) {
1356 bool oldResolving = resolving;
1357 resolving = true;
1358 llvm::scope_exit restoreResolving([&]() { resolving = oldResolving; });
1359
1360 if (index >= entries.size()) {
1361 emitError(fileLoc) << "invalid " << entryType << " index: " << index;
1362 return {};
1363 }
1364
1365 // Fast path: Try direct parsing without worklist overhead. This handles the
1366 // common case where there are no deferred dependencies.
1367 assert(deferredWorklist.empty());
1368 T result;
1369 if (succeeded(readEntry(entries, index, result, entryType, depth))) {
1370 assert(deferredWorklist.empty());
1371 return result;
1372 }
1373 if (deferredWorklist.empty()) {
1374 // Failed with no deferred entries is error.
1375 return T();
1376 }
1377
1378 // Slow path: Use worklist to handle deferred dependencies. Use a deque to
1379 // iteratively resolve entries with dependencies.
1380 // - Pop from front to process
1381 // - Push new dependencies to front (depth-first)
1382 // - Move failed entries to back (retry after dependencies)
1383 std::deque<size_t> worklist;
1384 llvm::DenseSet<size_t> inWorklist;
1385
1386 // Add the original index and any dependencies from the fast path attempt.
1387 worklist.push_back(index);
1388 inWorklist.insert(index);
1389 for (uint64_t idx : llvm::reverse(deferredWorklist)) {
1390 if (inWorklist.insert(idx).second)
1391 worklist.push_front(idx);
1392 }
1393
1394 while (!worklist.empty()) {
1395 size_t currentIndex = worklist.front();
1396 worklist.pop_front();
1397
1398 // Clear the deferred worklist before parsing to capture any new entries.
1399 deferredWorklist.clear();
1400
1401 T result;
1402 if (succeeded(readEntry(entries, currentIndex, result, entryType, depth))) {
1403 inWorklist.erase(currentIndex);
1404 continue;
1405 }
1406
1407 if (deferredWorklist.empty()) {
1408 // Parsing failed with no deferred entries which implies an error.
1409 return T();
1410 }
1411
1412 // Move this entry to the back to retry after dependencies.
1413 worklist.push_back(currentIndex);
1414
1415 // Add dependencies to the front (in reverse so they maintain order).
1416 for (uint64_t idx : llvm::reverse(deferredWorklist)) {
1417 if (inWorklist.insert(idx).second)
1418 worklist.push_front(idx);
1419 }
1420 deferredWorklist.clear();
1421 }
1422 return entries[index].entry;
1423}
1424
1425template <typename T>
1426LogicalResult AttrTypeReader::readEntry(SmallVectorImpl<Entry<T>> &entries,
1427 uint64_t index, T &result,
1428 StringRef entryType, uint64_t depth) {
1429 if (index >= entries.size())
1430 return emitError(fileLoc) << "invalid " << entryType << " index: " << index;
1431
1432 // If the entry has already been resolved, return it.
1433 Entry<T> &entry = entries[index];
1434 if (entry.entry) {
1435 result = entry.entry;
1436 return success();
1437 }
1438
1439 // If the entry hasn't been resolved, try to parse it.
1440 EncodingReader reader(entry.data, fileLoc);
1441 LogicalResult parseResult =
1442 entry.hasCustomEncoding
1443 ? parseCustomEntry(entry, reader, entryType, index, depth)
1444 : parseAsmEntry(entry.entry, reader, entryType);
1445 if (failed(parseResult))
1446 return failure();
1447
1448 if (!reader.empty())
1449 return reader.emitError("unexpected trailing bytes after " + entryType +
1450 " entry");
1451
1452 result = entry.entry;
1453 return success();
1454}
1455
1456template <typename T>
1457LogicalResult AttrTypeReader::parseCustomEntry(Entry<T> &entry,
1458 EncodingReader &reader,
1459 StringRef entryType,
1460 uint64_t index, uint64_t depth) {
1461 DialectReader dialectReader(*this, stringReader, resourceReader, dialectsMap,
1462 reader, bytecodeVersion, depth);
1463 if (failed(entry.dialect->load(dialectReader, fileLoc.getContext())))
1464 return failure();
1465
1466 if constexpr (std::is_same_v<T, Type>) {
1467 // Try parsing with callbacks first if available.
1468 for (const auto &callback :
1469 parserConfig.getBytecodeReaderConfig().getTypeCallbacks()) {
1470 if (failed(
1471 callback->read(dialectReader, entry.dialect->name, entry.entry)))
1472 return failure();
1473 // Early return if parsing was successful.
1474 if (!!entry.entry)
1475 return success();
1476
1477 // Reset the reader if we failed to parse, so we can fall through the
1478 // other parsing functions.
1479 reader = EncodingReader(entry.data, reader.getLoc());
1480 }
1481 } else {
1482 // Try parsing with callbacks first if available.
1483 for (const auto &callback :
1485 if (failed(
1486 callback->read(dialectReader, entry.dialect->name, entry.entry)))
1487 return failure();
1488 // Early return if parsing was successful.
1489 if (!!entry.entry)
1490 return success();
1491
1492 // Reset the reader if we failed to parse, so we can fall through the
1493 // other parsing functions.
1494 reader = EncodingReader(entry.data, reader.getLoc());
1495 }
1496 }
1497
1498 // Ensure that the dialect implements the bytecode interface.
1499 if (!entry.dialect->interface) {
1500 return reader.emitError("dialect '", entry.dialect->name,
1501 "' does not implement the bytecode interface");
1502 }
1503
1504 if constexpr (std::is_same_v<T, Type>)
1505 entry.entry = entry.dialect->interface->readType(dialectReader);
1506 else
1507 entry.entry = entry.dialect->interface->readAttribute(dialectReader);
1508
1509 return success(!!entry.entry);
1510}
1511
1512template <typename T>
1513LogicalResult AttrTypeReader::parseAsmEntry(T &result, EncodingReader &reader,
1514 StringRef entryType) {
1515 StringRef asmStr;
1516 if (failed(reader.parseNullTerminatedString(asmStr)))
1517 return failure();
1518
1519 // Invoke the MLIR assembly parser to parse the entry text.
1520 size_t numRead = 0;
1521 MLIRContext *context = fileLoc->getContext();
1522 if constexpr (std::is_same_v<T, Type>)
1523 result =
1524 ::parseType(asmStr, context, &numRead, /*isKnownNullTerminated=*/true);
1525 else
1526 result = ::parseAttribute(asmStr, context, Type(), &numRead,
1527 /*isKnownNullTerminated=*/true);
1528 if (!result)
1529 return failure();
1530
1531 // Ensure there weren't dangling characters after the entry.
1532 if (numRead != asmStr.size()) {
1533 return reader.emitError("trailing characters found after ", entryType,
1534 " assembly format: ", asmStr.drop_front(numRead));
1535 }
1536 return success();
1537}
1538
1539//===----------------------------------------------------------------------===//
1540// Bytecode Reader
1541//===----------------------------------------------------------------------===//
1542
1543/// This class is used to read a bytecode buffer and translate it into MLIR.
1545 struct RegionReadState;
1546 using LazyLoadableOpsInfo =
1547 std::list<std::pair<Operation *, RegionReadState>>;
1548 using LazyLoadableOpsMap =
1550
1551public:
1552 Impl(Location fileLoc, const ParserConfig &config, bool lazyLoading,
1553 llvm::MemoryBufferRef buffer,
1554 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef)
1555 : config(config), fileLoc(fileLoc), lazyLoading(lazyLoading),
1556 attrTypeReader(stringReader, resourceReader, dialectsMap, version,
1557 fileLoc, config),
1558 // Use the builtin unrealized conversion cast operation to represent
1559 // forward references to values that aren't yet defined.
1560 forwardRefOpState(UnknownLoc::get(config.getContext()),
1561 "builtin.unrealized_conversion_cast", ValueRange(),
1562 NoneType::get(config.getContext())),
1563 buffer(buffer), bufferOwnerRef(bufferOwnerRef) {}
1564
1565 /// Read the bytecode defined within `buffer` into the given block.
1566 LogicalResult read(Block *block,
1567 llvm::function_ref<bool(Operation *)> lazyOps);
1568
1569 /// Return the number of ops that haven't been materialized yet.
1570 int64_t getNumOpsToMaterialize() const { return lazyLoadableOpsMap.size(); }
1571
1572 bool isMaterializable(Operation *op) { return lazyLoadableOpsMap.count(op); }
1573
1574 /// Materialize the provided operation, invoke the lazyOpsCallback on every
1575 /// newly found lazy operation.
1576 LogicalResult
1578 llvm::function_ref<bool(Operation *)> lazyOpsCallback) {
1579 this->lazyOpsCallback = lazyOpsCallback;
1580 llvm::scope_exit resetlazyOpsCallback(
1581 [&] { this->lazyOpsCallback = nullptr; });
1582 auto it = lazyLoadableOpsMap.find(op);
1583 assert(it != lazyLoadableOpsMap.end() &&
1584 "materialize called on non-materializable op");
1585 return materialize(it);
1586 }
1587
1588 /// Materialize all operations.
1589 LogicalResult materializeAll() {
1590 while (!lazyLoadableOpsMap.empty()) {
1591 if (failed(materialize(lazyLoadableOpsMap.begin())))
1592 return failure();
1593 }
1594 return success();
1595 }
1596
1597 /// Finalize the lazy-loading by calling back with every op that hasn't been
1598 /// materialized to let the client decide if the op should be deleted or
1599 /// materialized. The op is materialized if the callback returns true, deleted
1600 /// otherwise.
1601 LogicalResult finalize(function_ref<bool(Operation *)> shouldMaterialize) {
1602 while (!lazyLoadableOps.empty()) {
1603 Operation *op = lazyLoadableOps.begin()->first;
1604 if (shouldMaterialize(op)) {
1605 if (failed(materialize(lazyLoadableOpsMap.find(op))))
1606 return failure();
1607 continue;
1608 }
1609 op->dropAllReferences();
1610 op->erase();
1611 lazyLoadableOps.pop_front();
1612 lazyLoadableOpsMap.erase(op);
1613 }
1614 return success();
1615 }
1616
1617private:
1618 LogicalResult materialize(LazyLoadableOpsMap::iterator it) {
1619 assert(it != lazyLoadableOpsMap.end() &&
1620 "materialize called on non-materializable op");
1621 valueScopes.emplace_back();
1622 std::vector<RegionReadState> regionStack;
1623 regionStack.push_back(std::move(it->getSecond()->second));
1624 lazyLoadableOps.erase(it->getSecond());
1625 lazyLoadableOpsMap.erase(it);
1626
1627 while (!regionStack.empty())
1628 if (failed(parseRegions(regionStack, regionStack.back())))
1629 return failure();
1630 return success();
1631 }
1632
1633 LogicalResult checkSectionAlignment(
1634 unsigned alignment,
1635 function_ref<InFlightDiagnostic(const Twine &error)> emitError) {
1636 // Check that the bytecode buffer meets the requested section alignment.
1637 //
1638 // If it does not, the virtual address of the item in the section will
1639 // not be aligned to the requested alignment.
1640 //
1641 // The typical case where this is necessary is the resource blob
1642 // optimization in `parseAsBlob` where we reference the weights from the
1643 // provided buffer instead of copying them to a new allocation.
1644 const bool isGloballyAligned =
1645 ((uintptr_t)buffer.getBufferStart() & (alignment - 1)) == 0;
1646
1647 if (!isGloballyAligned)
1648 return emitError("expected section alignment ")
1649 << alignment << " but bytecode buffer 0x"
1650 << Twine::utohexstr((uint64_t)buffer.getBufferStart())
1651 << " is not aligned";
1652
1653 return success();
1654 };
1655
1656 /// Return the context for this config.
1657 MLIRContext *getContext() const { return config.getContext(); }
1658
1659 /// Parse the bytecode version.
1660 LogicalResult parseVersion(EncodingReader &reader);
1661
1662 //===--------------------------------------------------------------------===//
1663 // Dialect Section
1664
1665 LogicalResult parseDialectSection(ArrayRef<uint8_t> sectionData);
1666
1667 /// Parse an operation name reference using the given reader, and set the
1668 /// `wasRegistered` flag that indicates if the bytecode was produced by a
1669 /// context where opName was registered.
1670 FailureOr<OperationName> parseOpName(EncodingReader &reader,
1671 std::optional<bool> &wasRegistered);
1672
1673 //===--------------------------------------------------------------------===//
1674 // Attribute/Type Section
1675
1676 /// Parse an attribute or type using the given reader.
1677 template <typename T>
1678 LogicalResult parseAttribute(EncodingReader &reader, T &result) {
1679 return attrTypeReader.parseAttribute(reader, result);
1680 }
1681 LogicalResult parseType(EncodingReader &reader, Type &result) {
1682 return attrTypeReader.parseType(reader, result);
1683 }
1684
1685 //===--------------------------------------------------------------------===//
1686 // Resource Section
1687
1688 LogicalResult
1689 parseResourceSection(EncodingReader &reader,
1690 std::optional<ArrayRef<uint8_t>> resourceData,
1691 std::optional<ArrayRef<uint8_t>> resourceOffsetData);
1692
1693 //===--------------------------------------------------------------------===//
1694 // IR Section
1695
1696 /// This struct represents the current read state of a range of regions. This
1697 /// struct is used to enable iterative parsing of regions.
1698 struct RegionReadState {
1699 RegionReadState(Operation *op, EncodingReader *reader,
1700 bool isIsolatedFromAbove)
1701 : RegionReadState(op->getRegions(), reader, isIsolatedFromAbove) {}
1702 RegionReadState(MutableArrayRef<Region> regions, EncodingReader *reader,
1703 bool isIsolatedFromAbove)
1704 : curRegion(regions.begin()), endRegion(regions.end()), reader(reader),
1705 isIsolatedFromAbove(isIsolatedFromAbove) {}
1706
1707 /// The current regions being read.
1708 MutableArrayRef<Region>::iterator curRegion, endRegion;
1709 /// This is the reader to use for this region, this pointer is pointing to
1710 /// the parent region reader unless the current region is IsolatedFromAbove,
1711 /// in which case the pointer is pointing to the `owningReader` which is a
1712 /// section dedicated to the current region.
1713 EncodingReader *reader;
1714 std::unique_ptr<EncodingReader> owningReader;
1715
1716 /// The number of values defined immediately within this region.
1717 unsigned numValues = 0;
1718
1719 /// The current blocks of the region being read.
1720 SmallVector<Block *> curBlocks;
1721 Region::iterator curBlock = {};
1722
1723 /// The number of operations remaining to be read from the current block
1724 /// being read.
1725 uint64_t numOpsRemaining = 0;
1726
1727 /// A flag indicating if the regions being read are isolated from above.
1728 bool isIsolatedFromAbove = false;
1729 };
1730
1731 LogicalResult parseIRSection(ArrayRef<uint8_t> sectionData, Block *block);
1732 LogicalResult parseRegions(std::vector<RegionReadState> &regionStack,
1733 RegionReadState &readState);
1734 FailureOr<Operation *> parseOpWithoutRegions(EncodingReader &reader,
1735 RegionReadState &readState,
1736 bool &isIsolatedFromAbove);
1737
1738 LogicalResult parseRegion(RegionReadState &readState);
1739 LogicalResult parseBlockHeader(EncodingReader &reader,
1740 RegionReadState &readState);
1741 LogicalResult parseBlockArguments(EncodingReader &reader, Block *block);
1742
1743 //===--------------------------------------------------------------------===//
1744 // Value Processing
1745
1746 /// Parse an operand reference using the given reader. Returns nullptr in the
1747 /// case of failure.
1748 Value parseOperand(EncodingReader &reader);
1749
1750 /// Sequentially define the given value range.
1751 LogicalResult defineValues(EncodingReader &reader, ValueRange values);
1752
1753 /// Create a value to use for a forward reference.
1754 Value createForwardRef();
1755
1756 //===--------------------------------------------------------------------===//
1757 // Use-list order helpers
1758
1759 /// This struct is a simple storage that contains information required to
1760 /// reorder the use-list of a value with respect to the pre-order traversal
1761 /// ordering.
1762 struct UseListOrderStorage {
1763 UseListOrderStorage(bool isIndexPairEncoding,
1764 SmallVector<unsigned, 4> &&indices)
1765 : indices(std::move(indices)),
1766 isIndexPairEncoding(isIndexPairEncoding) {};
1767 /// The vector containing the information required to reorder the
1768 /// use-list of a value.
1769 SmallVector<unsigned, 4> indices;
1770
1771 /// Whether indices represent a pair of type `(src, dst)` or it is a direct
1772 /// indexing, such as `dst = order[src]`.
1773 bool isIndexPairEncoding;
1774 };
1775
1776 /// Parse use-list order from bytecode for a range of values if available. The
1777 /// range is expected to be either a block argument or an op result range. On
1778 /// success, return a map of the position in the range and the use-list order
1779 /// encoding. The function assumes to know the size of the range it is
1780 /// processing.
1781 using UseListMapT = DenseMap<unsigned, UseListOrderStorage>;
1782 FailureOr<UseListMapT> parseUseListOrderForRange(EncodingReader &reader,
1783 uint64_t rangeSize);
1784
1785 /// Shuffle the use-chain according to the order parsed.
1786 LogicalResult sortUseListOrder(Value value);
1787
1788 /// Recursively visit all the values defined within topLevelOp and sort the
1789 /// use-list orders according to the indices parsed.
1790 LogicalResult processUseLists(Operation *topLevelOp);
1791
1792 //===--------------------------------------------------------------------===//
1793 // Fields
1794
1795 /// This class represents a single value scope, in which a value scope is
1796 /// delimited by isolated from above regions.
1797 struct ValueScope {
1798 /// Push a new region state onto this scope, reserving enough values for
1799 /// those defined within the current region of the provided state.
1800 void push(RegionReadState &readState) {
1801 nextValueIDs.push_back(values.size());
1802 values.resize(values.size() + readState.numValues);
1803 }
1804
1805 /// Pop the values defined for the current region within the provided region
1806 /// state.
1807 void pop(RegionReadState &readState) {
1808 values.resize(values.size() - readState.numValues);
1809 nextValueIDs.pop_back();
1810 }
1811
1812 /// The set of values defined in this scope.
1813 std::vector<Value> values;
1814
1815 /// The ID for the next defined value for each region current being
1816 /// processed in this scope.
1817 SmallVector<unsigned, 4> nextValueIDs;
1818 };
1819
1820 /// The configuration of the parser.
1821 const ParserConfig &config;
1822
1823 /// A location to use when emitting errors.
1824 Location fileLoc;
1825
1826 /// Flag that indicates if lazyloading is enabled.
1827 bool lazyLoading;
1828
1829 /// Keep track of operations that have been lazy loaded (their regions haven't
1830 /// been materialized), along with the `RegionReadState` that allows to
1831 /// lazy-load the regions nested under the operation.
1832 LazyLoadableOpsInfo lazyLoadableOps;
1833 LazyLoadableOpsMap lazyLoadableOpsMap;
1834 llvm::function_ref<bool(Operation *)> lazyOpsCallback;
1835
1836 /// The reader used to process attribute and types within the bytecode.
1837 AttrTypeReader attrTypeReader;
1838
1839 /// The version of the bytecode being read.
1840 uint64_t version = 0;
1841
1842 /// The producer of the bytecode being read.
1843 StringRef producer;
1844
1845 /// The table of IR units referenced within the bytecode file.
1846 SmallVector<std::unique_ptr<BytecodeDialect>> dialects;
1847 llvm::StringMap<BytecodeDialect *> dialectsMap;
1848 SmallVector<BytecodeOperationName> opNames;
1849
1850 /// The reader used to process resources within the bytecode.
1851 ResourceSectionReader resourceReader;
1852
1853 /// Worklist of values with custom use-list orders to process before the end
1854 /// of the parsing.
1855 DenseMap<void *, UseListOrderStorage> valueToUseListMap;
1856
1857 /// The table of strings referenced within the bytecode file.
1858 StringSectionReader stringReader;
1859
1860 /// The table of properties referenced by the operation in the bytecode file.
1861 PropertiesSectionReader propertiesReader;
1862
1863 /// The current set of available IR value scopes.
1864 std::vector<ValueScope> valueScopes;
1865
1866 /// The global pre-order operation ordering.
1868
1869 /// A block containing the set of operations defined to create forward
1870 /// references.
1871 Block forwardRefOps;
1872
1873 /// A block containing previously created, and no longer used, forward
1874 /// reference operations.
1875 Block openForwardRefOps;
1876
1877 /// An operation state used when instantiating forward references.
1878 OperationState forwardRefOpState;
1879
1880 /// Reference to the input buffer.
1881 llvm::MemoryBufferRef buffer;
1882
1883 /// The optional owning source manager, which when present may be used to
1884 /// extend the lifetime of the input buffer.
1885 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef;
1886};
1887
1889 Block *block, llvm::function_ref<bool(Operation *)> lazyOpsCallback) {
1890 EncodingReader reader(buffer.getBuffer(), fileLoc);
1891 this->lazyOpsCallback = lazyOpsCallback;
1892 llvm::scope_exit resetlazyOpsCallback(
1893 [&] { this->lazyOpsCallback = nullptr; });
1894
1895 // Skip over the bytecode header, this should have already been checked.
1896 if (failed(reader.skipBytes(StringRef("ML\xefR").size())))
1897 return failure();
1898 // Parse the bytecode version and producer.
1899 if (failed(parseVersion(reader)) ||
1900 failed(reader.parseNullTerminatedString(producer)))
1901 return failure();
1902
1903 // Add a diagnostic handler that attaches a note that includes the original
1904 // producer of the bytecode.
1905 ScopedDiagnosticHandler diagHandler(getContext(), [&](Diagnostic &diag) {
1906 diag.attachNote() << "in bytecode version " << version
1907 << " produced by: " << producer;
1908 return failure();
1909 });
1910
1911 const auto checkSectionAlignment = [&](unsigned alignment) {
1912 return this->checkSectionAlignment(
1913 alignment, [&](const auto &msg) { return reader.emitError(msg); });
1914 };
1915
1916 // Parse the raw data for each of the top-level sections of the bytecode.
1917 std::optional<ArrayRef<uint8_t>>
1918 sectionDatas[bytecode::Section::kNumSections];
1919 while (!reader.empty()) {
1920 // Read the next section from the bytecode.
1921 bytecode::Section::ID sectionID;
1922 ArrayRef<uint8_t> sectionData;
1923 if (failed(
1924 reader.parseSection(sectionID, checkSectionAlignment, sectionData)))
1925 return failure();
1926
1927 // Check for duplicate sections, we only expect one instance of each.
1928 if (sectionDatas[sectionID]) {
1929 return reader.emitError("duplicate top-level section: ",
1930 ::toString(sectionID));
1931 }
1932 sectionDatas[sectionID] = sectionData;
1933 }
1934 // Check that all of the required sections were found.
1935 for (int i = 0; i < bytecode::Section::kNumSections; ++i) {
1936 bytecode::Section::ID sectionID = static_cast<bytecode::Section::ID>(i);
1937 if (!sectionDatas[i] && !isSectionOptional(sectionID, version)) {
1938 return reader.emitError("missing data for top-level section: ",
1939 ::toString(sectionID));
1940 }
1941 }
1942
1943 // Process the string section first.
1944 if (failed(stringReader.initialize(
1945 fileLoc, *sectionDatas[bytecode::Section::kString])))
1946 return failure();
1947
1948 // Process the properties section.
1949 if (sectionDatas[bytecode::Section::kProperties] &&
1950 failed(propertiesReader.initialize(
1951 fileLoc, *sectionDatas[bytecode::Section::kProperties])))
1952 return failure();
1953
1954 // Process the dialect section.
1955 if (failed(parseDialectSection(*sectionDatas[bytecode::Section::kDialect])))
1956 return failure();
1957
1958 // Process the resource section if present.
1959 if (failed(parseResourceSection(
1960 reader, sectionDatas[bytecode::Section::kResource],
1961 sectionDatas[bytecode::Section::kResourceOffset])))
1962 return failure();
1963
1964 // Process the attribute and type section.
1965 if (failed(attrTypeReader.initialize(
1966 dialects, *sectionDatas[bytecode::Section::kAttrType],
1967 *sectionDatas[bytecode::Section::kAttrTypeOffset])))
1968 return failure();
1969
1970 // Finally, process the IR section.
1971 return parseIRSection(*sectionDatas[bytecode::Section::kIR], block);
1972}
1973
1974LogicalResult BytecodeReader::Impl::parseVersion(EncodingReader &reader) {
1975 if (failed(reader.parseVarInt(version)))
1976 return failure();
1977
1978 // Validate the bytecode version.
1979 uint64_t currentVersion = bytecode::kVersion;
1980 uint64_t minSupportedVersion = bytecode::kMinSupportedVersion;
1981 if (version < minSupportedVersion) {
1982 return reader.emitError("bytecode version ", version,
1983 " is older than the current version of ",
1984 currentVersion, ", and upgrade is not supported");
1985 }
1986 if (version > currentVersion) {
1987 return reader.emitError("bytecode version ", version,
1988 " is newer than the current version ",
1989 currentVersion);
1990 }
1991 // Override any request to lazy-load if the bytecode version is too old.
1992 if (version < bytecode::kLazyLoading)
1993 lazyLoading = false;
1994 return success();
1995}
1996
1997//===----------------------------------------------------------------------===//
1998// Dialect Section
1999//===----------------------------------------------------------------------===//
2000
2001LogicalResult BytecodeDialect::load(const DialectReader &reader,
2002 MLIRContext *ctx) {
2003 if (dialect)
2004 return success();
2005 Dialect *loadedDialect = ctx->getOrLoadDialect(name);
2006 if (!loadedDialect && !ctx->allowsUnregisteredDialects()) {
2007 return reader.emitError("dialect '")
2008 << name
2009 << "' is unknown. If this is intended, please call "
2010 "allowUnregisteredDialects() on the MLIRContext, or use "
2011 "-allow-unregistered-dialect with the MLIR tool used.";
2012 }
2013 dialect = loadedDialect;
2014
2015 // If the dialect was actually loaded, check to see if it has a bytecode
2016 // interface.
2017 if (loadedDialect)
2018 interface = dyn_cast<BytecodeDialectInterface>(loadedDialect);
2019 if (!versionBuffer.empty()) {
2020 if (!interface)
2021 return reader.emitError("dialect '")
2022 << name
2023 << "' does not implement the bytecode interface, "
2024 "but found a version entry";
2025 EncodingReader encReader(versionBuffer, reader.getLoc());
2026 DialectReader versionReader = reader.withEncodingReader(encReader);
2027 loadedVersion = interface->readVersion(versionReader);
2028 if (!loadedVersion)
2029 return failure();
2030 }
2031 return success();
2032}
2033
2034LogicalResult
2035BytecodeReader::Impl::parseDialectSection(ArrayRef<uint8_t> sectionData) {
2036 EncodingReader sectionReader(sectionData, fileLoc);
2037
2038 // Parse the number of dialects in the section.
2039 uint64_t numDialects;
2040 if (failed(sectionReader.parseVarInt(numDialects)))
2041 return failure();
2042 dialects.resize(numDialects);
2043
2044 const auto checkSectionAlignment = [&](unsigned alignment) {
2045 return this->checkSectionAlignment(alignment, [&](const auto &msg) {
2046 return sectionReader.emitError(msg);
2047 });
2048 };
2049
2050 // Parse each of the dialects.
2051 for (uint64_t i = 0; i < numDialects; ++i) {
2052 dialects[i] = std::make_unique<BytecodeDialect>();
2053 /// Before version kDialectVersioning, there wasn't any versioning available
2054 /// for dialects, and the entryIdx represent the string itself.
2055 if (version < bytecode::kDialectVersioning) {
2056 if (failed(stringReader.parseString(sectionReader, dialects[i]->name)))
2057 return failure();
2058 continue;
2059 }
2060
2061 // Parse ID representing dialect and version.
2062 uint64_t dialectNameIdx;
2063 bool versionAvailable;
2064 if (failed(sectionReader.parseVarIntWithFlag(dialectNameIdx,
2065 versionAvailable)))
2066 return failure();
2067 if (failed(stringReader.parseStringAtIndex(sectionReader, dialectNameIdx,
2068 dialects[i]->name)))
2069 return failure();
2070 if (versionAvailable) {
2071 bytecode::Section::ID sectionID;
2072 if (failed(sectionReader.parseSection(sectionID, checkSectionAlignment,
2073 dialects[i]->versionBuffer)))
2074 return failure();
2075 if (sectionID != bytecode::Section::kDialectVersions) {
2076 emitError(fileLoc, "expected dialect version section");
2077 return failure();
2078 }
2079 }
2080 dialectsMap[dialects[i]->name] = dialects[i].get();
2081 }
2082
2083 // Parse the operation names, which are grouped by dialect.
2084 auto parseOpName = [&](BytecodeDialect *dialect) {
2085 StringRef opName;
2086 std::optional<bool> wasRegistered;
2087 // Prior to version kNativePropertiesEncoding, the information about wheter
2088 // an op was registered or not wasn't encoded.
2090 if (failed(stringReader.parseString(sectionReader, opName)))
2091 return failure();
2092 } else {
2093 bool wasRegisteredFlag;
2094 if (failed(stringReader.parseStringWithFlag(sectionReader, opName,
2095 wasRegisteredFlag)))
2096 return failure();
2097 wasRegistered = wasRegisteredFlag;
2098 }
2099 opNames.emplace_back(dialect, opName, wasRegistered);
2100 return success();
2101 };
2102 // Avoid re-allocation in bytecode version >=kElideUnknownBlockArgLocation
2103 // where the number of ops are known.
2105 uint64_t numOps;
2106 if (failed(sectionReader.parseVarInt(numOps)))
2107 return failure();
2108 opNames.reserve(numOps);
2109 }
2110 while (!sectionReader.empty())
2111 if (failed(parseDialectGrouping(sectionReader, dialects, parseOpName)))
2112 return failure();
2113 return success();
2114}
2115
2116FailureOr<OperationName>
2117BytecodeReader::Impl::parseOpName(EncodingReader &reader,
2118 std::optional<bool> &wasRegistered) {
2119 BytecodeOperationName *opName = nullptr;
2120 if (failed(parseEntry(reader, opNames, opName, "operation name")))
2121 return failure();
2122 wasRegistered = opName->wasRegistered;
2123 // Check to see if this operation name has already been resolved. If we
2124 // haven't, load the dialect and build the operation name.
2125 if (!opName->opName) {
2126 // If the opName is empty, this is because we use to accept names such as
2127 // `foo` without any `.` separator. We shouldn't tolerate this in textual
2128 // format anymore but for now we'll be backward compatible. This can only
2129 // happen with unregistered dialects.
2130 if (opName->name.empty()) {
2131 opName->opName.emplace(opName->dialect->name, getContext());
2132 } else {
2133 // Load the dialect and its version.
2134 DialectReader dialectReader(attrTypeReader, stringReader, resourceReader,
2135 dialectsMap, reader, version);
2136 if (failed(opName->dialect->load(dialectReader, getContext())))
2137 return failure();
2138 opName->opName.emplace((opName->dialect->name + "." + opName->name).str(),
2139 getContext());
2140 }
2141 }
2142 return *opName->opName;
2143}
2144
2145//===----------------------------------------------------------------------===//
2146// Resource Section
2147//===----------------------------------------------------------------------===//
2148
2149LogicalResult BytecodeReader::Impl::parseResourceSection(
2150 EncodingReader &reader, std::optional<ArrayRef<uint8_t>> resourceData,
2151 std::optional<ArrayRef<uint8_t>> resourceOffsetData) {
2152 // Ensure both sections are either present or not.
2153 if (resourceData.has_value() != resourceOffsetData.has_value()) {
2154 if (resourceOffsetData)
2155 return emitError(fileLoc, "unexpected resource offset section when "
2156 "resource section is not present");
2157 return emitError(
2158 fileLoc,
2159 "expected resource offset section when resource section is present");
2160 }
2161
2162 // If the resource sections are absent, there is nothing to do.
2163 if (!resourceData)
2164 return success();
2165
2166 // Initialize the resource reader with the resource sections.
2167 DialectReader dialectReader(attrTypeReader, stringReader, resourceReader,
2168 dialectsMap, reader, version);
2169 return resourceReader.initialize(fileLoc, config, dialects, stringReader,
2170 *resourceData, *resourceOffsetData,
2171 dialectReader, bufferOwnerRef);
2172}
2173
2174//===----------------------------------------------------------------------===//
2175// UseListOrder Helpers
2176//===----------------------------------------------------------------------===//
2177
2178FailureOr<BytecodeReader::Impl::UseListMapT>
2179BytecodeReader::Impl::parseUseListOrderForRange(EncodingReader &reader,
2180 uint64_t numResults) {
2181 BytecodeReader::Impl::UseListMapT map;
2182 uint64_t numValuesToRead = 1;
2183 if (numResults > 1 && failed(reader.parseVarInt(numValuesToRead)))
2184 return failure();
2185
2186 for (size_t valueIdx = 0; valueIdx < numValuesToRead; valueIdx++) {
2187 uint64_t resultIdx = 0;
2188 if (numResults > 1 && failed(reader.parseVarInt(resultIdx)))
2189 return failure();
2190
2191 uint64_t numValues;
2192 bool indexPairEncoding;
2193 if (failed(reader.parseVarIntWithFlag(numValues, indexPairEncoding)))
2194 return failure();
2195
2196 SmallVector<unsigned, 4> useListOrders;
2197 for (size_t idx = 0; idx < numValues; idx++) {
2198 uint64_t index;
2199 if (failed(reader.parseVarInt(index)))
2200 return failure();
2201 useListOrders.push_back(index);
2202 }
2203
2204 // Store in a map the result index
2205 map.try_emplace(resultIdx, UseListOrderStorage(indexPairEncoding,
2206 std::move(useListOrders)));
2207 }
2208
2209 return map;
2210}
2211
2212/// Sorts each use according to the order specified in the use-list parsed. If
2213/// the custom use-list is not found, this means that the order needs to be
2214/// consistent with the reverse pre-order walk of the IR. If multiple uses lie
2215/// on the same operation, the order will follow the reverse operand number
2216/// ordering.
2217LogicalResult BytecodeReader::Impl::sortUseListOrder(Value value) {
2218 // Early return for trivial use-lists.
2219 if (value.use_empty() || value.hasOneUse())
2220 return success();
2221
2222 bool hasIncomingOrder =
2223 valueToUseListMap.contains(value.getAsOpaquePointer());
2224
2225 // Compute the current order of the use-list with respect to the global
2226 // ordering. Detect if the order is already sorted while doing so.
2227 bool alreadySorted = true;
2228 auto &firstUse = *value.use_begin();
2229 uint64_t prevID =
2230 bytecode::getUseID(firstUse, operationIDs.at(firstUse.getOwner()));
2231 llvm::SmallVector<std::pair<unsigned, uint64_t>> currentOrder = {{0, prevID}};
2232 for (auto item : llvm::drop_begin(llvm::enumerate(value.getUses()))) {
2233 uint64_t currentID = bytecode::getUseID(
2234 item.value(), operationIDs.at(item.value().getOwner()));
2235 alreadySorted &= prevID > currentID;
2236 currentOrder.push_back({item.index(), currentID});
2237 prevID = currentID;
2238 }
2239
2240 // If the order is already sorted, and there wasn't a custom order to apply
2241 // from the bytecode file, we are done.
2242 if (alreadySorted && !hasIncomingOrder)
2243 return success();
2244
2245 // If not already sorted, sort the indices of the current order by descending
2246 // useIDs.
2247 if (!alreadySorted)
2248 std::sort(
2249 currentOrder.begin(), currentOrder.end(),
2250 [](auto elem1, auto elem2) { return elem1.second > elem2.second; });
2251
2252 if (!hasIncomingOrder) {
2253 // If the bytecode file did not contain any custom use-list order, it means
2254 // that the order was descending useID. Hence, shuffle by the first index
2255 // of the `currentOrder` pair.
2256 SmallVector<unsigned> shuffle(llvm::make_first_range(currentOrder));
2257 value.shuffleUseList(shuffle);
2258 return success();
2259 }
2260
2261 // Pull the custom order info from the map.
2262 UseListOrderStorage customOrder =
2263 valueToUseListMap.at(value.getAsOpaquePointer());
2264 SmallVector<unsigned, 4> shuffle = std::move(customOrder.indices);
2265 uint64_t numUses = value.getNumUses();
2266
2267 // If the encoding was a pair of indices `(src, dst)` for every permutation,
2268 // reconstruct the shuffle vector for every use. Initialize the shuffle vector
2269 // as identity, and then apply the mapping encoded in the indices.
2270 if (customOrder.isIndexPairEncoding) {
2271 // Return failure if the number of indices was not representing pairs.
2272 if (shuffle.size() & 1)
2273 return failure();
2274
2275 SmallVector<unsigned, 4> newShuffle(numUses);
2276 size_t idx = 0;
2277 std::iota(newShuffle.begin(), newShuffle.end(), idx);
2278 for (idx = 0; idx < shuffle.size(); idx += 2)
2279 newShuffle[shuffle[idx]] = shuffle[idx + 1];
2280
2281 shuffle = std::move(newShuffle);
2282 }
2283
2284 // Make sure that the indices represent a valid mapping. That is, the sum of
2285 // all the values needs to be equal to (numUses - 1) * numUses / 2, and no
2286 // duplicates are allowed in the list.
2288 uint64_t accumulator = 0;
2289 for (const auto &elem : shuffle) {
2290 if (!set.insert(elem).second)
2291 return failure();
2292 accumulator += elem;
2293 }
2294 if (numUses != shuffle.size() ||
2295 accumulator != (((numUses - 1) * numUses) >> 1))
2296 return failure();
2297
2298 // Apply the current ordering map onto the shuffle vector to get the final
2299 // use-list sorting indices before shuffling.
2300 shuffle = SmallVector<unsigned, 4>(llvm::map_range(
2301 currentOrder, [&](auto item) { return shuffle[item.first]; }));
2302 value.shuffleUseList(shuffle);
2303 return success();
2304}
2305
2306LogicalResult BytecodeReader::Impl::processUseLists(Operation *topLevelOp) {
2307 // Precompute operation IDs according to the pre-order walk of the IR. We
2308 // can't do this while parsing since parseRegions ordering is not strictly
2309 // equal to the pre-order walk.
2310 unsigned operationID = 0;
2311 topLevelOp->walk<mlir::WalkOrder::PreOrder>(
2312 [&](Operation *op) { operationIDs.try_emplace(op, operationID++); });
2313
2314 auto blockWalk = topLevelOp->walk([this](Block *block) {
2315 for (auto arg : block->getArguments())
2316 if (failed(sortUseListOrder(arg)))
2317 return WalkResult::interrupt();
2318 return WalkResult::advance();
2319 });
2320
2321 auto resultWalk = topLevelOp->walk([this](Operation *op) {
2322 for (auto result : op->getResults())
2323 if (failed(sortUseListOrder(result)))
2324 return WalkResult::interrupt();
2325 return WalkResult::advance();
2326 });
2327
2328 return failure(blockWalk.wasInterrupted() || resultWalk.wasInterrupted());
2329}
2330
2331//===----------------------------------------------------------------------===//
2332// IR Section
2333//===----------------------------------------------------------------------===//
2334
2335LogicalResult
2336BytecodeReader::Impl::parseIRSection(ArrayRef<uint8_t> sectionData,
2337 Block *block) {
2338 EncodingReader reader(sectionData, fileLoc);
2339
2340 // A stack of operation regions currently being read from the bytecode.
2341 std::vector<RegionReadState> regionStack;
2342
2343 // Parse the top-level block using a temporary module operation.
2344 OwningOpRef<ModuleOp> moduleOp = ModuleOp::create(fileLoc);
2345 regionStack.emplace_back(*moduleOp, &reader, /*isIsolatedFromAbove=*/true);
2346 regionStack.back().curBlocks.push_back(moduleOp->getBody());
2347 regionStack.back().curBlock = regionStack.back().curRegion->begin();
2348 if (failed(parseBlockHeader(reader, regionStack.back())))
2349 return failure();
2350 valueScopes.emplace_back();
2351 valueScopes.back().push(regionStack.back());
2352
2353 // Iteratively parse regions until everything has been resolved.
2354 while (!regionStack.empty())
2355 if (failed(parseRegions(regionStack, regionStack.back())))
2356 return failure();
2357 if (!forwardRefOps.empty()) {
2358 return reader.emitError(
2359 "not all forward unresolved forward operand references");
2360 }
2361
2362 // Sort use-lists according to what specified in bytecode.
2363 if (failed(processUseLists(*moduleOp)))
2364 return reader.emitError(
2365 "parsed use-list orders were invalid and could not be applied");
2366
2367 // Resolve dialect version.
2368 for (const std::unique_ptr<BytecodeDialect> &byteCodeDialect : dialects) {
2369 // Parsing is complete, give an opportunity to each dialect to visit the
2370 // IR and perform upgrades.
2371 if (!byteCodeDialect->loadedVersion)
2372 continue;
2373 if (byteCodeDialect->interface &&
2374 failed(byteCodeDialect->interface->upgradeFromVersion(
2375 *moduleOp, *byteCodeDialect->loadedVersion)))
2376 return failure();
2377 }
2378
2379 // Verify that the parsed operations are valid.
2380 if (config.shouldVerifyAfterParse() && failed(verify(*moduleOp)))
2381 return failure();
2382
2383 // Splice the parsed operations over to the provided top-level block.
2384 auto &parsedOps = moduleOp->getBody()->getOperations();
2385 auto &destOps = block->getOperations();
2386 destOps.splice(destOps.end(), parsedOps, parsedOps.begin(), parsedOps.end());
2387 return success();
2388}
2389
2390LogicalResult
2391BytecodeReader::Impl::parseRegions(std::vector<RegionReadState> &regionStack,
2392 RegionReadState &readState) {
2393 const auto checkSectionAlignment = [&](unsigned alignment) {
2394 return this->checkSectionAlignment(
2395 alignment, [&](const auto &msg) { return emitError(fileLoc, msg); });
2396 };
2397
2398 // Process regions, blocks, and operations until the end or if a nested
2399 // region is encountered. In this case we push a new state in regionStack and
2400 // return, the processing of the current region will resume afterward.
2401 for (; readState.curRegion != readState.endRegion; ++readState.curRegion) {
2402 // If the current block hasn't been setup yet, parse the header for this
2403 // region. The current block is already setup when this function was
2404 // interrupted to recurse down in a nested region and we resume the current
2405 // block after processing the nested region.
2406 if (readState.curBlock == Region::iterator()) {
2407 if (failed(parseRegion(readState)))
2408 return failure();
2409
2410 // If the region is empty, there is nothing to more to do.
2411 if (readState.curRegion->empty())
2412 continue;
2413 }
2414
2415 // Parse the blocks within the region.
2416 EncodingReader &reader = *readState.reader;
2417 do {
2418 while (readState.numOpsRemaining--) {
2419 // Read in the next operation. We don't read its regions directly, we
2420 // handle those afterwards as necessary.
2421 bool isIsolatedFromAbove = false;
2422 FailureOr<Operation *> op =
2423 parseOpWithoutRegions(reader, readState, isIsolatedFromAbove);
2424 if (failed(op))
2425 return failure();
2426
2427 // If the op has regions, add it to the stack for processing and return:
2428 // we stop the processing of the current region and resume it after the
2429 // inner one is completed. Unless LazyLoading is activated in which case
2430 // nested region parsing is delayed.
2431 if ((*op)->getNumRegions()) {
2432 RegionReadState childState(*op, &reader, isIsolatedFromAbove);
2433
2434 // Isolated regions are encoded as a section in version 2 and above.
2435 if (version >= bytecode::kLazyLoading && isIsolatedFromAbove) {
2436 bytecode::Section::ID sectionID;
2437 ArrayRef<uint8_t> sectionData;
2438 if (failed(reader.parseSection(sectionID, checkSectionAlignment,
2439 sectionData)))
2440 return failure();
2441 if (sectionID != bytecode::Section::kIR)
2442 return emitError(fileLoc, "expected IR section for region");
2443 childState.owningReader =
2444 std::make_unique<EncodingReader>(sectionData, fileLoc);
2445 childState.reader = childState.owningReader.get();
2446
2447 // If the user has a callback set, they have the opportunity to
2448 // control lazyloading as we go.
2449 if (lazyLoading && (!lazyOpsCallback || !lazyOpsCallback(*op))) {
2450 lazyLoadableOps.emplace_back(*op, std::move(childState));
2451 lazyLoadableOpsMap.try_emplace(*op,
2452 std::prev(lazyLoadableOps.end()));
2453 continue;
2454 }
2455 }
2456 regionStack.push_back(std::move(childState));
2457
2458 // If the op is isolated from above, push a new value scope.
2459 if (isIsolatedFromAbove)
2460 valueScopes.emplace_back();
2461 return success();
2462 }
2463 }
2464
2465 // Move to the next block of the region.
2466 if (++readState.curBlock == readState.curRegion->end())
2467 break;
2468 if (failed(parseBlockHeader(reader, readState)))
2469 return failure();
2470 } while (true);
2471
2472 // Reset the current block and any values reserved for this region.
2473 readState.curBlock = {};
2474 valueScopes.back().pop(readState);
2475 }
2476
2477 // When the regions have been fully parsed, pop them off of the read stack. If
2478 // the regions were isolated from above, we also pop the last value scope.
2479 if (readState.isIsolatedFromAbove) {
2480 assert(!valueScopes.empty() && "Expect a valueScope after reading region");
2481 valueScopes.pop_back();
2482 }
2483 assert(!regionStack.empty() && "Expect a regionStack after reading region");
2484 regionStack.pop_back();
2485 return success();
2486}
2487
2488FailureOr<Operation *>
2489BytecodeReader::Impl::parseOpWithoutRegions(EncodingReader &reader,
2490 RegionReadState &readState,
2491 bool &isIsolatedFromAbove) {
2492 // Parse the name of the operation.
2493 std::optional<bool> wasRegistered;
2494 FailureOr<OperationName> opName = parseOpName(reader, wasRegistered);
2495 if (failed(opName))
2496 return failure();
2497
2498 // Parse the operation mask, which indicates which components of the operation
2499 // are present.
2500 uint8_t opMask;
2501 if (failed(reader.parseByte(opMask)))
2502 return failure();
2503
2504 /// Parse the location.
2505 LocationAttr opLoc;
2506 if (failed(parseAttribute(reader, opLoc)))
2507 return failure();
2508
2509 // With the location and name resolved, we can start building the operation
2510 // state.
2511 OperationState opState(opLoc, *opName);
2512
2513 // Parse the attributes of the operation.
2515 DictionaryAttr dictAttr;
2516 if (failed(parseAttribute(reader, dictAttr)))
2517 return failure();
2518 opState.attributes = dictAttr;
2519 }
2520
2522 // kHasProperties wasn't emitted in older bytecode, we should never get
2523 // there without also having the `wasRegistered` flag available.
2524 if (!wasRegistered)
2525 return emitError(fileLoc,
2526 "Unexpected missing `wasRegistered` opname flag at "
2527 "bytecode version ")
2528 << version << " with properties.";
2529 // When an operation is emitted without being registered, the properties are
2530 // stored as an attribute. Otherwise the op must implement the bytecode
2531 // interface and control the serialization.
2532 if (wasRegistered) {
2533 DialectReader dialectReader(attrTypeReader, stringReader, resourceReader,
2534 dialectsMap, reader, version);
2535 if (failed(
2536 propertiesReader.read(fileLoc, dialectReader, &*opName, opState)))
2537 return failure();
2538 } else {
2539 // If the operation wasn't registered when it was emitted, the properties
2540 // was serialized as an attribute.
2541 if (failed(parseAttribute(reader, opState.propertiesAttr)))
2542 return failure();
2543 }
2544 }
2545
2546 /// Parse the results of the operation.
2548 uint64_t numResults;
2549 if (failed(reader.parseVarInt(numResults)))
2550 return failure();
2551 opState.types.resize(numResults);
2552 for (int i = 0, e = numResults; i < e; ++i)
2553 if (failed(parseType(reader, opState.types[i])))
2554 return failure();
2555 }
2556
2557 /// Parse the operands of the operation.
2559 uint64_t numOperands;
2560 if (failed(reader.parseVarInt(numOperands)))
2561 return failure();
2562 opState.operands.resize(numOperands);
2563 for (int i = 0, e = numOperands; i < e; ++i)
2564 if (!(opState.operands[i] = parseOperand(reader)))
2565 return failure();
2566 }
2567
2568 /// Parse the successors of the operation.
2570 uint64_t numSuccs;
2571 if (failed(reader.parseVarInt(numSuccs)))
2572 return failure();
2573 opState.successors.resize(numSuccs);
2574 for (int i = 0, e = numSuccs; i < e; ++i) {
2575 if (failed(parseEntry(reader, readState.curBlocks, opState.successors[i],
2576 "successor")))
2577 return failure();
2578 }
2579 }
2580
2581 /// Parse the use-list orders for the results of the operation. Use-list
2582 /// orders are available since version 3 of the bytecode.
2583 std::optional<UseListMapT> resultIdxToUseListMap = std::nullopt;
2584 if (version >= bytecode::kUseListOrdering &&
2586 size_t numResults = opState.types.size();
2587 auto parseResult = parseUseListOrderForRange(reader, numResults);
2588 if (failed(parseResult))
2589 return failure();
2590 resultIdxToUseListMap = std::move(*parseResult);
2591 }
2592
2593 /// Parse the regions of the operation.
2595 uint64_t numRegions;
2596 if (failed(reader.parseVarIntWithFlag(numRegions, isIsolatedFromAbove)))
2597 return failure();
2598
2599 opState.regions.reserve(numRegions);
2600 for (int i = 0, e = numRegions; i < e; ++i)
2601 opState.regions.push_back(std::make_unique<Region>());
2602 }
2603
2604 // Create the operation at the back of the current block.
2605 Operation *op = Operation::create(opState);
2606 readState.curBlock->push_back(op);
2607
2608 // If the operation had results, update the value references. We don't need to
2609 // do this if the current value scope is empty. That is, the op was not
2610 // encoded within a parent region.
2611 if (readState.numValues && op->getNumResults() &&
2612 failed(defineValues(reader, op->getResults())))
2613 return failure();
2614
2615 /// Store a map for every value that received a custom use-list order from the
2616 /// bytecode file.
2617 if (resultIdxToUseListMap.has_value()) {
2618 for (size_t idx = 0; idx < op->getNumResults(); idx++) {
2619 if (resultIdxToUseListMap->contains(idx)) {
2620 valueToUseListMap.try_emplace(op->getResult(idx).getAsOpaquePointer(),
2621 resultIdxToUseListMap->at(idx));
2622 }
2623 }
2624 }
2625 return op;
2626}
2627
2628LogicalResult BytecodeReader::Impl::parseRegion(RegionReadState &readState) {
2629 EncodingReader &reader = *readState.reader;
2630
2631 // Parse the number of blocks in the region.
2632 uint64_t numBlocks;
2633 if (failed(reader.parseVarInt(numBlocks)))
2634 return failure();
2635
2636 // If the region is empty, there is nothing else to do.
2637 if (numBlocks == 0)
2638 return success();
2639
2640 // Parse the number of values defined in this region.
2641 uint64_t numValues;
2642 if (failed(reader.parseVarInt(numValues)))
2643 return failure();
2644 readState.numValues = numValues;
2645
2646 // Create the blocks within this region. We do this before processing so that
2647 // we can rely on the blocks existing when creating operations.
2648 readState.curBlocks.clear();
2649 readState.curBlocks.reserve(numBlocks);
2650 for (uint64_t i = 0; i < numBlocks; ++i) {
2651 readState.curBlocks.push_back(new Block());
2652 readState.curRegion->push_back(readState.curBlocks.back());
2653 }
2654
2655 // Prepare the current value scope for this region.
2656 valueScopes.back().push(readState);
2657
2658 // Parse the entry block of the region.
2659 readState.curBlock = readState.curRegion->begin();
2660 return parseBlockHeader(reader, readState);
2661}
2662
2663LogicalResult
2664BytecodeReader::Impl::parseBlockHeader(EncodingReader &reader,
2665 RegionReadState &readState) {
2666 bool hasArgs;
2667 if (failed(reader.parseVarIntWithFlag(readState.numOpsRemaining, hasArgs)))
2668 return failure();
2669
2670 // Parse the arguments of the block.
2671 if (hasArgs && failed(parseBlockArguments(reader, &*readState.curBlock)))
2672 return failure();
2673
2674 // Uselist orders are available since version 3 of the bytecode.
2675 if (version < bytecode::kUseListOrdering)
2676 return success();
2677
2678 uint8_t hasUseListOrders = 0;
2679 if (hasArgs && failed(reader.parseByte(hasUseListOrders)))
2680 return failure();
2681
2682 if (!hasUseListOrders)
2683 return success();
2684
2685 Block &blk = *readState.curBlock;
2686 auto argIdxToUseListMap =
2687 parseUseListOrderForRange(reader, blk.getNumArguments());
2688 if (failed(argIdxToUseListMap) || argIdxToUseListMap->empty())
2689 return failure();
2690
2691 for (size_t idx = 0; idx < blk.getNumArguments(); idx++)
2692 if (argIdxToUseListMap->contains(idx))
2693 valueToUseListMap.try_emplace(blk.getArgument(idx).getAsOpaquePointer(),
2694 argIdxToUseListMap->at(idx));
2695
2696 // We don't parse the operations of the block here, that's done elsewhere.
2697 return success();
2698}
2699
2700LogicalResult BytecodeReader::Impl::parseBlockArguments(EncodingReader &reader,
2701 Block *block) {
2702 // Parse the value ID for the first argument, and the number of arguments.
2703 uint64_t numArgs;
2704 if (failed(reader.parseVarInt(numArgs)))
2705 return failure();
2706
2707 SmallVector<Type> argTypes;
2708 SmallVector<Location> argLocs;
2709 argTypes.reserve(numArgs);
2710 argLocs.reserve(numArgs);
2711
2712 Location unknownLoc = UnknownLoc::get(config.getContext());
2713 while (numArgs--) {
2714 Type argType;
2715 LocationAttr argLoc = unknownLoc;
2717 // Parse the type with hasLoc flag to determine if it has type.
2718 uint64_t typeIdx;
2719 bool hasLoc;
2720 if (failed(reader.parseVarIntWithFlag(typeIdx, hasLoc)) ||
2721 !(argType = attrTypeReader.resolveType(typeIdx)))
2722 return failure();
2723 if (hasLoc && failed(parseAttribute(reader, argLoc)))
2724 return failure();
2725 } else {
2726 // All args has type and location.
2727 if (failed(parseType(reader, argType)) ||
2728 failed(parseAttribute(reader, argLoc)))
2729 return failure();
2730 }
2731 argTypes.push_back(argType);
2732 argLocs.push_back(argLoc);
2733 }
2734 block->addArguments(argTypes, argLocs);
2735 return defineValues(reader, block->getArguments());
2736}
2737
2738//===----------------------------------------------------------------------===//
2739// Value Processing
2740//===----------------------------------------------------------------------===//
2741
2742Value BytecodeReader::Impl::parseOperand(EncodingReader &reader) {
2743 std::vector<Value> &values = valueScopes.back().values;
2744 Value *value = nullptr;
2745 if (failed(parseEntry(reader, values, value, "value")))
2746 return Value();
2747
2748 // Create a new forward reference if necessary.
2749 if (!*value)
2750 *value = createForwardRef();
2751 return *value;
2752}
2753
2754LogicalResult BytecodeReader::Impl::defineValues(EncodingReader &reader,
2755 ValueRange newValues) {
2756 ValueScope &valueScope = valueScopes.back();
2757 std::vector<Value> &values = valueScope.values;
2758
2759 unsigned &valueID = valueScope.nextValueIDs.back();
2760 unsigned valueIDEnd = valueID + newValues.size();
2761 if (valueIDEnd > values.size()) {
2762 return reader.emitError(
2763 "value index range was outside of the expected range for "
2764 "the parent region, got [",
2765 valueID, ", ", valueIDEnd, "), but the maximum index was ",
2766 values.size() - 1);
2767 }
2768
2769 // Assign the values and update any forward references.
2770 for (unsigned i = 0, e = newValues.size(); i != e; ++i, ++valueID) {
2771 Value newValue = newValues[i];
2772
2773 // Check to see if a definition for this value already exists.
2774 if (Value oldValue = std::exchange(values[valueID], newValue)) {
2775 Operation *forwardRefOp = oldValue.getDefiningOp();
2776
2777 // Assert that this is a forward reference operation. Given how we compute
2778 // definition ids (incrementally as we parse), it shouldn't be possible
2779 // for the value to be defined any other way.
2780 assert(forwardRefOp && forwardRefOp->getBlock() == &forwardRefOps &&
2781 "value index was already defined?");
2782
2783 oldValue.replaceAllUsesWith(newValue);
2784 forwardRefOp->moveBefore(&openForwardRefOps, openForwardRefOps.end());
2785 }
2786 }
2787 return success();
2788}
2789
2790Value BytecodeReader::Impl::createForwardRef() {
2791 // Check for an available existing operation to use. Otherwise, create a new
2792 // fake operation to use for the reference.
2793 if (!openForwardRefOps.empty()) {
2794 Operation *op = &openForwardRefOps.back();
2795 op->moveBefore(&forwardRefOps, forwardRefOps.end());
2796 } else {
2797 forwardRefOps.push_back(Operation::create(forwardRefOpState));
2798 }
2799 return forwardRefOps.back().getResult(0);
2800}
2801
2802//===----------------------------------------------------------------------===//
2803// Entry Points
2804//===----------------------------------------------------------------------===//
2805
2807
2809 llvm::MemoryBufferRef buffer, const ParserConfig &config, bool lazyLoading,
2810 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef) {
2811 Location sourceFileLoc =
2812 FileLineColLoc::get(config.getContext(), buffer.getBufferIdentifier(),
2813 /*line=*/0, /*column=*/0);
2814 impl = std::make_unique<Impl>(sourceFileLoc, config, lazyLoading, buffer,
2815 bufferOwnerRef);
2816}
2817
2819 Block *block, llvm::function_ref<bool(Operation *)> lazyOpsCallback) {
2820 return impl->read(block, lazyOpsCallback);
2821}
2822
2824 return impl->getNumOpsToMaterialize();
2825}
2826
2828 return impl->isMaterializable(op);
2829}
2830
2832 Operation *op, llvm::function_ref<bool(Operation *)> lazyOpsCallback) {
2833 return impl->materialize(op, lazyOpsCallback);
2834}
2835
2836LogicalResult
2838 return impl->finalize(shouldMaterialize);
2839}
2840
2841bool mlir::isBytecode(llvm::MemoryBufferRef buffer) {
2842 return buffer.getBuffer().starts_with("ML\xefR");
2843}
2844
2845/// Read the bytecode from the provided memory buffer reference.
2846/// `bufferOwnerRef` if provided is the owning source manager for the buffer,
2847/// and may be used to extend the lifetime of the buffer.
2848static LogicalResult
2849readBytecodeFileImpl(llvm::MemoryBufferRef buffer, Block *block,
2850 const ParserConfig &config,
2851 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef) {
2852 Location sourceFileLoc =
2853 FileLineColLoc::get(config.getContext(), buffer.getBufferIdentifier(),
2854 /*line=*/0, /*column=*/0);
2855 if (!isBytecode(buffer)) {
2856 return emitError(sourceFileLoc,
2857 "input buffer is not an MLIR bytecode file");
2858 }
2859
2860 BytecodeReader::Impl reader(sourceFileLoc, config, /*lazyLoading=*/false,
2861 buffer, bufferOwnerRef);
2862 return reader.read(block, /*lazyOpsCallback=*/nullptr);
2863}
2864
2865LogicalResult mlir::readBytecodeFile(llvm::MemoryBufferRef buffer, Block *block,
2866 const ParserConfig &config) {
2867 return readBytecodeFileImpl(buffer, block, config, /*bufferOwnerRef=*/{});
2868}
2869LogicalResult
2870mlir::readBytecodeFile(const std::shared_ptr<llvm::SourceMgr> &sourceMgr,
2871 Block *block, const ParserConfig &config) {
2872 return readBytecodeFileImpl(
2873 *sourceMgr->getMemoryBuffer(sourceMgr->getMainFileID()), block, config,
2874 sourceMgr);
2875}
return success()
static LogicalResult parseDialectGrouping(EncodingReader &reader, MutableArrayRef< std::unique_ptr< BytecodeDialect > > dialects, function_ref< LogicalResult(BytecodeDialect *)> entryCallback)
Parse a single dialect group encoded in the byte stream.
static LogicalResult readBytecodeFileImpl(llvm::MemoryBufferRef buffer, Block *block, const ParserConfig &config, const std::shared_ptr< llvm::SourceMgr > &bufferOwnerRef)
Read the bytecode from the provided memory buffer reference.
static bool isSectionOptional(bytecode::Section::ID sectionID, int version)
Returns true if the given top-level section ID is optional.
static LogicalResult parseResourceGroup(Location fileLoc, bool allowEmpty, EncodingReader &offsetReader, EncodingReader &resourceReader, StringSectionReader &stringReader, T *handler, const std::shared_ptr< llvm::SourceMgr > &bufferOwnerRef, function_ref< StringRef(StringRef)> remapKey={}, function_ref< LogicalResult(StringRef)> processKeyFn={})
static LogicalResult resolveEntry(EncodingReader &reader, RangeT &entries, uint64_t index, T &entry, StringRef entryStr)
Resolve an index into the given entry list.
static LogicalResult parseEntry(EncodingReader &reader, RangeT &entries, T &entry, StringRef entryStr)
Parse and resolve an index into the given entry list.
LogicalResult initialize(unsigned origNumLoops, ArrayRef< ReassociationIndices > foldedIterationDims)
b getContext())
auto load
static std::string diag(const llvm::Value &value)
static ParseResult parseRegions(OpAsmParser &parser, OperationState &state, unsigned nRegions=1)
Definition OpenACC.cpp:1173
MutableArrayRef< char > getMutableData()
Return a mutable reference to the raw underlying data of this blob.
Definition AsmState.h:157
ArrayRef< char > getData() const
Return the raw underlying data of this blob.
Definition AsmState.h:145
bool isMutable() const
Return if the data of this blob is mutable.
Definition AsmState.h:164
MLIRContext * getContext() const
Return the context this attribute belongs to.
Block represents an ordered list of Operations.
Definition Block.h:33
BlockArgument getArgument(unsigned i)
Definition Block.h:139
unsigned getNumArguments()
Definition Block.h:138
iterator_range< args_iterator > addArguments(TypeRange types, ArrayRef< Location > locs)
Add one argument to the argument list for each type specified in the list.
Definition Block.cpp:165
OpListType & getOperations()
Definition Block.h:147
BlockArgListType getArguments()
Definition Block.h:97
ArrayRef< std::unique_ptr< AttrTypeBytecodeReader< Type > > > getTypeCallbacks() const
ArrayRef< std::unique_ptr< AttrTypeBytecodeReader< Attribute > > > getAttributeCallbacks() const
Returns the callbacks available to the parser.
This class is used to read a bytecode buffer and translate it into MLIR.
LogicalResult materializeAll()
Materialize all operations.
LogicalResult read(Block *block, llvm::function_ref< bool(Operation *)> lazyOps)
Read the bytecode defined within buffer into the given block.
bool isMaterializable(Operation *op)
Impl(Location fileLoc, const ParserConfig &config, bool lazyLoading, llvm::MemoryBufferRef buffer, const std::shared_ptr< llvm::SourceMgr > &bufferOwnerRef)
LogicalResult finalize(function_ref< bool(Operation *)> shouldMaterialize)
Finalize the lazy-loading by calling back with every op that hasn't been materialized to let the clie...
LogicalResult materialize(Operation *op, llvm::function_ref< bool(Operation *)> lazyOpsCallback)
Materialize the provided operation, invoke the lazyOpsCallback on every newly found lazy operation.
int64_t getNumOpsToMaterialize() const
Return the number of ops that haven't been materialized yet.
LogicalResult materialize(Operation *op, llvm::function_ref< bool(Operation *)> lazyOpsCallback=[](Operation *) { return false;})
Materialize the provide operation.
LogicalResult finalize(function_ref< bool(Operation *)> shouldMaterialize=[](Operation *) { return true;})
Finalize the lazy-loading by calling back with every op that hasn't been materialized to let the clie...
BytecodeReader(llvm::MemoryBufferRef buffer, const ParserConfig &config, bool lazyLoad, const std::shared_ptr< llvm::SourceMgr > &bufferOwnerRef={})
Create a bytecode reader for the given buffer.
int64_t getNumOpsToMaterialize() const
Return the number of ops that haven't been materialized yet.
bool isMaterializable(Operation *op)
Return true if the provided op is materializable.
LogicalResult readTopLevel(Block *block, llvm::function_ref< bool(Operation *)> lazyOps=[](Operation *) { return false;})
Read the operations defined within the given memory buffer, containing MLIR bytecode,...
This class contains all of the information necessary to report a diagnostic to the DiagnosticEngine.
static FileLineColLoc get(StringAttr filename, unsigned line, unsigned column)
Definition Location.cpp:157
This class represents a diagnostic that is inflight and set to be reported.
InFlightDiagnostic & append(Args &&...args) &
Append arguments to the diagnostic.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext * getContext() const
Return the context this location is uniqued in.
Definition Location.h:86
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
T * getOrLoadDialect()
Get (or create) a dialect for the given derived dialect type.
bool allowsUnregisteredDialects()
Return true if we allow to create operation for unregistered dialects.
StringRef getStringRef() const
Return the name of this operation. This always succeeds.
bool isRegistered() const
Return if this operation is registered.
T::Concept * getInterface() const
Returns an instance of the concept object for the given interface if it was registered to this operat...
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
void dropAllReferences()
This drops all operand uses from this operation, which is an essential step in breaking cyclic depend...
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:213
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:407
static Operation * create(Location location, OperationName name, TypeRange resultTypes, ValueRange operands, NamedAttrList &&attributes, OpaqueProperties properties, BlockRange successors, unsigned numRegions)
Create a new Operation with the specific fields.
Definition Operation.cpp:67
void moveBefore(Operation *existingOp)
Unlink this operation from its current block and insert it right before existingOp which may be in th...
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:797
result_range getResults()
Definition Operation.h:415
void erase()
Remove this operation from its parent block and delete it.
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:404
This class represents a configuration for the MLIR assembly parser.
Definition AsmState.h:469
BytecodeReaderConfig & getBytecodeReaderConfig() const
Returns the parsing configurations associated to the bytecode read.
Definition AsmState.h:489
BlockListType::iterator iterator
Definition Region.h:52
This diagnostic handler is a simple RAII class that registers and erases a diagnostic handler on a gi...
static AsmResourceBlob allocateWithAlign(ArrayRef< char > data, size_t align, AsmResourceBlob::DeleterFn deleter={}, bool dataIsMutable=false)
Create a new unmanaged resource directly referencing the provided data.
Definition AsmState.h:228
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:387
bool use_empty() const
Returns true if this value has no uses.
Definition Value.h:208
void shuffleUseList(ArrayRef< unsigned > indices)
Shuffle the use list order according to the provided indices.
Definition Value.cpp:106
use_range getUses() const
Returns a range of all uses, which is useful for iterating over all uses.
Definition Value.h:188
void * getAsOpaquePointer() const
Methods for supporting PointerLikeTypeTraits.
Definition Value.h:233
unsigned getNumUses() const
This method computes the number of uses of this Value.
Definition Value.cpp:52
bool hasOneUse() const
Returns true if this value has exactly one use.
Definition Value.h:197
use_iterator use_begin() const
Definition Value.h:184
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
@ kAttrType
This section contains the attributes and types referenced within an IR module.
Definition Encoding.h:73
@ kAttrTypeOffset
This section contains the offsets for the attribute and types within the AttrType section.
Definition Encoding.h:77
@ kIR
This section contains the list of operations serialized into the bytecode, and their nested regions/o...
Definition Encoding.h:81
@ kResource
This section contains the resources of the bytecode.
Definition Encoding.h:84
@ kResourceOffset
This section contains the offsets of resources within the Resource section.
Definition Encoding.h:88
@ kDialect
This section contains the dialects referenced within an IR module.
Definition Encoding.h:69
@ kString
This section contains strings referenced within the bytecode.
Definition Encoding.h:66
@ kDialectVersions
This section contains the versions of each dialect.
Definition Encoding.h:91
@ kProperties
This section contains the properties for the operations.
Definition Encoding.h:94
@ kNumSections
The total number of section types.
Definition Encoding.h:97
static uint64_t getUseID(OperandT &val, unsigned ownerID)
Get the unique ID of a value use.
Definition Encoding.h:127
@ kUseListOrdering
Use-list ordering started to be encoded in version 3.
Definition Encoding.h:38
@ kAlignmentByte
An arbitrary value used to fill alignment padding.
Definition Encoding.h:56
@ kVersion
The current bytecode version.
Definition Encoding.h:53
@ kLazyLoading
Support for lazy-loading of isolated region was added in version 2.
Definition Encoding.h:35
@ kDialectVersioning
Dialects versioning was added in version 1.
Definition Encoding.h:32
@ kElideUnknownBlockArgLocation
Avoid recording unknown locations on block arguments (compression) started in version 4.
Definition Encoding.h:42
@ kNativePropertiesEncoding
Support for encoding properties natively in bytecode instead of merged with the discardable attribute...
Definition Encoding.h:46
@ kMinSupportedVersion
The minimum supported version of the bytecode.
Definition Encoding.h:29
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:573
Include the generated interface declarations.
InFlightDiagnostic emitWarning(Location loc)
Utility method to emit a warning message using this location.
StringRef toString(AsmResourceEntryKind kind)
static LogicalResult readResourceHandle(DialectBytecodeReader &reader, FailureOr< T > &value, Ts &&...params)
Helper for resource handle reading that returns LogicalResult.
bool isBytecode(llvm::MemoryBufferRef buffer)
Returns true if the given buffer starts with the magic bytes that signal MLIR bytecode.
const FrozenRewritePatternSet GreedyRewriteConfig config
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:128
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
Attribute parseAttribute(llvm::StringRef attrStr, MLIRContext *context, Type type={}, size_t *numRead=nullptr, bool isKnownNullTerminated=false)
This parses a single MLIR attribute to an MLIR context if it was valid.
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
Type parseType(llvm::StringRef typeStr, MLIRContext *context, size_t *numRead=nullptr, bool isKnownNullTerminated=false)
This parses a single MLIR type to an MLIR context if it was valid.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:126
AsmResourceEntryKind
This enum represents the different kinds of resource values.
Definition AsmState.h:280
LogicalResult readBytecodeFile(llvm::MemoryBufferRef buffer, Block *block, const ParserConfig &config)
Read the operations defined within the given memory buffer, containing MLIR bytecode,...
LogicalResult verify(Operation *op, bool verifyRecursively=true)
Perform (potentially expensive) checks of invariants, used to detect compiler bugs,...
Definition Verifier.cpp:423
llvm::function_ref< Fn > function_ref
Definition LLVM.h:152
SmallVector< Block *, 1 > successors
Successors of this operation and their respective operands.
SmallVector< Value, 4 > operands
SmallVector< std::unique_ptr< Region >, 1 > regions
Regions that the op will hold.
Attribute propertiesAttr
This Attribute is used to opaquely construct the properties of the operation.
SmallVector< Type, 4 > types
Types of the results of this operation.