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 if (failed(
1507 callback->read(dialectReader, entry.dialect->name, entry.entry)))
1508 return failure();
1509 // Early return if parsing was successful.
1510 if (!!entry.entry)
1511 return success();
1512
1513 // Reset the reader if we failed to parse, so we can fall through the
1514 // other parsing functions.
1515 reader = EncodingReader(entry.data, reader.getLoc());
1516 }
1517 } else {
1518 // Try parsing with callbacks first if available.
1519 for (const auto &callback :
1521 if (failed(
1522 callback->read(dialectReader, entry.dialect->name, entry.entry)))
1523 return failure();
1524 // Early return if parsing was successful.
1525 if (!!entry.entry)
1526 return success();
1527
1528 // Reset the reader if we failed to parse, so we can fall through the
1529 // other parsing functions.
1530 reader = EncodingReader(entry.data, reader.getLoc());
1531 }
1532 }
1533
1534 // Ensure that the dialect implements the bytecode interface.
1535 if (!entry.dialect->interface) {
1536 return reader.emitError("dialect '", entry.dialect->name,
1537 "' does not implement the bytecode interface");
1538 }
1539
1540 if constexpr (std::is_same_v<T, Type>)
1541 entry.entry = entry.dialect->interface->readType(dialectReader);
1542 else
1543 entry.entry = entry.dialect->interface->readAttribute(dialectReader);
1544
1545 return success(!!entry.entry);
1546}
1547
1548template <typename T>
1549LogicalResult AttrTypeReader::parseAsmEntry(T &result, EncodingReader &reader,
1550 StringRef entryType) {
1551 StringRef asmStr;
1552 if (failed(reader.parseNullTerminatedString(asmStr)))
1553 return failure();
1554
1555 // Invoke the MLIR assembly parser to parse the entry text.
1556 size_t numRead = 0;
1557 MLIRContext *context = fileLoc->getContext();
1558 if constexpr (std::is_same_v<T, Type>)
1559 result =
1560 ::parseType(asmStr, context, &numRead, /*isKnownNullTerminated=*/true);
1561 else
1562 result = ::parseAttribute(asmStr, context, Type(), &numRead,
1563 /*isKnownNullTerminated=*/true);
1564 if (!result)
1565 return failure();
1566
1567 // Ensure there weren't dangling characters after the entry.
1568 if (numRead != asmStr.size()) {
1569 return reader.emitError("trailing characters found after ", entryType,
1570 " assembly format: ", asmStr.drop_front(numRead));
1571 }
1572 return success();
1573}
1574
1575//===----------------------------------------------------------------------===//
1576// Bytecode Reader
1577//===----------------------------------------------------------------------===//
1578
1579/// This class is used to read a bytecode buffer and translate it into MLIR.
1581 struct RegionReadState;
1582 using LazyLoadableOpsInfo =
1583 std::list<std::pair<Operation *, RegionReadState>>;
1584 using LazyLoadableOpsMap =
1586
1587public:
1588 Impl(Location fileLoc, const ParserConfig &config, bool lazyLoading,
1589 llvm::MemoryBufferRef buffer,
1590 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef)
1591 : config(config), fileLoc(fileLoc), lazyLoading(lazyLoading),
1592 attrTypeReader(stringReader, resourceReader, dialectsMap, version,
1593 fileLoc, config),
1594 // Use the builtin unrealized conversion cast operation to represent
1595 // forward references to values that aren't yet defined.
1596 forwardRefOpState(UnknownLoc::get(config.getContext()),
1597 "builtin.unrealized_conversion_cast", ValueRange(),
1598 NoneType::get(config.getContext())),
1599 buffer(buffer), bufferOwnerRef(bufferOwnerRef) {}
1600
1601 /// Read the bytecode defined within `buffer` into the given block.
1602 LogicalResult read(Block *block,
1603 llvm::function_ref<bool(Operation *)> lazyOps);
1604
1605 /// Return the number of ops that haven't been materialized yet.
1606 int64_t getNumOpsToMaterialize() const { return lazyLoadableOpsMap.size(); }
1607
1608 bool isMaterializable(Operation *op) { return lazyLoadableOpsMap.count(op); }
1609
1610 /// Materialize the provided operation, invoke the lazyOpsCallback on every
1611 /// newly found lazy operation.
1612 LogicalResult
1614 llvm::function_ref<bool(Operation *)> lazyOpsCallback) {
1615 this->lazyOpsCallback = lazyOpsCallback;
1616 llvm::scope_exit resetlazyOpsCallback(
1617 [&] { this->lazyOpsCallback = nullptr; });
1618 auto it = lazyLoadableOpsMap.find(op);
1619 assert(it != lazyLoadableOpsMap.end() &&
1620 "materialize called on non-materializable op");
1621 return materialize(it);
1622 }
1623
1624 /// Materialize all operations.
1625 LogicalResult materializeAll() {
1626 while (!lazyLoadableOpsMap.empty()) {
1627 if (failed(materialize(lazyLoadableOpsMap.begin())))
1628 return failure();
1629 }
1630 return success();
1631 }
1632
1633 /// Finalize the lazy-loading by calling back with every op that hasn't been
1634 /// materialized to let the client decide if the op should be deleted or
1635 /// materialized. The op is materialized if the callback returns true, deleted
1636 /// otherwise.
1637 LogicalResult finalize(function_ref<bool(Operation *)> shouldMaterialize) {
1638 while (!lazyLoadableOps.empty()) {
1639 Operation *op = lazyLoadableOps.begin()->first;
1640 if (shouldMaterialize(op)) {
1641 if (failed(materialize(lazyLoadableOpsMap.find(op))))
1642 return failure();
1643 continue;
1644 }
1645 op->dropAllReferences();
1646 op->erase();
1647 lazyLoadableOps.pop_front();
1648 lazyLoadableOpsMap.erase(op);
1649 }
1650 return success();
1651 }
1652
1653private:
1654 LogicalResult materialize(LazyLoadableOpsMap::iterator it) {
1655 assert(it != lazyLoadableOpsMap.end() &&
1656 "materialize called on non-materializable op");
1657 valueScopes.emplace_back();
1658 std::vector<RegionReadState> regionStack;
1659 regionStack.push_back(std::move(it->getSecond()->second));
1660 lazyLoadableOps.erase(it->getSecond());
1661 lazyLoadableOpsMap.erase(it);
1662
1663 while (!regionStack.empty())
1664 if (failed(parseRegions(regionStack, regionStack.back())))
1665 return failure();
1666 return success();
1667 }
1668
1669 LogicalResult checkSectionAlignment(
1670 unsigned alignment,
1671 function_ref<InFlightDiagnostic(const Twine &error)> emitError) {
1672 // Check that the bytecode buffer meets the requested section alignment.
1673 //
1674 // If it does not, the virtual address of the item in the section will
1675 // not be aligned to the requested alignment.
1676 //
1677 // The typical case where this is necessary is the resource blob
1678 // optimization in `parseAsBlob` where we reference the weights from the
1679 // provided buffer instead of copying them to a new allocation.
1680 const bool isGloballyAligned =
1681 ((uintptr_t)buffer.getBufferStart() & (alignment - 1)) == 0;
1682
1683 if (!isGloballyAligned)
1684 return emitError("expected section alignment ")
1685 << alignment << " but bytecode buffer 0x"
1686 << Twine::utohexstr((uint64_t)buffer.getBufferStart())
1687 << " is not aligned";
1688
1689 return success();
1690 };
1691
1692 /// Return the context for this config.
1693 MLIRContext *getContext() const { return config.getContext(); }
1694
1695 /// Parse the bytecode version.
1696 LogicalResult parseVersion(EncodingReader &reader);
1697
1698 //===--------------------------------------------------------------------===//
1699 // Dialect Section
1700
1701 LogicalResult parseDialectSection(ArrayRef<uint8_t> sectionData);
1702
1703 /// Parse an operation name reference using the given reader, and set the
1704 /// `wasRegistered` flag that indicates if the bytecode was produced by a
1705 /// context where opName was registered.
1706 FailureOr<OperationName> parseOpName(EncodingReader &reader,
1707 std::optional<bool> &wasRegistered);
1708
1709 //===--------------------------------------------------------------------===//
1710 // Attribute/Type Section
1711
1712 /// Parse an attribute or type using the given reader.
1713 template <typename T>
1714 LogicalResult parseAttribute(EncodingReader &reader, T &result) {
1715 return attrTypeReader.parseAttribute(reader, result);
1716 }
1717 LogicalResult parseType(EncodingReader &reader, Type &result) {
1718 return attrTypeReader.parseType(reader, result);
1719 }
1720
1721 //===--------------------------------------------------------------------===//
1722 // Resource Section
1723
1724 LogicalResult
1725 parseResourceSection(EncodingReader &reader,
1726 std::optional<ArrayRef<uint8_t>> resourceData,
1727 std::optional<ArrayRef<uint8_t>> resourceOffsetData);
1728
1729 //===--------------------------------------------------------------------===//
1730 // IR Section
1731
1732 /// This struct represents the current read state of a range of regions. This
1733 /// struct is used to enable iterative parsing of regions.
1734 struct RegionReadState {
1735 RegionReadState(Operation *op, EncodingReader *reader,
1736 bool isIsolatedFromAbove)
1737 : RegionReadState(op->getRegions(), reader, isIsolatedFromAbove) {}
1738 RegionReadState(MutableArrayRef<Region> regions, EncodingReader *reader,
1739 bool isIsolatedFromAbove)
1740 : curRegion(regions.begin()), endRegion(regions.end()), reader(reader),
1741 isIsolatedFromAbove(isIsolatedFromAbove) {}
1742
1743 /// The current regions being read.
1744 MutableArrayRef<Region>::iterator curRegion, endRegion;
1745 /// This is the reader to use for this region, this pointer is pointing to
1746 /// the parent region reader unless the current region is IsolatedFromAbove,
1747 /// in which case the pointer is pointing to the `owningReader` which is a
1748 /// section dedicated to the current region.
1749 EncodingReader *reader;
1750 std::unique_ptr<EncodingReader> owningReader;
1751
1752 /// The number of values defined immediately within this region.
1753 unsigned numValues = 0;
1754
1755 /// The current blocks of the region being read.
1756 SmallVector<Block *> curBlocks;
1757 Region::iterator curBlock = {};
1758
1759 /// The number of operations remaining to be read from the current block
1760 /// being read.
1761 uint64_t numOpsRemaining = 0;
1762
1763 /// A flag indicating if the regions being read are isolated from above.
1764 bool isIsolatedFromAbove = false;
1765 };
1766
1767 LogicalResult parseIRSection(ArrayRef<uint8_t> sectionData, Block *block);
1768 LogicalResult parseRegions(std::vector<RegionReadState> &regionStack,
1769 RegionReadState &readState);
1770 FailureOr<Operation *> parseOpWithoutRegions(EncodingReader &reader,
1771 RegionReadState &readState,
1772 bool &isIsolatedFromAbove);
1773
1774 LogicalResult parseRegion(RegionReadState &readState);
1775 LogicalResult parseBlockHeader(EncodingReader &reader,
1776 RegionReadState &readState);
1777 LogicalResult parseBlockArguments(EncodingReader &reader, Block *block);
1778
1779 //===--------------------------------------------------------------------===//
1780 // Value Processing
1781
1782 /// Parse an operand reference using the given reader. Returns nullptr in the
1783 /// case of failure.
1784 Value parseOperand(EncodingReader &reader);
1785
1786 /// Sequentially define the given value range.
1787 LogicalResult defineValues(EncodingReader &reader, ValueRange values);
1788
1789 /// Create a value to use for a forward reference.
1790 Value createForwardRef();
1791
1792 //===--------------------------------------------------------------------===//
1793 // Use-list order helpers
1794
1795 /// This struct is a simple storage that contains information required to
1796 /// reorder the use-list of a value with respect to the pre-order traversal
1797 /// ordering.
1798 struct UseListOrderStorage {
1799 UseListOrderStorage(bool isIndexPairEncoding,
1800 SmallVector<unsigned, 4> &&indices)
1801 : indices(std::move(indices)),
1802 isIndexPairEncoding(isIndexPairEncoding) {};
1803 /// The vector containing the information required to reorder the
1804 /// use-list of a value.
1805 SmallVector<unsigned, 4> indices;
1806
1807 /// Whether indices represent a pair of type `(src, dst)` or it is a direct
1808 /// indexing, such as `dst = order[src]`.
1809 bool isIndexPairEncoding;
1810 };
1811
1812 /// Parse use-list order from bytecode for a range of values if available. The
1813 /// range is expected to be either a block argument or an op result range. On
1814 /// success, return a map of the position in the range and the use-list order
1815 /// encoding. The function assumes to know the size of the range it is
1816 /// processing.
1817 using UseListMapT = DenseMap<unsigned, UseListOrderStorage>;
1818 FailureOr<UseListMapT> parseUseListOrderForRange(EncodingReader &reader,
1819 uint64_t rangeSize);
1820
1821 /// Shuffle the use-chain according to the order parsed.
1822 LogicalResult sortUseListOrder(Value value);
1823
1824 /// Recursively visit all the values defined within topLevelOp and sort the
1825 /// use-list orders according to the indices parsed.
1826 LogicalResult processUseLists(Operation *topLevelOp);
1827
1828 //===--------------------------------------------------------------------===//
1829 // Fields
1830
1831 /// This class represents a single value scope, in which a value scope is
1832 /// delimited by isolated from above regions.
1833 struct ValueScope {
1834 /// Push a new region state onto this scope, reserving enough values for
1835 /// those defined within the current region of the provided state.
1836 void push(RegionReadState &readState) {
1837 nextValueIDs.push_back(values.size());
1838 values.resize(values.size() + readState.numValues);
1839 }
1840
1841 /// Pop the values defined for the current region within the provided region
1842 /// state.
1843 void pop(RegionReadState &readState) {
1844 values.resize(values.size() - readState.numValues);
1845 nextValueIDs.pop_back();
1846 }
1847
1848 /// The set of values defined in this scope.
1849 std::vector<Value> values;
1850
1851 /// The ID for the next defined value for each region current being
1852 /// processed in this scope.
1853 SmallVector<unsigned, 4> nextValueIDs;
1854 };
1855
1856 /// The configuration of the parser.
1857 const ParserConfig &config;
1858
1859 /// A location to use when emitting errors.
1860 Location fileLoc;
1861
1862 /// Flag that indicates if lazyloading is enabled.
1863 bool lazyLoading;
1864
1865 /// Keep track of operations that have been lazy loaded (their regions haven't
1866 /// been materialized), along with the `RegionReadState` that allows to
1867 /// lazy-load the regions nested under the operation.
1868 LazyLoadableOpsInfo lazyLoadableOps;
1869 LazyLoadableOpsMap lazyLoadableOpsMap;
1870 llvm::function_ref<bool(Operation *)> lazyOpsCallback;
1871
1872 /// The reader used to process attribute and types within the bytecode.
1873 AttrTypeReader attrTypeReader;
1874
1875 /// The version of the bytecode being read.
1876 uint64_t version = 0;
1877
1878 /// The producer of the bytecode being read.
1879 StringRef producer;
1880
1881 /// The table of IR units referenced within the bytecode file.
1882 SmallVector<std::unique_ptr<BytecodeDialect>> dialects;
1883 llvm::StringMap<BytecodeDialect *> dialectsMap;
1884 SmallVector<BytecodeOperationName> opNames;
1885
1886 /// The reader used to process resources within the bytecode.
1887 ResourceSectionReader resourceReader;
1888
1889 /// Worklist of values with custom use-list orders to process before the end
1890 /// of the parsing.
1891 DenseMap<void *, UseListOrderStorage> valueToUseListMap;
1892
1893 /// The table of strings referenced within the bytecode file.
1894 StringSectionReader stringReader;
1895
1896 /// The table of properties referenced by the operation in the bytecode file.
1897 PropertiesSectionReader propertiesReader;
1898
1899 /// The current set of available IR value scopes.
1900 std::vector<ValueScope> valueScopes;
1901
1902 /// The global pre-order operation ordering.
1904
1905 /// A block containing the set of operations defined to create forward
1906 /// references.
1907 Block forwardRefOps;
1908
1909 /// A block containing previously created, and no longer used, forward
1910 /// reference operations.
1911 Block openForwardRefOps;
1912
1913 /// An operation state used when instantiating forward references.
1914 OperationState forwardRefOpState;
1915
1916 /// Reference to the input buffer.
1917 llvm::MemoryBufferRef buffer;
1918
1919 /// The optional owning source manager, which when present may be used to
1920 /// extend the lifetime of the input buffer.
1921 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef;
1922};
1923
1925 Block *block, llvm::function_ref<bool(Operation *)> lazyOpsCallback) {
1926 EncodingReader reader(buffer.getBuffer(), fileLoc);
1927 this->lazyOpsCallback = lazyOpsCallback;
1928 llvm::scope_exit resetlazyOpsCallback(
1929 [&] { this->lazyOpsCallback = nullptr; });
1930
1931 // Skip over the bytecode header, this should have already been checked.
1932 if (failed(reader.skipBytes(StringRef("ML\xefR").size())))
1933 return failure();
1934 // Parse the bytecode version and producer.
1935 if (failed(parseVersion(reader)) ||
1936 failed(reader.parseNullTerminatedString(producer)))
1937 return failure();
1938
1939 // Add a diagnostic handler that attaches a note that includes the original
1940 // producer of the bytecode.
1941 ScopedDiagnosticHandler diagHandler(getContext(), [&](Diagnostic &diag) {
1942 diag.attachNote() << "in bytecode version " << version
1943 << " produced by: " << producer;
1944 return failure();
1945 });
1946
1947 const auto checkSectionAlignment = [&](unsigned alignment) {
1948 return this->checkSectionAlignment(
1949 alignment, [&](const auto &msg) { return reader.emitError(msg); });
1950 };
1951
1952 // Parse the raw data for each of the top-level sections of the bytecode.
1953 std::optional<ArrayRef<uint8_t>>
1954 sectionDatas[bytecode::Section::kNumSections];
1955 while (!reader.empty()) {
1956 // Read the next section from the bytecode.
1957 bytecode::Section::ID sectionID;
1958 ArrayRef<uint8_t> sectionData;
1959 if (failed(
1960 reader.parseSection(sectionID, checkSectionAlignment, sectionData)))
1961 return failure();
1962
1963 // Check for duplicate sections, we only expect one instance of each.
1964 if (sectionDatas[sectionID]) {
1965 return reader.emitError("duplicate top-level section: ",
1966 ::toString(sectionID));
1967 }
1968 sectionDatas[sectionID] = sectionData;
1969 }
1970 // Check that all of the required sections were found.
1971 for (int i = 0; i < bytecode::Section::kNumSections; ++i) {
1972 bytecode::Section::ID sectionID = static_cast<bytecode::Section::ID>(i);
1973 if (!sectionDatas[i] && !isSectionOptional(sectionID, version)) {
1974 return reader.emitError("missing data for top-level section: ",
1975 ::toString(sectionID));
1976 }
1977 }
1978
1979 // Process the string section first.
1980 if (failed(stringReader.initialize(
1981 fileLoc, *sectionDatas[bytecode::Section::kString])))
1982 return failure();
1983
1984 // Process the properties section.
1985 if (sectionDatas[bytecode::Section::kProperties] &&
1986 failed(propertiesReader.initialize(
1987 fileLoc, *sectionDatas[bytecode::Section::kProperties])))
1988 return failure();
1989
1990 // Process the dialect section.
1991 if (failed(parseDialectSection(*sectionDatas[bytecode::Section::kDialect])))
1992 return failure();
1993
1994 // Process the resource section if present.
1995 if (failed(parseResourceSection(
1996 reader, sectionDatas[bytecode::Section::kResource],
1997 sectionDatas[bytecode::Section::kResourceOffset])))
1998 return failure();
1999
2000 // Process the attribute and type section.
2001 if (failed(attrTypeReader.initialize(
2002 dialects, *sectionDatas[bytecode::Section::kAttrType],
2003 *sectionDatas[bytecode::Section::kAttrTypeOffset])))
2004 return failure();
2005
2006 // Finally, process the IR section.
2007 return parseIRSection(*sectionDatas[bytecode::Section::kIR], block);
2008}
2009
2010LogicalResult BytecodeReader::Impl::parseVersion(EncodingReader &reader) {
2011 if (failed(reader.parseVarInt(version)))
2012 return failure();
2013
2014 // Validate the bytecode version.
2015 uint64_t currentVersion = bytecode::kVersion;
2016 uint64_t minSupportedVersion = bytecode::kMinSupportedVersion;
2017 if (version < minSupportedVersion) {
2018 return reader.emitError("bytecode version ", version,
2019 " is older than the current version of ",
2020 currentVersion, ", and upgrade is not supported");
2021 }
2022 if (version > currentVersion) {
2023 return reader.emitError("bytecode version ", version,
2024 " is newer than the current version ",
2025 currentVersion);
2026 }
2027 // Override any request to lazy-load if the bytecode version is too old.
2028 if (version < bytecode::kLazyLoading)
2029 lazyLoading = false;
2030 return success();
2031}
2032
2033//===----------------------------------------------------------------------===//
2034// Dialect Section
2035//===----------------------------------------------------------------------===//
2036
2037LogicalResult BytecodeDialect::load(const DialectReader &reader,
2038 MLIRContext *ctx) {
2039 if (dialect)
2040 return success();
2041 Dialect *loadedDialect = ctx->getOrLoadDialect(name);
2042 if (!loadedDialect && !ctx->allowsUnregisteredDialects()) {
2043 return reader.emitError("dialect '")
2044 << name
2045 << "' is unknown. If this is intended, please call "
2046 "allowUnregisteredDialects() on the MLIRContext, or use "
2047 "-allow-unregistered-dialect with the MLIR tool used.";
2048 }
2049 dialect = loadedDialect;
2050
2051 // If the dialect was actually loaded, check to see if it has a bytecode
2052 // interface.
2053 if (loadedDialect)
2054 interface = dyn_cast<BytecodeDialectInterface>(loadedDialect);
2055 if (!versionBuffer.empty()) {
2056 if (!interface)
2057 return reader.emitError("dialect '")
2058 << name
2059 << "' does not implement the bytecode interface, "
2060 "but found a version entry";
2061 EncodingReader encReader(versionBuffer, reader.getLoc());
2062 DialectReader versionReader = reader.withEncodingReader(encReader);
2063 loadedVersion = interface->readVersion(versionReader);
2064 if (!loadedVersion)
2065 return failure();
2066 }
2067 return success();
2068}
2069
2070LogicalResult
2071BytecodeReader::Impl::parseDialectSection(ArrayRef<uint8_t> sectionData) {
2072 EncodingReader sectionReader(sectionData, fileLoc);
2073
2074 // Parse the number of dialects in the section.
2075 uint64_t numDialects;
2076 if (failed(sectionReader.parseVarInt(numDialects)))
2077 return failure();
2078 dialects.resize(numDialects);
2079
2080 const auto checkSectionAlignment = [&](unsigned alignment) {
2081 return this->checkSectionAlignment(alignment, [&](const auto &msg) {
2082 return sectionReader.emitError(msg);
2083 });
2084 };
2085
2086 // Parse each of the dialects.
2087 for (uint64_t i = 0; i < numDialects; ++i) {
2088 dialects[i] = std::make_unique<BytecodeDialect>();
2089 /// Before version kDialectVersioning, there wasn't any versioning available
2090 /// for dialects, and the entryIdx represent the string itself.
2091 if (version < bytecode::kDialectVersioning) {
2092 if (failed(stringReader.parseString(sectionReader, dialects[i]->name)))
2093 return failure();
2094 continue;
2095 }
2096
2097 // Parse ID representing dialect and version.
2098 uint64_t dialectNameIdx;
2099 bool versionAvailable;
2100 if (failed(sectionReader.parseVarIntWithFlag(dialectNameIdx,
2101 versionAvailable)))
2102 return failure();
2103 if (failed(stringReader.parseStringAtIndex(sectionReader, dialectNameIdx,
2104 dialects[i]->name)))
2105 return failure();
2106 if (versionAvailable) {
2107 bytecode::Section::ID sectionID;
2108 if (failed(sectionReader.parseSection(sectionID, checkSectionAlignment,
2109 dialects[i]->versionBuffer)))
2110 return failure();
2111 if (sectionID != bytecode::Section::kDialectVersions) {
2112 emitError(fileLoc, "expected dialect version section");
2113 return failure();
2114 }
2115 }
2116 dialectsMap[dialects[i]->name] = dialects[i].get();
2117 }
2118
2119 // Parse the operation names, which are grouped by dialect.
2120 auto parseOpName = [&](BytecodeDialect *dialect) {
2121 StringRef opName;
2122 std::optional<bool> wasRegistered;
2123 // Prior to version kNativePropertiesEncoding, the information about wheter
2124 // an op was registered or not wasn't encoded.
2126 if (failed(stringReader.parseString(sectionReader, opName)))
2127 return failure();
2128 } else {
2129 bool wasRegisteredFlag;
2130 if (failed(stringReader.parseStringWithFlag(sectionReader, opName,
2131 wasRegisteredFlag)))
2132 return failure();
2133 wasRegistered = wasRegisteredFlag;
2134 }
2135 opNames.emplace_back(dialect, opName, wasRegistered);
2136 return success();
2137 };
2138 // Avoid re-allocation in bytecode version >=kElideUnknownBlockArgLocation
2139 // where the number of ops are known.
2141 uint64_t numOps;
2142 if (failed(sectionReader.parseVarInt(numOps)))
2143 return failure();
2144 opNames.reserve(numOps);
2145 }
2146 while (!sectionReader.empty())
2147 if (failed(parseDialectGrouping(sectionReader, dialects, parseOpName)))
2148 return failure();
2149 return success();
2150}
2151
2152FailureOr<OperationName>
2153BytecodeReader::Impl::parseOpName(EncodingReader &reader,
2154 std::optional<bool> &wasRegistered) {
2155 BytecodeOperationName *opName = nullptr;
2156 if (failed(parseEntry(reader, opNames, opName, "operation name")))
2157 return failure();
2158 wasRegistered = opName->wasRegistered;
2159 // Check to see if this operation name has already been resolved. If we
2160 // haven't, load the dialect and build the operation name.
2161 if (!opName->opName) {
2162 // If the opName is empty, this is because we use to accept names such as
2163 // `foo` without any `.` separator. We shouldn't tolerate this in textual
2164 // format anymore but for now we'll be backward compatible. This can only
2165 // happen with unregistered dialects.
2166 if (opName->name.empty()) {
2167 opName->opName.emplace(opName->dialect->name, getContext());
2168 } else {
2169 // Load the dialect and its version.
2170 DialectReader dialectReader(attrTypeReader, stringReader, resourceReader,
2171 dialectsMap, reader, version);
2172 if (failed(opName->dialect->load(dialectReader, getContext())))
2173 return failure();
2174 opName->opName.emplace((opName->dialect->name + "." + opName->name).str(),
2175 getContext());
2176 }
2177 }
2178 return *opName->opName;
2179}
2180
2181//===----------------------------------------------------------------------===//
2182// Resource Section
2183//===----------------------------------------------------------------------===//
2184
2185LogicalResult BytecodeReader::Impl::parseResourceSection(
2186 EncodingReader &reader, std::optional<ArrayRef<uint8_t>> resourceData,
2187 std::optional<ArrayRef<uint8_t>> resourceOffsetData) {
2188 // Ensure both sections are either present or not.
2189 if (resourceData.has_value() != resourceOffsetData.has_value()) {
2190 if (resourceOffsetData)
2191 return emitError(fileLoc, "unexpected resource offset section when "
2192 "resource section is not present");
2193 return emitError(
2194 fileLoc,
2195 "expected resource offset section when resource section is present");
2196 }
2197
2198 // If the resource sections are absent, there is nothing to do.
2199 if (!resourceData)
2200 return success();
2201
2202 // Initialize the resource reader with the resource sections.
2203 DialectReader dialectReader(attrTypeReader, stringReader, resourceReader,
2204 dialectsMap, reader, version);
2205 return resourceReader.initialize(fileLoc, config, dialects, stringReader,
2206 *resourceData, *resourceOffsetData,
2207 dialectReader, bufferOwnerRef);
2208}
2209
2210//===----------------------------------------------------------------------===//
2211// UseListOrder Helpers
2212//===----------------------------------------------------------------------===//
2213
2214FailureOr<BytecodeReader::Impl::UseListMapT>
2215BytecodeReader::Impl::parseUseListOrderForRange(EncodingReader &reader,
2216 uint64_t numResults) {
2217 BytecodeReader::Impl::UseListMapT map;
2218 uint64_t numValuesToRead = 1;
2219 if (numResults > 1 && failed(reader.parseVarInt(numValuesToRead)))
2220 return failure();
2221
2222 for (size_t valueIdx = 0; valueIdx < numValuesToRead; valueIdx++) {
2223 uint64_t resultIdx = 0;
2224 if (numResults > 1 && failed(reader.parseVarInt(resultIdx)))
2225 return failure();
2226
2227 uint64_t numValues;
2228 bool indexPairEncoding;
2229 if (failed(reader.parseVarIntWithFlag(numValues, indexPairEncoding)))
2230 return failure();
2231
2232 SmallVector<unsigned, 4> useListOrders;
2233 for (size_t idx = 0; idx < numValues; idx++) {
2234 uint64_t index;
2235 if (failed(reader.parseVarInt(index)))
2236 return failure();
2237 useListOrders.push_back(index);
2238 }
2239
2240 // Store in a map the result index
2241 map.try_emplace(resultIdx, UseListOrderStorage(indexPairEncoding,
2242 std::move(useListOrders)));
2243 }
2244
2245 return map;
2246}
2247
2248/// Sorts each use according to the order specified in the use-list parsed. If
2249/// the custom use-list is not found, this means that the order needs to be
2250/// consistent with the reverse pre-order walk of the IR. If multiple uses lie
2251/// on the same operation, the order will follow the reverse operand number
2252/// ordering.
2253LogicalResult BytecodeReader::Impl::sortUseListOrder(Value value) {
2254 // Early return for trivial use-lists.
2255 if (value.use_empty() || value.hasOneUse())
2256 return success();
2257
2258 bool hasIncomingOrder =
2259 valueToUseListMap.contains(value.getAsOpaquePointer());
2260
2261 // Compute the current order of the use-list with respect to the global
2262 // ordering. Detect if the order is already sorted while doing so.
2263 bool alreadySorted = true;
2264 auto &firstUse = *value.use_begin();
2265 uint64_t prevID =
2266 bytecode::getUseID(firstUse, operationIDs.at(firstUse.getOwner()));
2267 llvm::SmallVector<std::pair<unsigned, uint64_t>> currentOrder = {{0, prevID}};
2268 for (auto item : llvm::drop_begin(llvm::enumerate(value.getUses()))) {
2269 uint64_t currentID = bytecode::getUseID(
2270 item.value(), operationIDs.at(item.value().getOwner()));
2271 alreadySorted &= prevID > currentID;
2272 currentOrder.push_back({item.index(), currentID});
2273 prevID = currentID;
2274 }
2275
2276 // If the order is already sorted, and there wasn't a custom order to apply
2277 // from the bytecode file, we are done.
2278 if (alreadySorted && !hasIncomingOrder)
2279 return success();
2280
2281 // If not already sorted, sort the indices of the current order by descending
2282 // useIDs.
2283 if (!alreadySorted)
2284 std::sort(
2285 currentOrder.begin(), currentOrder.end(),
2286 [](auto elem1, auto elem2) { return elem1.second > elem2.second; });
2287
2288 if (!hasIncomingOrder) {
2289 // If the bytecode file did not contain any custom use-list order, it means
2290 // that the order was descending useID. Hence, shuffle by the first index
2291 // of the `currentOrder` pair.
2292 SmallVector<unsigned> shuffle(llvm::make_first_range(currentOrder));
2293 value.shuffleUseList(shuffle);
2294 return success();
2295 }
2296
2297 // Pull the custom order info from the map.
2298 UseListOrderStorage customOrder =
2299 valueToUseListMap.at(value.getAsOpaquePointer());
2300 SmallVector<unsigned, 4> shuffle = std::move(customOrder.indices);
2301 uint64_t numUses = value.getNumUses();
2302
2303 // If the encoding was a pair of indices `(src, dst)` for every permutation,
2304 // reconstruct the shuffle vector for every use. Initialize the shuffle vector
2305 // as identity, and then apply the mapping encoded in the indices.
2306 if (customOrder.isIndexPairEncoding) {
2307 // Return failure if the number of indices was not representing pairs.
2308 if (shuffle.size() & 1)
2309 return failure();
2310
2311 SmallVector<unsigned, 4> newShuffle(numUses);
2312 size_t idx = 0;
2313 std::iota(newShuffle.begin(), newShuffle.end(), idx);
2314 for (idx = 0; idx < shuffle.size(); idx += 2)
2315 newShuffle[shuffle[idx]] = shuffle[idx + 1];
2316
2317 shuffle = std::move(newShuffle);
2318 }
2319
2320 // Make sure that the indices represent a valid mapping. That is, the sum of
2321 // all the values needs to be equal to (numUses - 1) * numUses / 2, and no
2322 // duplicates are allowed in the list.
2324 uint64_t accumulator = 0;
2325 for (const auto &elem : shuffle) {
2326 if (!set.insert(elem).second)
2327 return failure();
2328 accumulator += elem;
2329 }
2330 if (numUses != shuffle.size() ||
2331 accumulator != (((numUses - 1) * numUses) >> 1))
2332 return failure();
2333
2334 // Apply the current ordering map onto the shuffle vector to get the final
2335 // use-list sorting indices before shuffling.
2336 shuffle = SmallVector<unsigned, 4>(llvm::map_range(
2337 currentOrder, [&](auto item) { return shuffle[item.first]; }));
2338 value.shuffleUseList(shuffle);
2339 return success();
2340}
2341
2342LogicalResult BytecodeReader::Impl::processUseLists(Operation *topLevelOp) {
2343 // Precompute operation IDs according to the pre-order walk of the IR. We
2344 // can't do this while parsing since parseRegions ordering is not strictly
2345 // equal to the pre-order walk.
2346 unsigned operationID = 0;
2347 topLevelOp->walk<mlir::WalkOrder::PreOrder>(
2348 [&](Operation *op) { operationIDs.try_emplace(op, operationID++); });
2349
2350 auto blockWalk = topLevelOp->walk([this](Block *block) {
2351 for (auto arg : block->getArguments())
2352 if (failed(sortUseListOrder(arg)))
2353 return WalkResult::interrupt();
2354 return WalkResult::advance();
2355 });
2356
2357 auto resultWalk = topLevelOp->walk([this](Operation *op) {
2358 for (auto result : op->getResults())
2359 if (failed(sortUseListOrder(result)))
2360 return WalkResult::interrupt();
2361 return WalkResult::advance();
2362 });
2363
2364 return failure(blockWalk.wasInterrupted() || resultWalk.wasInterrupted());
2365}
2366
2367//===----------------------------------------------------------------------===//
2368// IR Section
2369//===----------------------------------------------------------------------===//
2370
2371LogicalResult
2372BytecodeReader::Impl::parseIRSection(ArrayRef<uint8_t> sectionData,
2373 Block *block) {
2374 EncodingReader reader(sectionData, fileLoc);
2375
2376 // A stack of operation regions currently being read from the bytecode.
2377 std::vector<RegionReadState> regionStack;
2378
2379 // Parse the top-level block using a temporary module operation.
2380 OwningOpRef<ModuleOp> moduleOp = ModuleOp::create(fileLoc);
2381 regionStack.emplace_back(*moduleOp, &reader, /*isIsolatedFromAbove=*/true);
2382 regionStack.back().curBlocks.push_back(moduleOp->getBody());
2383 regionStack.back().curBlock = regionStack.back().curRegion->begin();
2384 if (failed(parseBlockHeader(reader, regionStack.back())))
2385 return failure();
2386 valueScopes.emplace_back();
2387 valueScopes.back().push(regionStack.back());
2388
2389 // Iteratively parse regions until everything has been resolved.
2390 while (!regionStack.empty())
2391 if (failed(parseRegions(regionStack, regionStack.back())))
2392 return failure();
2393 if (!forwardRefOps.empty()) {
2394 return reader.emitError(
2395 "not all forward unresolved forward operand references");
2396 }
2397
2398 // Sort use-lists according to what specified in bytecode.
2399 if (failed(processUseLists(*moduleOp)))
2400 return reader.emitError(
2401 "parsed use-list orders were invalid and could not be applied");
2402
2403 // Resolve dialect version.
2404 for (const std::unique_ptr<BytecodeDialect> &byteCodeDialect : dialects) {
2405 // Parsing is complete, give an opportunity to each dialect to visit the
2406 // IR and perform upgrades.
2407 if (!byteCodeDialect->loadedVersion)
2408 continue;
2409 if (byteCodeDialect->interface &&
2410 failed(byteCodeDialect->interface->upgradeFromVersion(
2411 *moduleOp, *byteCodeDialect->loadedVersion)))
2412 return failure();
2413 }
2414
2415 // Verify that the parsed operations are valid.
2416 if (config.shouldVerifyAfterParse() && failed(verify(*moduleOp)))
2417 return failure();
2418
2419 // Splice the parsed operations over to the provided top-level block.
2420 auto &parsedOps = moduleOp->getBody()->getOperations();
2421 auto &destOps = block->getOperations();
2422 destOps.splice(destOps.end(), parsedOps, parsedOps.begin(), parsedOps.end());
2423 return success();
2424}
2425
2426LogicalResult
2427BytecodeReader::Impl::parseRegions(std::vector<RegionReadState> &regionStack,
2428 RegionReadState &readState) {
2429 const auto checkSectionAlignment = [&](unsigned alignment) {
2430 return this->checkSectionAlignment(
2431 alignment, [&](const auto &msg) { return emitError(fileLoc, msg); });
2432 };
2433
2434 // Process regions, blocks, and operations until the end or if a nested
2435 // region is encountered. In this case we push a new state in regionStack and
2436 // return, the processing of the current region will resume afterward.
2437 for (; readState.curRegion != readState.endRegion; ++readState.curRegion) {
2438 // If the current block hasn't been setup yet, parse the header for this
2439 // region. The current block is already setup when this function was
2440 // interrupted to recurse down in a nested region and we resume the current
2441 // block after processing the nested region.
2442 if (readState.curBlock == Region::iterator()) {
2443 if (failed(parseRegion(readState)))
2444 return failure();
2445
2446 // If the region is empty, there is nothing to more to do.
2447 if (readState.curRegion->empty())
2448 continue;
2449 }
2450
2451 // Parse the blocks within the region.
2452 EncodingReader &reader = *readState.reader;
2453 do {
2454 while (readState.numOpsRemaining--) {
2455 // Read in the next operation. We don't read its regions directly, we
2456 // handle those afterwards as necessary.
2457 bool isIsolatedFromAbove = false;
2458 FailureOr<Operation *> op =
2459 parseOpWithoutRegions(reader, readState, isIsolatedFromAbove);
2460 if (failed(op))
2461 return failure();
2462
2463 // If the op has regions, add it to the stack for processing and return:
2464 // we stop the processing of the current region and resume it after the
2465 // inner one is completed. Unless LazyLoading is activated in which case
2466 // nested region parsing is delayed.
2467 if ((*op)->getNumRegions()) {
2468 RegionReadState childState(*op, &reader, isIsolatedFromAbove);
2469
2470 // Isolated regions are encoded as a section in version 2 and above.
2471 if (version >= bytecode::kLazyLoading && isIsolatedFromAbove) {
2472 bytecode::Section::ID sectionID;
2473 ArrayRef<uint8_t> sectionData;
2474 if (failed(reader.parseSection(sectionID, checkSectionAlignment,
2475 sectionData)))
2476 return failure();
2477 if (sectionID != bytecode::Section::kIR)
2478 return emitError(fileLoc, "expected IR section for region");
2479 childState.owningReader =
2480 std::make_unique<EncodingReader>(sectionData, fileLoc);
2481 childState.reader = childState.owningReader.get();
2482
2483 // If the user has a callback set, they have the opportunity to
2484 // control lazyloading as we go.
2485 if (lazyLoading && (!lazyOpsCallback || !lazyOpsCallback(*op))) {
2486 lazyLoadableOps.emplace_back(*op, std::move(childState));
2487 lazyLoadableOpsMap.try_emplace(*op,
2488 std::prev(lazyLoadableOps.end()));
2489 continue;
2490 }
2491 }
2492 regionStack.push_back(std::move(childState));
2493
2494 // If the op is isolated from above, push a new value scope.
2495 if (isIsolatedFromAbove)
2496 valueScopes.emplace_back();
2497 return success();
2498 }
2499 }
2500
2501 // Move to the next block of the region.
2502 if (++readState.curBlock == readState.curRegion->end())
2503 break;
2504 if (failed(parseBlockHeader(reader, readState)))
2505 return failure();
2506 } while (true);
2507
2508 // Reset the current block and any values reserved for this region.
2509 readState.curBlock = {};
2510 valueScopes.back().pop(readState);
2511 }
2512
2513 // When the regions have been fully parsed, pop them off of the read stack. If
2514 // the regions were isolated from above, we also pop the last value scope.
2515 if (readState.isIsolatedFromAbove) {
2516 assert(!valueScopes.empty() && "Expect a valueScope after reading region");
2517 valueScopes.pop_back();
2518 }
2519 assert(!regionStack.empty() && "Expect a regionStack after reading region");
2520 regionStack.pop_back();
2521 return success();
2522}
2523
2524FailureOr<Operation *>
2525BytecodeReader::Impl::parseOpWithoutRegions(EncodingReader &reader,
2526 RegionReadState &readState,
2527 bool &isIsolatedFromAbove) {
2528 // Parse the name of the operation.
2529 std::optional<bool> wasRegistered;
2530 FailureOr<OperationName> opName = parseOpName(reader, wasRegistered);
2531 if (failed(opName))
2532 return failure();
2533
2534 // Parse the operation mask, which indicates which components of the operation
2535 // are present.
2536 uint8_t opMask;
2537 if (failed(reader.parseByte(opMask)))
2538 return failure();
2539
2540 /// Parse the location.
2541 LocationAttr opLoc;
2542 if (failed(parseAttribute(reader, opLoc)))
2543 return failure();
2544
2545 // With the location and name resolved, we can start building the operation
2546 // state.
2547 OperationState opState(opLoc, *opName);
2548
2549 // Parse the attributes of the operation.
2551 DictionaryAttr dictAttr;
2552 if (failed(parseAttribute(reader, dictAttr)))
2553 return failure();
2554 opState.attributes = dictAttr;
2555 }
2556
2558 // kHasProperties wasn't emitted in older bytecode, we should never get
2559 // there without also having the `wasRegistered` flag available.
2560 if (!wasRegistered)
2561 return emitError(fileLoc,
2562 "Unexpected missing `wasRegistered` opname flag at "
2563 "bytecode version ")
2564 << version << " with properties.";
2565 // When an operation is emitted without being registered, the properties are
2566 // stored as an attribute. Otherwise the op must implement the bytecode
2567 // interface and control the serialization.
2568 if (wasRegistered) {
2569 DialectReader dialectReader(attrTypeReader, stringReader, resourceReader,
2570 dialectsMap, reader, version);
2571 if (failed(
2572 propertiesReader.read(fileLoc, dialectReader, &*opName, opState)))
2573 return failure();
2574 } else {
2575 // If the operation wasn't registered when it was emitted, the properties
2576 // was serialized as an attribute.
2577 if (failed(parseAttribute(reader, opState.propertiesAttr)))
2578 return failure();
2579 }
2580 }
2581
2582 /// Parse the results of the operation.
2584 uint64_t numResults;
2585 if (failed(reader.parseVarInt(numResults)))
2586 return failure();
2587 opState.types.resize(numResults);
2588 for (int i = 0, e = numResults; i < e; ++i)
2589 if (failed(parseType(reader, opState.types[i])))
2590 return failure();
2591 }
2592
2593 /// Parse the operands of the operation.
2595 uint64_t numOperands;
2596 if (failed(reader.parseVarInt(numOperands)))
2597 return failure();
2598 opState.operands.resize(numOperands);
2599 for (int i = 0, e = numOperands; i < e; ++i)
2600 if (!(opState.operands[i] = parseOperand(reader)))
2601 return failure();
2602 }
2603
2604 /// Parse the successors of the operation.
2606 uint64_t numSuccs;
2607 if (failed(reader.parseVarInt(numSuccs)))
2608 return failure();
2609 opState.successors.resize(numSuccs);
2610 for (int i = 0, e = numSuccs; i < e; ++i) {
2611 if (failed(parseEntry(reader, readState.curBlocks, opState.successors[i],
2612 "successor")))
2613 return failure();
2614 }
2615 }
2616
2617 /// Parse the use-list orders for the results of the operation. Use-list
2618 /// orders are available since version 3 of the bytecode.
2619 std::optional<UseListMapT> resultIdxToUseListMap = std::nullopt;
2620 if (version >= bytecode::kUseListOrdering &&
2622 size_t numResults = opState.types.size();
2623 auto parseResult = parseUseListOrderForRange(reader, numResults);
2624 if (failed(parseResult))
2625 return failure();
2626 resultIdxToUseListMap = std::move(*parseResult);
2627 }
2628
2629 /// Parse the regions of the operation.
2631 uint64_t numRegions;
2632 if (failed(reader.parseVarIntWithFlag(numRegions, isIsolatedFromAbove)))
2633 return failure();
2634
2635 opState.regions.reserve(numRegions);
2636 for (int i = 0, e = numRegions; i < e; ++i)
2637 opState.regions.push_back(std::make_unique<Region>());
2638 }
2639
2640 // Create the operation at the back of the current block.
2641 Operation *op = Operation::create(opState);
2642 readState.curBlock->push_back(op);
2643
2644 // If the operation had results, update the value references. We don't need to
2645 // do this if the current value scope is empty. That is, the op was not
2646 // encoded within a parent region.
2647 if (readState.numValues && op->getNumResults() &&
2648 failed(defineValues(reader, op->getResults())))
2649 return failure();
2650
2651 /// Store a map for every value that received a custom use-list order from the
2652 /// bytecode file.
2653 if (resultIdxToUseListMap.has_value()) {
2654 for (size_t idx = 0; idx < op->getNumResults(); idx++) {
2655 if (resultIdxToUseListMap->contains(idx)) {
2656 valueToUseListMap.try_emplace(op->getResult(idx).getAsOpaquePointer(),
2657 resultIdxToUseListMap->at(idx));
2658 }
2659 }
2660 }
2661 return op;
2662}
2663
2664LogicalResult BytecodeReader::Impl::parseRegion(RegionReadState &readState) {
2665 EncodingReader &reader = *readState.reader;
2666
2667 // Parse the number of blocks in the region.
2668 uint64_t numBlocks;
2669 if (failed(reader.parseVarInt(numBlocks)))
2670 return failure();
2671
2672 // If the region is empty, there is nothing else to do.
2673 if (numBlocks == 0)
2674 return success();
2675
2676 // Parse the number of values defined in this region.
2677 uint64_t numValues;
2678 if (failed(reader.parseVarInt(numValues)))
2679 return failure();
2680 readState.numValues = numValues;
2681
2682 // Create the blocks within this region. We do this before processing so that
2683 // we can rely on the blocks existing when creating operations.
2684 readState.curBlocks.clear();
2685 readState.curBlocks.reserve(numBlocks);
2686 for (uint64_t i = 0; i < numBlocks; ++i) {
2687 readState.curBlocks.push_back(new Block());
2688 readState.curRegion->push_back(readState.curBlocks.back());
2689 }
2690
2691 // Prepare the current value scope for this region.
2692 valueScopes.back().push(readState);
2693
2694 // Parse the entry block of the region.
2695 readState.curBlock = readState.curRegion->begin();
2696 return parseBlockHeader(reader, readState);
2697}
2698
2699LogicalResult
2700BytecodeReader::Impl::parseBlockHeader(EncodingReader &reader,
2701 RegionReadState &readState) {
2702 bool hasArgs;
2703 if (failed(reader.parseVarIntWithFlag(readState.numOpsRemaining, hasArgs)))
2704 return failure();
2705
2706 // Parse the arguments of the block.
2707 if (hasArgs && failed(parseBlockArguments(reader, &*readState.curBlock)))
2708 return failure();
2709
2710 // Uselist orders are available since version 3 of the bytecode.
2711 if (version < bytecode::kUseListOrdering)
2712 return success();
2713
2714 uint8_t hasUseListOrders = 0;
2715 if (hasArgs && failed(reader.parseByte(hasUseListOrders)))
2716 return failure();
2717
2718 if (!hasUseListOrders)
2719 return success();
2720
2721 Block &blk = *readState.curBlock;
2722 auto argIdxToUseListMap =
2723 parseUseListOrderForRange(reader, blk.getNumArguments());
2724 if (failed(argIdxToUseListMap) || argIdxToUseListMap->empty())
2725 return failure();
2726
2727 for (size_t idx = 0; idx < blk.getNumArguments(); idx++)
2728 if (argIdxToUseListMap->contains(idx))
2729 valueToUseListMap.try_emplace(blk.getArgument(idx).getAsOpaquePointer(),
2730 argIdxToUseListMap->at(idx));
2731
2732 // We don't parse the operations of the block here, that's done elsewhere.
2733 return success();
2734}
2735
2736LogicalResult BytecodeReader::Impl::parseBlockArguments(EncodingReader &reader,
2737 Block *block) {
2738 // Parse the value ID for the first argument, and the number of arguments.
2739 uint64_t numArgs;
2740 if (failed(reader.parseVarInt(numArgs)))
2741 return failure();
2742
2743 SmallVector<Type> argTypes;
2744 SmallVector<Location> argLocs;
2745 argTypes.reserve(numArgs);
2746 argLocs.reserve(numArgs);
2747
2748 Location unknownLoc = UnknownLoc::get(config.getContext());
2749 while (numArgs--) {
2750 Type argType;
2751 LocationAttr argLoc = unknownLoc;
2753 // Parse the type with hasLoc flag to determine if it has type.
2754 uint64_t typeIdx;
2755 bool hasLoc;
2756 if (failed(reader.parseVarIntWithFlag(typeIdx, hasLoc)) ||
2757 !(argType = attrTypeReader.resolveType(typeIdx)))
2758 return failure();
2759 if (hasLoc && failed(parseAttribute(reader, argLoc)))
2760 return failure();
2761 } else {
2762 // All args has type and location.
2763 if (failed(parseType(reader, argType)) ||
2764 failed(parseAttribute(reader, argLoc)))
2765 return failure();
2766 }
2767 argTypes.push_back(argType);
2768 argLocs.push_back(argLoc);
2769 }
2770 block->addArguments(argTypes, argLocs);
2771 return defineValues(reader, block->getArguments());
2772}
2773
2774//===----------------------------------------------------------------------===//
2775// Value Processing
2776//===----------------------------------------------------------------------===//
2777
2778Value BytecodeReader::Impl::parseOperand(EncodingReader &reader) {
2779 std::vector<Value> &values = valueScopes.back().values;
2780 Value *value = nullptr;
2781 if (failed(parseEntry(reader, values, value, "value")))
2782 return Value();
2783
2784 // Create a new forward reference if necessary.
2785 if (!*value)
2786 *value = createForwardRef();
2787 return *value;
2788}
2789
2790LogicalResult BytecodeReader::Impl::defineValues(EncodingReader &reader,
2791 ValueRange newValues) {
2792 ValueScope &valueScope = valueScopes.back();
2793 std::vector<Value> &values = valueScope.values;
2794
2795 unsigned &valueID = valueScope.nextValueIDs.back();
2796 unsigned valueIDEnd = valueID + newValues.size();
2797 if (valueIDEnd > values.size()) {
2798 return reader.emitError(
2799 "value index range was outside of the expected range for "
2800 "the parent region, got [",
2801 valueID, ", ", valueIDEnd, "), but the maximum index was ",
2802 values.size() - 1);
2803 }
2804
2805 // Assign the values and update any forward references.
2806 for (unsigned i = 0, e = newValues.size(); i != e; ++i, ++valueID) {
2807 Value newValue = newValues[i];
2808
2809 // Check to see if a definition for this value already exists.
2810 if (Value oldValue = std::exchange(values[valueID], newValue)) {
2811 Operation *forwardRefOp = oldValue.getDefiningOp();
2812
2813 // Assert that this is a forward reference operation. Given how we compute
2814 // definition ids (incrementally as we parse), it shouldn't be possible
2815 // for the value to be defined any other way.
2816 assert(forwardRefOp && forwardRefOp->getBlock() == &forwardRefOps &&
2817 "value index was already defined?");
2818
2819 oldValue.replaceAllUsesWith(newValue);
2820 forwardRefOp->moveBefore(&openForwardRefOps, openForwardRefOps.end());
2821 }
2822 }
2823 return success();
2824}
2825
2826Value BytecodeReader::Impl::createForwardRef() {
2827 // Check for an available existing operation to use. Otherwise, create a new
2828 // fake operation to use for the reference.
2829 if (!openForwardRefOps.empty()) {
2830 Operation *op = &openForwardRefOps.back();
2831 op->moveBefore(&forwardRefOps, forwardRefOps.end());
2832 } else {
2833 forwardRefOps.push_back(Operation::create(forwardRefOpState));
2834 }
2835 return forwardRefOps.back().getResult(0);
2836}
2837
2838//===----------------------------------------------------------------------===//
2839// Entry Points
2840//===----------------------------------------------------------------------===//
2841
2843
2845 llvm::MemoryBufferRef buffer, const ParserConfig &config, bool lazyLoading,
2846 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef) {
2847 Location sourceFileLoc =
2848 FileLineColLoc::get(config.getContext(), buffer.getBufferIdentifier(),
2849 /*line=*/0, /*column=*/0);
2850 impl = std::make_unique<Impl>(sourceFileLoc, config, lazyLoading, buffer,
2851 bufferOwnerRef);
2852}
2853
2855 Block *block, llvm::function_ref<bool(Operation *)> lazyOpsCallback) {
2856 return impl->read(block, lazyOpsCallback);
2857}
2858
2860 return impl->getNumOpsToMaterialize();
2861}
2862
2864 return impl->isMaterializable(op);
2865}
2866
2868 Operation *op, llvm::function_ref<bool(Operation *)> lazyOpsCallback) {
2869 return impl->materialize(op, lazyOpsCallback);
2870}
2871
2872LogicalResult
2874 return impl->finalize(shouldMaterialize);
2875}
2876
2877bool mlir::isBytecode(llvm::MemoryBufferRef buffer) {
2878 return buffer.getBuffer().starts_with("ML\xefR");
2879}
2880
2881/// Read the bytecode from the provided memory buffer reference.
2882/// `bufferOwnerRef` if provided is the owning source manager for the buffer,
2883/// and may be used to extend the lifetime of the buffer.
2884static LogicalResult
2885readBytecodeFileImpl(llvm::MemoryBufferRef buffer, Block *block,
2886 const ParserConfig &config,
2887 const std::shared_ptr<llvm::SourceMgr> &bufferOwnerRef) {
2888 Location sourceFileLoc =
2889 FileLineColLoc::get(config.getContext(), buffer.getBufferIdentifier(),
2890 /*line=*/0, /*column=*/0);
2891 if (!isBytecode(buffer)) {
2892 return emitError(sourceFileLoc,
2893 "input buffer is not an MLIR bytecode file");
2894 }
2895
2896 BytecodeReader::Impl reader(sourceFileLoc, config, /*lazyLoading=*/false,
2897 buffer, bufferOwnerRef);
2898 return reader.read(block, /*lazyOpsCallback=*/nullptr);
2899}
2900
2901LogicalResult mlir::readBytecodeFile(llvm::MemoryBufferRef buffer, Block *block,
2902 const ParserConfig &config) {
2903 return readBytecodeFileImpl(buffer, block, config, /*bufferOwnerRef=*/{});
2904}
2905LogicalResult
2906mlir::readBytecodeFile(const std::shared_ptr<llvm::SourceMgr> &sourceMgr,
2907 Block *block, const ParserConfig &config) {
2908 return readBytecodeFileImpl(
2909 *sourceMgr->getMemoryBuffer(sourceMgr->getMainFileID()), block, config,
2910 sourceMgr);
2911}
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:234
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:436
static Operation * create(Location location, OperationName name, TypeRange resultTypes, ValueRange operands, NamedAttrList &&attributes, OpaqueProperties properties, BlockRange successors, unsigned numRegions)
Create a new Operation with the specific fields.
Definition Operation.cpp:67
void moveBefore(Operation *existingOp)
Unlink this operation from its current block and insert it right before existingOp which may be in th...
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:826
result_range getResults()
Definition Operation.h:444
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:433
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:387
bool use_empty() const
Returns true if this value has no uses.
Definition Value.h:208
void shuffleUseList(ArrayRef< unsigned > indices)
Shuffle the use list order according to the provided indices.
Definition Value.cpp:106
use_range getUses() const
Returns a range of all uses, which is useful for iterating over all uses.
Definition Value.h:188
void * getAsOpaquePointer() const
Methods for supporting PointerLikeTypeTraits.
Definition Value.h:233
unsigned getNumUses() const
This method computes the number of uses of this Value.
Definition Value.cpp:52
bool hasOneUse() const
Returns true if this value has exactly one use.
Definition Value.h:197
use_iterator use_begin() const
Definition Value.h:184
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
@ kAttrType
This section contains the attributes and types referenced within an IR module.
Definition Encoding.h:73
@ kAttrTypeOffset
This section contains the offsets for the attribute and types within the AttrType section.
Definition Encoding.h:77
@ kIR
This section contains the list of operations serialized into the bytecode, and their nested regions/o...
Definition Encoding.h:81
@ kResource
This section contains the resources of the bytecode.
Definition Encoding.h:84
@ kResourceOffset
This section contains the offsets of resources within the Resource section.
Definition Encoding.h:88
@ kDialect
This section contains the dialects referenced within an IR module.
Definition Encoding.h:69
@ kString
This section contains strings referenced within the bytecode.
Definition Encoding.h:66
@ kDialectVersions
This section contains the versions of each dialect.
Definition Encoding.h:91
@ kProperties
This section contains the properties for the operations.
Definition Encoding.h:94
@ kNumSections
The total number of section types.
Definition Encoding.h:97
static uint64_t getUseID(OperandT &val, unsigned ownerID)
Get the unique ID of a value use.
Definition Encoding.h:127
@ kUseListOrdering
Use-list ordering started to be encoded in version 3.
Definition Encoding.h:38
@ kAlignmentByte
An arbitrary value used to fill alignment padding.
Definition Encoding.h:56
@ kVersion
The current bytecode version.
Definition Encoding.h:53
@ kLazyLoading
Support for lazy-loading of isolated region was added in version 2.
Definition Encoding.h:35
@ kDialectVersioning
Dialects versioning was added in version 1.
Definition Encoding.h:32
@ kElideUnknownBlockArgLocation
Avoid recording unknown locations on block arguments (compression) started in version 4.
Definition Encoding.h:42
@ kNativePropertiesEncoding
Support for encoding properties natively in bytecode instead of merged with the discardable attribute...
Definition Encoding.h:46
@ kMinSupportedVersion
The minimum supported version of the bytecode.
Definition Encoding.h:29
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h: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:120
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
Attribute parseAttribute(llvm::StringRef attrStr, MLIRContext *context, Type type={}, size_t *numRead=nullptr, bool isKnownNullTerminated=false)
This parses a single MLIR attribute to an MLIR context if it was valid.
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
Type parseType(llvm::StringRef typeStr, MLIRContext *context, size_t *numRead=nullptr, bool isKnownNullTerminated=false)
This parses a single MLIR type to an MLIR context if it was valid.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:118
AsmResourceEntryKind
This enum represents the different kinds of resource values.
Definition AsmState.h:280
LogicalResult readBytecodeFile(llvm::MemoryBufferRef buffer, Block *block, const ParserConfig &config)
Read the operations defined within the given memory buffer, containing MLIR bytecode,...
LogicalResult verify(Operation *op, bool verifyRecursively=true)
Perform (potentially expensive) checks of invariants, used to detect compiler bugs,...
Definition Verifier.cpp:480
llvm::function_ref< Fn > function_ref
Definition LLVM.h:144
SmallVector< Block *, 1 > successors
Successors of this operation and their respective operands.
SmallVector< Value, 4 > operands
SmallVector< std::unique_ptr< Region >, 1 > regions
Regions that the op will hold.
Attribute propertiesAttr
This Attribute is used to opaquely construct the properties of the operation.
SmallVector< Type, 4 > types
Types of the results of this operation.