1 //===- BytecodeWriter.cpp - MLIR Bytecode Writer --------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "mlir/Bytecode/BytecodeWriter.h"
10 #include "IRNumbering.h"
11 #include "mlir/Bytecode/BytecodeImplementation.h"
12 #include "mlir/Bytecode/BytecodeOpInterface.h"
13 #include "mlir/Bytecode/Encoding.h"
14 #include "mlir/IR/Attributes.h"
15 #include "mlir/IR/Diagnostics.h"
16 #include "mlir/IR/OpImplementation.h"
17 #include "llvm/ADT/ArrayRef.h"
18 #include "llvm/ADT/CachedHashString.h"
19 #include "llvm/ADT/MapVector.h"
20 #include "llvm/ADT/SmallVector.h"
21 #include "llvm/Support/Debug.h"
22 #include "llvm/Support/Endian.h"
23 #include "llvm/Support/raw_ostream.h"
26 #define DEBUG_TYPE "mlir-bytecode-writer"
29 using namespace mlir::bytecode::detail
;
31 //===----------------------------------------------------------------------===//
32 // BytecodeWriterConfig
33 //===----------------------------------------------------------------------===//
35 struct BytecodeWriterConfig::Impl
{
36 Impl(StringRef producer
) : producer(producer
) {}
38 /// Version to use when writing.
39 /// Note: This only differs from kVersion if a specific version is set.
40 int64_t bytecodeVersion
= bytecode::kVersion
;
42 /// A flag specifying whether to elide emission of resources into the bytecode
44 bool shouldElideResourceData
= false;
46 /// A map containing dialect version information for each dialect to emit.
47 llvm::StringMap
<std::unique_ptr
<DialectVersion
>> dialectVersionMap
;
49 /// The producer of the bytecode.
52 /// Printer callbacks used to emit custom type and attribute encodings.
53 llvm::SmallVector
<std::unique_ptr
<AttrTypeBytecodeWriter
<Attribute
>>>
54 attributeWriterCallbacks
;
55 llvm::SmallVector
<std::unique_ptr
<AttrTypeBytecodeWriter
<Type
>>>
58 /// A collection of non-dialect resource printers.
59 SmallVector
<std::unique_ptr
<AsmResourcePrinter
>> externalResourcePrinters
;
62 BytecodeWriterConfig::BytecodeWriterConfig(StringRef producer
)
63 : impl(std::make_unique
<Impl
>(producer
)) {}
64 BytecodeWriterConfig::BytecodeWriterConfig(FallbackAsmResourceMap
&map
,
66 : BytecodeWriterConfig(producer
) {
67 attachFallbackResourcePrinter(map
);
69 BytecodeWriterConfig::~BytecodeWriterConfig() = default;
71 ArrayRef
<std::unique_ptr
<AttrTypeBytecodeWriter
<Attribute
>>>
72 BytecodeWriterConfig::getAttributeWriterCallbacks() const {
73 return impl
->attributeWriterCallbacks
;
76 ArrayRef
<std::unique_ptr
<AttrTypeBytecodeWriter
<Type
>>>
77 BytecodeWriterConfig::getTypeWriterCallbacks() const {
78 return impl
->typeWriterCallbacks
;
81 void BytecodeWriterConfig::attachAttributeCallback(
82 std::unique_ptr
<AttrTypeBytecodeWriter
<Attribute
>> callback
) {
83 impl
->attributeWriterCallbacks
.emplace_back(std::move(callback
));
86 void BytecodeWriterConfig::attachTypeCallback(
87 std::unique_ptr
<AttrTypeBytecodeWriter
<Type
>> callback
) {
88 impl
->typeWriterCallbacks
.emplace_back(std::move(callback
));
91 void BytecodeWriterConfig::attachResourcePrinter(
92 std::unique_ptr
<AsmResourcePrinter
> printer
) {
93 impl
->externalResourcePrinters
.emplace_back(std::move(printer
));
96 void BytecodeWriterConfig::setElideResourceDataFlag(
97 bool shouldElideResourceData
) {
98 impl
->shouldElideResourceData
= shouldElideResourceData
;
101 void BytecodeWriterConfig::setDesiredBytecodeVersion(int64_t bytecodeVersion
) {
102 impl
->bytecodeVersion
= bytecodeVersion
;
105 int64_t BytecodeWriterConfig::getDesiredBytecodeVersion() const {
106 return impl
->bytecodeVersion
;
109 llvm::StringMap
<std::unique_ptr
<DialectVersion
>> &
110 BytecodeWriterConfig::getDialectVersionMap() const {
111 return impl
->dialectVersionMap
;
114 void BytecodeWriterConfig::setDialectVersion(
115 llvm::StringRef dialectName
,
116 std::unique_ptr
<DialectVersion
> dialectVersion
) const {
117 assert(!impl
->dialectVersionMap
.contains(dialectName
) &&
118 "cannot override a previously set dialect version");
119 impl
->dialectVersionMap
.insert({dialectName
, std::move(dialectVersion
)});
122 //===----------------------------------------------------------------------===//
124 //===----------------------------------------------------------------------===//
127 /// This class functions as the underlying encoding emitter for the bytecode
128 /// writer. This class is a bit different compared to other types of encoders;
129 /// it does not use a single buffer, but instead may contain several buffers
130 /// (some owned by the writer, and some not) that get concatted during the final
132 class EncodingEmitter
{
134 EncodingEmitter() = default;
135 EncodingEmitter(const EncodingEmitter
&) = delete;
136 EncodingEmitter
&operator=(const EncodingEmitter
&) = delete;
138 /// Write the current contents to the provided stream.
139 void writeTo(raw_ostream
&os
) const;
141 /// Return the current size of the encoded buffer.
142 size_t size() const { return prevResultSize
+ currentResult
.size(); }
144 //===--------------------------------------------------------------------===//
146 //===--------------------------------------------------------------------===//
148 /// Backpatch a byte in the result buffer at the given offset.
149 void patchByte(uint64_t offset
, uint8_t value
, StringLiteral desc
) {
150 LLVM_DEBUG(llvm::dbgs() << "patchByte(" << offset
<< ',' << uint64_t(value
)
151 << ")\t" << desc
<< '\n');
152 assert(offset
< size() && offset
>= prevResultSize
&&
153 "cannot patch previously emitted data");
154 currentResult
[offset
- prevResultSize
] = value
;
157 /// Emit the provided blob of data, which is owned by the caller and is
158 /// guaranteed to not die before the end of the bytecode process.
159 void emitOwnedBlob(ArrayRef
<uint8_t> data
, StringLiteral desc
) {
160 LLVM_DEBUG(llvm::dbgs()
161 << "emitOwnedBlob(" << data
.size() << "b)\t" << desc
<< '\n');
162 // Push the current buffer before adding the provided data.
163 appendResult(std::move(currentResult
));
164 appendOwnedResult(data
);
167 /// Emit the provided blob of data that has the given alignment, which is
168 /// owned by the caller and is guaranteed to not die before the end of the
169 /// bytecode process. The alignment value is also encoded, making it available
171 void emitOwnedBlobAndAlignment(ArrayRef
<uint8_t> data
, uint32_t alignment
,
172 StringLiteral desc
) {
173 emitVarInt(alignment
, desc
);
174 emitVarInt(data
.size(), desc
);
177 emitOwnedBlob(data
, desc
);
179 void emitOwnedBlobAndAlignment(ArrayRef
<char> data
, uint32_t alignment
,
180 StringLiteral desc
) {
181 ArrayRef
<uint8_t> castedData(reinterpret_cast<const uint8_t *>(data
.data()),
183 emitOwnedBlobAndAlignment(castedData
, alignment
, desc
);
186 /// Align the emitter to the given alignment.
187 void alignTo(unsigned alignment
) {
190 assert(llvm::isPowerOf2_32(alignment
) && "expected valid alignment");
192 // Check to see if we need to emit any padding bytes to meet the desired
194 size_t curOffset
= size();
195 size_t paddingSize
= llvm::alignTo(curOffset
, alignment
) - curOffset
;
196 while (paddingSize
--)
197 emitByte(bytecode::kAlignmentByte
, "alignment byte");
199 // Keep track of the maximum required alignment.
200 requiredAlignment
= std::max(requiredAlignment
, alignment
);
203 //===--------------------------------------------------------------------===//
206 /// Emit a single byte.
207 template <typename T
>
208 void emitByte(T byte
, StringLiteral desc
) {
209 LLVM_DEBUG(llvm::dbgs()
210 << "emitByte(" << uint64_t(byte
) << ")\t" << desc
<< '\n');
211 currentResult
.push_back(static_cast<uint8_t>(byte
));
214 /// Emit a range of bytes.
215 void emitBytes(ArrayRef
<uint8_t> bytes
, StringLiteral desc
) {
216 LLVM_DEBUG(llvm::dbgs()
217 << "emitBytes(" << bytes
.size() << "b)\t" << desc
<< '\n');
218 llvm::append_range(currentResult
, bytes
);
221 /// Emit a variable length integer. The first encoded byte contains a prefix
222 /// in the low bits indicating the encoded length of the value. This length
223 /// prefix is a bit sequence of '0's followed by a '1'. The number of '0' bits
224 /// indicate the number of _additional_ bytes (not including the prefix byte).
225 /// All remaining bits in the first byte, along with all of the bits in
226 /// additional bytes, provide the value of the integer encoded in
227 /// little-endian order.
228 void emitVarInt(uint64_t value
, StringLiteral desc
) {
229 LLVM_DEBUG(llvm::dbgs() << "emitVarInt(" << value
<< ")\t" << desc
<< '\n');
231 // In the most common case, the value can be represented in a single byte.
232 // Given how hot this case is, explicitly handle that here.
233 if ((value
>> 7) == 0)
234 return emitByte((value
<< 1) | 0x1, desc
);
235 emitMultiByteVarInt(value
, desc
);
238 /// Emit a signed variable length integer. Signed varints are encoded using
239 /// a varint with zigzag encoding, meaning that we use the low bit of the
240 /// value to indicate the sign of the value. This allows for more efficient
241 /// encoding of negative values by limiting the number of active bits
242 void emitSignedVarInt(uint64_t value
, StringLiteral desc
) {
243 emitVarInt((value
<< 1) ^ (uint64_t)((int64_t)value
>> 63), desc
);
246 /// Emit a variable length integer whose low bit is used to encode the
247 /// provided flag, i.e. encoded as: (value << 1) | (flag ? 1 : 0).
248 void emitVarIntWithFlag(uint64_t value
, bool flag
, StringLiteral desc
) {
249 emitVarInt((value
<< 1) | (flag
? 1 : 0), desc
);
252 //===--------------------------------------------------------------------===//
255 /// Emit the given string as a nul terminated string.
256 void emitNulTerminatedString(StringRef str
, StringLiteral desc
) {
257 emitString(str
, desc
);
258 emitByte(0, "null terminator");
261 /// Emit the given string without a nul terminator.
262 void emitString(StringRef str
, StringLiteral desc
) {
263 emitBytes({reinterpret_cast<const uint8_t *>(str
.data()), str
.size()},
267 //===--------------------------------------------------------------------===//
270 /// Emit a nested section of the given code, whose contents are encoded in the
271 /// provided emitter.
272 void emitSection(bytecode::Section::ID code
, EncodingEmitter
&&emitter
) {
273 // Emit the section code and length. The high bit of the code is used to
274 // indicate whether the section alignment is present, so save an offset to
276 uint64_t codeOffset
= currentResult
.size();
277 emitByte(code
, "section code");
278 emitVarInt(emitter
.size(), "section size");
280 // Integrate the alignment of the section into this emitter if necessary.
281 unsigned emitterAlign
= emitter
.requiredAlignment
;
282 if (emitterAlign
> 1) {
283 if (size() & (emitterAlign
- 1)) {
284 emitVarInt(emitterAlign
, "section alignment");
285 alignTo(emitterAlign
);
287 // Indicate that we needed to align the section, the high bit of the
288 // code field is used for this.
289 currentResult
[codeOffset
] |= 0b10000000;
291 // Otherwise, if we happen to be at a compatible offset, we just
292 // remember that we need this alignment.
293 requiredAlignment
= std::max(requiredAlignment
, emitterAlign
);
297 // Push our current buffer and then merge the provided section body into
299 appendResult(std::move(currentResult
));
300 for (std::vector
<uint8_t> &result
: emitter
.prevResultStorage
)
301 prevResultStorage
.push_back(std::move(result
));
302 llvm::append_range(prevResultList
, emitter
.prevResultList
);
303 prevResultSize
+= emitter
.prevResultSize
;
304 appendResult(std::move(emitter
.currentResult
));
308 /// Emit the given value using a variable width encoding. This method is a
309 /// fallback when the number of bytes needed to encode the value is greater
310 /// than 1. We mark it noinline here so that the single byte hot path isn't
312 LLVM_ATTRIBUTE_NOINLINE
void emitMultiByteVarInt(uint64_t value
,
315 /// Append a new result buffer to the current contents.
316 void appendResult(std::vector
<uint8_t> &&result
) {
319 prevResultStorage
.emplace_back(std::move(result
));
320 appendOwnedResult(prevResultStorage
.back());
322 void appendOwnedResult(ArrayRef
<uint8_t> result
) {
325 prevResultSize
+= result
.size();
326 prevResultList
.emplace_back(result
);
329 /// The result of the emitter currently being built. We refrain from building
330 /// a single buffer to simplify emitting sections, large data, and more. The
331 /// result is thus represented using multiple distinct buffers, some of which
332 /// we own (via prevResultStorage), and some of which are just pointers into
333 /// externally owned buffers.
334 std::vector
<uint8_t> currentResult
;
335 std::vector
<ArrayRef
<uint8_t>> prevResultList
;
336 std::vector
<std::vector
<uint8_t>> prevResultStorage
;
338 /// An up-to-date total size of all of the buffers within `prevResultList`.
339 /// This enables O(1) size checks of the current encoding.
340 size_t prevResultSize
= 0;
342 /// The highest required alignment for the start of this section.
343 unsigned requiredAlignment
= 1;
346 //===----------------------------------------------------------------------===//
347 // StringSectionBuilder
348 //===----------------------------------------------------------------------===//
351 /// This class is used to simplify the process of emitting the string section.
352 class StringSectionBuilder
{
354 /// Add the given string to the string section, and return the index of the
355 /// string within the section.
356 size_t insert(StringRef str
) {
357 auto it
= strings
.insert({llvm::CachedHashStringRef(str
), strings
.size()});
358 return it
.first
->second
;
361 /// Write the current set of strings to the given emitter.
362 void write(EncodingEmitter
&emitter
) {
363 emitter
.emitVarInt(strings
.size(), "string section size");
365 // Emit the sizes in reverse order, so that we don't need to backpatch an
366 // offset to the string data or have a separate section.
367 for (const auto &it
: llvm::reverse(strings
))
368 emitter
.emitVarInt(it
.first
.size() + 1, "string size");
369 // Emit the string data itself.
370 for (const auto &it
: strings
)
371 emitter
.emitNulTerminatedString(it
.first
.val(), "string");
375 /// A set of strings referenced within the bytecode. The value of the map is
377 llvm::MapVector
<llvm::CachedHashStringRef
, size_t> strings
;
381 class DialectWriter
: public DialectBytecodeWriter
{
382 using DialectVersionMapT
= llvm::StringMap
<std::unique_ptr
<DialectVersion
>>;
385 DialectWriter(int64_t bytecodeVersion
, EncodingEmitter
&emitter
,
386 IRNumberingState
&numberingState
,
387 StringSectionBuilder
&stringSection
,
388 const DialectVersionMapT
&dialectVersionMap
)
389 : bytecodeVersion(bytecodeVersion
), emitter(emitter
),
390 numberingState(numberingState
), stringSection(stringSection
),
391 dialectVersionMap(dialectVersionMap
) {}
393 //===--------------------------------------------------------------------===//
395 //===--------------------------------------------------------------------===//
397 void writeAttribute(Attribute attr
) override
{
398 emitter
.emitVarInt(numberingState
.getNumber(attr
), "dialect attr");
400 void writeOptionalAttribute(Attribute attr
) override
{
402 emitter
.emitVarInt(0, "dialect optional attr none");
405 emitter
.emitVarIntWithFlag(numberingState
.getNumber(attr
), true,
406 "dialect optional attr");
409 void writeType(Type type
) override
{
410 emitter
.emitVarInt(numberingState
.getNumber(type
), "dialect type");
413 void writeResourceHandle(const AsmDialectResourceHandle
&resource
) override
{
414 emitter
.emitVarInt(numberingState
.getNumber(resource
), "dialect resource");
417 //===--------------------------------------------------------------------===//
419 //===--------------------------------------------------------------------===//
421 void writeVarInt(uint64_t value
) override
{
422 emitter
.emitVarInt(value
, "dialect writer");
425 void writeSignedVarInt(int64_t value
) override
{
426 emitter
.emitSignedVarInt(value
, "dialect writer");
429 void writeAPIntWithKnownWidth(const APInt
&value
) override
{
430 size_t bitWidth
= value
.getBitWidth();
432 // If the value is a single byte, just emit it directly without going
435 return emitter
.emitByte(value
.getLimitedValue(), "dialect APInt");
437 // If the value fits within a single varint, emit it directly.
439 return emitter
.emitSignedVarInt(value
.getLimitedValue(), "dialect APInt");
441 // Otherwise, we need to encode a variable number of active words. We use
442 // active words instead of the number of total words under the observation
443 // that smaller values will be more common.
444 unsigned numActiveWords
= value
.getActiveWords();
445 emitter
.emitVarInt(numActiveWords
, "dialect APInt word count");
447 const uint64_t *rawValueData
= value
.getRawData();
448 for (unsigned i
= 0; i
< numActiveWords
; ++i
)
449 emitter
.emitSignedVarInt(rawValueData
[i
], "dialect APInt word");
452 void writeAPFloatWithKnownSemantics(const APFloat
&value
) override
{
453 writeAPIntWithKnownWidth(value
.bitcastToAPInt());
456 void writeOwnedString(StringRef str
) override
{
457 emitter
.emitVarInt(stringSection
.insert(str
), "dialect string");
460 void writeOwnedBlob(ArrayRef
<char> blob
) override
{
461 emitter
.emitVarInt(blob
.size(), "dialect blob");
462 emitter
.emitOwnedBlob(
463 ArrayRef
<uint8_t>(reinterpret_cast<const uint8_t *>(blob
.data()),
468 void writeOwnedBool(bool value
) override
{
469 emitter
.emitByte(value
, "dialect bool");
472 int64_t getBytecodeVersion() const override
{ return bytecodeVersion
; }
474 FailureOr
<const DialectVersion
*>
475 getDialectVersion(StringRef dialectName
) const override
{
476 auto dialectEntry
= dialectVersionMap
.find(dialectName
);
477 if (dialectEntry
== dialectVersionMap
.end())
479 return dialectEntry
->getValue().get();
483 int64_t bytecodeVersion
;
484 EncodingEmitter
&emitter
;
485 IRNumberingState
&numberingState
;
486 StringSectionBuilder
&stringSection
;
487 const DialectVersionMapT
&dialectVersionMap
;
491 class PropertiesSectionBuilder
{
493 PropertiesSectionBuilder(IRNumberingState
&numberingState
,
494 StringSectionBuilder
&stringSection
,
495 const BytecodeWriterConfig::Impl
&config
)
496 : numberingState(numberingState
), stringSection(stringSection
),
499 /// Emit the op properties in the properties section and return the index of
500 /// the properties within the section. Return -1 if no properties was emitted.
501 std::optional
<ssize_t
> emit(Operation
*op
) {
502 EncodingEmitter propertiesEmitter
;
503 if (!op
->getPropertiesStorageSize())
505 if (!op
->isRegistered()) {
506 // Unregistered op are storing properties as an optional attribute.
507 Attribute prop
= *op
->getPropertiesStorage().as
<Attribute
*>();
510 EncodingEmitter sizeEmitter
;
511 sizeEmitter
.emitVarInt(numberingState
.getNumber(prop
), "properties size");
513 llvm::raw_svector_ostream
os(scratch
);
514 sizeEmitter
.writeTo(os
);
515 return emit(scratch
);
518 EncodingEmitter emitter
;
519 DialectWriter
propertiesWriter(config
.bytecodeVersion
, emitter
,
520 numberingState
, stringSection
,
521 config
.dialectVersionMap
);
522 auto iface
= cast
<BytecodeOpInterface
>(op
);
523 iface
.writeProperties(propertiesWriter
);
525 llvm::raw_svector_ostream
os(scratch
);
527 return emit(scratch
);
530 /// Write the current set of properties to the given emitter.
531 void write(EncodingEmitter
&emitter
) {
532 emitter
.emitVarInt(propertiesStorage
.size(), "properties size");
533 if (propertiesStorage
.empty())
535 for (const auto &storage
: propertiesStorage
) {
536 if (storage
.empty()) {
537 emitter
.emitBytes(ArrayRef
<uint8_t>(), "empty properties");
540 emitter
.emitBytes(ArrayRef(reinterpret_cast<const uint8_t *>(&storage
[0]),
546 /// Returns true if the section is empty.
547 bool empty() { return propertiesStorage
.empty(); }
550 /// Emit raw data and returns the offset in the internal buffer.
551 /// Data are deduplicated and will be copied in the internal buffer only if
552 /// they don't exist there already.
553 ssize_t
emit(ArrayRef
<char> rawProperties
) {
554 // Populate a scratch buffer with the properties size.
555 SmallVector
<char> sizeScratch
;
557 EncodingEmitter sizeEmitter
;
558 sizeEmitter
.emitVarInt(rawProperties
.size(), "properties");
559 llvm::raw_svector_ostream
os(sizeScratch
);
560 sizeEmitter
.writeTo(os
);
562 // Append a new storage to the table now.
563 size_t index
= propertiesStorage
.size();
564 propertiesStorage
.emplace_back();
565 std::vector
<char> &newStorage
= propertiesStorage
.back();
566 size_t propertiesSize
= sizeScratch
.size() + rawProperties
.size();
567 newStorage
.reserve(propertiesSize
);
568 newStorage
.insert(newStorage
.end(), sizeScratch
.begin(), sizeScratch
.end());
569 newStorage
.insert(newStorage
.end(), rawProperties
.begin(),
570 rawProperties
.end());
572 // Try to de-duplicate the new serialized properties.
573 // If the properties is a duplicate, pop it back from the storage.
574 auto inserted
= propertiesUniquing
.insert(
575 std::make_pair(ArrayRef
<char>(newStorage
), index
));
576 if (!inserted
.second
)
577 propertiesStorage
.pop_back();
578 return inserted
.first
->getSecond();
581 /// Storage for properties.
582 std::vector
<std::vector
<char>> propertiesStorage
;
583 SmallVector
<char> scratch
;
584 DenseMap
<ArrayRef
<char>, int64_t> propertiesUniquing
;
585 IRNumberingState
&numberingState
;
586 StringSectionBuilder
&stringSection
;
587 const BytecodeWriterConfig::Impl
&config
;
591 /// A simple raw_ostream wrapper around a EncodingEmitter. This removes the need
592 /// to go through an intermediate buffer when interacting with code that wants a
594 class RawEmitterOstream
: public raw_ostream
{
596 explicit RawEmitterOstream(EncodingEmitter
&emitter
) : emitter(emitter
) {
601 void write_impl(const char *ptr
, size_t size
) override
{
602 emitter
.emitBytes({reinterpret_cast<const uint8_t *>(ptr
), size
},
605 uint64_t current_pos() const override
{ return emitter
.size(); }
607 /// The section being emitted to.
608 EncodingEmitter
&emitter
;
612 void EncodingEmitter::writeTo(raw_ostream
&os
) const {
613 for (auto &prevResult
: prevResultList
)
614 os
.write((const char *)prevResult
.data(), prevResult
.size());
615 os
.write((const char *)currentResult
.data(), currentResult
.size());
618 void EncodingEmitter::emitMultiByteVarInt(uint64_t value
, StringLiteral desc
) {
619 // Compute the number of bytes needed to encode the value. Each byte can hold
620 // up to 7-bits of data. We only check up to the number of bits we can encode
621 // in the first byte (8).
622 uint64_t it
= value
>> 7;
623 for (size_t numBytes
= 2; numBytes
< 9; ++numBytes
) {
624 if (LLVM_LIKELY(it
>>= 7) == 0) {
625 uint64_t encodedValue
= (value
<< 1) | 0x1;
626 encodedValue
<<= (numBytes
- 1);
627 llvm::support::ulittle64_t
encodedValueLE(encodedValue
);
628 emitBytes({reinterpret_cast<uint8_t *>(&encodedValueLE
), numBytes
}, desc
);
633 // If the value is too large to encode in a single byte, emit a special all
634 // zero marker byte and splat the value directly.
636 llvm::support::ulittle64_t
valueLE(value
);
637 emitBytes({reinterpret_cast<uint8_t *>(&valueLE
), sizeof(valueLE
)}, desc
);
640 //===----------------------------------------------------------------------===//
642 //===----------------------------------------------------------------------===//
645 class BytecodeWriter
{
647 BytecodeWriter(Operation
*op
, const BytecodeWriterConfig
&config
)
648 : numberingState(op
, config
), config(config
.getImpl()),
649 propertiesSection(numberingState
, stringSection
, config
.getImpl()) {}
651 /// Write the bytecode for the given root operation.
652 LogicalResult
write(Operation
*rootOp
, raw_ostream
&os
);
655 //===--------------------------------------------------------------------===//
658 void writeDialectSection(EncodingEmitter
&emitter
);
660 //===--------------------------------------------------------------------===//
661 // Attributes and Types
663 void writeAttrTypeSection(EncodingEmitter
&emitter
);
665 //===--------------------------------------------------------------------===//
668 LogicalResult
writeBlock(EncodingEmitter
&emitter
, Block
*block
);
669 LogicalResult
writeOp(EncodingEmitter
&emitter
, Operation
*op
);
670 LogicalResult
writeRegion(EncodingEmitter
&emitter
, Region
*region
);
671 LogicalResult
writeIRSection(EncodingEmitter
&emitter
, Operation
*op
);
673 LogicalResult
writeRegions(EncodingEmitter
&emitter
,
674 MutableArrayRef
<Region
> regions
) {
675 return success(llvm::all_of(regions
, [&](Region
®ion
) {
676 return succeeded(writeRegion(emitter
, ®ion
));
680 //===--------------------------------------------------------------------===//
683 void writeResourceSection(Operation
*op
, EncodingEmitter
&emitter
);
685 //===--------------------------------------------------------------------===//
688 void writeStringSection(EncodingEmitter
&emitter
);
690 //===--------------------------------------------------------------------===//
693 void writePropertiesSection(EncodingEmitter
&emitter
);
695 //===--------------------------------------------------------------------===//
698 void writeUseListOrders(EncodingEmitter
&emitter
, uint8_t &opEncodingMask
,
701 //===--------------------------------------------------------------------===//
704 /// The builder used for the string section.
705 StringSectionBuilder stringSection
;
707 /// The IR numbering state generated for the root operation.
708 IRNumberingState numberingState
;
710 /// Configuration dictating bytecode emission.
711 const BytecodeWriterConfig::Impl
&config
;
713 /// Storage for the properties section
714 PropertiesSectionBuilder propertiesSection
;
718 LogicalResult
BytecodeWriter::write(Operation
*rootOp
, raw_ostream
&os
) {
719 EncodingEmitter emitter
;
721 // Emit the bytecode file header. This is how we identify the output as a
723 emitter
.emitString("ML\xefR", "bytecode header");
725 // Emit the bytecode version.
726 if (config
.bytecodeVersion
< bytecode::kMinSupportedVersion
||
727 config
.bytecodeVersion
> bytecode::kVersion
)
728 return rootOp
->emitError()
729 << "unsupported version requested " << config
.bytecodeVersion
730 << ", must be in range ["
731 << static_cast<int64_t>(bytecode::kMinSupportedVersion
) << ", "
732 << static_cast<int64_t>(bytecode::kVersion
) << ']';
733 emitter
.emitVarInt(config
.bytecodeVersion
, "bytecode version");
735 // Emit the producer.
736 emitter
.emitNulTerminatedString(config
.producer
, "bytecode producer");
738 // Emit the dialect section.
739 writeDialectSection(emitter
);
741 // Emit the attributes and types section.
742 writeAttrTypeSection(emitter
);
744 // Emit the IR section.
745 if (failed(writeIRSection(emitter
, rootOp
)))
748 // Emit the resources section.
749 writeResourceSection(rootOp
, emitter
);
751 // Emit the string section.
752 writeStringSection(emitter
);
754 // Emit the properties section.
755 if (config
.bytecodeVersion
>= bytecode::kNativePropertiesEncoding
)
756 writePropertiesSection(emitter
);
757 else if (!propertiesSection
.empty())
758 return rootOp
->emitError(
759 "unexpected properties emitted incompatible with bytecode <5");
761 // Write the generated bytecode to the provided output stream.
767 //===----------------------------------------------------------------------===//
770 /// Write the given entries in contiguous groups with the same parent dialect.
771 /// Each dialect sub-group is encoded with the parent dialect and number of
772 /// elements, followed by the encoding for the entries. The given callback is
773 /// invoked to encode each individual entry.
774 template <typename EntriesT
, typename EntryCallbackT
>
775 static void writeDialectGrouping(EncodingEmitter
&emitter
, EntriesT
&&entries
,
776 EntryCallbackT
&&callback
) {
777 for (auto it
= entries
.begin(), e
= entries
.end(); it
!= e
;) {
778 auto groupStart
= it
++;
780 // Find the end of the group that shares the same parent dialect.
781 DialectNumbering
*currentDialect
= groupStart
->dialect
;
782 it
= std::find_if(it
, e
, [&](const auto &entry
) {
783 return entry
.dialect
!= currentDialect
;
786 // Emit the dialect and number of elements.
787 emitter
.emitVarInt(currentDialect
->number
, "dialect number");
788 emitter
.emitVarInt(std::distance(groupStart
, it
), "dialect offset");
790 // Emit the entries within the group.
791 for (auto &entry
: llvm::make_range(groupStart
, it
))
796 void BytecodeWriter::writeDialectSection(EncodingEmitter
&emitter
) {
797 EncodingEmitter dialectEmitter
;
799 // Emit the referenced dialects.
800 auto dialects
= numberingState
.getDialects();
801 dialectEmitter
.emitVarInt(llvm::size(dialects
), "dialects count");
802 for (DialectNumbering
&dialect
: dialects
) {
803 // Write the string section and get the ID.
804 size_t nameID
= stringSection
.insert(dialect
.name
);
806 if (config
.bytecodeVersion
< bytecode::kDialectVersioning
) {
807 dialectEmitter
.emitVarInt(nameID
, "dialect name ID");
811 // Try writing the version to the versionEmitter.
812 EncodingEmitter versionEmitter
;
813 if (dialect
.interface
) {
814 // The writer used when emitting using a custom bytecode encoding.
815 DialectWriter
versionWriter(config
.bytecodeVersion
, versionEmitter
,
816 numberingState
, stringSection
,
817 config
.dialectVersionMap
);
818 dialect
.interface
->writeVersion(versionWriter
);
821 // If the version emitter is empty, version is not available. We can encode
822 // this in the dialect ID, so if there is no version, we don't write the
824 size_t versionAvailable
= versionEmitter
.size() > 0;
825 dialectEmitter
.emitVarIntWithFlag(nameID
, versionAvailable
,
827 if (versionAvailable
)
828 dialectEmitter
.emitSection(bytecode::Section::kDialectVersions
,
829 std::move(versionEmitter
));
832 if (config
.bytecodeVersion
>= bytecode::kElideUnknownBlockArgLocation
)
833 dialectEmitter
.emitVarInt(size(numberingState
.getOpNames()),
836 // Emit the referenced operation names grouped by dialect.
837 auto emitOpName
= [&](OpNameNumbering
&name
) {
838 size_t stringId
= stringSection
.insert(name
.name
.stripDialect());
839 if (config
.bytecodeVersion
< bytecode::kNativePropertiesEncoding
)
840 dialectEmitter
.emitVarInt(stringId
, "dialect op name");
842 dialectEmitter
.emitVarIntWithFlag(stringId
, name
.name
.isRegistered(),
845 writeDialectGrouping(dialectEmitter
, numberingState
.getOpNames(), emitOpName
);
847 emitter
.emitSection(bytecode::Section::kDialect
, std::move(dialectEmitter
));
850 //===----------------------------------------------------------------------===//
851 // Attributes and Types
853 void BytecodeWriter::writeAttrTypeSection(EncodingEmitter
&emitter
) {
854 EncodingEmitter attrTypeEmitter
;
855 EncodingEmitter offsetEmitter
;
856 offsetEmitter
.emitVarInt(llvm::size(numberingState
.getAttributes()),
858 offsetEmitter
.emitVarInt(llvm::size(numberingState
.getTypes()),
861 // A functor used to emit an attribute or type entry.
862 uint64_t prevOffset
= 0;
863 auto emitAttrOrType
= [&](auto &entry
) {
864 auto entryValue
= entry
.getValue();
866 auto emitAttrOrTypeRawImpl
= [&]() -> void {
867 RawEmitterOstream(attrTypeEmitter
) << entryValue
;
868 attrTypeEmitter
.emitByte(0, "attr/type separator");
870 auto emitAttrOrTypeImpl
= [&]() -> bool {
871 // TODO: We don't currently support custom encoded mutable types and
873 if (entryValue
.template hasTrait
<TypeTrait::IsMutable
>() ||
874 entryValue
.template hasTrait
<AttributeTrait::IsMutable
>()) {
875 emitAttrOrTypeRawImpl();
879 DialectWriter
dialectWriter(config
.bytecodeVersion
, attrTypeEmitter
,
880 numberingState
, stringSection
,
881 config
.dialectVersionMap
);
882 if constexpr (std::is_same_v
<std::decay_t
<decltype(entryValue
)>, Type
>) {
883 for (const auto &callback
: config
.typeWriterCallbacks
) {
884 if (succeeded(callback
->write(entryValue
, dialectWriter
)))
887 if (const BytecodeDialectInterface
*interface
=
888 entry
.dialect
->interface
) {
889 if (succeeded(interface
->writeType(entryValue
, dialectWriter
)))
893 for (const auto &callback
: config
.attributeWriterCallbacks
) {
894 if (succeeded(callback
->write(entryValue
, dialectWriter
)))
897 if (const BytecodeDialectInterface
*interface
=
898 entry
.dialect
->interface
) {
899 if (succeeded(interface
->writeAttribute(entryValue
, dialectWriter
)))
904 // If the entry was not emitted using a callback or a dialect interface,
905 // emit it using the textual format.
906 emitAttrOrTypeRawImpl();
910 bool hasCustomEncoding
= emitAttrOrTypeImpl();
912 // Record the offset of this entry.
913 uint64_t curOffset
= attrTypeEmitter
.size();
914 offsetEmitter
.emitVarIntWithFlag(curOffset
- prevOffset
, hasCustomEncoding
,
916 prevOffset
= curOffset
;
919 // Emit the attribute and type entries for each dialect.
920 writeDialectGrouping(offsetEmitter
, numberingState
.getAttributes(),
922 writeDialectGrouping(offsetEmitter
, numberingState
.getTypes(),
925 // Emit the sections to the stream.
926 emitter
.emitSection(bytecode::Section::kAttrTypeOffset
,
927 std::move(offsetEmitter
));
928 emitter
.emitSection(bytecode::Section::kAttrType
, std::move(attrTypeEmitter
));
931 //===----------------------------------------------------------------------===//
934 LogicalResult
BytecodeWriter::writeBlock(EncodingEmitter
&emitter
,
936 ArrayRef
<BlockArgument
> args
= block
->getArguments();
937 bool hasArgs
= !args
.empty();
939 // Emit the number of operations in this block, and if it has arguments. We
940 // use the low bit of the operation count to indicate if the block has
942 unsigned numOps
= numberingState
.getOperationCount(block
);
943 emitter
.emitVarIntWithFlag(numOps
, hasArgs
, "block num ops");
945 // Emit the arguments of the block.
947 emitter
.emitVarInt(args
.size(), "block args count");
948 for (BlockArgument arg
: args
) {
949 Location argLoc
= arg
.getLoc();
950 if (config
.bytecodeVersion
>= bytecode::kElideUnknownBlockArgLocation
) {
951 emitter
.emitVarIntWithFlag(numberingState
.getNumber(arg
.getType()),
952 !isa
<UnknownLoc
>(argLoc
), "block arg type");
953 if (!isa
<UnknownLoc
>(argLoc
))
954 emitter
.emitVarInt(numberingState
.getNumber(argLoc
),
955 "block arg location");
957 emitter
.emitVarInt(numberingState
.getNumber(arg
.getType()),
959 emitter
.emitVarInt(numberingState
.getNumber(argLoc
),
960 "block arg location");
963 if (config
.bytecodeVersion
>= bytecode::kUseListOrdering
) {
964 uint64_t maskOffset
= emitter
.size();
965 uint8_t encodingMask
= 0;
966 emitter
.emitByte(0, "use-list separator");
967 writeUseListOrders(emitter
, encodingMask
, args
);
969 emitter
.patchByte(maskOffset
, encodingMask
, "block patch encoding");
973 // Emit the operations within the block.
974 for (Operation
&op
: *block
)
975 if (failed(writeOp(emitter
, &op
)))
980 LogicalResult
BytecodeWriter::writeOp(EncodingEmitter
&emitter
, Operation
*op
) {
981 emitter
.emitVarInt(numberingState
.getNumber(op
->getName()), "op name ID");
983 // Emit a mask for the operation components. We need to fill this in later
984 // (when we actually know what needs to be emitted), so emit a placeholder for
986 uint64_t maskOffset
= emitter
.size();
987 uint8_t opEncodingMask
= 0;
988 emitter
.emitByte(0, "op separator");
990 // Emit the location for this operation.
991 emitter
.emitVarInt(numberingState
.getNumber(op
->getLoc()), "op location");
993 // Emit the attributes of this operation.
994 DictionaryAttr attrs
= op
->getDiscardableAttrDictionary();
995 // Allow deployment to version <kNativePropertiesEncoding by merging inherent
996 // attribute with the discardable ones. We should fail if there are any
997 // conflicts. When properties are not used by the op, also store everything as
999 if (config
.bytecodeVersion
< bytecode::kNativePropertiesEncoding
||
1000 !op
->getPropertiesStorage()) {
1001 attrs
= op
->getAttrDictionary();
1003 if (!attrs
.empty()) {
1004 opEncodingMask
|= bytecode::OpEncodingMask::kHasAttrs
;
1005 emitter
.emitVarInt(numberingState
.getNumber(attrs
), "op attrs count");
1008 // Emit the properties of this operation, for now we still support deployment
1009 // to version <kNativePropertiesEncoding.
1010 if (config
.bytecodeVersion
>= bytecode::kNativePropertiesEncoding
) {
1011 std::optional
<ssize_t
> propertiesId
= propertiesSection
.emit(op
);
1012 if (propertiesId
.has_value()) {
1013 opEncodingMask
|= bytecode::OpEncodingMask::kHasProperties
;
1014 emitter
.emitVarInt(*propertiesId
, "op properties ID");
1018 // Emit the result types of the operation.
1019 if (unsigned numResults
= op
->getNumResults()) {
1020 opEncodingMask
|= bytecode::OpEncodingMask::kHasResults
;
1021 emitter
.emitVarInt(numResults
, "op results count");
1022 for (Type type
: op
->getResultTypes())
1023 emitter
.emitVarInt(numberingState
.getNumber(type
), "op result type");
1026 // Emit the operands of the operation.
1027 if (unsigned numOperands
= op
->getNumOperands()) {
1028 opEncodingMask
|= bytecode::OpEncodingMask::kHasOperands
;
1029 emitter
.emitVarInt(numOperands
, "op operands count");
1030 for (Value operand
: op
->getOperands())
1031 emitter
.emitVarInt(numberingState
.getNumber(operand
), "op operand types");
1034 // Emit the successors of the operation.
1035 if (unsigned numSuccessors
= op
->getNumSuccessors()) {
1036 opEncodingMask
|= bytecode::OpEncodingMask::kHasSuccessors
;
1037 emitter
.emitVarInt(numSuccessors
, "op successors count");
1038 for (Block
*successor
: op
->getSuccessors())
1039 emitter
.emitVarInt(numberingState
.getNumber(successor
), "op successor");
1042 // Emit the use-list orders to bytecode, so we can reconstruct the same order
1044 if (config
.bytecodeVersion
>= bytecode::kUseListOrdering
)
1045 writeUseListOrders(emitter
, opEncodingMask
, ValueRange(op
->getResults()));
1047 // Check for regions.
1048 unsigned numRegions
= op
->getNumRegions();
1050 opEncodingMask
|= bytecode::OpEncodingMask::kHasInlineRegions
;
1052 // Update the mask for the operation.
1053 emitter
.patchByte(maskOffset
, opEncodingMask
, "op encoding mask");
1055 // With the mask emitted, we can now emit the regions of the operation. We do
1056 // this after mask emission to avoid offset complications that may arise by
1057 // emitting the regions first (e.g. if the regions are huge, backpatching the
1058 // op encoding mask is more annoying).
1060 bool isIsolatedFromAbove
= numberingState
.isIsolatedFromAbove(op
);
1061 emitter
.emitVarIntWithFlag(numRegions
, isIsolatedFromAbove
,
1062 "op regions count");
1064 // If the region is not isolated from above, or we are emitting bytecode
1065 // targeting version <kLazyLoading, we don't use a section.
1066 if (isIsolatedFromAbove
&&
1067 config
.bytecodeVersion
>= bytecode::kLazyLoading
) {
1068 EncodingEmitter regionEmitter
;
1069 if (failed(writeRegions(regionEmitter
, op
->getRegions())))
1071 emitter
.emitSection(bytecode::Section::kIR
, std::move(regionEmitter
));
1073 } else if (failed(writeRegions(emitter
, op
->getRegions()))) {
1080 void BytecodeWriter::writeUseListOrders(EncodingEmitter
&emitter
,
1081 uint8_t &opEncodingMask
,
1083 // Loop over the results and store the use-list order per result index.
1084 DenseMap
<unsigned, llvm::SmallVector
<unsigned>> map
;
1085 for (auto item
: llvm::enumerate(range
)) {
1086 auto value
= item
.value();
1087 // No need to store a custom use-list order if the result does not have
1089 if (value
.use_empty() || value
.hasOneUse())
1092 // For each result, assemble the list of pairs (use-list-index,
1093 // global-value-index). While doing so, detect if the global-value-index is
1094 // already ordered with respect to the use-list-index.
1095 bool alreadyOrdered
= true;
1096 auto &firstUse
= *value
.use_begin();
1097 uint64_t prevID
= bytecode::getUseID(
1098 firstUse
, numberingState
.getNumber(firstUse
.getOwner()));
1099 llvm::SmallVector
<std::pair
<unsigned, uint64_t>> useListPairs(
1102 for (auto use
: llvm::drop_begin(llvm::enumerate(value
.getUses()))) {
1103 uint64_t currentID
= bytecode::getUseID(
1104 use
.value(), numberingState
.getNumber(use
.value().getOwner()));
1105 // The use-list order achieved when building the IR at parsing always
1106 // pushes new uses on front. Hence, if the order by unique ID is
1107 // monotonically decreasing, a roundtrip to bytecode preserves such order.
1108 alreadyOrdered
&= (prevID
> currentID
);
1109 useListPairs
.push_back({use
.index(), currentID
});
1113 // Do not emit if the order is already sorted.
1117 // Sort the use indices by the unique ID indices in descending order.
1119 useListPairs
.begin(), useListPairs
.end(),
1120 [](auto elem1
, auto elem2
) { return elem1
.second
> elem2
.second
; });
1122 map
.try_emplace(item
.index(), llvm::map_range(useListPairs
, [](auto elem
) {
1130 opEncodingMask
|= bytecode::OpEncodingMask::kHasUseListOrders
;
1131 // Emit the number of results that have a custom use-list order if the number
1132 // of results is greater than one.
1133 if (range
.size() != 1) {
1134 emitter
.emitVarInt(map
.size(), "custom use-list size");
1137 for (const auto &item
: map
) {
1138 auto resultIdx
= item
.getFirst();
1139 auto useListOrder
= item
.getSecond();
1141 // Compute the number of uses that are actually shuffled. If those are less
1142 // than half of the total uses, encoding the index pair `(src, dst)` is more
1144 size_t shuffledElements
=
1145 llvm::count_if(llvm::enumerate(useListOrder
),
1146 [](auto item
) { return item
.index() != item
.value(); });
1147 bool indexPairEncoding
= shuffledElements
< (useListOrder
.size() / 2);
1149 // For single result, we don't need to store the result index.
1150 if (range
.size() != 1)
1151 emitter
.emitVarInt(resultIdx
, "use-list result index");
1153 if (indexPairEncoding
) {
1154 emitter
.emitVarIntWithFlag(shuffledElements
* 2, indexPairEncoding
,
1155 "use-list index pair size");
1156 for (auto pair
: llvm::enumerate(useListOrder
)) {
1157 if (pair
.index() != pair
.value()) {
1158 emitter
.emitVarInt(pair
.value(), "use-list index pair first");
1159 emitter
.emitVarInt(pair
.index(), "use-list index pair second");
1163 emitter
.emitVarIntWithFlag(useListOrder
.size(), indexPairEncoding
,
1165 for (const auto &index
: useListOrder
)
1166 emitter
.emitVarInt(index
, "use-list order");
1171 LogicalResult
BytecodeWriter::writeRegion(EncodingEmitter
&emitter
,
1173 // If the region is empty, we only need to emit the number of blocks (which is
1175 if (region
->empty()) {
1176 emitter
.emitVarInt(/*numBlocks*/ 0, "region block count empty");
1180 // Emit the number of blocks and values within the region.
1181 unsigned numBlocks
, numValues
;
1182 std::tie(numBlocks
, numValues
) = numberingState
.getBlockValueCount(region
);
1183 emitter
.emitVarInt(numBlocks
, "region block count");
1184 emitter
.emitVarInt(numValues
, "region value count");
1186 // Emit the blocks within the region.
1187 for (Block
&block
: *region
)
1188 if (failed(writeBlock(emitter
, &block
)))
1193 LogicalResult
BytecodeWriter::writeIRSection(EncodingEmitter
&emitter
,
1195 EncodingEmitter irEmitter
;
1197 // Write the IR section the same way as a block with no arguments. Note that
1198 // the low-bit of the operation count for a block is used to indicate if the
1199 // block has arguments, which in this case is always false.
1200 irEmitter
.emitVarIntWithFlag(/*numOps*/ 1, /*hasArgs*/ false, "ir section");
1202 // Emit the operations.
1203 if (failed(writeOp(irEmitter
, op
)))
1206 emitter
.emitSection(bytecode::Section::kIR
, std::move(irEmitter
));
1210 //===----------------------------------------------------------------------===//
1214 /// This class represents a resource builder implementation for the MLIR
1215 /// bytecode format.
1216 class ResourceBuilder
: public AsmResourceBuilder
{
1218 using PostProcessFn
= function_ref
<void(StringRef
, AsmResourceEntryKind
)>;
1220 ResourceBuilder(EncodingEmitter
&emitter
, StringSectionBuilder
&stringSection
,
1221 PostProcessFn postProcessFn
, bool shouldElideData
)
1222 : emitter(emitter
), stringSection(stringSection
),
1223 postProcessFn(postProcessFn
), shouldElideData(shouldElideData
) {}
1224 ~ResourceBuilder() override
= default;
1226 void buildBlob(StringRef key
, ArrayRef
<char> data
,
1227 uint32_t dataAlignment
) final
{
1228 if (!shouldElideData
)
1229 emitter
.emitOwnedBlobAndAlignment(data
, dataAlignment
, "resource blob");
1230 postProcessFn(key
, AsmResourceEntryKind::Blob
);
1232 void buildBool(StringRef key
, bool data
) final
{
1233 if (!shouldElideData
)
1234 emitter
.emitByte(data
, "resource bool");
1235 postProcessFn(key
, AsmResourceEntryKind::Bool
);
1237 void buildString(StringRef key
, StringRef data
) final
{
1238 if (!shouldElideData
)
1239 emitter
.emitVarInt(stringSection
.insert(data
), "resource string");
1240 postProcessFn(key
, AsmResourceEntryKind::String
);
1244 EncodingEmitter
&emitter
;
1245 StringSectionBuilder
&stringSection
;
1246 PostProcessFn postProcessFn
;
1247 bool shouldElideData
= false;
1251 void BytecodeWriter::writeResourceSection(Operation
*op
,
1252 EncodingEmitter
&emitter
) {
1253 EncodingEmitter resourceEmitter
;
1254 EncodingEmitter resourceOffsetEmitter
;
1255 uint64_t prevOffset
= 0;
1256 SmallVector
<std::tuple
<StringRef
, AsmResourceEntryKind
, uint64_t>>
1259 // Functor used to process the offset for a resource of `kind` defined by
1261 auto appendResourceOffset
= [&](StringRef key
, AsmResourceEntryKind kind
) {
1262 uint64_t curOffset
= resourceEmitter
.size();
1263 curResourceEntries
.emplace_back(key
, kind
, curOffset
- prevOffset
);
1264 prevOffset
= curOffset
;
1267 // Functor used to emit a resource group defined by 'key'.
1268 auto emitResourceGroup
= [&](uint64_t key
) {
1269 resourceOffsetEmitter
.emitVarInt(key
, "resource group key");
1270 resourceOffsetEmitter
.emitVarInt(curResourceEntries
.size(),
1271 "resource group size");
1272 for (auto [key
, kind
, size
] : curResourceEntries
) {
1273 resourceOffsetEmitter
.emitVarInt(stringSection
.insert(key
),
1275 resourceOffsetEmitter
.emitVarInt(size
, "resource size");
1276 resourceOffsetEmitter
.emitByte(kind
, "resource kind");
1280 // Builder used to emit resources.
1281 ResourceBuilder
entryBuilder(resourceEmitter
, stringSection
,
1282 appendResourceOffset
,
1283 config
.shouldElideResourceData
);
1285 // Emit the external resource entries.
1286 resourceOffsetEmitter
.emitVarInt(config
.externalResourcePrinters
.size(),
1287 "external resource printer count");
1288 for (const auto &printer
: config
.externalResourcePrinters
) {
1289 curResourceEntries
.clear();
1290 printer
->buildResources(op
, entryBuilder
);
1291 emitResourceGroup(stringSection
.insert(printer
->getName()));
1294 // Emit the dialect resource entries.
1295 for (DialectNumbering
&dialect
: numberingState
.getDialects()) {
1296 if (!dialect
.asmInterface
)
1298 curResourceEntries
.clear();
1299 dialect
.asmInterface
->buildResources(op
, dialect
.resources
, entryBuilder
);
1301 // Emit the declaration resources for this dialect, these didn't get emitted
1302 // by the interface. These resources don't have data attached, so just use a
1303 // "blob" kind as a placeholder.
1304 for (const auto &resource
: dialect
.resourceMap
)
1305 if (resource
.second
->isDeclaration
)
1306 appendResourceOffset(resource
.first
, AsmResourceEntryKind::Blob
);
1308 // Emit the resource group for this dialect.
1309 if (!curResourceEntries
.empty())
1310 emitResourceGroup(dialect
.number
);
1313 // If we didn't emit any resource groups, elide the resource sections.
1314 if (resourceOffsetEmitter
.size() == 0)
1317 emitter
.emitSection(bytecode::Section::kResourceOffset
,
1318 std::move(resourceOffsetEmitter
));
1319 emitter
.emitSection(bytecode::Section::kResource
, std::move(resourceEmitter
));
1322 //===----------------------------------------------------------------------===//
1325 void BytecodeWriter::writeStringSection(EncodingEmitter
&emitter
) {
1326 EncodingEmitter stringEmitter
;
1327 stringSection
.write(stringEmitter
);
1328 emitter
.emitSection(bytecode::Section::kString
, std::move(stringEmitter
));
1331 //===----------------------------------------------------------------------===//
1334 void BytecodeWriter::writePropertiesSection(EncodingEmitter
&emitter
) {
1335 EncodingEmitter propertiesEmitter
;
1336 propertiesSection
.write(propertiesEmitter
);
1337 emitter
.emitSection(bytecode::Section::kProperties
,
1338 std::move(propertiesEmitter
));
1341 //===----------------------------------------------------------------------===//
1343 //===----------------------------------------------------------------------===//
1345 LogicalResult
mlir::writeBytecodeToFile(Operation
*op
, raw_ostream
&os
,
1346 const BytecodeWriterConfig
&config
) {
1347 BytecodeWriter
writer(op
, config
);
1348 return writer
.write(op
, os
);