MLIR 24.0.0git
BytecodeWriter.cpp
Go to the documentation of this file.
1//===- BytecodeWriter.cpp - MLIR Bytecode Writer --------------------------===//
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
10#include "IRNumbering.h"
14#include "mlir/IR/Attributes.h"
15#include "mlir/IR/Diagnostics.h"
16#include "mlir/IR/MLIRContext.h"
18#include "llvm/ADT/ArrayRef.h"
19#include "llvm/ADT/CachedHashString.h"
20#include "llvm/ADT/MapVector.h"
21#include "llvm/ADT/SmallVector.h"
22#include "llvm/Support/Debug.h"
23#include "llvm/Support/DebugLog.h"
24#include "llvm/Support/Endian.h"
25#include "llvm/Support/raw_ostream.h"
26#include <optional>
27
28#define DEBUG_TYPE "mlir-bytecode-writer"
29
30using namespace mlir;
31using namespace mlir::bytecode::detail;
32
33//===----------------------------------------------------------------------===//
34// BytecodeWriterConfig
35//===----------------------------------------------------------------------===//
36
38 Impl(StringRef producer) : producer(producer) {}
39
40 /// Version to use when writing.
41 /// Note: This only differs from kVersion if a specific version is set.
43
44 /// A flag specifying whether to elide emission of resources into the bytecode
45 /// file.
47
48 /// A flag specifying whether to elide emission of locations.
50
51 /// A map containing dialect version information for each dialect to emit.
52 llvm::StringMap<std::unique_ptr<DialectVersion>> dialectVersionMap;
53
54 /// The producer of the bytecode.
55 StringRef producer;
56
57 /// Printer callbacks used to emit custom type and attribute encodings.
62
63 /// A collection of non-dialect resource printers.
65};
66
68 : impl(std::make_unique<Impl>(producer)) {}
76
78
81 return impl->attributeWriterCallbacks;
82}
83
86 return impl->typeWriterCallbacks;
87}
88
90 std::unique_ptr<AttrTypeBytecodeWriter<Attribute>> callback) {
91 impl->attributeWriterCallbacks.emplace_back(std::move(callback));
92}
93
95 std::unique_ptr<AttrTypeBytecodeWriter<Type>> callback) {
96 impl->typeWriterCallbacks.emplace_back(std::move(callback));
97}
98
100 std::unique_ptr<AsmResourcePrinter> printer) {
101 impl->externalResourcePrinters.emplace_back(std::move(printer));
102}
103
105 bool shouldElideResourceData) {
106 impl->shouldElideResourceData = shouldElideResourceData;
107}
108
112
114 return impl->shouldElideLocations;
115}
116
118 impl->bytecodeVersion = bytecodeVersion;
119}
120
122 return impl->bytecodeVersion;
123}
124
125llvm::StringMap<std::unique_ptr<DialectVersion>> &
127 return impl->dialectVersionMap;
128}
129
131 llvm::StringRef dialectName,
132 std::unique_ptr<DialectVersion> dialectVersion) const {
133 assert(!impl->dialectVersionMap.contains(dialectName) &&
134 "cannot override a previously set dialect version");
135 impl->dialectVersionMap.insert({dialectName, std::move(dialectVersion)});
136}
137
138//===----------------------------------------------------------------------===//
139// EncodingEmitter
140//===----------------------------------------------------------------------===//
141
142namespace {
143/// This class functions as the underlying encoding emitter for the bytecode
144/// writer. This class is a bit different compared to other types of encoders;
145/// it does not use a single buffer, but instead may contain several buffers
146/// (some owned by the writer, and some not) that get concatted during the final
147/// emission.
148class EncodingEmitter {
149public:
150 EncodingEmitter() = default;
151 EncodingEmitter(const EncodingEmitter &) = delete;
152 EncodingEmitter &operator=(const EncodingEmitter &) = delete;
153
154 /// Write the current contents to the provided stream.
155 void writeTo(raw_ostream &os) const;
156
157 /// Return the current size of the encoded buffer.
158 size_t size() const { return prevResultSize + currentResult.size(); }
159
160 //===--------------------------------------------------------------------===//
161 // Emission
162 //===--------------------------------------------------------------------===//
163
164 /// Backpatch a byte in the result buffer at the given offset.
165 void patchByte(uint64_t offset, uint8_t value, StringLiteral desc) {
166 LDBG() << "patchByte(" << offset << ',' << uint64_t(value) << ")\t" << desc;
167 assert(offset < size() && offset >= prevResultSize &&
168 "cannot patch previously emitted data");
169 currentResult[offset - prevResultSize] = value;
170 }
171
172 /// Emit the provided blob of data, which is owned by the caller and is
173 /// guaranteed to not die before the end of the bytecode process.
174 void emitOwnedBlob(ArrayRef<uint8_t> data, StringLiteral desc) {
175 LDBG() << "emitOwnedBlob(" << data.size() << "b)\t" << desc;
176 // Push the current buffer before adding the provided data.
177 appendResult(std::move(currentResult));
178 appendOwnedResult(data);
179 }
180
181 /// Emit the provided blob of data that has the given alignment, which is
182 /// owned by the caller and is guaranteed to not die before the end of the
183 /// bytecode process. The alignment value is also encoded, making it available
184 /// on load.
185 void emitOwnedBlobAndAlignment(ArrayRef<uint8_t> data, uint32_t alignment,
186 StringLiteral desc) {
187 emitVarInt(alignment, desc);
188 emitVarInt(data.size(), desc);
189
190 alignTo(alignment);
191 emitOwnedBlob(data, desc);
192 }
193 void emitOwnedBlobAndAlignment(ArrayRef<char> data, uint32_t alignment,
194 StringLiteral desc) {
195 ArrayRef<uint8_t> castedData(reinterpret_cast<const uint8_t *>(data.data()),
196 data.size());
197 emitOwnedBlobAndAlignment(castedData, alignment, desc);
198 }
199
200 /// Align the emitter to the given alignment.
201 void alignTo(unsigned alignment) {
202 if (alignment < 2)
203 return;
204 assert(llvm::isPowerOf2_32(alignment) && "expected valid alignment");
205
206 // Check to see if we need to emit any padding bytes to meet the desired
207 // alignment.
208 size_t curOffset = size();
209 size_t paddingSize = llvm::alignTo(curOffset, alignment) - curOffset;
210 while (paddingSize--)
211 emitByte(bytecode::kAlignmentByte, "alignment byte");
212
213 // Keep track of the maximum required alignment.
214 requiredAlignment = std::max(requiredAlignment, alignment);
215 }
216
217 //===--------------------------------------------------------------------===//
218 // Integer Emission
219
220 /// Emit a single byte.
221 template <typename T>
222 void emitByte(T byte, StringLiteral desc) {
223 LDBG() << "emitByte(" << uint64_t(byte) << ")\t" << desc;
224 currentResult.push_back(static_cast<uint8_t>(byte));
225 }
226
227 /// Emit a range of bytes.
228 void emitBytes(ArrayRef<uint8_t> bytes, StringLiteral desc) {
229 LDBG() << "emitBytes(" << bytes.size() << "b)\t" << desc;
230 llvm::append_range(currentResult, bytes);
231 }
232
233 /// Emit a variable length integer. The first encoded byte contains a prefix
234 /// in the low bits indicating the encoded length of the value. This length
235 /// prefix is a bit sequence of '0's followed by a '1'. The number of '0' bits
236 /// indicate the number of _additional_ bytes (not including the prefix byte).
237 /// All remaining bits in the first byte, along with all of the bits in
238 /// additional bytes, provide the value of the integer encoded in
239 /// little-endian order.
240 void emitVarInt(uint64_t value, StringLiteral desc) {
241 LDBG() << "emitVarInt(" << value << ")\t" << desc;
242
243 // In the most common case, the value can be represented in a single byte.
244 // Given how hot this case is, explicitly handle that here.
245 if ((value >> 7) == 0)
246 return emitByte((value << 1) | 0x1, desc);
247 emitMultiByteVarInt(value, desc);
248 }
249
250 /// Emit a signed variable length integer. Signed varints are encoded using
251 /// a varint with zigzag encoding, meaning that we use the low bit of the
252 /// value to indicate the sign of the value. This allows for more efficient
253 /// encoding of negative values by limiting the number of active bits
254 void emitSignedVarInt(uint64_t value, StringLiteral desc) {
255 emitVarInt((value << 1) ^ (uint64_t)((int64_t)value >> 63), desc);
256 }
257
258 /// Emit a variable length integer whose low bit is used to encode the
259 /// provided flag, i.e. encoded as: (value << 1) | (flag ? 1 : 0).
260 void emitVarIntWithFlag(uint64_t value, bool flag, StringLiteral desc) {
261 emitVarInt((value << 1) | (flag ? 1 : 0), desc);
262 }
263
264 //===--------------------------------------------------------------------===//
265 // String Emission
266
267 /// Emit the given string as a nul terminated string.
268 void emitNulTerminatedString(StringRef str, StringLiteral desc) {
269 emitString(str, desc);
270 emitByte(0, "null terminator");
271 }
272
273 /// Emit the given string without a nul terminator.
274 void emitString(StringRef str, StringLiteral desc) {
275 emitBytes({reinterpret_cast<const uint8_t *>(str.data()), str.size()},
276 desc);
277 }
278
279 //===--------------------------------------------------------------------===//
280 // Section Emission
281
282 /// Emit a nested section of the given code, whose contents are encoded in the
283 /// provided emitter.
284 void emitSection(bytecode::Section::ID code, EncodingEmitter &&emitter) {
285 // Emit the section code and length. The high bit of the code is used to
286 // indicate whether the section alignment is present, so save an offset to
287 // it.
288 uint64_t codeOffset = currentResult.size();
289 emitByte(code, "section code");
290 emitVarInt(emitter.size(), "section size");
291
292 // Integrate the alignment of the section into this emitter if necessary.
293 unsigned emitterAlign = emitter.requiredAlignment;
294 if (emitterAlign > 1) {
295 if (size() & (emitterAlign - 1)) {
296 emitVarInt(emitterAlign, "section alignment");
297 alignTo(emitterAlign);
298
299 // Indicate that we needed to align the section, the high bit of the
300 // code field is used for this.
301 currentResult[codeOffset] |= 0b10000000;
302 } else {
303 // Otherwise, if we happen to be at a compatible offset, we just
304 // remember that we need this alignment.
305 requiredAlignment = std::max(requiredAlignment, emitterAlign);
306 }
307 }
308
309 // Push our current buffer and then merge the provided section body into
310 // ours.
311 appendResult(std::move(currentResult));
312 for (std::vector<uint8_t> &result : emitter.prevResultStorage)
313 prevResultStorage.push_back(std::move(result));
314 llvm::append_range(prevResultList, emitter.prevResultList);
315 prevResultSize += emitter.prevResultSize;
316 appendResult(std::move(emitter.currentResult));
317 }
318
319private:
320 /// Emit the given value using a variable width encoding. This method is a
321 /// fallback when the number of bytes needed to encode the value is greater
322 /// than 1. We mark it noinline here so that the single byte hot path isn't
323 /// pessimized.
324 LLVM_ATTRIBUTE_NOINLINE void emitMultiByteVarInt(uint64_t value,
325 StringLiteral desc);
326
327 /// Append a new result buffer to the current contents.
328 void appendResult(std::vector<uint8_t> &&result) {
329 if (result.empty())
330 return;
331 prevResultStorage.emplace_back(std::move(result));
332 appendOwnedResult(prevResultStorage.back());
333 }
334 void appendOwnedResult(ArrayRef<uint8_t> result) {
335 if (result.empty())
336 return;
337 prevResultSize += result.size();
338 prevResultList.emplace_back(result);
339 }
340
341 /// The result of the emitter currently being built. We refrain from building
342 /// a single buffer to simplify emitting sections, large data, and more. The
343 /// result is thus represented using multiple distinct buffers, some of which
344 /// we own (via prevResultStorage), and some of which are just pointers into
345 /// externally owned buffers.
346 std::vector<uint8_t> currentResult;
347 std::vector<ArrayRef<uint8_t>> prevResultList;
348 std::vector<std::vector<uint8_t>> prevResultStorage;
349
350 /// An up-to-date total size of all of the buffers within `prevResultList`.
351 /// This enables O(1) size checks of the current encoding.
352 size_t prevResultSize = 0;
353
354 /// The highest required alignment for the start of this section.
355 unsigned requiredAlignment = 1;
356};
357
358//===----------------------------------------------------------------------===//
359// StringSectionBuilder
360//===----------------------------------------------------------------------===//
361
362namespace {
363/// This class is used to simplify the process of emitting the string section.
364class StringSectionBuilder {
365public:
366 /// Add the given string to the string section, and return the index of the
367 /// string within the section.
368 size_t insert(StringRef str) {
369 auto it = strings.insert({llvm::CachedHashStringRef(str), strings.size()});
370 return it.first->second;
371 }
372
373 /// Write the current set of strings to the given emitter.
374 void write(EncodingEmitter &emitter) {
375 emitter.emitVarInt(strings.size(), "string section size");
376
377 // Emit the sizes in reverse order, so that we don't need to backpatch an
378 // offset to the string data or have a separate section.
379 for (const auto &it : llvm::reverse(strings))
380 emitter.emitVarInt(it.first.size() + 1, "string size");
381 // Emit the string data itself.
382 for (const auto &it : strings)
383 emitter.emitNulTerminatedString(it.first.val(), "string");
384 }
385
386private:
387 /// A set of strings referenced within the bytecode. The value of the map is
388 /// unused.
389 llvm::MapVector<llvm::CachedHashStringRef, size_t> strings;
390};
391} // namespace
392
393class DialectWriter : public DialectBytecodeWriter {
394 using DialectVersionMapT = llvm::StringMap<std::unique_ptr<DialectVersion>>;
395
396public:
397 DialectWriter(int64_t bytecodeVersion, EncodingEmitter &emitter,
398 IRNumberingState &numberingState,
399 StringSectionBuilder &stringSection,
400 const DialectVersionMapT &dialectVersionMap)
401 : bytecodeVersion(bytecodeVersion), emitter(emitter),
402 numberingState(numberingState), stringSection(stringSection),
403 dialectVersionMap(dialectVersionMap) {}
404
405 //===--------------------------------------------------------------------===//
406 // IR
407 //===--------------------------------------------------------------------===//
408
409 void writeAttribute(Attribute attr) override {
410 emitter.emitVarInt(numberingState.getNumber(attr), "dialect attr");
411 }
412 void writeOptionalAttribute(Attribute attr) override {
413 if (!attr) {
414 emitter.emitVarInt(0, "dialect optional attr none");
415 return;
416 }
417 emitter.emitVarIntWithFlag(numberingState.getNumber(attr), true,
418 "dialect optional attr");
419 }
420
421 void writeType(Type type) override {
422 emitter.emitVarInt(numberingState.getNumber(type), "dialect type");
423 }
424
425 void writeResourceHandle(const AsmDialectResourceHandle &resource) override {
426 emitter.emitVarInt(numberingState.getNumber(resource), "dialect resource");
427 }
428
429 //===--------------------------------------------------------------------===//
430 // Primitives
431 //===--------------------------------------------------------------------===//
432
433 void writeVarInt(uint64_t value) override {
434 emitter.emitVarInt(value, "dialect writer");
435 }
436
437 void writeSignedVarInt(int64_t value) override {
438 emitter.emitSignedVarInt(value, "dialect writer");
439 }
440
441 void writeAPIntWithKnownWidth(const APInt &value) override {
442 size_t bitWidth = value.getBitWidth();
443
444 // If the value is a single byte, just emit it directly without going
445 // through a varint.
446 if (bitWidth <= 8)
447 return emitter.emitByte(value.getLimitedValue(), "dialect APInt");
448
449 // If the value fits within a single varint, emit it directly.
450 if (bitWidth <= 64)
451 return emitter.emitSignedVarInt(value.getLimitedValue(), "dialect APInt");
452
453 // Otherwise, we need to encode a variable number of active words. We use
454 // active words instead of the number of total words under the observation
455 // that smaller values will be more common.
456 unsigned numActiveWords = value.getActiveWords();
457 emitter.emitVarInt(numActiveWords, "dialect APInt word count");
458
459 const uint64_t *rawValueData = value.getRawData();
460 for (unsigned i = 0; i < numActiveWords; ++i)
461 emitter.emitSignedVarInt(rawValueData[i], "dialect APInt word");
462 }
463
464 void writeAPFloatWithKnownSemantics(const APFloat &value) override {
465 writeAPIntWithKnownWidth(value.bitcastToAPInt());
466 }
467
468 void writeOwnedString(StringRef str) override {
469 emitter.emitVarInt(stringSection.insert(str), "dialect string");
470 }
471
472 void writeOwnedBlob(ArrayRef<char> blob) override {
473 emitter.emitVarInt(blob.size(), "dialect blob");
474 emitter.emitOwnedBlob(
475 ArrayRef<uint8_t>(reinterpret_cast<const uint8_t *>(blob.data()),
476 blob.size()),
477 "dialect blob");
478 }
479
480 void writeUnownedBlob(ArrayRef<char> blob) override {
481 emitter.emitVarInt(blob.size(), "dialect blob");
482 emitter.emitBytes(
483 ArrayRef<uint8_t>(reinterpret_cast<const uint8_t *>(blob.data()),
484 blob.size()),
485 "dialect blob");
486 }
487
488 void writeOwnedBool(bool value) override {
489 emitter.emitByte(value, "dialect bool");
490 }
491
492 int64_t getBytecodeVersion() const override { return bytecodeVersion; }
493
494 FailureOr<const DialectVersion *>
495 getDialectVersion(StringRef dialectName) const override {
496 auto dialectEntry = dialectVersionMap.find(dialectName);
497 if (dialectEntry == dialectVersionMap.end())
498 return failure();
499 return dialectEntry->getValue().get();
500 }
501
502private:
503 int64_t bytecodeVersion;
504 EncodingEmitter &emitter;
505 IRNumberingState &numberingState;
506 StringSectionBuilder &stringSection;
507 const DialectVersionMapT &dialectVersionMap;
508};
509
510namespace {
511class PropertiesSectionBuilder {
512public:
513 PropertiesSectionBuilder(IRNumberingState &numberingState,
514 StringSectionBuilder &stringSection,
515 const BytecodeWriterConfig::Impl &config)
516 : numberingState(numberingState), stringSection(stringSection),
517 config(config) {}
518
519 /// Emit the op properties in the properties section and return the index of
520 /// the properties within the section. Return -1 if no properties was emitted.
521 std::optional<ssize_t> emit(Operation *op) {
522 EncodingEmitter propertiesEmitter;
523 if (!op->getPropertiesStorageSize())
524 return std::nullopt;
525 if (!op->isRegistered()) {
526 // Unregistered op are storing properties as an optional attribute.
527 Attribute prop = *op->getPropertiesStorage().as<Attribute *>();
528 if (!prop)
529 return std::nullopt;
530 EncodingEmitter sizeEmitter;
531 sizeEmitter.emitVarInt(numberingState.getNumber(prop), "properties size");
532 scratch.clear();
533 llvm::raw_svector_ostream os(scratch);
534 sizeEmitter.writeTo(os);
535 return emit(scratch);
536 }
537
538 EncodingEmitter emitter;
539 DialectWriter propertiesWriter(config.bytecodeVersion, emitter,
540 numberingState, stringSection,
541 config.dialectVersionMap);
542 auto iface = cast<BytecodeOpInterface>(op);
543 iface.writeProperties(propertiesWriter);
544 scratch.clear();
545 llvm::raw_svector_ostream os(scratch);
546 emitter.writeTo(os);
547 return emit(scratch);
548 }
549
550 /// Write the current set of properties to the given emitter.
551 void write(EncodingEmitter &emitter) {
552 emitter.emitVarInt(propertiesStorage.size(), "properties size");
553 if (propertiesStorage.empty())
554 return;
555 for (const auto &storage : propertiesStorage) {
556 if (storage.empty()) {
557 emitter.emitBytes(ArrayRef<uint8_t>(), "empty properties");
558 continue;
559 }
560 emitter.emitBytes(ArrayRef(reinterpret_cast<const uint8_t *>(&storage[0]),
561 storage.size()),
562 "property");
563 }
564 }
565
566 /// Returns true if the section is empty.
567 bool empty() { return propertiesStorage.empty(); }
568
569private:
570 /// Emit raw data and returns the offset in the internal buffer.
571 /// Data are deduplicated and will be copied in the internal buffer only if
572 /// they don't exist there already.
573 ssize_t emit(ArrayRef<char> rawProperties) {
574 // Populate a scratch buffer with the properties size.
575 SmallVector<char> sizeScratch;
576 {
577 EncodingEmitter sizeEmitter;
578 sizeEmitter.emitVarInt(rawProperties.size(), "properties");
579 llvm::raw_svector_ostream os(sizeScratch);
580 sizeEmitter.writeTo(os);
581 }
582 // Append a new storage to the table now.
583 size_t index = propertiesStorage.size();
584 propertiesStorage.emplace_back();
585 std::vector<char> &newStorage = propertiesStorage.back();
586 size_t propertiesSize = sizeScratch.size() + rawProperties.size();
587 newStorage.reserve(propertiesSize);
588 llvm::append_range(newStorage, sizeScratch);
589 llvm::append_range(newStorage, rawProperties);
590
591 // Try to de-duplicate the new serialized properties.
592 // If the properties is a duplicate, pop it back from the storage.
593 auto inserted = propertiesUniquing.insert(
594 std::make_pair(ArrayRef<char>(newStorage), index));
595 if (!inserted.second)
596 propertiesStorage.pop_back();
597 return inserted.first->getSecond();
598 }
599
600 /// Storage for properties.
601 std::vector<std::vector<char>> propertiesStorage;
602 SmallVector<char> scratch;
603 DenseMap<ArrayRef<char>, int64_t> propertiesUniquing;
604 IRNumberingState &numberingState;
605 StringSectionBuilder &stringSection;
606 const BytecodeWriterConfig::Impl &config;
607};
608} // namespace
609
610/// A simple raw_ostream wrapper around a EncodingEmitter. This removes the need
611/// to go through an intermediate buffer when interacting with code that wants a
612/// raw_ostream.
613class RawEmitterOstream : public raw_ostream {
614public:
615 explicit RawEmitterOstream(EncodingEmitter &emitter) : emitter(emitter) {
616 SetUnbuffered();
617 }
618
619private:
620 void write_impl(const char *ptr, size_t size) override {
621 emitter.emitBytes({reinterpret_cast<const uint8_t *>(ptr), size},
622 "raw emitter");
623 }
624 uint64_t current_pos() const override { return emitter.size(); }
625
626 /// The section being emitted to.
627 EncodingEmitter &emitter;
628};
629} // namespace
630
631void EncodingEmitter::writeTo(raw_ostream &os) const {
632 // Reserve space in the ostream for the encoded contents.
633 os.reserveExtraSpace(size());
634
635 for (auto &prevResult : prevResultList)
636 os.write((const char *)prevResult.data(), prevResult.size());
637 os.write((const char *)currentResult.data(), currentResult.size());
638}
639
640void EncodingEmitter::emitMultiByteVarInt(uint64_t value, StringLiteral desc) {
641 // Compute the number of bytes needed to encode the value. Each byte can hold
642 // up to 7-bits of data. We only check up to the number of bits we can encode
643 // in the first byte (8).
644 uint64_t it = value >> 7;
645 for (size_t numBytes = 2; numBytes < 9; ++numBytes) {
646 if (LLVM_LIKELY(it >>= 7) == 0) {
647 uint64_t encodedValue = (value << 1) | 0x1;
648 encodedValue <<= (numBytes - 1);
649 llvm::support::ulittle64_t encodedValueLE(encodedValue);
650 emitBytes({reinterpret_cast<uint8_t *>(&encodedValueLE), numBytes}, desc);
651 return;
652 }
653 }
654
655 // If the value is too large to encode in a single byte, emit a special all
656 // zero marker byte and splat the value directly.
657 emitByte(0, desc);
658 llvm::support::ulittle64_t valueLE(value);
659 emitBytes({reinterpret_cast<uint8_t *>(&valueLE), sizeof(valueLE)}, desc);
660}
661
662//===----------------------------------------------------------------------===//
663// Bytecode Writer
664//===----------------------------------------------------------------------===//
665
666namespace {
667class BytecodeWriter {
668public:
669 BytecodeWriter(Operation *op, const BytecodeWriterConfig &config)
670 : numberingState(op, config), config(config.getImpl()),
671 propertiesSection(numberingState, stringSection, config.getImpl()) {}
672
673 /// Write the bytecode for the given root operation.
674 LogicalResult write(Operation *rootOp, raw_ostream &os);
675
676private:
677 //===--------------------------------------------------------------------===//
678 // Dialects
679
680 void writeDialectSection(EncodingEmitter &emitter);
681
682 //===--------------------------------------------------------------------===//
683 // Attributes and Types
684
685 void writeAttrTypeSection(EncodingEmitter &emitter);
686
687 //===--------------------------------------------------------------------===//
688 // Operations
689
690 LogicalResult writeBlock(EncodingEmitter &emitter, Block *block);
691 LogicalResult writeOp(EncodingEmitter &emitter, Operation *op);
692 LogicalResult writeRegion(EncodingEmitter &emitter, Region *region);
693 LogicalResult writeIRSection(EncodingEmitter &emitter, Operation *op);
694
695 LogicalResult writeRegions(EncodingEmitter &emitter,
696 MutableArrayRef<Region> regions) {
697 return success(llvm::all_of(regions, [&](Region &region) {
698 return succeeded(writeRegion(emitter, &region));
699 }));
700 }
701
702 //===--------------------------------------------------------------------===//
703 // Resources
704
705 void writeResourceSection(Operation *op, EncodingEmitter &emitter);
706
707 //===--------------------------------------------------------------------===//
708 // Strings
709
710 void writeStringSection(EncodingEmitter &emitter);
711
712 //===--------------------------------------------------------------------===//
713 // Properties
714
715 void writePropertiesSection(EncodingEmitter &emitter);
716
717 //===--------------------------------------------------------------------===//
718 // Helpers
719
720 void writeUseListOrders(EncodingEmitter &emitter, uint8_t &opEncodingMask,
721 ValueRange range);
722
723 //===--------------------------------------------------------------------===//
724 // Fields
725
726 /// The builder used for the string section.
727 StringSectionBuilder stringSection;
728
729 /// The IR numbering state generated for the root operation.
730 IRNumberingState numberingState;
731
732 /// Configuration dictating bytecode emission.
733 const BytecodeWriterConfig::Impl &config;
734
735 /// Storage for the properties section
736 PropertiesSectionBuilder propertiesSection;
737};
738} // namespace
739
740LogicalResult BytecodeWriter::write(Operation *rootOp, raw_ostream &os) {
741 EncodingEmitter emitter;
742
743 // Emit the bytecode file header. This is how we identify the output as a
744 // bytecode file.
745 emitter.emitString("ML\xefR", "bytecode header");
746
747 // Emit the bytecode version.
748 if (config.bytecodeVersion < bytecode::kMinSupportedVersion ||
749 config.bytecodeVersion > bytecode::kVersion)
750 return rootOp->emitError()
751 << "unsupported version requested " << config.bytecodeVersion
752 << ", must be in range ["
753 << static_cast<int64_t>(bytecode::kMinSupportedVersion) << ", "
754 << static_cast<int64_t>(bytecode::kVersion) << ']';
755 emitter.emitVarInt(config.bytecodeVersion, "bytecode version");
756
757 // Emit the producer.
758 emitter.emitNulTerminatedString(config.producer, "bytecode producer");
759
760 // Emit the dialect section.
761 writeDialectSection(emitter);
762
763 // Emit the attributes and types section.
764 writeAttrTypeSection(emitter);
765
766 // Emit the IR section.
767 if (failed(writeIRSection(emitter, rootOp)))
768 return failure();
769
770 // Emit the resources section.
771 writeResourceSection(rootOp, emitter);
772
773 // Emit the string section.
774 writeStringSection(emitter);
775
776 // Emit the properties section.
777 if (config.bytecodeVersion >= bytecode::kNativePropertiesEncoding)
778 writePropertiesSection(emitter);
779 else if (!propertiesSection.empty())
780 return rootOp->emitError(
781 "unexpected properties emitted incompatible with bytecode <5");
782
783 // Write the generated bytecode to the provided output stream.
784 emitter.writeTo(os);
785
786 return success();
787}
788
789//===----------------------------------------------------------------------===//
790// Dialects
791//===----------------------------------------------------------------------===//
792
793/// Write the given entries in contiguous groups with the same parent dialect.
794/// Each dialect sub-group is encoded with the parent dialect and number of
795/// elements, followed by the encoding for the entries. The given callback is
796/// invoked to encode each individual entry.
797template <typename EntriesT, typename EntryCallbackT>
798static void writeDialectGrouping(EncodingEmitter &emitter, EntriesT &&entries,
799 EntryCallbackT &&callback) {
800 for (auto it = entries.begin(), e = entries.end(); it != e;) {
801 auto groupStart = it++;
802
803 // Find the end of the group that shares the same parent dialect.
804 DialectNumbering *currentDialect = groupStart->dialect;
805 it = std::find_if(it, e, [&](const auto &entry) {
806 return entry.dialect != currentDialect;
807 });
808
809 // Emit the dialect and number of elements.
810 emitter.emitVarInt(currentDialect->number, "dialect number");
811 emitter.emitVarInt(std::distance(groupStart, it), "dialect offset");
812
813 // Emit the entries within the group.
814 for (auto &entry : llvm::make_range(groupStart, it))
815 callback(entry);
816 }
817}
818
819void BytecodeWriter::writeDialectSection(EncodingEmitter &emitter) {
820 EncodingEmitter dialectEmitter;
821
822 // Emit the referenced dialects.
823 auto dialects = numberingState.getDialects();
824 dialectEmitter.emitVarInt(llvm::size(dialects), "dialects count");
825 for (DialectNumbering &dialect : dialects) {
826 // Write the string section and get the ID.
827 size_t nameID = stringSection.insert(dialect.name);
828
829 if (config.bytecodeVersion < bytecode::kDialectVersioning) {
830 dialectEmitter.emitVarInt(nameID, "dialect name ID");
831 continue;
832 }
833
834 // Try writing the version to the versionEmitter.
835 EncodingEmitter versionEmitter;
836 if (dialect.interface) {
837 // The writer used when emitting using a custom bytecode encoding.
838 DialectWriter versionWriter(config.bytecodeVersion, versionEmitter,
839 numberingState, stringSection,
840 config.dialectVersionMap);
841 dialect.interface->writeVersion(versionWriter);
842 }
843
844 // If the version emitter is empty, version is not available. We can encode
845 // this in the dialect ID, so if there is no version, we don't write the
846 // section.
847 size_t versionAvailable = versionEmitter.size() > 0;
848 dialectEmitter.emitVarIntWithFlag(nameID, versionAvailable,
849 "dialect version");
850 if (versionAvailable)
851 dialectEmitter.emitSection(bytecode::Section::kDialectVersions,
852 std::move(versionEmitter));
853 }
854
855 if (config.bytecodeVersion >= bytecode::kElideUnknownBlockArgLocation)
856 dialectEmitter.emitVarInt(size(numberingState.getOpNames()),
857 "op names count");
858
859 // Emit the referenced operation names grouped by dialect.
860 auto emitOpName = [&](OpNameNumbering &name) {
861 size_t stringId = stringSection.insert(name.name.stripDialect());
862 if (config.bytecodeVersion < bytecode::kNativePropertiesEncoding)
863 dialectEmitter.emitVarInt(stringId, "dialect op name");
864 else
865 dialectEmitter.emitVarIntWithFlag(stringId, name.name.isRegistered(),
866 "dialect op name");
867 };
868 writeDialectGrouping(dialectEmitter, numberingState.getOpNames(), emitOpName);
869
870 emitter.emitSection(bytecode::Section::kDialect, std::move(dialectEmitter));
871}
872
873//===----------------------------------------------------------------------===//
874// Attributes and Types
875//===----------------------------------------------------------------------===//
876
877void BytecodeWriter::writeAttrTypeSection(EncodingEmitter &emitter) {
878 EncodingEmitter attrTypeEmitter;
879 EncodingEmitter offsetEmitter;
880 offsetEmitter.emitVarInt(llvm::size(numberingState.getAttributes()),
881 "attributes count");
882 offsetEmitter.emitVarInt(llvm::size(numberingState.getTypes()),
883 "types count");
884
885 // A functor used to emit an attribute or type entry.
886 uint64_t prevOffset = 0;
887 auto emitAttrOrType = [&](auto &entry) {
888 auto entryValue = entry.getValue();
889
890 auto emitAttrOrTypeRawImpl = [&]() -> void {
891 RawEmitterOstream(attrTypeEmitter) << entryValue;
892 attrTypeEmitter.emitByte(0, "attr/type separator");
893 };
894 auto emitAttrOrTypeImpl = [&]() -> bool {
895 // TODO: We don't currently support custom encoded mutable types and
896 // attributes.
897 if (entryValue.template hasTrait<TypeTrait::IsMutable>() ||
898 entryValue.template hasTrait<AttributeTrait::IsMutable>()) {
899 emitAttrOrTypeRawImpl();
900 return false;
901 }
902
903 DialectWriter dialectWriter(config.bytecodeVersion, attrTypeEmitter,
904 numberingState, stringSection,
905 config.dialectVersionMap);
906 if constexpr (std::is_same_v<std::decay_t<decltype(entryValue)>, Type>) {
907 for (const auto &callback : config.typeWriterCallbacks) {
908 if (succeeded(callback->write(entryValue, dialectWriter)))
909 return true;
910 }
911 if (const BytecodeDialectInterface *interface =
912 entry.dialect->interface) {
913 if (succeeded(interface->writeType(entryValue, dialectWriter)))
914 return true;
915 }
916 } else {
917 for (const auto &callback : config.attributeWriterCallbacks) {
918 if (succeeded(callback->write(entryValue, dialectWriter)))
919 return true;
920 }
921 if (const BytecodeDialectInterface *interface =
922 entry.dialect->interface) {
923 if (succeeded(interface->writeAttribute(entryValue, dialectWriter)))
924 return true;
925 }
926 }
927
928 // If the entry was not emitted using a callback or a dialect interface,
929 // emit it using the textual format.
930 emitAttrOrTypeRawImpl();
931 return false;
932 };
933
934 bool hasCustomEncoding = emitAttrOrTypeImpl();
935
936 // Record the offset of this entry.
937 uint64_t curOffset = attrTypeEmitter.size();
938 offsetEmitter.emitVarIntWithFlag(curOffset - prevOffset, hasCustomEncoding,
939 "attr/type offset");
940 prevOffset = curOffset;
941 };
942
943 // Emit the attribute and type entries for each dialect.
944 writeDialectGrouping(offsetEmitter, numberingState.getAttributes(),
945 emitAttrOrType);
946 writeDialectGrouping(offsetEmitter, numberingState.getTypes(),
947 emitAttrOrType);
948
949 // Emit the sections to the stream.
950 emitter.emitSection(bytecode::Section::kAttrTypeOffset,
951 std::move(offsetEmitter));
952 emitter.emitSection(bytecode::Section::kAttrType, std::move(attrTypeEmitter));
953}
954
955//===----------------------------------------------------------------------===//
956// Operations
957//===----------------------------------------------------------------------===//
958
959LogicalResult BytecodeWriter::writeBlock(EncodingEmitter &emitter,
960 Block *block) {
962 bool hasArgs = !args.empty();
963
964 // Emit the number of operations in this block, and if it has arguments. We
965 // use the low bit of the operation count to indicate if the block has
966 // arguments.
967 unsigned numOps = numberingState.getOperationCount(block);
968 emitter.emitVarIntWithFlag(numOps, hasArgs, "block num ops");
969
970 // Emit the arguments of the block.
971 if (hasArgs) {
972 emitter.emitVarInt(args.size(), "block args count");
973 for (BlockArgument arg : args) {
974 Location argLoc = arg.getLoc();
975 if (config.bytecodeVersion >= bytecode::kElideUnknownBlockArgLocation) {
976 emitter.emitVarIntWithFlag(numberingState.getNumber(arg.getType()),
977 !isa<UnknownLoc>(argLoc), "block arg type");
978 if (!isa<UnknownLoc>(argLoc))
979 emitter.emitVarInt(numberingState.getNumber(argLoc),
980 "block arg location");
981 } else {
982 emitter.emitVarInt(numberingState.getNumber(arg.getType()),
983 "block arg type");
984 emitter.emitVarInt(numberingState.getNumber(argLoc),
985 "block arg location");
986 }
987 }
988 if (config.bytecodeVersion >= bytecode::kUseListOrdering) {
989 uint64_t maskOffset = emitter.size();
990 uint8_t encodingMask = 0;
991 emitter.emitByte(0, "use-list separator");
992 writeUseListOrders(emitter, encodingMask, args);
993 if (encodingMask)
994 emitter.patchByte(maskOffset, encodingMask, "block patch encoding");
995 }
996 }
997
998 // Emit the operations within the block.
999 for (Operation &op : *block)
1000 if (failed(writeOp(emitter, &op)))
1001 return failure();
1002 return success();
1003}
1004
1005LogicalResult BytecodeWriter::writeOp(EncodingEmitter &emitter, Operation *op) {
1006 emitter.emitVarInt(numberingState.getNumber(op->getName()), "op name ID");
1007
1008 // Emit a mask for the operation components. We need to fill this in later
1009 // (when we actually know what needs to be emitted), so emit a placeholder for
1010 // now.
1011 uint64_t maskOffset = emitter.size();
1012 uint8_t opEncodingMask = 0;
1013 emitter.emitByte(0, "op separator");
1014
1015 // Emit the location for this operation.
1016 emitter.emitVarInt(numberingState.getNumber(op->getLoc()), "op location");
1017
1018 // Emit the attributes of this operation.
1019 DictionaryAttr attrs = op->getRawDictionaryAttrs();
1020 // Allow deployment to version <kNativePropertiesEncoding by merging inherent
1021 // attribute with the discardable ones. We should fail if there are any
1022 // conflicts. When properties are not used by the op, also store everything as
1023 // attributes.
1024 if (config.bytecodeVersion < bytecode::kNativePropertiesEncoding &&
1025 op->getPropertiesStorage()) {
1026 NamedAttrList allAttrs;
1027 op->getName().walkInherentAttrs(op, [&](StringRef name, Attribute &attr) {
1028 allAttrs.append(name, attr);
1029 });
1030 allAttrs.append(op->getDiscardableAttrDictionary().getValue());
1031 attrs = allAttrs.getDictionary(op->getContext());
1032 }
1033 if (!attrs.empty()) {
1034 opEncodingMask |= bytecode::OpEncodingMask::kHasAttrs;
1035 emitter.emitVarInt(numberingState.getNumber(attrs), "op attrs count");
1036 }
1037
1038 // Emit the properties of this operation, for now we still support deployment
1039 // to version <kNativePropertiesEncoding.
1040 if (config.bytecodeVersion >= bytecode::kNativePropertiesEncoding) {
1041 std::optional<ssize_t> propertiesId = propertiesSection.emit(op);
1042 if (propertiesId.has_value()) {
1044 emitter.emitVarInt(*propertiesId, "op properties ID");
1045 }
1046 }
1047
1048 // Emit the result types of the operation.
1049 if (unsigned numResults = op->getNumResults()) {
1050 opEncodingMask |= bytecode::OpEncodingMask::kHasResults;
1051 emitter.emitVarInt(numResults, "op results count");
1052 for (Type type : op->getResultTypes())
1053 emitter.emitVarInt(numberingState.getNumber(type), "op result type");
1054 }
1055
1056 // Emit the operands of the operation.
1057 if (unsigned numOperands = op->getNumOperands()) {
1059 emitter.emitVarInt(numOperands, "op operands count");
1060 for (Value operand : op->getOperands())
1061 emitter.emitVarInt(numberingState.getNumber(operand), "op operand types");
1062 }
1063
1064 // Emit the successors of the operation.
1065 if (unsigned numSuccessors = op->getNumSuccessors()) {
1067 emitter.emitVarInt(numSuccessors, "op successors count");
1068 for (Block *successor : op->getSuccessors())
1069 emitter.emitVarInt(numberingState.getNumber(successor), "op successor");
1070 }
1071
1072 // Emit the use-list orders to bytecode, so we can reconstruct the same order
1073 // at parsing.
1074 if (config.bytecodeVersion >= bytecode::kUseListOrdering)
1075 writeUseListOrders(emitter, opEncodingMask, ValueRange(op->getResults()));
1076
1077 // Check for regions.
1078 unsigned numRegions = op->getNumRegions();
1079 if (numRegions)
1081
1082 // Update the mask for the operation.
1083 emitter.patchByte(maskOffset, opEncodingMask, "op encoding mask");
1084
1085 // With the mask emitted, we can now emit the regions of the operation. We do
1086 // this after mask emission to avoid offset complications that may arise by
1087 // emitting the regions first (e.g. if the regions are huge, backpatching the
1088 // op encoding mask is more annoying).
1089 if (numRegions) {
1090 bool isIsolatedFromAbove = numberingState.isIsolatedFromAbove(op);
1091 emitter.emitVarIntWithFlag(numRegions, isIsolatedFromAbove,
1092 "op regions count");
1093
1094 // If the region is not isolated from above, or we are emitting bytecode
1095 // targeting version <kLazyLoading, we don't use a section.
1096 if (isIsolatedFromAbove &&
1097 config.bytecodeVersion >= bytecode::kLazyLoading) {
1098 EncodingEmitter regionEmitter;
1099 if (failed(writeRegions(regionEmitter, op->getRegions())))
1100 return failure();
1101 emitter.emitSection(bytecode::Section::kIR, std::move(regionEmitter));
1102
1103 } else if (failed(writeRegions(emitter, op->getRegions()))) {
1104 return failure();
1105 }
1106 }
1107 return success();
1108}
1109
1110void BytecodeWriter::writeUseListOrders(EncodingEmitter &emitter,
1111 uint8_t &opEncodingMask,
1112 ValueRange range) {
1113 // Loop over the results and store the use-list order per result index.
1114 llvm::MapVector<unsigned, llvm::SmallVector<unsigned>> map;
1115 for (auto item : llvm::enumerate(range)) {
1116 auto value = item.value();
1117 // No need to store a custom use-list order if the result does not have
1118 // multiple uses.
1119 if (value.use_empty() || value.hasOneUse())
1120 continue;
1121
1122 // For each result, assemble the list of pairs (use-list-index,
1123 // global-value-index). While doing so, detect if the global-value-index is
1124 // already ordered with respect to the use-list-index.
1125 bool alreadyOrdered = true;
1126 auto &firstUse = *value.use_begin();
1127 uint64_t prevID = bytecode::getUseID(
1128 firstUse, numberingState.getNumber(firstUse.getOwner()));
1129 llvm::SmallVector<std::pair<unsigned, uint64_t>> useListPairs(
1130 {{0, prevID}});
1131
1132 for (auto use : llvm::drop_begin(llvm::enumerate(value.getUses()))) {
1133 uint64_t currentID = bytecode::getUseID(
1134 use.value(), numberingState.getNumber(use.value().getOwner()));
1135 // The use-list order achieved when building the IR at parsing always
1136 // pushes new uses on front. Hence, if the order by unique ID is
1137 // monotonically decreasing, a roundtrip to bytecode preserves such order.
1138 alreadyOrdered &= (prevID > currentID);
1139 useListPairs.push_back({use.index(), currentID});
1140 prevID = currentID;
1141 }
1142
1143 // Do not emit if the order is already sorted.
1144 if (alreadyOrdered)
1145 continue;
1146
1147 // Sort the use indices by the unique ID indices in descending order.
1148 std::sort(
1149 useListPairs.begin(), useListPairs.end(),
1150 [](auto elem1, auto elem2) { return elem1.second > elem2.second; });
1151
1152 map.try_emplace(item.index(), llvm::map_range(useListPairs, [](auto elem) {
1153 return elem.first;
1154 }));
1155 }
1156
1157 if (map.empty())
1158 return;
1159
1161 // Emit the number of results that have a custom use-list order if the number
1162 // of results is greater than one.
1163 if (range.size() != 1) {
1164 emitter.emitVarInt(map.size(), "custom use-list size");
1165 }
1166
1167 for (const auto &[resultIdx, useListOrder] : map) {
1168 // Compute the number of uses that are actually shuffled. If those are less
1169 // than half of the total uses, encoding the index pair `(src, dst)` is more
1170 // space efficient.
1171 size_t shuffledElements =
1172 llvm::count_if(llvm::enumerate(useListOrder),
1173 [](auto item) { return item.index() != item.value(); });
1174 bool indexPairEncoding = shuffledElements < (useListOrder.size() / 2);
1175
1176 // For single result, we don't need to store the result index.
1177 if (range.size() != 1)
1178 emitter.emitVarInt(resultIdx, "use-list result index");
1179
1180 if (indexPairEncoding) {
1181 emitter.emitVarIntWithFlag(shuffledElements * 2, indexPairEncoding,
1182 "use-list index pair size");
1183 for (auto pair : llvm::enumerate(useListOrder)) {
1184 if (pair.index() != pair.value()) {
1185 emitter.emitVarInt(pair.value(), "use-list index pair first");
1186 emitter.emitVarInt(pair.index(), "use-list index pair second");
1187 }
1188 }
1189 } else {
1190 emitter.emitVarIntWithFlag(useListOrder.size(), indexPairEncoding,
1191 "use-list size");
1192 for (const auto &index : useListOrder)
1193 emitter.emitVarInt(index, "use-list order");
1194 }
1195 }
1196}
1197
1198LogicalResult BytecodeWriter::writeRegion(EncodingEmitter &emitter,
1199 Region *region) {
1200 // If the region is empty, we only need to emit the number of blocks (which is
1201 // zero).
1202 if (region->empty()) {
1203 emitter.emitVarInt(/*numBlocks*/ 0, "region block count empty");
1204 return success();
1205 }
1206
1207 // Emit the number of blocks and values within the region.
1208 unsigned numBlocks, numValues;
1209 std::tie(numBlocks, numValues) = numberingState.getBlockValueCount(region);
1210 emitter.emitVarInt(numBlocks, "region block count");
1211 emitter.emitVarInt(numValues, "region value count");
1212
1213 // Emit the blocks within the region.
1214 for (Block &block : *region)
1215 if (failed(writeBlock(emitter, &block)))
1216 return failure();
1217 return success();
1218}
1219
1220LogicalResult BytecodeWriter::writeIRSection(EncodingEmitter &emitter,
1221 Operation *op) {
1222 EncodingEmitter irEmitter;
1223
1224 // Write the IR section the same way as a block with no arguments. Note that
1225 // the low-bit of the operation count for a block is used to indicate if the
1226 // block has arguments, which in this case is always false.
1227 irEmitter.emitVarIntWithFlag(/*numOps*/ 1, /*hasArgs*/ false, "ir section");
1228
1229 // Emit the operations.
1230 if (failed(writeOp(irEmitter, op)))
1231 return failure();
1232
1233 emitter.emitSection(bytecode::Section::kIR, std::move(irEmitter));
1234 return success();
1235}
1236
1237//===----------------------------------------------------------------------===//
1238// Resources
1239//===----------------------------------------------------------------------===//
1240
1241namespace {
1242/// This class represents a resource builder implementation for the MLIR
1243/// bytecode format.
1244class ResourceBuilder : public AsmResourceBuilder {
1245public:
1246 using PostProcessFn = function_ref<void(StringRef, AsmResourceEntryKind)>;
1247
1248 ResourceBuilder(EncodingEmitter &emitter, StringSectionBuilder &stringSection,
1249 PostProcessFn postProcessFn, bool shouldElideData)
1250 : emitter(emitter), stringSection(stringSection),
1251 postProcessFn(postProcessFn), shouldElideData(shouldElideData) {}
1252 ~ResourceBuilder() override = default;
1253
1254 void buildBlob(StringRef key, ArrayRef<char> data,
1255 uint32_t dataAlignment) final {
1256 if (!shouldElideData)
1257 emitter.emitOwnedBlobAndAlignment(data, dataAlignment, "resource blob");
1258 postProcessFn(key, AsmResourceEntryKind::Blob);
1259 }
1260 void buildBool(StringRef key, bool data) final {
1261 if (!shouldElideData)
1262 emitter.emitByte(data, "resource bool");
1263 postProcessFn(key, AsmResourceEntryKind::Bool);
1264 }
1265 void buildString(StringRef key, StringRef data) final {
1266 if (!shouldElideData)
1267 emitter.emitVarInt(stringSection.insert(data), "resource string");
1268 postProcessFn(key, AsmResourceEntryKind::String);
1269 }
1270
1271private:
1272 EncodingEmitter &emitter;
1273 StringSectionBuilder &stringSection;
1274 PostProcessFn postProcessFn;
1275 bool shouldElideData = false;
1276};
1277} // namespace
1278
1279void BytecodeWriter::writeResourceSection(Operation *op,
1280 EncodingEmitter &emitter) {
1281 EncodingEmitter resourceEmitter;
1282 EncodingEmitter resourceOffsetEmitter;
1283 uint64_t prevOffset = 0;
1285 curResourceEntries;
1286
1287 // Functor used to process the offset for a resource of `kind` defined by
1288 // 'key'.
1289 auto appendResourceOffset = [&](StringRef key, AsmResourceEntryKind kind) {
1290 uint64_t curOffset = resourceEmitter.size();
1291 curResourceEntries.emplace_back(key, kind, curOffset - prevOffset);
1292 prevOffset = curOffset;
1293 };
1294
1295 // Functor used to emit a resource group defined by 'key'.
1296 auto emitResourceGroup = [&](uint64_t key) {
1297 resourceOffsetEmitter.emitVarInt(key, "resource group key");
1298 resourceOffsetEmitter.emitVarInt(curResourceEntries.size(),
1299 "resource group size");
1300 for (auto [key, kind, size] : curResourceEntries) {
1301 resourceOffsetEmitter.emitVarInt(stringSection.insert(key),
1302 "resource key");
1303 resourceOffsetEmitter.emitVarInt(size, "resource size");
1304 resourceOffsetEmitter.emitByte(kind, "resource kind");
1305 }
1306 };
1307
1308 // Builder used to emit resources.
1309 ResourceBuilder entryBuilder(resourceEmitter, stringSection,
1310 appendResourceOffset,
1311 config.shouldElideResourceData);
1312
1313 // Emit the external resource entries.
1314 resourceOffsetEmitter.emitVarInt(config.externalResourcePrinters.size(),
1315 "external resource printer count");
1316 for (const auto &printer : config.externalResourcePrinters) {
1317 curResourceEntries.clear();
1318 printer->buildResources(op, entryBuilder);
1319 emitResourceGroup(stringSection.insert(printer->getName()));
1320 }
1321
1322 // Emit the dialect resource entries.
1323 for (DialectNumbering &dialect : numberingState.getDialects()) {
1324 if (!dialect.asmInterface)
1325 continue;
1326 curResourceEntries.clear();
1327 dialect.asmInterface->buildResources(op, dialect.resources, entryBuilder);
1328
1329 // Emit the declaration resources for this dialect, these didn't get emitted
1330 // by the interface. These resources don't have data attached, so just use a
1331 // "blob" kind as a placeholder.
1332 for (const auto &resource : dialect.resourceMap)
1333 if (resource.second->isDeclaration)
1334 appendResourceOffset(resource.first, AsmResourceEntryKind::Blob);
1335
1336 // Emit the resource group for this dialect.
1337 if (!curResourceEntries.empty())
1338 emitResourceGroup(dialect.number);
1339 }
1340
1341 // If we didn't emit any resource groups, elide the resource sections.
1342 if (resourceOffsetEmitter.size() == 0)
1343 return;
1344
1345 emitter.emitSection(bytecode::Section::kResourceOffset,
1346 std::move(resourceOffsetEmitter));
1347 emitter.emitSection(bytecode::Section::kResource, std::move(resourceEmitter));
1348}
1349
1350//===----------------------------------------------------------------------===//
1351// Strings
1352//===----------------------------------------------------------------------===//
1353
1354void BytecodeWriter::writeStringSection(EncodingEmitter &emitter) {
1355 EncodingEmitter stringEmitter;
1356 stringSection.write(stringEmitter);
1357 emitter.emitSection(bytecode::Section::kString, std::move(stringEmitter));
1358}
1359
1360//===----------------------------------------------------------------------===//
1361// Properties
1362//===----------------------------------------------------------------------===//
1363
1364void BytecodeWriter::writePropertiesSection(EncodingEmitter &emitter) {
1365 EncodingEmitter propertiesEmitter;
1366 propertiesSection.write(propertiesEmitter);
1367 emitter.emitSection(bytecode::Section::kProperties,
1368 std::move(propertiesEmitter));
1369}
1370
1371//===----------------------------------------------------------------------===//
1372// Entry Points
1373//===----------------------------------------------------------------------===//
1374
1376 const BytecodeWriterConfig &config) {
1377 // Validate before numbering the IR, which may invoke dialect writers.
1378 for (const auto &entry : config.getDialectVersionMap()) {
1379 Dialect *dialect = op->getContext()->getLoadedDialect(entry.getKey());
1380 if (!dialect)
1381 continue;
1382 auto *interface =
1383 dialect->getRegisteredInterface<BytecodeDialectInterface>();
1384 if (interface && failed(interface->verifyVersion(op, *entry.getValue())))
1385 return failure();
1386 }
1387 BytecodeWriter writer(op, config);
1388 return writer.write(op, os);
1389}
return success()
static void writeDialectGrouping(EncodingEmitter &emitter, EntriesT &&entries, EntryCallbackT &&callback)
Write the given entries in contiguous groups with the same parent dialect.
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be inserted(the insertion happens right before the *insertion point). Since `begin` can itself be invalidated due to the memref *rewriting done from this method
static LogicalResult emit(SolverOp solver, const SMTEmissionOptions &options, mlir::raw_indented_ostream &stream)
Emit the SMT operations in the given 'solver' to the 'stream'.
This class is used to build resource entries for use by the printer.
Definition AsmState.h:247
A class to interact with the attributes and types printer when emitting MLIR bytecode.
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class represents an argument of a Block.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:34
BlockArgListType getArguments()
Definition Block.h:112
This class contains the configuration used for the bytecode writer.
llvm::StringMap< std::unique_ptr< DialectVersion > > & getDialectVersionMap() const
A map containing the dialect versions to emit.
void setElideResourceDataFlag(bool shouldElideResourceData=true)
Set a boolean flag to skip emission of resources into the bytecode file.
BytecodeWriterConfig(StringRef producer="MLIR" LLVM_VERSION_STRING)
producer is an optional string that can be used to identify the producer of the bytecode when reading...
void attachFallbackResourcePrinter(FallbackAsmResourceMap &map)
Attach resource printers to the AsmState for the fallback resources in the given map.
void attachTypeCallback(std::unique_ptr< AttrTypeBytecodeWriter< Type > > callback)
int64_t getDesiredBytecodeVersion() const
Get the set desired bytecode version to emit.
void setDialectVersion(std::unique_ptr< DialectVersion > dialectVersion) const
Set a given dialect version to emit on the map.
void attachAttributeCallback(std::unique_ptr< AttrTypeBytecodeWriter< Attribute > > callback)
Attach a custom bytecode printer callback to the configuration for the emission of custom type/attrib...
ArrayRef< std::unique_ptr< AttrTypeBytecodeWriter< Type > > > getTypeWriterCallbacks() const
ArrayRef< std::unique_ptr< AttrTypeBytecodeWriter< Attribute > > > getAttributeWriterCallbacks() const
Retrieve the callbacks.
void setDesiredBytecodeVersion(int64_t bytecodeVersion)
Set the desired bytecode version to emit.
void setElideLocations(bool shouldElideLocations=true)
Set a boolean flag to skip emission of unique locations into the bytecode file.
void attachResourcePrinter(std::unique_ptr< AsmResourcePrinter > printer)
Attach the given resource printer to the writer configuration.
This class defines a virtual interface for writing to a bytecode stream, providing hooks into the byt...
Dialects are groups of MLIR operations, types and attributes, as well as behavior associated with the...
Definition Dialect.h:38
A fallback map containing external resources not explicitly handled by another parser/printer.
Definition AsmState.h:421
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
Dialect * getLoadedDialect(StringRef name)
Get a registered IR dialect with the given namespace.
NamedAttrList is array of NamedAttributes that tracks whether it is sorted and does some basic work t...
DictionaryAttr getDictionary(MLIRContext *context) const
Return a dictionary attribute for the underlying dictionary.
void append(StringRef name, Attribute attr)
Add an attribute with the specified name.
void walkInherentAttrs(Operation *op, InherentAttrVisitor visitor) const
Visit the inherent attributes stored in the properties of op.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
PropertyRef getPropertiesStorage()
Return a generic (but typed) reference to the property type storage.
Definition Operation.h:953
unsigned getNumSuccessors()
Definition Operation.h:758
bool isRegistered()
Returns true if this operation has a registered operation description, otherwise false.
Definition Operation.h:125
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:726
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
DictionaryAttr getRawDictionaryAttrs()
Return all attributes that are not stored as properties.
Definition Operation.h:561
unsigned getNumOperands()
Definition Operation.h:371
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
DictionaryAttr getDiscardableAttrDictionary()
Return all of the discardable attributes on this operation as a DictionaryAttr.
Definition Operation.h:553
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:729
result_type_range getResultTypes()
Definition Operation.h:453
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
SuccessorRange getSuccessors()
Definition Operation.h:755
result_range getResults()
Definition Operation.h:440
int getPropertiesStorageSize() const
Returns the properties storage size.
Definition Operation.h:948
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
bool empty()
Definition Region.h:60
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
@ 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
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:734
Include the generated interface declarations.
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
@ Blob
A blob of data with an accompanying alignment.
Definition AsmState.h:282
LogicalResult writeBytecodeToFile(Operation *op, raw_ostream &os, const BytecodeWriterConfig &config={})
Write the bytecode for the given operation to the provided output stream.
llvm::function_ref< Fn > function_ref
Definition LLVM.h:155
StringRef producer
The producer of the bytecode.
llvm::StringMap< std::unique_ptr< DialectVersion > > dialectVersionMap
A map containing dialect version information for each dialect to emit.
llvm::SmallVector< std::unique_ptr< AttrTypeBytecodeWriter< Attribute > > > attributeWriterCallbacks
Printer callbacks used to emit custom type and attribute encodings.
SmallVector< std::unique_ptr< AsmResourcePrinter > > externalResourcePrinters
A collection of non-dialect resource printers.
bool shouldElideLocations
A flag specifying whether to elide emission of locations.
llvm::SmallVector< std::unique_ptr< AttrTypeBytecodeWriter< Type > > > typeWriterCallbacks
int64_t bytecodeVersion
Version to use when writing.
bool shouldElideResourceData
A flag specifying whether to elide emission of resources into the bytecode file.
This class represents a numbering entry for an Dialect.
unsigned number
The number assigned to the dialect.