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