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