MLIR 23.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 /// The kind of entry being parsed.
953 enum class EntryKind { Attribute, Type };
954
955 /// Add an index to the deferred worklist for re-parsing.
956 void addDeferredParsing(uint64_t index, EntryKind kind) {
957 deferredWorklist.emplace_back(index, kind);
958 }
959
960 /// Whether currently resolving.
961 bool isResolving() const { return resolving; }
962
963private:
964 /// Resolve the given entry at `index`.
965 template <typename T>
966 T resolveEntry(SmallVectorImpl<Entry<T>> &entries, uint64_t index,
967 StringRef entryType, uint64_t depth = 0);
968
969 /// Read the entry at the given index, returning failure if the entry is not
970 /// yet resolved.
971 template <typename T>
972 LogicalResult readEntry(SmallVectorImpl<Entry<T>> &entries, uint64_t index,
973 T &result, StringRef entryType, uint64_t depth);
974
975 /// Parse an entry using the given reader that was encoded using a custom
976 /// bytecode format.
977 template <typename T>
978 LogicalResult parseCustomEntry(Entry<T> &entry, EncodingReader &reader,
979 StringRef entryType, uint64_t index,
980 uint64_t depth);
981
982 /// Parse an entry using the given reader that was encoded using the textual
983 /// assembly format.
984 template <typename T>
985 LogicalResult parseAsmEntry(T &result, EncodingReader &reader,
986 StringRef entryType);
987
988 /// The string section reader used to resolve string references when parsing
989 /// custom encoded attribute/type entries.
990 const StringSectionReader &stringReader;
991
992 /// The resource section reader used to resolve resource references when
993 /// parsing custom encoded attribute/type entries.
994 const ResourceSectionReader &resourceReader;
995
996 /// The map of the loaded dialects used to retrieve dialect information, such
997 /// as the dialect version.
998 const llvm::StringMap<BytecodeDialect *> &dialectsMap;
999
1000 /// The set of attribute and type entries.
1001 SmallVector<AttrEntry> attributes;
1002 SmallVector<TypeEntry> types;
1003
1004 /// A location used for error emission.
1005 Location fileLoc;
1006
1007 /// Current bytecode version being used.
1008 uint64_t &bytecodeVersion;
1009
1010 /// Reference to the parser configuration.
1011 const ParserConfig &parserConfig;
1012
1013 /// Worklist for deferred attribute/type parsing. This is used to handle
1014 /// deeply nested structures like CallSiteLoc iteratively.
1015 /// - The first element is the index of the attribute/type to parse.
1016 /// - The second element is the kind of entry being parsed.
1017 std::vector<std::pair<uint64_t, EntryKind>> deferredWorklist;
1018
1019 /// Flag indicating if we are currently resolving an attribute or type.
1020 bool resolving = false;
1021};
1022
1023class DialectReader : public DialectBytecodeReader {
1024public:
1025 DialectReader(AttrTypeReader &attrTypeReader,
1026 const StringSectionReader &stringReader,
1027 const ResourceSectionReader &resourceReader,
1028 const llvm::StringMap<BytecodeDialect *> &dialectsMap,
1029 EncodingReader &reader, uint64_t &bytecodeVersion,
1030 uint64_t depth = 0)
1031 : attrTypeReader(attrTypeReader), stringReader(stringReader),
1032 resourceReader(resourceReader), dialectsMap(dialectsMap),
1033 reader(reader), bytecodeVersion(bytecodeVersion), depth(depth) {}
1034
1035 InFlightDiagnostic emitError(const Twine &msg) const override {
1036 return reader.emitError(msg);
1037 }
1038
1039 FailureOr<const DialectVersion *>
1040 getDialectVersion(StringRef dialectName) const override {
1041 // First check if the dialect is available in the map.
1042 auto dialectEntry = dialectsMap.find(dialectName);
1043 if (dialectEntry == dialectsMap.end())
1044 return failure();
1045 // If the dialect was found, try to load it. This will trigger reading the
1046 // bytecode version from the version buffer if it wasn't already processed.
1047 // Return failure if either of those two actions could not be completed.
1048 if (failed(dialectEntry->getValue()->load(*this, getLoc().getContext())) ||
1049 dialectEntry->getValue()->loadedVersion == nullptr)
1050 return failure();
1051 return dialectEntry->getValue()->loadedVersion.get();
1052 }
1053
1054 MLIRContext *getContext() const override { return getLoc().getContext(); }
1055
1056 uint64_t getBytecodeVersion() const override { return bytecodeVersion; }
1057
1058 DialectReader withEncodingReader(EncodingReader &encReader) const {
1059 return DialectReader(attrTypeReader, stringReader, resourceReader,
1060 dialectsMap, encReader, bytecodeVersion);
1061 }
1062
1063 Location getLoc() const { return reader.getLoc(); }
1064
1065 //===--------------------------------------------------------------------===//
1066 // IR
1067 //===--------------------------------------------------------------------===//
1068
1069 /// The maximum depth to eagerly parse nested attributes/types before
1070 /// deferring.
1071 static constexpr uint64_t maxAttrTypeDepth = 5;
1072
1073 LogicalResult readAttribute(Attribute &result) override {
1074 uint64_t index;
1075 if (failed(reader.parseVarInt(index)))
1076 return failure();
1077
1078 // If we aren't currently resolving an attribute/type, we resolve this
1079 // attribute eagerly. This is the case when we are parsing properties, which
1080 // aren't processed via the worklist.
1081 if (!attrTypeReader.isResolving()) {
1082 if (Attribute attr = attrTypeReader.resolveAttribute(index)) {
1083 result = attr;
1084 return success();
1085 }
1086 return failure();
1087 }
1088
1089 if (depth > maxAttrTypeDepth) {
1090 if (Attribute attr = attrTypeReader.getAttributeOrSentinel(index)) {
1091 result = attr;
1092 return success();
1093 }
1094 attrTypeReader.addDeferredParsing(index,
1095 AttrTypeReader::EntryKind::Attribute);
1096 return failure();
1097 }
1098 return attrTypeReader.readAttribute(index, result, depth + 1);
1099 }
1100 LogicalResult readOptionalAttribute(Attribute &result) override {
1101 return attrTypeReader.parseOptionalAttribute(reader, result);
1102 }
1103 LogicalResult readType(Type &result) override {
1104 uint64_t index;
1105 if (failed(reader.parseVarInt(index)))
1106 return failure();
1107
1108 // If we aren't currently resolving an attribute/type, we resolve this
1109 // type eagerly. This is the case when we are parsing properties, which
1110 // aren't processed via the worklist.
1111 if (!attrTypeReader.isResolving()) {
1112 if (Type type = attrTypeReader.resolveType(index)) {
1113 result = type;
1114 return success();
1115 }
1116 return failure();
1117 }
1118
1119 if (depth > maxAttrTypeDepth) {
1120 if (Type type = attrTypeReader.getTypeOrSentinel(index)) {
1121 result = type;
1122 return success();
1123 }
1124 attrTypeReader.addDeferredParsing(index, AttrTypeReader::EntryKind::Type);
1125 return failure();
1126 }
1127 return attrTypeReader.readType(index, result, depth + 1);
1128 }
1129
1130 FailureOr<AsmDialectResourceHandle> readResourceHandle() override {
1131 AsmDialectResourceHandle handle;
1132 if (failed(resourceReader.parseResourceHandle(reader, handle)))
1133 return failure();
1134 return handle;
1135 }
1136
1137 //===--------------------------------------------------------------------===//
1138 // Primitives
1139 //===--------------------------------------------------------------------===//
1140
1141 LogicalResult readVarInt(uint64_t &result) override {
1142 return reader.parseVarInt(result);
1143 }
1144
1145 LogicalResult readSignedVarInt(int64_t &result) override {
1146 uint64_t unsignedResult;
1147 if (failed(reader.parseSignedVarInt(unsignedResult)))
1148 return failure();
1149 result = static_cast<int64_t>(unsignedResult);
1150 return success();
1151 }
1152
1153 FailureOr<APInt> readAPIntWithKnownWidth(unsigned bitWidth) override {
1154 // Small values are encoded using a single byte.
1155 if (bitWidth <= 8) {
1156 uint8_t value;
1157 if (failed(reader.parseByte(value)))
1158 return failure();
1159 return APInt(bitWidth, value);
1160 }
1161
1162 // Large values up to 64 bits are encoded using a single varint.
1163 if (bitWidth <= 64) {
1164 uint64_t value;
1165 if (failed(reader.parseSignedVarInt(value)))
1166 return failure();
1167 return APInt(bitWidth, value);
1168 }
1169
1170 // Otherwise, for really big values we encode the array of active words in
1171 // the value.
1172 uint64_t numActiveWords;
1173 if (failed(reader.parseVarInt(numActiveWords)))
1174 return failure();
1175 SmallVector<uint64_t, 4> words(numActiveWords);
1176 for (uint64_t i = 0; i < numActiveWords; ++i)
1177 if (failed(reader.parseSignedVarInt(words[i])))
1178 return failure();
1179 return APInt(bitWidth, words);
1180 }
1181
1182 FailureOr<APFloat>
1183 readAPFloatWithKnownSemantics(const llvm::fltSemantics &semantics) override {
1184 FailureOr<APInt> intVal =
1185 readAPIntWithKnownWidth(APFloat::getSizeInBits(semantics));
1186 if (failed(intVal))
1187 return failure();
1188 return APFloat(semantics, *intVal);
1189 }
1190
1191 LogicalResult readString(StringRef &result) override {
1192 return stringReader.parseString(reader, result);
1193 }
1194
1195 LogicalResult readBlob(ArrayRef<char> &result) override {
1196 uint64_t dataSize;
1197 ArrayRef<uint8_t> data;
1198 if (failed(reader.parseVarInt(dataSize)) ||
1199 failed(reader.parseBytes(dataSize, data)))
1200 return failure();
1201 result = llvm::ArrayRef(reinterpret_cast<const char *>(data.data()),
1202 data.size());
1203 return success();
1204 }
1205
1206 LogicalResult readBool(bool &result) override {
1207 return reader.parseByte(result);
1208 }
1209
1210private:
1211 AttrTypeReader &attrTypeReader;
1212 const StringSectionReader &stringReader;
1213 const ResourceSectionReader &resourceReader;
1214 const llvm::StringMap<BytecodeDialect *> &dialectsMap;
1215 EncodingReader &reader;
1216 uint64_t &bytecodeVersion;
1217 uint64_t depth;
1218};
1219
1220/// Wraps the properties section and handles reading properties out of it.
1221class PropertiesSectionReader {
1222public:
1223 /// Initialize the properties section reader with the given section data.
1224 LogicalResult initialize(Location fileLoc, ArrayRef<uint8_t> sectionData) {
1225 if (sectionData.empty())
1226 return success();
1227 EncodingReader propReader(sectionData, fileLoc);
1228 uint64_t count;
1229 if (failed(propReader.parseVarInt(count)))
1230 return failure();
1231 // Parse the raw properties buffer.
1232 if (failed(propReader.parseBytes(propReader.size(), propertiesBuffers)))
1233 return failure();
1234
1235 EncodingReader offsetsReader(propertiesBuffers, fileLoc);
1236 offsetTable.reserve(count);
1237 for (auto idx : llvm::seq<int64_t>(0, count)) {
1238 (void)idx;
1239 offsetTable.push_back(propertiesBuffers.size() - offsetsReader.size());
1240 ArrayRef<uint8_t> rawProperties;
1241 uint64_t dataSize;
1242 if (failed(offsetsReader.parseVarInt(dataSize)) ||
1243 failed(offsetsReader.parseBytes(dataSize, rawProperties)))
1244 return failure();
1245 }
1246 if (!offsetsReader.empty())
1247 return offsetsReader.emitError()
1248 << "Broken properties section: didn't exhaust the offsets table";
1249 return success();
1250 }
1251
1252 LogicalResult read(Location fileLoc, DialectReader &dialectReader,
1253 OperationName *opName, OperationState &opState) const {
1254 uint64_t propertiesIdx;
1255 if (failed(dialectReader.readVarInt(propertiesIdx)))
1256 return failure();
1257 if (propertiesIdx >= offsetTable.size())
1258 return dialectReader.emitError("Properties idx out-of-bound for ")
1259 << opName->getStringRef();
1260 size_t propertiesOffset = offsetTable[propertiesIdx];
1261 if (propertiesIdx >= propertiesBuffers.size())
1262 return dialectReader.emitError("Properties offset out-of-bound for ")
1263 << opName->getStringRef();
1264
1265 // Acquire the sub-buffer that represent the requested properties.
1266 ArrayRef<char> rawProperties;
1267 {
1268 // "Seek" to the requested offset by getting a new reader with the right
1269 // sub-buffer.
1270 EncodingReader reader(propertiesBuffers.drop_front(propertiesOffset),
1271 fileLoc);
1272 // Properties are stored as a sequence of {size + raw_data}.
1273 if (failed(
1274 dialectReader.withEncodingReader(reader).readBlob(rawProperties)))
1275 return failure();
1276 }
1277 // Setup a new reader to read from the `rawProperties` sub-buffer.
1278 EncodingReader reader(
1279 StringRef(rawProperties.begin(), rawProperties.size()), fileLoc);
1280 DialectReader propReader = dialectReader.withEncodingReader(reader);
1281
1282 auto *iface = opName->getInterface<BytecodeOpInterface>();
1283 if (iface)
1284 return iface->readProperties(propReader, opState);
1285 if (opName->isRegistered())
1286 return propReader.emitError(
1287 "has properties but missing BytecodeOpInterface for ")
1288 << opName->getStringRef();
1289 // Unregistered op are storing properties as an attribute.
1290 return propReader.readAttribute(opState.propertiesAttr);
1291 }
1292
1293private:
1294 /// The properties buffer referenced within the bytecode file.
1295 ArrayRef<uint8_t> propertiesBuffers;
1296
1297 /// Table of offset in the buffer above.
1298 SmallVector<int64_t> offsetTable;
1299};
1300} // namespace
1301
1302LogicalResult AttrTypeReader::initialize(
1303 MutableArrayRef<std::unique_ptr<BytecodeDialect>> dialects,
1304 ArrayRef<uint8_t> sectionData, ArrayRef<uint8_t> offsetSectionData) {
1305 EncodingReader offsetReader(offsetSectionData, fileLoc);
1306
1307 // Parse the number of attribute and type entries.
1308 uint64_t numAttributes, numTypes;
1309 if (failed(offsetReader.parseVarInt(numAttributes)) ||
1310 failed(offsetReader.parseVarInt(numTypes)))
1311 return failure();
1312 attributes.resize(numAttributes);
1313 types.resize(numTypes);
1314
1315 // A functor used to accumulate the offsets for the entries in the given
1316 // range.
1317 uint64_t currentOffset = 0;
1318 auto parseEntries = [&](auto &&range) {
1319 size_t currentIndex = 0, endIndex = range.size();
1320
1321 // Parse an individual entry.
1322 auto parseEntryFn = [&](BytecodeDialect *dialect) -> LogicalResult {
1323 auto &entry = range[currentIndex++];
1324
1325 uint64_t entrySize;
1326 if (failed(offsetReader.parseVarIntWithFlag(entrySize,
1327 entry.hasCustomEncoding)))
1328 return failure();
1329
1330 // Verify that the offset is actually valid.
1331 if (currentOffset + entrySize > sectionData.size()) {
1332 return offsetReader.emitError(
1333 "Attribute or Type entry offset points past the end of section");
1334 }
1335
1336 entry.data = sectionData.slice(currentOffset, entrySize);
1337 entry.dialect = dialect;
1338 currentOffset += entrySize;
1339 return success();
1340 };
1341 while (currentIndex != endIndex)
1342 if (failed(parseDialectGrouping(offsetReader, dialects, parseEntryFn)))
1343 return failure();
1344 return success();
1345 };
1346
1347 // Process each of the attributes, and then the types.
1348 if (failed(parseEntries(attributes)) || failed(parseEntries(types)))
1349 return failure();
1350
1351 // Ensure that we read everything from the section.
1352 if (!offsetReader.empty()) {
1353 return offsetReader.emitError(
1354 "unexpected trailing data in the Attribute/Type offset section");
1355 }
1356
1357 return success();
1358}
1359
1360template <typename T>
1361T AttrTypeReader::resolveEntry(SmallVectorImpl<Entry<T>> &entries,
1362 uint64_t index, StringRef entryType,
1363 uint64_t depth) {
1364 bool oldResolving = resolving;
1365 resolving = true;
1366 llvm::scope_exit restoreResolving([&]() { resolving = oldResolving; });
1367
1368 if (index >= entries.size()) {
1369 emitError(fileLoc) << "invalid " << entryType << " index: " << index;
1370 return {};
1371 }
1372
1373 // Fast path: Try direct parsing without worklist overhead. This handles the
1374 // common case where there are no deferred dependencies.
1375 assert(deferredWorklist.empty());
1376 T result;
1377 if (succeeded(readEntry(entries, index, result, entryType, depth))) {
1378 assert(deferredWorklist.empty());
1379 return result;
1380 }
1381 if (deferredWorklist.empty()) {
1382 // Failed with no deferred entries is error.
1383 return T();
1384 }
1385
1386 // Slow path: Use worklist to handle deferred dependencies. Use a deque to
1387 // iteratively resolve entries with dependencies.
1388 // - Pop from front to process
1389 // - Push new dependencies to front (depth-first)
1390 // - Move failed entries to back (retry after dependencies)
1391 std::deque<std::pair<uint64_t, EntryKind>> worklist;
1392 llvm::DenseSet<std::pair<uint64_t, EntryKind>> inWorklist;
1393
1394 EntryKind entryKind =
1395 std::is_same_v<T, Type> ? EntryKind::Type : EntryKind::Attribute;
1396
1397 static_assert((std::is_same_v<T, Type> || std::is_same_v<T, Attribute>) &&
1398 "Only support resolving Attributes and Types");
1399
1400 auto addToWorklistFront = [&](std::pair<uint64_t, EntryKind> entry) {
1401 if (inWorklist.insert(entry).second)
1402 worklist.push_front(entry);
1403 };
1404
1405 // Add the original index and any dependencies from the fast path attempt.
1406 worklist.emplace_back(index, entryKind);
1407 inWorklist.insert({index, entryKind});
1408 for (auto entry : llvm::reverse(deferredWorklist))
1409 addToWorklistFront(entry);
1410
1411 while (!worklist.empty()) {
1412 auto [currentIndex, entryKind] = worklist.front();
1413 worklist.pop_front();
1414
1415 // Clear the deferred worklist before parsing to capture any new entries.
1416 deferredWorklist.clear();
1417
1418 if (entryKind == EntryKind::Type) {
1419 Type result;
1420 if (succeeded(readType(currentIndex, result, depth))) {
1421 inWorklist.erase({currentIndex, entryKind});
1422 continue;
1423 }
1424 } else {
1425 assert(entryKind == EntryKind::Attribute && "Unexpected entry kind");
1426 Attribute result;
1427 if (succeeded(readAttribute(currentIndex, result, depth))) {
1428 inWorklist.erase({currentIndex, entryKind});
1429 continue;
1430 }
1431 }
1432
1433 if (deferredWorklist.empty()) {
1434 // Parsing failed with no deferred entries which implies an error.
1435 return T();
1436 }
1437
1438 // Move this entry to the back to retry after dependencies.
1439 worklist.emplace_back(currentIndex, entryKind);
1440
1441 // Add dependencies to the front (in reverse so they maintain order).
1442 for (auto entry : llvm::reverse(deferredWorklist))
1443 addToWorklistFront(entry);
1444
1445 deferredWorklist.clear();
1446 }
1447 return entries[index].entry;
1448}
1449
1450template <typename T>
1451LogicalResult AttrTypeReader::readEntry(SmallVectorImpl<Entry<T>> &entries,
1452 uint64_t index, T &result,
1453 StringRef entryType, uint64_t depth) {
1454 if (index >= entries.size())
1455 return emitError(fileLoc) << "invalid " << entryType << " index: " << index;
1456
1457 // If the entry has already been resolved, return it.
1458 Entry<T> &entry = entries[index];
1459 if (entry.entry) {
1460 result = entry.entry;
1461 return success();
1462 }
1463
1464 // If the entry hasn't been resolved, try to parse it.
1465 EncodingReader reader(entry.data, fileLoc);
1466 LogicalResult parseResult =
1467 entry.hasCustomEncoding
1468 ? parseCustomEntry(entry, reader, entryType, index, depth)
1469 : parseAsmEntry(entry.entry, reader, entryType);
1470 if (failed(parseResult))
1471 return failure();
1472
1473 if (!reader.empty())
1474 return reader.emitError("unexpected trailing bytes after " + entryType +
1475 " entry");
1476
1477 result = entry.entry;
1478 return success();
1479}
1480
1481template <typename T>
1482LogicalResult AttrTypeReader::parseCustomEntry(Entry<T> &entry,
1483 EncodingReader &reader,
1484 StringRef entryType,
1485 uint64_t index, uint64_t depth) {
1486 DialectReader dialectReader(*this, stringReader, resourceReader, dialectsMap,
1487 reader, bytecodeVersion, depth);
1488 if (failed(entry.dialect->load(dialectReader, fileLoc.getContext())))
1489 return failure();
1490
1491 if constexpr (std::is_same_v<T, Type>) {
1492 // Try parsing with callbacks first if available.
1493 for (const auto &callback :
1494 parserConfig.getBytecodeReaderConfig().getTypeCallbacks()) {
1495 if (failed(
1496 callback->read(dialectReader, entry.dialect->name, entry.entry)))
1497 return failure();
1498 // Early return if parsing was successful.
1499 if (!!entry.entry)
1500 return success();
1501
1502 // Reset the reader if we failed to parse, so we can fall through the
1503 // other parsing functions.
1504 reader = EncodingReader(entry.data, reader.getLoc());
1505 }
1506 } else {
1507 // Try parsing with callbacks first if available.
1508 for (const auto &callback :
1510 if (failed(
1511 callback->read(dialectReader, entry.dialect->name, entry.entry)))
1512 return failure();
1513 // Early return if parsing was successful.
1514 if (!!entry.entry)
1515 return success();
1516
1517 // Reset the reader if we failed to parse, so we can fall through the
1518 // other parsing functions.
1519 reader = EncodingReader(entry.data, reader.getLoc());
1520 }
1521 }
1522
1523 // Ensure that the dialect implements the bytecode interface.
1524 if (!entry.dialect->interface) {
1525 return reader.emitError("dialect '", entry.dialect->name,
1526 "' does not implement the bytecode interface");
1527 }
1528
1529 if constexpr (std::is_same_v<T, Type>)
1530 entry.entry = entry.dialect->interface->readType(dialectReader);
1531 else
1532 entry.entry = entry.dialect->interface->readAttribute(dialectReader);
1533
1534 return success(!!entry.entry);
1535}
1536
1537template <typename T>
1538LogicalResult AttrTypeReader::parseAsmEntry(T &result, EncodingReader &reader,
1539 StringRef entryType) {
1540 StringRef asmStr;
1541 if (failed(reader.parseNullTerminatedString(asmStr)))
1542 return failure();
1543
1544 // Invoke the MLIR assembly parser to parse the entry text.
1545 size_t numRead = 0;
1546 MLIRContext *context = fileLoc->getContext();
1547 if constexpr (std::is_same_v<T, Type>)
1548 result =
1549 ::parseType(asmStr, context, &numRead, /*isKnownNullTerminated=*/true);
1550 else
1551 result = ::parseAttribute(asmStr, context, Type(), &numRead,
1552 /*isKnownNullTerminated=*/true);
1553 if (!result)
1554 return failure();
1555
1556 // Ensure there weren't dangling characters after the entry.
1557 if (numRead != asmStr.size()) {
1558 return reader.emitError("trailing characters found after ", entryType,
1559 " assembly format: ", asmStr.drop_front(numRead));
1560 }
1561 return success();
1562}
1563
1564//===----------------------------------------------------------------------===//
1565// Bytecode Reader
1566//===----------------------------------------------------------------------===//
1567
1568/// This class is used to read a bytecode buffer and translate it into MLIR.
1570 struct RegionReadState;
1571 using LazyLoadableOpsInfo =
1572 std::list<std::pair<Operation *, RegionReadState>>;
1573 using LazyLoadableOpsMap =
1575
1576public:
1577 Impl(Location fileLoc, const ParserConfig &config, bool lazyLoading,
1578 llvm::MemoryBufferRef buffer,
1579 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef)
1580 : config(config), fileLoc(fileLoc), lazyLoading(lazyLoading),
1581 attrTypeReader(stringReader, resourceReader, dialectsMap, version,
1582 fileLoc, config),
1583 // Use the builtin unrealized conversion cast operation to represent
1584 // forward references to values that aren't yet defined.
1585 forwardRefOpState(UnknownLoc::get(config.getContext()),
1586 "builtin.unrealized_conversion_cast", ValueRange(),
1587 NoneType::get(config.getContext())),
1588 buffer(buffer), bufferOwnerRef(bufferOwnerRef) {}
1589
1590 /// Read the bytecode defined within `buffer` into the given block.
1591 LogicalResult read(Block *block,
1592 llvm::function_ref<bool(Operation *)> lazyOps);
1593
1594 /// Return the number of ops that haven't been materialized yet.
1595 int64_t getNumOpsToMaterialize() const { return lazyLoadableOpsMap.size(); }
1596
1597 bool isMaterializable(Operation *op) { return lazyLoadableOpsMap.count(op); }
1598
1599 /// Materialize the provided operation, invoke the lazyOpsCallback on every
1600 /// newly found lazy operation.
1601 LogicalResult
1603 llvm::function_ref<bool(Operation *)> lazyOpsCallback) {
1604 this->lazyOpsCallback = lazyOpsCallback;
1605 llvm::scope_exit resetlazyOpsCallback(
1606 [&] { this->lazyOpsCallback = nullptr; });
1607 auto it = lazyLoadableOpsMap.find(op);
1608 assert(it != lazyLoadableOpsMap.end() &&
1609 "materialize called on non-materializable op");
1610 return materialize(it);
1611 }
1612
1613 /// Materialize all operations.
1614 LogicalResult materializeAll() {
1615 while (!lazyLoadableOpsMap.empty()) {
1616 if (failed(materialize(lazyLoadableOpsMap.begin())))
1617 return failure();
1618 }
1619 return success();
1620 }
1621
1622 /// Finalize the lazy-loading by calling back with every op that hasn't been
1623 /// materialized to let the client decide if the op should be deleted or
1624 /// materialized. The op is materialized if the callback returns true, deleted
1625 /// otherwise.
1626 LogicalResult finalize(function_ref<bool(Operation *)> shouldMaterialize) {
1627 while (!lazyLoadableOps.empty()) {
1628 Operation *op = lazyLoadableOps.begin()->first;
1629 if (shouldMaterialize(op)) {
1630 if (failed(materialize(lazyLoadableOpsMap.find(op))))
1631 return failure();
1632 continue;
1633 }
1634 op->dropAllReferences();
1635 op->erase();
1636 lazyLoadableOps.pop_front();
1637 lazyLoadableOpsMap.erase(op);
1638 }
1639 return success();
1640 }
1641
1642private:
1643 LogicalResult materialize(LazyLoadableOpsMap::iterator it) {
1644 assert(it != lazyLoadableOpsMap.end() &&
1645 "materialize called on non-materializable op");
1646 valueScopes.emplace_back();
1647 std::vector<RegionReadState> regionStack;
1648 regionStack.push_back(std::move(it->getSecond()->second));
1649 lazyLoadableOps.erase(it->getSecond());
1650 lazyLoadableOpsMap.erase(it);
1651
1652 while (!regionStack.empty())
1653 if (failed(parseRegions(regionStack, regionStack.back())))
1654 return failure();
1655 return success();
1656 }
1657
1658 LogicalResult checkSectionAlignment(
1659 unsigned alignment,
1660 function_ref<InFlightDiagnostic(const Twine &error)> emitError) {
1661 // Check that the bytecode buffer meets the requested section alignment.
1662 //
1663 // If it does not, the virtual address of the item in the section will
1664 // not be aligned to the requested alignment.
1665 //
1666 // The typical case where this is necessary is the resource blob
1667 // optimization in `parseAsBlob` where we reference the weights from the
1668 // provided buffer instead of copying them to a new allocation.
1669 const bool isGloballyAligned =
1670 ((uintptr_t)buffer.getBufferStart() & (alignment - 1)) == 0;
1671
1672 if (!isGloballyAligned)
1673 return emitError("expected section alignment ")
1674 << alignment << " but bytecode buffer 0x"
1675 << Twine::utohexstr((uint64_t)buffer.getBufferStart())
1676 << " is not aligned";
1677
1678 return success();
1679 };
1680
1681 /// Return the context for this config.
1682 MLIRContext *getContext() const { return config.getContext(); }
1683
1684 /// Parse the bytecode version.
1685 LogicalResult parseVersion(EncodingReader &reader);
1686
1687 //===--------------------------------------------------------------------===//
1688 // Dialect Section
1689
1690 LogicalResult parseDialectSection(ArrayRef<uint8_t> sectionData);
1691
1692 /// Parse an operation name reference using the given reader, and set the
1693 /// `wasRegistered` flag that indicates if the bytecode was produced by a
1694 /// context where opName was registered.
1695 FailureOr<OperationName> parseOpName(EncodingReader &reader,
1696 std::optional<bool> &wasRegistered);
1697
1698 //===--------------------------------------------------------------------===//
1699 // Attribute/Type Section
1700
1701 /// Parse an attribute or type using the given reader.
1702 template <typename T>
1703 LogicalResult parseAttribute(EncodingReader &reader, T &result) {
1704 return attrTypeReader.parseAttribute(reader, result);
1705 }
1706 LogicalResult parseType(EncodingReader &reader, Type &result) {
1707 return attrTypeReader.parseType(reader, result);
1708 }
1709
1710 //===--------------------------------------------------------------------===//
1711 // Resource Section
1712
1713 LogicalResult
1714 parseResourceSection(EncodingReader &reader,
1715 std::optional<ArrayRef<uint8_t>> resourceData,
1716 std::optional<ArrayRef<uint8_t>> resourceOffsetData);
1717
1718 //===--------------------------------------------------------------------===//
1719 // IR Section
1720
1721 /// This struct represents the current read state of a range of regions. This
1722 /// struct is used to enable iterative parsing of regions.
1723 struct RegionReadState {
1724 RegionReadState(Operation *op, EncodingReader *reader,
1725 bool isIsolatedFromAbove)
1726 : RegionReadState(op->getRegions(), reader, isIsolatedFromAbove) {}
1727 RegionReadState(MutableArrayRef<Region> regions, EncodingReader *reader,
1728 bool isIsolatedFromAbove)
1729 : curRegion(regions.begin()), endRegion(regions.end()), reader(reader),
1730 isIsolatedFromAbove(isIsolatedFromAbove) {}
1731
1732 /// The current regions being read.
1733 MutableArrayRef<Region>::iterator curRegion, endRegion;
1734 /// This is the reader to use for this region, this pointer is pointing to
1735 /// the parent region reader unless the current region is IsolatedFromAbove,
1736 /// in which case the pointer is pointing to the `owningReader` which is a
1737 /// section dedicated to the current region.
1738 EncodingReader *reader;
1739 std::unique_ptr<EncodingReader> owningReader;
1740
1741 /// The number of values defined immediately within this region.
1742 unsigned numValues = 0;
1743
1744 /// The current blocks of the region being read.
1745 SmallVector<Block *> curBlocks;
1746 Region::iterator curBlock = {};
1747
1748 /// The number of operations remaining to be read from the current block
1749 /// being read.
1750 uint64_t numOpsRemaining = 0;
1751
1752 /// A flag indicating if the regions being read are isolated from above.
1753 bool isIsolatedFromAbove = false;
1754 };
1755
1756 LogicalResult parseIRSection(ArrayRef<uint8_t> sectionData, Block *block);
1757 LogicalResult parseRegions(std::vector<RegionReadState> &regionStack,
1758 RegionReadState &readState);
1759 FailureOr<Operation *> parseOpWithoutRegions(EncodingReader &reader,
1760 RegionReadState &readState,
1761 bool &isIsolatedFromAbove);
1762
1763 LogicalResult parseRegion(RegionReadState &readState);
1764 LogicalResult parseBlockHeader(EncodingReader &reader,
1765 RegionReadState &readState);
1766 LogicalResult parseBlockArguments(EncodingReader &reader, Block *block);
1767
1768 //===--------------------------------------------------------------------===//
1769 // Value Processing
1770
1771 /// Parse an operand reference using the given reader. Returns nullptr in the
1772 /// case of failure.
1773 Value parseOperand(EncodingReader &reader);
1774
1775 /// Sequentially define the given value range.
1776 LogicalResult defineValues(EncodingReader &reader, ValueRange values);
1777
1778 /// Create a value to use for a forward reference.
1779 Value createForwardRef();
1780
1781 //===--------------------------------------------------------------------===//
1782 // Use-list order helpers
1783
1784 /// This struct is a simple storage that contains information required to
1785 /// reorder the use-list of a value with respect to the pre-order traversal
1786 /// ordering.
1787 struct UseListOrderStorage {
1788 UseListOrderStorage(bool isIndexPairEncoding,
1789 SmallVector<unsigned, 4> &&indices)
1790 : indices(std::move(indices)),
1791 isIndexPairEncoding(isIndexPairEncoding) {};
1792 /// The vector containing the information required to reorder the
1793 /// use-list of a value.
1794 SmallVector<unsigned, 4> indices;
1795
1796 /// Whether indices represent a pair of type `(src, dst)` or it is a direct
1797 /// indexing, such as `dst = order[src]`.
1798 bool isIndexPairEncoding;
1799 };
1800
1801 /// Parse use-list order from bytecode for a range of values if available. The
1802 /// range is expected to be either a block argument or an op result range. On
1803 /// success, return a map of the position in the range and the use-list order
1804 /// encoding. The function assumes to know the size of the range it is
1805 /// processing.
1806 using UseListMapT = DenseMap<unsigned, UseListOrderStorage>;
1807 FailureOr<UseListMapT> parseUseListOrderForRange(EncodingReader &reader,
1808 uint64_t rangeSize);
1809
1810 /// Shuffle the use-chain according to the order parsed.
1811 LogicalResult sortUseListOrder(Value value);
1812
1813 /// Recursively visit all the values defined within topLevelOp and sort the
1814 /// use-list orders according to the indices parsed.
1815 LogicalResult processUseLists(Operation *topLevelOp);
1816
1817 //===--------------------------------------------------------------------===//
1818 // Fields
1819
1820 /// This class represents a single value scope, in which a value scope is
1821 /// delimited by isolated from above regions.
1822 struct ValueScope {
1823 /// Push a new region state onto this scope, reserving enough values for
1824 /// those defined within the current region of the provided state.
1825 void push(RegionReadState &readState) {
1826 nextValueIDs.push_back(values.size());
1827 values.resize(values.size() + readState.numValues);
1828 }
1829
1830 /// Pop the values defined for the current region within the provided region
1831 /// state.
1832 void pop(RegionReadState &readState) {
1833 values.resize(values.size() - readState.numValues);
1834 nextValueIDs.pop_back();
1835 }
1836
1837 /// The set of values defined in this scope.
1838 std::vector<Value> values;
1839
1840 /// The ID for the next defined value for each region current being
1841 /// processed in this scope.
1842 SmallVector<unsigned, 4> nextValueIDs;
1843 };
1844
1845 /// The configuration of the parser.
1846 const ParserConfig &config;
1847
1848 /// A location to use when emitting errors.
1849 Location fileLoc;
1850
1851 /// Flag that indicates if lazyloading is enabled.
1852 bool lazyLoading;
1853
1854 /// Keep track of operations that have been lazy loaded (their regions haven't
1855 /// been materialized), along with the `RegionReadState` that allows to
1856 /// lazy-load the regions nested under the operation.
1857 LazyLoadableOpsInfo lazyLoadableOps;
1858 LazyLoadableOpsMap lazyLoadableOpsMap;
1859 llvm::function_ref<bool(Operation *)> lazyOpsCallback;
1860
1861 /// The reader used to process attribute and types within the bytecode.
1862 AttrTypeReader attrTypeReader;
1863
1864 /// The version of the bytecode being read.
1865 uint64_t version = 0;
1866
1867 /// The producer of the bytecode being read.
1868 StringRef producer;
1869
1870 /// The table of IR units referenced within the bytecode file.
1871 SmallVector<std::unique_ptr<BytecodeDialect>> dialects;
1872 llvm::StringMap<BytecodeDialect *> dialectsMap;
1873 SmallVector<BytecodeOperationName> opNames;
1874
1875 /// The reader used to process resources within the bytecode.
1876 ResourceSectionReader resourceReader;
1877
1878 /// Worklist of values with custom use-list orders to process before the end
1879 /// of the parsing.
1880 DenseMap<void *, UseListOrderStorage> valueToUseListMap;
1881
1882 /// The table of strings referenced within the bytecode file.
1883 StringSectionReader stringReader;
1884
1885 /// The table of properties referenced by the operation in the bytecode file.
1886 PropertiesSectionReader propertiesReader;
1887
1888 /// The current set of available IR value scopes.
1889 std::vector<ValueScope> valueScopes;
1890
1891 /// The global pre-order operation ordering.
1893
1894 /// A block containing the set of operations defined to create forward
1895 /// references.
1896 Block forwardRefOps;
1897
1898 /// A block containing previously created, and no longer used, forward
1899 /// reference operations.
1900 Block openForwardRefOps;
1901
1902 /// An operation state used when instantiating forward references.
1903 OperationState forwardRefOpState;
1904
1905 /// Reference to the input buffer.
1906 llvm::MemoryBufferRef buffer;
1907
1908 /// The optional owning source manager, which when present may be used to
1909 /// extend the lifetime of the input buffer.
1910 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef;
1911};
1912
1914 Block *block, llvm::function_ref<bool(Operation *)> lazyOpsCallback) {
1915 EncodingReader reader(buffer.getBuffer(), fileLoc);
1916 this->lazyOpsCallback = lazyOpsCallback;
1917 llvm::scope_exit resetlazyOpsCallback(
1918 [&] { this->lazyOpsCallback = nullptr; });
1919
1920 // Skip over the bytecode header, this should have already been checked.
1921 if (failed(reader.skipBytes(StringRef("ML\xefR").size())))
1922 return failure();
1923 // Parse the bytecode version and producer.
1924 if (failed(parseVersion(reader)) ||
1925 failed(reader.parseNullTerminatedString(producer)))
1926 return failure();
1927
1928 // Add a diagnostic handler that attaches a note that includes the original
1929 // producer of the bytecode.
1930 ScopedDiagnosticHandler diagHandler(getContext(), [&](Diagnostic &diag) {
1931 diag.attachNote() << "in bytecode version " << version
1932 << " produced by: " << producer;
1933 return failure();
1934 });
1935
1936 const auto checkSectionAlignment = [&](unsigned alignment) {
1937 return this->checkSectionAlignment(
1938 alignment, [&](const auto &msg) { return reader.emitError(msg); });
1939 };
1940
1941 // Parse the raw data for each of the top-level sections of the bytecode.
1942 std::optional<ArrayRef<uint8_t>>
1943 sectionDatas[bytecode::Section::kNumSections];
1944 while (!reader.empty()) {
1945 // Read the next section from the bytecode.
1946 bytecode::Section::ID sectionID;
1947 ArrayRef<uint8_t> sectionData;
1948 if (failed(
1949 reader.parseSection(sectionID, checkSectionAlignment, sectionData)))
1950 return failure();
1951
1952 // Check for duplicate sections, we only expect one instance of each.
1953 if (sectionDatas[sectionID]) {
1954 return reader.emitError("duplicate top-level section: ",
1955 ::toString(sectionID));
1956 }
1957 sectionDatas[sectionID] = sectionData;
1958 }
1959 // Check that all of the required sections were found.
1960 for (int i = 0; i < bytecode::Section::kNumSections; ++i) {
1961 bytecode::Section::ID sectionID = static_cast<bytecode::Section::ID>(i);
1962 if (!sectionDatas[i] && !isSectionOptional(sectionID, version)) {
1963 return reader.emitError("missing data for top-level section: ",
1964 ::toString(sectionID));
1965 }
1966 }
1967
1968 // Process the string section first.
1969 if (failed(stringReader.initialize(
1970 fileLoc, *sectionDatas[bytecode::Section::kString])))
1971 return failure();
1972
1973 // Process the properties section.
1974 if (sectionDatas[bytecode::Section::kProperties] &&
1975 failed(propertiesReader.initialize(
1976 fileLoc, *sectionDatas[bytecode::Section::kProperties])))
1977 return failure();
1978
1979 // Process the dialect section.
1980 if (failed(parseDialectSection(*sectionDatas[bytecode::Section::kDialect])))
1981 return failure();
1982
1983 // Process the resource section if present.
1984 if (failed(parseResourceSection(
1985 reader, sectionDatas[bytecode::Section::kResource],
1986 sectionDatas[bytecode::Section::kResourceOffset])))
1987 return failure();
1988
1989 // Process the attribute and type section.
1990 if (failed(attrTypeReader.initialize(
1991 dialects, *sectionDatas[bytecode::Section::kAttrType],
1992 *sectionDatas[bytecode::Section::kAttrTypeOffset])))
1993 return failure();
1994
1995 // Finally, process the IR section.
1996 return parseIRSection(*sectionDatas[bytecode::Section::kIR], block);
1997}
1998
1999LogicalResult BytecodeReader::Impl::parseVersion(EncodingReader &reader) {
2000 if (failed(reader.parseVarInt(version)))
2001 return failure();
2002
2003 // Validate the bytecode version.
2004 uint64_t currentVersion = bytecode::kVersion;
2005 uint64_t minSupportedVersion = bytecode::kMinSupportedVersion;
2006 if (version < minSupportedVersion) {
2007 return reader.emitError("bytecode version ", version,
2008 " is older than the current version of ",
2009 currentVersion, ", and upgrade is not supported");
2010 }
2011 if (version > currentVersion) {
2012 return reader.emitError("bytecode version ", version,
2013 " is newer than the current version ",
2014 currentVersion);
2015 }
2016 // Override any request to lazy-load if the bytecode version is too old.
2017 if (version < bytecode::kLazyLoading)
2018 lazyLoading = false;
2019 return success();
2020}
2021
2022//===----------------------------------------------------------------------===//
2023// Dialect Section
2024//===----------------------------------------------------------------------===//
2025
2026LogicalResult BytecodeDialect::load(const DialectReader &reader,
2027 MLIRContext *ctx) {
2028 if (dialect)
2029 return success();
2030 Dialect *loadedDialect = ctx->getOrLoadDialect(name);
2031 if (!loadedDialect && !ctx->allowsUnregisteredDialects()) {
2032 return reader.emitError("dialect '")
2033 << name
2034 << "' is unknown. If this is intended, please call "
2035 "allowUnregisteredDialects() on the MLIRContext, or use "
2036 "-allow-unregistered-dialect with the MLIR tool used.";
2037 }
2038 dialect = loadedDialect;
2039
2040 // If the dialect was actually loaded, check to see if it has a bytecode
2041 // interface.
2042 if (loadedDialect)
2043 interface = dyn_cast<BytecodeDialectInterface>(loadedDialect);
2044 if (!versionBuffer.empty()) {
2045 if (!interface)
2046 return reader.emitError("dialect '")
2047 << name
2048 << "' does not implement the bytecode interface, "
2049 "but found a version entry";
2050 EncodingReader encReader(versionBuffer, reader.getLoc());
2051 DialectReader versionReader = reader.withEncodingReader(encReader);
2052 loadedVersion = interface->readVersion(versionReader);
2053 if (!loadedVersion)
2054 return failure();
2055 }
2056 return success();
2057}
2058
2059LogicalResult
2060BytecodeReader::Impl::parseDialectSection(ArrayRef<uint8_t> sectionData) {
2061 EncodingReader sectionReader(sectionData, fileLoc);
2062
2063 // Parse the number of dialects in the section.
2064 uint64_t numDialects;
2065 if (failed(sectionReader.parseVarInt(numDialects)))
2066 return failure();
2067 dialects.resize(numDialects);
2068
2069 const auto checkSectionAlignment = [&](unsigned alignment) {
2070 return this->checkSectionAlignment(alignment, [&](const auto &msg) {
2071 return sectionReader.emitError(msg);
2072 });
2073 };
2074
2075 // Parse each of the dialects.
2076 for (uint64_t i = 0; i < numDialects; ++i) {
2077 dialects[i] = std::make_unique<BytecodeDialect>();
2078 /// Before version kDialectVersioning, there wasn't any versioning available
2079 /// for dialects, and the entryIdx represent the string itself.
2080 if (version < bytecode::kDialectVersioning) {
2081 if (failed(stringReader.parseString(sectionReader, dialects[i]->name)))
2082 return failure();
2083 continue;
2084 }
2085
2086 // Parse ID representing dialect and version.
2087 uint64_t dialectNameIdx;
2088 bool versionAvailable;
2089 if (failed(sectionReader.parseVarIntWithFlag(dialectNameIdx,
2090 versionAvailable)))
2091 return failure();
2092 if (failed(stringReader.parseStringAtIndex(sectionReader, dialectNameIdx,
2093 dialects[i]->name)))
2094 return failure();
2095 if (versionAvailable) {
2096 bytecode::Section::ID sectionID;
2097 if (failed(sectionReader.parseSection(sectionID, checkSectionAlignment,
2098 dialects[i]->versionBuffer)))
2099 return failure();
2100 if (sectionID != bytecode::Section::kDialectVersions) {
2101 emitError(fileLoc, "expected dialect version section");
2102 return failure();
2103 }
2104 }
2105 dialectsMap[dialects[i]->name] = dialects[i].get();
2106 }
2107
2108 // Parse the operation names, which are grouped by dialect.
2109 auto parseOpName = [&](BytecodeDialect *dialect) {
2110 StringRef opName;
2111 std::optional<bool> wasRegistered;
2112 // Prior to version kNativePropertiesEncoding, the information about wheter
2113 // an op was registered or not wasn't encoded.
2115 if (failed(stringReader.parseString(sectionReader, opName)))
2116 return failure();
2117 } else {
2118 bool wasRegisteredFlag;
2119 if (failed(stringReader.parseStringWithFlag(sectionReader, opName,
2120 wasRegisteredFlag)))
2121 return failure();
2122 wasRegistered = wasRegisteredFlag;
2123 }
2124 opNames.emplace_back(dialect, opName, wasRegistered);
2125 return success();
2126 };
2127 // Avoid re-allocation in bytecode version >=kElideUnknownBlockArgLocation
2128 // where the number of ops are known.
2130 uint64_t numOps;
2131 if (failed(sectionReader.parseVarInt(numOps)))
2132 return failure();
2133 opNames.reserve(numOps);
2134 }
2135 while (!sectionReader.empty())
2136 if (failed(parseDialectGrouping(sectionReader, dialects, parseOpName)))
2137 return failure();
2138 return success();
2139}
2140
2141FailureOr<OperationName>
2142BytecodeReader::Impl::parseOpName(EncodingReader &reader,
2143 std::optional<bool> &wasRegistered) {
2144 BytecodeOperationName *opName = nullptr;
2145 if (failed(parseEntry(reader, opNames, opName, "operation name")))
2146 return failure();
2147 wasRegistered = opName->wasRegistered;
2148 // Check to see if this operation name has already been resolved. If we
2149 // haven't, load the dialect and build the operation name.
2150 if (!opName->opName) {
2151 // If the opName is empty, this is because we use to accept names such as
2152 // `foo` without any `.` separator. We shouldn't tolerate this in textual
2153 // format anymore but for now we'll be backward compatible. This can only
2154 // happen with unregistered dialects.
2155 if (opName->name.empty()) {
2156 opName->opName.emplace(opName->dialect->name, getContext());
2157 } else {
2158 // Load the dialect and its version.
2159 DialectReader dialectReader(attrTypeReader, stringReader, resourceReader,
2160 dialectsMap, reader, version);
2161 if (failed(opName->dialect->load(dialectReader, getContext())))
2162 return failure();
2163 opName->opName.emplace((opName->dialect->name + "." + opName->name).str(),
2164 getContext());
2165 }
2166 }
2167 return *opName->opName;
2168}
2169
2170//===----------------------------------------------------------------------===//
2171// Resource Section
2172//===----------------------------------------------------------------------===//
2173
2174LogicalResult BytecodeReader::Impl::parseResourceSection(
2175 EncodingReader &reader, std::optional<ArrayRef<uint8_t>> resourceData,
2176 std::optional<ArrayRef<uint8_t>> resourceOffsetData) {
2177 // Ensure both sections are either present or not.
2178 if (resourceData.has_value() != resourceOffsetData.has_value()) {
2179 if (resourceOffsetData)
2180 return emitError(fileLoc, "unexpected resource offset section when "
2181 "resource section is not present");
2182 return emitError(
2183 fileLoc,
2184 "expected resource offset section when resource section is present");
2185 }
2186
2187 // If the resource sections are absent, there is nothing to do.
2188 if (!resourceData)
2189 return success();
2190
2191 // Initialize the resource reader with the resource sections.
2192 DialectReader dialectReader(attrTypeReader, stringReader, resourceReader,
2193 dialectsMap, reader, version);
2194 return resourceReader.initialize(fileLoc, config, dialects, stringReader,
2195 *resourceData, *resourceOffsetData,
2196 dialectReader, bufferOwnerRef);
2197}
2198
2199//===----------------------------------------------------------------------===//
2200// UseListOrder Helpers
2201//===----------------------------------------------------------------------===//
2202
2203FailureOr<BytecodeReader::Impl::UseListMapT>
2204BytecodeReader::Impl::parseUseListOrderForRange(EncodingReader &reader,
2205 uint64_t numResults) {
2206 BytecodeReader::Impl::UseListMapT map;
2207 uint64_t numValuesToRead = 1;
2208 if (numResults > 1 && failed(reader.parseVarInt(numValuesToRead)))
2209 return failure();
2210
2211 for (size_t valueIdx = 0; valueIdx < numValuesToRead; valueIdx++) {
2212 uint64_t resultIdx = 0;
2213 if (numResults > 1 && failed(reader.parseVarInt(resultIdx)))
2214 return failure();
2215
2216 uint64_t numValues;
2217 bool indexPairEncoding;
2218 if (failed(reader.parseVarIntWithFlag(numValues, indexPairEncoding)))
2219 return failure();
2220
2221 SmallVector<unsigned, 4> useListOrders;
2222 for (size_t idx = 0; idx < numValues; idx++) {
2223 uint64_t index;
2224 if (failed(reader.parseVarInt(index)))
2225 return failure();
2226 useListOrders.push_back(index);
2227 }
2228
2229 // Store in a map the result index
2230 map.try_emplace(resultIdx, UseListOrderStorage(indexPairEncoding,
2231 std::move(useListOrders)));
2232 }
2233
2234 return map;
2235}
2236
2237/// Sorts each use according to the order specified in the use-list parsed. If
2238/// the custom use-list is not found, this means that the order needs to be
2239/// consistent with the reverse pre-order walk of the IR. If multiple uses lie
2240/// on the same operation, the order will follow the reverse operand number
2241/// ordering.
2242LogicalResult BytecodeReader::Impl::sortUseListOrder(Value value) {
2243 // Early return for trivial use-lists.
2244 if (value.use_empty() || value.hasOneUse())
2245 return success();
2246
2247 bool hasIncomingOrder =
2248 valueToUseListMap.contains(value.getAsOpaquePointer());
2249
2250 // Compute the current order of the use-list with respect to the global
2251 // ordering. Detect if the order is already sorted while doing so.
2252 bool alreadySorted = true;
2253 auto &firstUse = *value.use_begin();
2254 uint64_t prevID =
2255 bytecode::getUseID(firstUse, operationIDs.at(firstUse.getOwner()));
2256 llvm::SmallVector<std::pair<unsigned, uint64_t>> currentOrder = {{0, prevID}};
2257 for (auto item : llvm::drop_begin(llvm::enumerate(value.getUses()))) {
2258 uint64_t currentID = bytecode::getUseID(
2259 item.value(), operationIDs.at(item.value().getOwner()));
2260 alreadySorted &= prevID > currentID;
2261 currentOrder.push_back({item.index(), currentID});
2262 prevID = currentID;
2263 }
2264
2265 // If the order is already sorted, and there wasn't a custom order to apply
2266 // from the bytecode file, we are done.
2267 if (alreadySorted && !hasIncomingOrder)
2268 return success();
2269
2270 // If not already sorted, sort the indices of the current order by descending
2271 // useIDs.
2272 if (!alreadySorted)
2273 std::sort(
2274 currentOrder.begin(), currentOrder.end(),
2275 [](auto elem1, auto elem2) { return elem1.second > elem2.second; });
2276
2277 if (!hasIncomingOrder) {
2278 // If the bytecode file did not contain any custom use-list order, it means
2279 // that the order was descending useID. Hence, shuffle by the first index
2280 // of the `currentOrder` pair.
2281 SmallVector<unsigned> shuffle(llvm::make_first_range(currentOrder));
2282 value.shuffleUseList(shuffle);
2283 return success();
2284 }
2285
2286 // Pull the custom order info from the map.
2287 UseListOrderStorage customOrder =
2288 valueToUseListMap.at(value.getAsOpaquePointer());
2289 SmallVector<unsigned, 4> shuffle = std::move(customOrder.indices);
2290 uint64_t numUses = value.getNumUses();
2291
2292 // If the encoding was a pair of indices `(src, dst)` for every permutation,
2293 // reconstruct the shuffle vector for every use. Initialize the shuffle vector
2294 // as identity, and then apply the mapping encoded in the indices.
2295 if (customOrder.isIndexPairEncoding) {
2296 // Return failure if the number of indices was not representing pairs.
2297 if (shuffle.size() & 1)
2298 return failure();
2299
2300 SmallVector<unsigned, 4> newShuffle(numUses);
2301 size_t idx = 0;
2302 std::iota(newShuffle.begin(), newShuffle.end(), idx);
2303 for (idx = 0; idx < shuffle.size(); idx += 2)
2304 newShuffle[shuffle[idx]] = shuffle[idx + 1];
2305
2306 shuffle = std::move(newShuffle);
2307 }
2308
2309 // Make sure that the indices represent a valid mapping. That is, the sum of
2310 // all the values needs to be equal to (numUses - 1) * numUses / 2, and no
2311 // duplicates are allowed in the list.
2313 uint64_t accumulator = 0;
2314 for (const auto &elem : shuffle) {
2315 if (!set.insert(elem).second)
2316 return failure();
2317 accumulator += elem;
2318 }
2319 if (numUses != shuffle.size() ||
2320 accumulator != (((numUses - 1) * numUses) >> 1))
2321 return failure();
2322
2323 // Apply the current ordering map onto the shuffle vector to get the final
2324 // use-list sorting indices before shuffling.
2325 shuffle = SmallVector<unsigned, 4>(llvm::map_range(
2326 currentOrder, [&](auto item) { return shuffle[item.first]; }));
2327 value.shuffleUseList(shuffle);
2328 return success();
2329}
2330
2331LogicalResult BytecodeReader::Impl::processUseLists(Operation *topLevelOp) {
2332 // Precompute operation IDs according to the pre-order walk of the IR. We
2333 // can't do this while parsing since parseRegions ordering is not strictly
2334 // equal to the pre-order walk.
2335 unsigned operationID = 0;
2336 topLevelOp->walk<mlir::WalkOrder::PreOrder>(
2337 [&](Operation *op) { operationIDs.try_emplace(op, operationID++); });
2338
2339 auto blockWalk = topLevelOp->walk([this](Block *block) {
2340 for (auto arg : block->getArguments())
2341 if (failed(sortUseListOrder(arg)))
2342 return WalkResult::interrupt();
2343 return WalkResult::advance();
2344 });
2345
2346 auto resultWalk = topLevelOp->walk([this](Operation *op) {
2347 for (auto result : op->getResults())
2348 if (failed(sortUseListOrder(result)))
2349 return WalkResult::interrupt();
2350 return WalkResult::advance();
2351 });
2352
2353 return failure(blockWalk.wasInterrupted() || resultWalk.wasInterrupted());
2354}
2355
2356//===----------------------------------------------------------------------===//
2357// IR Section
2358//===----------------------------------------------------------------------===//
2359
2360LogicalResult
2361BytecodeReader::Impl::parseIRSection(ArrayRef<uint8_t> sectionData,
2362 Block *block) {
2363 EncodingReader reader(sectionData, fileLoc);
2364
2365 // A stack of operation regions currently being read from the bytecode.
2366 std::vector<RegionReadState> regionStack;
2367
2368 // Parse the top-level block using a temporary module operation.
2369 OwningOpRef<ModuleOp> moduleOp = ModuleOp::create(fileLoc);
2370 regionStack.emplace_back(*moduleOp, &reader, /*isIsolatedFromAbove=*/true);
2371 regionStack.back().curBlocks.push_back(moduleOp->getBody());
2372 regionStack.back().curBlock = regionStack.back().curRegion->begin();
2373 if (failed(parseBlockHeader(reader, regionStack.back())))
2374 return failure();
2375 valueScopes.emplace_back();
2376 valueScopes.back().push(regionStack.back());
2377
2378 // Iteratively parse regions until everything has been resolved.
2379 while (!regionStack.empty())
2380 if (failed(parseRegions(regionStack, regionStack.back())))
2381 return failure();
2382 if (!forwardRefOps.empty()) {
2383 return reader.emitError(
2384 "not all forward unresolved forward operand references");
2385 }
2386
2387 // Sort use-lists according to what specified in bytecode.
2388 if (failed(processUseLists(*moduleOp)))
2389 return reader.emitError(
2390 "parsed use-list orders were invalid and could not be applied");
2391
2392 // Resolve dialect version.
2393 for (const std::unique_ptr<BytecodeDialect> &byteCodeDialect : dialects) {
2394 // Parsing is complete, give an opportunity to each dialect to visit the
2395 // IR and perform upgrades.
2396 if (!byteCodeDialect->loadedVersion)
2397 continue;
2398 if (byteCodeDialect->interface &&
2399 failed(byteCodeDialect->interface->upgradeFromVersion(
2400 *moduleOp, *byteCodeDialect->loadedVersion)))
2401 return failure();
2402 }
2403
2404 // Verify that the parsed operations are valid.
2405 if (config.shouldVerifyAfterParse() && failed(verify(*moduleOp)))
2406 return failure();
2407
2408 // Splice the parsed operations over to the provided top-level block.
2409 auto &parsedOps = moduleOp->getBody()->getOperations();
2410 auto &destOps = block->getOperations();
2411 destOps.splice(destOps.end(), parsedOps, parsedOps.begin(), parsedOps.end());
2412 return success();
2413}
2414
2415LogicalResult
2416BytecodeReader::Impl::parseRegions(std::vector<RegionReadState> &regionStack,
2417 RegionReadState &readState) {
2418 const auto checkSectionAlignment = [&](unsigned alignment) {
2419 return this->checkSectionAlignment(
2420 alignment, [&](const auto &msg) { return emitError(fileLoc, msg); });
2421 };
2422
2423 // Process regions, blocks, and operations until the end or if a nested
2424 // region is encountered. In this case we push a new state in regionStack and
2425 // return, the processing of the current region will resume afterward.
2426 for (; readState.curRegion != readState.endRegion; ++readState.curRegion) {
2427 // If the current block hasn't been setup yet, parse the header for this
2428 // region. The current block is already setup when this function was
2429 // interrupted to recurse down in a nested region and we resume the current
2430 // block after processing the nested region.
2431 if (readState.curBlock == Region::iterator()) {
2432 if (failed(parseRegion(readState)))
2433 return failure();
2434
2435 // If the region is empty, there is nothing to more to do.
2436 if (readState.curRegion->empty())
2437 continue;
2438 }
2439
2440 // Parse the blocks within the region.
2441 EncodingReader &reader = *readState.reader;
2442 do {
2443 while (readState.numOpsRemaining--) {
2444 // Read in the next operation. We don't read its regions directly, we
2445 // handle those afterwards as necessary.
2446 bool isIsolatedFromAbove = false;
2447 FailureOr<Operation *> op =
2448 parseOpWithoutRegions(reader, readState, isIsolatedFromAbove);
2449 if (failed(op))
2450 return failure();
2451
2452 // If the op has regions, add it to the stack for processing and return:
2453 // we stop the processing of the current region and resume it after the
2454 // inner one is completed. Unless LazyLoading is activated in which case
2455 // nested region parsing is delayed.
2456 if ((*op)->getNumRegions()) {
2457 RegionReadState childState(*op, &reader, isIsolatedFromAbove);
2458
2459 // Isolated regions are encoded as a section in version 2 and above.
2460 if (version >= bytecode::kLazyLoading && isIsolatedFromAbove) {
2461 bytecode::Section::ID sectionID;
2462 ArrayRef<uint8_t> sectionData;
2463 if (failed(reader.parseSection(sectionID, checkSectionAlignment,
2464 sectionData)))
2465 return failure();
2466 if (sectionID != bytecode::Section::kIR)
2467 return emitError(fileLoc, "expected IR section for region");
2468 childState.owningReader =
2469 std::make_unique<EncodingReader>(sectionData, fileLoc);
2470 childState.reader = childState.owningReader.get();
2471
2472 // If the user has a callback set, they have the opportunity to
2473 // control lazyloading as we go.
2474 if (lazyLoading && (!lazyOpsCallback || !lazyOpsCallback(*op))) {
2475 lazyLoadableOps.emplace_back(*op, std::move(childState));
2476 lazyLoadableOpsMap.try_emplace(*op,
2477 std::prev(lazyLoadableOps.end()));
2478 continue;
2479 }
2480 }
2481 regionStack.push_back(std::move(childState));
2482
2483 // If the op is isolated from above, push a new value scope.
2484 if (isIsolatedFromAbove)
2485 valueScopes.emplace_back();
2486 return success();
2487 }
2488 }
2489
2490 // Move to the next block of the region.
2491 if (++readState.curBlock == readState.curRegion->end())
2492 break;
2493 if (failed(parseBlockHeader(reader, readState)))
2494 return failure();
2495 } while (true);
2496
2497 // Reset the current block and any values reserved for this region.
2498 readState.curBlock = {};
2499 valueScopes.back().pop(readState);
2500 }
2501
2502 // When the regions have been fully parsed, pop them off of the read stack. If
2503 // the regions were isolated from above, we also pop the last value scope.
2504 if (readState.isIsolatedFromAbove) {
2505 assert(!valueScopes.empty() && "Expect a valueScope after reading region");
2506 valueScopes.pop_back();
2507 }
2508 assert(!regionStack.empty() && "Expect a regionStack after reading region");
2509 regionStack.pop_back();
2510 return success();
2511}
2512
2513FailureOr<Operation *>
2514BytecodeReader::Impl::parseOpWithoutRegions(EncodingReader &reader,
2515 RegionReadState &readState,
2516 bool &isIsolatedFromAbove) {
2517 // Parse the name of the operation.
2518 std::optional<bool> wasRegistered;
2519 FailureOr<OperationName> opName = parseOpName(reader, wasRegistered);
2520 if (failed(opName))
2521 return failure();
2522
2523 // Parse the operation mask, which indicates which components of the operation
2524 // are present.
2525 uint8_t opMask;
2526 if (failed(reader.parseByte(opMask)))
2527 return failure();
2528
2529 /// Parse the location.
2530 LocationAttr opLoc;
2531 if (failed(parseAttribute(reader, opLoc)))
2532 return failure();
2533
2534 // With the location and name resolved, we can start building the operation
2535 // state.
2536 OperationState opState(opLoc, *opName);
2537
2538 // Parse the attributes of the operation.
2540 DictionaryAttr dictAttr;
2541 if (failed(parseAttribute(reader, dictAttr)))
2542 return failure();
2543 opState.attributes = dictAttr;
2544 }
2545
2547 // kHasProperties wasn't emitted in older bytecode, we should never get
2548 // there without also having the `wasRegistered` flag available.
2549 if (!wasRegistered)
2550 return emitError(fileLoc,
2551 "Unexpected missing `wasRegistered` opname flag at "
2552 "bytecode version ")
2553 << version << " with properties.";
2554 // When an operation is emitted without being registered, the properties are
2555 // stored as an attribute. Otherwise the op must implement the bytecode
2556 // interface and control the serialization.
2557 if (wasRegistered) {
2558 DialectReader dialectReader(attrTypeReader, stringReader, resourceReader,
2559 dialectsMap, reader, version);
2560 if (failed(
2561 propertiesReader.read(fileLoc, dialectReader, &*opName, opState)))
2562 return failure();
2563 } else {
2564 // If the operation wasn't registered when it was emitted, the properties
2565 // was serialized as an attribute.
2566 if (failed(parseAttribute(reader, opState.propertiesAttr)))
2567 return failure();
2568 }
2569 }
2570
2571 /// Parse the results of the operation.
2573 uint64_t numResults;
2574 if (failed(reader.parseVarInt(numResults)))
2575 return failure();
2576 opState.types.resize(numResults);
2577 for (int i = 0, e = numResults; i < e; ++i)
2578 if (failed(parseType(reader, opState.types[i])))
2579 return failure();
2580 }
2581
2582 /// Parse the operands of the operation.
2584 uint64_t numOperands;
2585 if (failed(reader.parseVarInt(numOperands)))
2586 return failure();
2587 opState.operands.resize(numOperands);
2588 for (int i = 0, e = numOperands; i < e; ++i)
2589 if (!(opState.operands[i] = parseOperand(reader)))
2590 return failure();
2591 }
2592
2593 /// Parse the successors of the operation.
2595 uint64_t numSuccs;
2596 if (failed(reader.parseVarInt(numSuccs)))
2597 return failure();
2598 opState.successors.resize(numSuccs);
2599 for (int i = 0, e = numSuccs; i < e; ++i) {
2600 if (failed(parseEntry(reader, readState.curBlocks, opState.successors[i],
2601 "successor")))
2602 return failure();
2603 }
2604 }
2605
2606 /// Parse the use-list orders for the results of the operation. Use-list
2607 /// orders are available since version 3 of the bytecode.
2608 std::optional<UseListMapT> resultIdxToUseListMap = std::nullopt;
2609 if (version >= bytecode::kUseListOrdering &&
2611 size_t numResults = opState.types.size();
2612 auto parseResult = parseUseListOrderForRange(reader, numResults);
2613 if (failed(parseResult))
2614 return failure();
2615 resultIdxToUseListMap = std::move(*parseResult);
2616 }
2617
2618 /// Parse the regions of the operation.
2620 uint64_t numRegions;
2621 if (failed(reader.parseVarIntWithFlag(numRegions, isIsolatedFromAbove)))
2622 return failure();
2623
2624 opState.regions.reserve(numRegions);
2625 for (int i = 0, e = numRegions; i < e; ++i)
2626 opState.regions.push_back(std::make_unique<Region>());
2627 }
2628
2629 // Create the operation at the back of the current block.
2630 Operation *op = Operation::create(opState);
2631 readState.curBlock->push_back(op);
2632
2633 // If the operation had results, update the value references. We don't need to
2634 // do this if the current value scope is empty. That is, the op was not
2635 // encoded within a parent region.
2636 if (readState.numValues && op->getNumResults() &&
2637 failed(defineValues(reader, op->getResults())))
2638 return failure();
2639
2640 /// Store a map for every value that received a custom use-list order from the
2641 /// bytecode file.
2642 if (resultIdxToUseListMap.has_value()) {
2643 for (size_t idx = 0; idx < op->getNumResults(); idx++) {
2644 if (resultIdxToUseListMap->contains(idx)) {
2645 valueToUseListMap.try_emplace(op->getResult(idx).getAsOpaquePointer(),
2646 resultIdxToUseListMap->at(idx));
2647 }
2648 }
2649 }
2650 return op;
2651}
2652
2653LogicalResult BytecodeReader::Impl::parseRegion(RegionReadState &readState) {
2654 EncodingReader &reader = *readState.reader;
2655
2656 // Parse the number of blocks in the region.
2657 uint64_t numBlocks;
2658 if (failed(reader.parseVarInt(numBlocks)))
2659 return failure();
2660
2661 // If the region is empty, there is nothing else to do.
2662 if (numBlocks == 0)
2663 return success();
2664
2665 // Parse the number of values defined in this region.
2666 uint64_t numValues;
2667 if (failed(reader.parseVarInt(numValues)))
2668 return failure();
2669 readState.numValues = numValues;
2670
2671 // Create the blocks within this region. We do this before processing so that
2672 // we can rely on the blocks existing when creating operations.
2673 readState.curBlocks.clear();
2674 readState.curBlocks.reserve(numBlocks);
2675 for (uint64_t i = 0; i < numBlocks; ++i) {
2676 readState.curBlocks.push_back(new Block());
2677 readState.curRegion->push_back(readState.curBlocks.back());
2678 }
2679
2680 // Prepare the current value scope for this region.
2681 valueScopes.back().push(readState);
2682
2683 // Parse the entry block of the region.
2684 readState.curBlock = readState.curRegion->begin();
2685 return parseBlockHeader(reader, readState);
2686}
2687
2688LogicalResult
2689BytecodeReader::Impl::parseBlockHeader(EncodingReader &reader,
2690 RegionReadState &readState) {
2691 bool hasArgs;
2692 if (failed(reader.parseVarIntWithFlag(readState.numOpsRemaining, hasArgs)))
2693 return failure();
2694
2695 // Parse the arguments of the block.
2696 if (hasArgs && failed(parseBlockArguments(reader, &*readState.curBlock)))
2697 return failure();
2698
2699 // Uselist orders are available since version 3 of the bytecode.
2700 if (version < bytecode::kUseListOrdering)
2701 return success();
2702
2703 uint8_t hasUseListOrders = 0;
2704 if (hasArgs && failed(reader.parseByte(hasUseListOrders)))
2705 return failure();
2706
2707 if (!hasUseListOrders)
2708 return success();
2709
2710 Block &blk = *readState.curBlock;
2711 auto argIdxToUseListMap =
2712 parseUseListOrderForRange(reader, blk.getNumArguments());
2713 if (failed(argIdxToUseListMap) || argIdxToUseListMap->empty())
2714 return failure();
2715
2716 for (size_t idx = 0; idx < blk.getNumArguments(); idx++)
2717 if (argIdxToUseListMap->contains(idx))
2718 valueToUseListMap.try_emplace(blk.getArgument(idx).getAsOpaquePointer(),
2719 argIdxToUseListMap->at(idx));
2720
2721 // We don't parse the operations of the block here, that's done elsewhere.
2722 return success();
2723}
2724
2725LogicalResult BytecodeReader::Impl::parseBlockArguments(EncodingReader &reader,
2726 Block *block) {
2727 // Parse the value ID for the first argument, and the number of arguments.
2728 uint64_t numArgs;
2729 if (failed(reader.parseVarInt(numArgs)))
2730 return failure();
2731
2732 SmallVector<Type> argTypes;
2733 SmallVector<Location> argLocs;
2734 argTypes.reserve(numArgs);
2735 argLocs.reserve(numArgs);
2736
2737 Location unknownLoc = UnknownLoc::get(config.getContext());
2738 while (numArgs--) {
2739 Type argType;
2740 LocationAttr argLoc = unknownLoc;
2742 // Parse the type with hasLoc flag to determine if it has type.
2743 uint64_t typeIdx;
2744 bool hasLoc;
2745 if (failed(reader.parseVarIntWithFlag(typeIdx, hasLoc)) ||
2746 !(argType = attrTypeReader.resolveType(typeIdx)))
2747 return failure();
2748 if (hasLoc && failed(parseAttribute(reader, argLoc)))
2749 return failure();
2750 } else {
2751 // All args has type and location.
2752 if (failed(parseType(reader, argType)) ||
2753 failed(parseAttribute(reader, argLoc)))
2754 return failure();
2755 }
2756 argTypes.push_back(argType);
2757 argLocs.push_back(argLoc);
2758 }
2759 block->addArguments(argTypes, argLocs);
2760 return defineValues(reader, block->getArguments());
2761}
2762
2763//===----------------------------------------------------------------------===//
2764// Value Processing
2765//===----------------------------------------------------------------------===//
2766
2767Value BytecodeReader::Impl::parseOperand(EncodingReader &reader) {
2768 std::vector<Value> &values = valueScopes.back().values;
2769 Value *value = nullptr;
2770 if (failed(parseEntry(reader, values, value, "value")))
2771 return Value();
2772
2773 // Create a new forward reference if necessary.
2774 if (!*value)
2775 *value = createForwardRef();
2776 return *value;
2777}
2778
2779LogicalResult BytecodeReader::Impl::defineValues(EncodingReader &reader,
2780 ValueRange newValues) {
2781 ValueScope &valueScope = valueScopes.back();
2782 std::vector<Value> &values = valueScope.values;
2783
2784 unsigned &valueID = valueScope.nextValueIDs.back();
2785 unsigned valueIDEnd = valueID + newValues.size();
2786 if (valueIDEnd > values.size()) {
2787 return reader.emitError(
2788 "value index range was outside of the expected range for "
2789 "the parent region, got [",
2790 valueID, ", ", valueIDEnd, "), but the maximum index was ",
2791 values.size() - 1);
2792 }
2793
2794 // Assign the values and update any forward references.
2795 for (unsigned i = 0, e = newValues.size(); i != e; ++i, ++valueID) {
2796 Value newValue = newValues[i];
2797
2798 // Check to see if a definition for this value already exists.
2799 if (Value oldValue = std::exchange(values[valueID], newValue)) {
2800 Operation *forwardRefOp = oldValue.getDefiningOp();
2801
2802 // Assert that this is a forward reference operation. Given how we compute
2803 // definition ids (incrementally as we parse), it shouldn't be possible
2804 // for the value to be defined any other way.
2805 assert(forwardRefOp && forwardRefOp->getBlock() == &forwardRefOps &&
2806 "value index was already defined?");
2807
2808 oldValue.replaceAllUsesWith(newValue);
2809 forwardRefOp->moveBefore(&openForwardRefOps, openForwardRefOps.end());
2810 }
2811 }
2812 return success();
2813}
2814
2815Value BytecodeReader::Impl::createForwardRef() {
2816 // Check for an available existing operation to use. Otherwise, create a new
2817 // fake operation to use for the reference.
2818 if (!openForwardRefOps.empty()) {
2819 Operation *op = &openForwardRefOps.back();
2820 op->moveBefore(&forwardRefOps, forwardRefOps.end());
2821 } else {
2822 forwardRefOps.push_back(Operation::create(forwardRefOpState));
2823 }
2824 return forwardRefOps.back().getResult(0);
2825}
2826
2827//===----------------------------------------------------------------------===//
2828// Entry Points
2829//===----------------------------------------------------------------------===//
2830
2832
2834 llvm::MemoryBufferRef buffer, const ParserConfig &config, bool lazyLoading,
2835 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef) {
2836 Location sourceFileLoc =
2837 FileLineColLoc::get(config.getContext(), buffer.getBufferIdentifier(),
2838 /*line=*/0, /*column=*/0);
2839 impl = std::make_unique<Impl>(sourceFileLoc, config, lazyLoading, buffer,
2840 bufferOwnerRef);
2841}
2842
2844 Block *block, llvm::function_ref<bool(Operation *)> lazyOpsCallback) {
2845 return impl->read(block, lazyOpsCallback);
2846}
2847
2849 return impl->getNumOpsToMaterialize();
2850}
2851
2853 return impl->isMaterializable(op);
2854}
2855
2857 Operation *op, llvm::function_ref<bool(Operation *)> lazyOpsCallback) {
2858 return impl->materialize(op, lazyOpsCallback);
2859}
2860
2861LogicalResult
2863 return impl->finalize(shouldMaterialize);
2864}
2865
2866bool mlir::isBytecode(llvm::MemoryBufferRef buffer) {
2867 return buffer.getBuffer().starts_with("ML\xefR");
2868}
2869
2870/// Read the bytecode from the provided memory buffer reference.
2871/// `bufferOwnerRef` if provided is the owning source manager for the buffer,
2872/// and may be used to extend the lifetime of the buffer.
2873static LogicalResult
2874readBytecodeFileImpl(llvm::MemoryBufferRef buffer, Block *block,
2875 const ParserConfig &config,
2876 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef) {
2877 Location sourceFileLoc =
2878 FileLineColLoc::get(config.getContext(), buffer.getBufferIdentifier(),
2879 /*line=*/0, /*column=*/0);
2880 if (!isBytecode(buffer)) {
2881 return emitError(sourceFileLoc,
2882 "input buffer is not an MLIR bytecode file");
2883 }
2884
2885 BytecodeReader::Impl reader(sourceFileLoc, config, /*lazyLoading=*/false,
2886 buffer, bufferOwnerRef);
2887 return reader.read(block, /*lazyOpsCallback=*/nullptr);
2888}
2889
2890LogicalResult mlir::readBytecodeFile(llvm::MemoryBufferRef buffer, Block *block,
2891 const ParserConfig &config) {
2892 return readBytecodeFileImpl(buffer, block, config, /*bufferOwnerRef=*/{});
2893}
2894LogicalResult
2895mlir::readBytecodeFile(const std::shared_ptr<llvm::SourceMgr> &sourceMgr,
2896 Block *block, const ParserConfig &config) {
2897 return readBytecodeFileImpl(
2898 *sourceMgr->getMemoryBuffer(sourceMgr->getMainFileID()), block, config,
2899 sourceMgr);
2900}
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.
static ParseResult parseRegions(OpAsmParser &parser, OperationState &state, unsigned nRegions=1)
Definition OpenACC.cpp:1224
LogicalResult initialize(unsigned origNumLoops, ArrayRef< ReassociationIndices > foldedIterationDims)
b getContext())
auto load
static std::string diag(const llvm::Value &value)
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:120
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:118
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:144
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.