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