1358 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 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/DebugLog.h"23#include "llvm/Support/Endian.h"24#include "llvm/Support/raw_ostream.h"25#include <optional>26 27#define DEBUG_TYPE "mlir-bytecode-writer"28 29using namespace mlir;30using namespace mlir::bytecode::detail;31 32//===----------------------------------------------------------------------===//33// BytecodeWriterConfig34//===----------------------------------------------------------------------===//35 36struct BytecodeWriterConfig::Impl {37 Impl(StringRef producer) : producer(producer) {}38 39 /// Version to use when writing.40 /// Note: This only differs from kVersion if a specific version is set.41 int64_t bytecodeVersion = bytecode::kVersion;42 43 /// A flag specifying whether to elide emission of resources into the bytecode44 /// file.45 bool shouldElideResourceData = false;46 47 /// A map containing dialect version information for each dialect to emit.48 llvm::StringMap<std::unique_ptr<DialectVersion>> dialectVersionMap;49 50 /// The producer of the bytecode.51 StringRef producer;52 53 /// Printer callbacks used to emit custom type and attribute encodings.54 llvm::SmallVector<std::unique_ptr<AttrTypeBytecodeWriter<Attribute>>>55 attributeWriterCallbacks;56 llvm::SmallVector<std::unique_ptr<AttrTypeBytecodeWriter<Type>>>57 typeWriterCallbacks;58 59 /// A collection of non-dialect resource printers.60 SmallVector<std::unique_ptr<AsmResourcePrinter>> externalResourcePrinters;61};62 63BytecodeWriterConfig::BytecodeWriterConfig(StringRef producer)64 : impl(std::make_unique<Impl>(producer)) {}65BytecodeWriterConfig::BytecodeWriterConfig(FallbackAsmResourceMap &map,66 StringRef producer)67 : BytecodeWriterConfig(producer) {68 attachFallbackResourcePrinter(map);69}70BytecodeWriterConfig::BytecodeWriterConfig(BytecodeWriterConfig &&config)71 : impl(std::move(config.impl)) {}72 73BytecodeWriterConfig::~BytecodeWriterConfig() = default;74 75ArrayRef<std::unique_ptr<AttrTypeBytecodeWriter<Attribute>>>76BytecodeWriterConfig::getAttributeWriterCallbacks() const {77 return impl->attributeWriterCallbacks;78}79 80ArrayRef<std::unique_ptr<AttrTypeBytecodeWriter<Type>>>81BytecodeWriterConfig::getTypeWriterCallbacks() const {82 return impl->typeWriterCallbacks;83}84 85void BytecodeWriterConfig::attachAttributeCallback(86 std::unique_ptr<AttrTypeBytecodeWriter<Attribute>> callback) {87 impl->attributeWriterCallbacks.emplace_back(std::move(callback));88}89 90void BytecodeWriterConfig::attachTypeCallback(91 std::unique_ptr<AttrTypeBytecodeWriter<Type>> callback) {92 impl->typeWriterCallbacks.emplace_back(std::move(callback));93}94 95void BytecodeWriterConfig::attachResourcePrinter(96 std::unique_ptr<AsmResourcePrinter> printer) {97 impl->externalResourcePrinters.emplace_back(std::move(printer));98}99 100void BytecodeWriterConfig::setElideResourceDataFlag(101 bool shouldElideResourceData) {102 impl->shouldElideResourceData = shouldElideResourceData;103}104 105void BytecodeWriterConfig::setDesiredBytecodeVersion(int64_t bytecodeVersion) {106 impl->bytecodeVersion = bytecodeVersion;107}108 109int64_t BytecodeWriterConfig::getDesiredBytecodeVersion() const {110 return impl->bytecodeVersion;111}112 113llvm::StringMap<std::unique_ptr<DialectVersion>> &114BytecodeWriterConfig::getDialectVersionMap() const {115 return impl->dialectVersionMap;116}117 118void BytecodeWriterConfig::setDialectVersion(119 llvm::StringRef dialectName,120 std::unique_ptr<DialectVersion> dialectVersion) const {121 assert(!impl->dialectVersionMap.contains(dialectName) &&122 "cannot override a previously set dialect version");123 impl->dialectVersionMap.insert({dialectName, std::move(dialectVersion)});124}125 126//===----------------------------------------------------------------------===//127// EncodingEmitter128//===----------------------------------------------------------------------===//129 130namespace {131/// This class functions as the underlying encoding emitter for the bytecode132/// writer. This class is a bit different compared to other types of encoders;133/// it does not use a single buffer, but instead may contain several buffers134/// (some owned by the writer, and some not) that get concatted during the final135/// emission.136class EncodingEmitter {137public:138 EncodingEmitter() = default;139 EncodingEmitter(const EncodingEmitter &) = delete;140 EncodingEmitter &operator=(const EncodingEmitter &) = delete;141 142 /// Write the current contents to the provided stream.143 void writeTo(raw_ostream &os) const;144 145 /// Return the current size of the encoded buffer.146 size_t size() const { return prevResultSize + currentResult.size(); }147 148 //===--------------------------------------------------------------------===//149 // Emission150 //===--------------------------------------------------------------------===//151 152 /// Backpatch a byte in the result buffer at the given offset.153 void patchByte(uint64_t offset, uint8_t value, StringLiteral desc) {154 LDBG() << "patchByte(" << offset << ',' << uint64_t(value) << ")\t" << desc;155 assert(offset < size() && offset >= prevResultSize &&156 "cannot patch previously emitted data");157 currentResult[offset - prevResultSize] = value;158 }159 160 /// Emit the provided blob of data, which is owned by the caller and is161 /// guaranteed to not die before the end of the bytecode process.162 void emitOwnedBlob(ArrayRef<uint8_t> data, StringLiteral desc) {163 LDBG() << "emitOwnedBlob(" << data.size() << "b)\t" << desc;164 // Push the current buffer before adding the provided data.165 appendResult(std::move(currentResult));166 appendOwnedResult(data);167 }168 169 /// Emit the provided blob of data that has the given alignment, which is170 /// owned by the caller and is guaranteed to not die before the end of the171 /// bytecode process. The alignment value is also encoded, making it available172 /// on load.173 void emitOwnedBlobAndAlignment(ArrayRef<uint8_t> data, uint32_t alignment,174 StringLiteral desc) {175 emitVarInt(alignment, desc);176 emitVarInt(data.size(), desc);177 178 alignTo(alignment);179 emitOwnedBlob(data, desc);180 }181 void emitOwnedBlobAndAlignment(ArrayRef<char> data, uint32_t alignment,182 StringLiteral desc) {183 ArrayRef<uint8_t> castedData(reinterpret_cast<const uint8_t *>(data.data()),184 data.size());185 emitOwnedBlobAndAlignment(castedData, alignment, desc);186 }187 188 /// Align the emitter to the given alignment.189 void alignTo(unsigned alignment) {190 if (alignment < 2)191 return;192 assert(llvm::isPowerOf2_32(alignment) && "expected valid alignment");193 194 // Check to see if we need to emit any padding bytes to meet the desired195 // alignment.196 size_t curOffset = size();197 size_t paddingSize = llvm::alignTo(curOffset, alignment) - curOffset;198 while (paddingSize--)199 emitByte(bytecode::kAlignmentByte, "alignment byte");200 201 // Keep track of the maximum required alignment.202 requiredAlignment = std::max(requiredAlignment, alignment);203 }204 205 //===--------------------------------------------------------------------===//206 // Integer Emission207 208 /// Emit a single byte.209 template <typename T>210 void emitByte(T byte, StringLiteral desc) {211 LDBG() << "emitByte(" << uint64_t(byte) << ")\t" << desc;212 currentResult.push_back(static_cast<uint8_t>(byte));213 }214 215 /// Emit a range of bytes.216 void emitBytes(ArrayRef<uint8_t> bytes, StringLiteral desc) {217 LDBG() << "emitBytes(" << bytes.size() << "b)\t" << desc;218 llvm::append_range(currentResult, bytes);219 }220 221 /// Emit a variable length integer. The first encoded byte contains a prefix222 /// in the low bits indicating the encoded length of the value. This length223 /// prefix is a bit sequence of '0's followed by a '1'. The number of '0' bits224 /// 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 in226 /// additional bytes, provide the value of the integer encoded in227 /// little-endian order.228 void emitVarInt(uint64_t value, StringLiteral desc) {229 LDBG() << "emitVarInt(" << value << ")\t" << desc;230 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);236 }237 238 /// Emit a signed variable length integer. Signed varints are encoded using239 /// a varint with zigzag encoding, meaning that we use the low bit of the240 /// value to indicate the sign of the value. This allows for more efficient241 /// encoding of negative values by limiting the number of active bits242 void emitSignedVarInt(uint64_t value, StringLiteral desc) {243 emitVarInt((value << 1) ^ (uint64_t)((int64_t)value >> 63), desc);244 }245 246 /// Emit a variable length integer whose low bit is used to encode the247 /// 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);250 }251 252 //===--------------------------------------------------------------------===//253 // String Emission254 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");259 }260 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()},264 desc);265 }266 267 //===--------------------------------------------------------------------===//268 // Section Emission269 270 /// Emit a nested section of the given code, whose contents are encoded in the271 /// 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 to274 // indicate whether the section alignment is present, so save an offset to275 // it.276 uint64_t codeOffset = currentResult.size();277 emitByte(code, "section code");278 emitVarInt(emitter.size(), "section size");279 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);286 287 // Indicate that we needed to align the section, the high bit of the288 // code field is used for this.289 currentResult[codeOffset] |= 0b10000000;290 } else {291 // Otherwise, if we happen to be at a compatible offset, we just292 // remember that we need this alignment.293 requiredAlignment = std::max(requiredAlignment, emitterAlign);294 }295 }296 297 // Push our current buffer and then merge the provided section body into298 // ours.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));305 }306 307private:308 /// Emit the given value using a variable width encoding. This method is a309 /// fallback when the number of bytes needed to encode the value is greater310 /// than 1. We mark it noinline here so that the single byte hot path isn't311 /// pessimized.312 LLVM_ATTRIBUTE_NOINLINE void emitMultiByteVarInt(uint64_t value,313 StringLiteral desc);314 315 /// Append a new result buffer to the current contents.316 void appendResult(std::vector<uint8_t> &&result) {317 if (result.empty())318 return;319 prevResultStorage.emplace_back(std::move(result));320 appendOwnedResult(prevResultStorage.back());321 }322 void appendOwnedResult(ArrayRef<uint8_t> result) {323 if (result.empty())324 return;325 prevResultSize += result.size();326 prevResultList.emplace_back(result);327 }328 329 /// The result of the emitter currently being built. We refrain from building330 /// a single buffer to simplify emitting sections, large data, and more. The331 /// result is thus represented using multiple distinct buffers, some of which332 /// we own (via prevResultStorage), and some of which are just pointers into333 /// externally owned buffers.334 std::vector<uint8_t> currentResult;335 std::vector<ArrayRef<uint8_t>> prevResultList;336 std::vector<std::vector<uint8_t>> prevResultStorage;337 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;341 342 /// The highest required alignment for the start of this section.343 unsigned requiredAlignment = 1;344};345 346//===----------------------------------------------------------------------===//347// StringSectionBuilder348//===----------------------------------------------------------------------===//349 350namespace {351/// This class is used to simplify the process of emitting the string section.352class StringSectionBuilder {353public:354 /// Add the given string to the string section, and return the index of the355 /// 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;359 }360 361 /// Write the current set of strings to the given emitter.362 void write(EncodingEmitter &emitter) {363 emitter.emitVarInt(strings.size(), "string section size");364 365 // Emit the sizes in reverse order, so that we don't need to backpatch an366 // 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");372 }373 374private:375 /// A set of strings referenced within the bytecode. The value of the map is376 /// unused.377 llvm::MapVector<llvm::CachedHashStringRef, size_t> strings;378};379} // namespace380 381class DialectWriter : public DialectBytecodeWriter {382 using DialectVersionMapT = llvm::StringMap<std::unique_ptr<DialectVersion>>;383 384public: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) {}392 393 //===--------------------------------------------------------------------===//394 // IR395 //===--------------------------------------------------------------------===//396 397 void writeAttribute(Attribute attr) override {398 emitter.emitVarInt(numberingState.getNumber(attr), "dialect attr");399 }400 void writeOptionalAttribute(Attribute attr) override {401 if (!attr) {402 emitter.emitVarInt(0, "dialect optional attr none");403 return;404 }405 emitter.emitVarIntWithFlag(numberingState.getNumber(attr), true,406 "dialect optional attr");407 }408 409 void writeType(Type type) override {410 emitter.emitVarInt(numberingState.getNumber(type), "dialect type");411 }412 413 void writeResourceHandle(const AsmDialectResourceHandle &resource) override {414 emitter.emitVarInt(numberingState.getNumber(resource), "dialect resource");415 }416 417 //===--------------------------------------------------------------------===//418 // Primitives419 //===--------------------------------------------------------------------===//420 421 void writeVarInt(uint64_t value) override {422 emitter.emitVarInt(value, "dialect writer");423 }424 425 void writeSignedVarInt(int64_t value) override {426 emitter.emitSignedVarInt(value, "dialect writer");427 }428 429 void writeAPIntWithKnownWidth(const APInt &value) override {430 size_t bitWidth = value.getBitWidth();431 432 // If the value is a single byte, just emit it directly without going433 // through a varint.434 if (bitWidth <= 8)435 return emitter.emitByte(value.getLimitedValue(), "dialect APInt");436 437 // If the value fits within a single varint, emit it directly.438 if (bitWidth <= 64)439 return emitter.emitSignedVarInt(value.getLimitedValue(), "dialect APInt");440 441 // Otherwise, we need to encode a variable number of active words. We use442 // active words instead of the number of total words under the observation443 // that smaller values will be more common.444 unsigned numActiveWords = value.getActiveWords();445 emitter.emitVarInt(numActiveWords, "dialect APInt word count");446 447 const uint64_t *rawValueData = value.getRawData();448 for (unsigned i = 0; i < numActiveWords; ++i)449 emitter.emitSignedVarInt(rawValueData[i], "dialect APInt word");450 }451 452 void writeAPFloatWithKnownSemantics(const APFloat &value) override {453 writeAPIntWithKnownWidth(value.bitcastToAPInt());454 }455 456 void writeOwnedString(StringRef str) override {457 emitter.emitVarInt(stringSection.insert(str), "dialect string");458 }459 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()),464 blob.size()),465 "dialect blob");466 }467 468 void writeOwnedBool(bool value) override {469 emitter.emitByte(value, "dialect bool");470 }471 472 int64_t getBytecodeVersion() const override { return bytecodeVersion; }473 474 FailureOr<const DialectVersion *>475 getDialectVersion(StringRef dialectName) const override {476 auto dialectEntry = dialectVersionMap.find(dialectName);477 if (dialectEntry == dialectVersionMap.end())478 return failure();479 return dialectEntry->getValue().get();480 }481 482private:483 int64_t bytecodeVersion;484 EncodingEmitter &emitter;485 IRNumberingState &numberingState;486 StringSectionBuilder &stringSection;487 const DialectVersionMapT &dialectVersionMap;488};489 490namespace {491class PropertiesSectionBuilder {492public:493 PropertiesSectionBuilder(IRNumberingState &numberingState,494 StringSectionBuilder &stringSection,495 const BytecodeWriterConfig::Impl &config)496 : numberingState(numberingState), stringSection(stringSection),497 config(config) {}498 499 /// Emit the op properties in the properties section and return the index of500 /// 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())504 return std::nullopt;505 if (!op->isRegistered()) {506 // Unregistered op are storing properties as an optional attribute.507 Attribute prop = *op->getPropertiesStorage().as<Attribute *>();508 if (!prop)509 return std::nullopt;510 EncodingEmitter sizeEmitter;511 sizeEmitter.emitVarInt(numberingState.getNumber(prop), "properties size");512 scratch.clear();513 llvm::raw_svector_ostream os(scratch);514 sizeEmitter.writeTo(os);515 return emit(scratch);516 }517 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);524 scratch.clear();525 llvm::raw_svector_ostream os(scratch);526 emitter.writeTo(os);527 return emit(scratch);528 }529 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())534 return;535 for (const auto &storage : propertiesStorage) {536 if (storage.empty()) {537 emitter.emitBytes(ArrayRef<uint8_t>(), "empty properties");538 continue;539 }540 emitter.emitBytes(ArrayRef(reinterpret_cast<const uint8_t *>(&storage[0]),541 storage.size()),542 "property");543 }544 }545 546 /// Returns true if the section is empty.547 bool empty() { return propertiesStorage.empty(); }548 549private: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 if552 /// 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;556 {557 EncodingEmitter sizeEmitter;558 sizeEmitter.emitVarInt(rawProperties.size(), "properties");559 llvm::raw_svector_ostream os(sizeScratch);560 sizeEmitter.writeTo(os);561 }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 llvm::append_range(newStorage, sizeScratch);569 llvm::append_range(newStorage, rawProperties);570 571 // Try to de-duplicate the new serialized properties.572 // If the properties is a duplicate, pop it back from the storage.573 auto inserted = propertiesUniquing.insert(574 std::make_pair(ArrayRef<char>(newStorage), index));575 if (!inserted.second)576 propertiesStorage.pop_back();577 return inserted.first->getSecond();578 }579 580 /// Storage for properties.581 std::vector<std::vector<char>> propertiesStorage;582 SmallVector<char> scratch;583 DenseMap<ArrayRef<char>, int64_t> propertiesUniquing;584 IRNumberingState &numberingState;585 StringSectionBuilder &stringSection;586 const BytecodeWriterConfig::Impl &config;587};588} // namespace589 590/// A simple raw_ostream wrapper around a EncodingEmitter. This removes the need591/// to go through an intermediate buffer when interacting with code that wants a592/// raw_ostream.593class RawEmitterOstream : public raw_ostream {594public:595 explicit RawEmitterOstream(EncodingEmitter &emitter) : emitter(emitter) {596 SetUnbuffered();597 }598 599private:600 void write_impl(const char *ptr, size_t size) override {601 emitter.emitBytes({reinterpret_cast<const uint8_t *>(ptr), size},602 "raw emitter");603 }604 uint64_t current_pos() const override { return emitter.size(); }605 606 /// The section being emitted to.607 EncodingEmitter &emitter;608};609} // namespace610 611void EncodingEmitter::writeTo(raw_ostream &os) const {612 // Reserve space in the ostream for the encoded contents.613 os.reserveExtraSpace(size());614 615 for (auto &prevResult : prevResultList)616 os.write((const char *)prevResult.data(), prevResult.size());617 os.write((const char *)currentResult.data(), currentResult.size());618}619 620void EncodingEmitter::emitMultiByteVarInt(uint64_t value, StringLiteral desc) {621 // Compute the number of bytes needed to encode the value. Each byte can hold622 // up to 7-bits of data. We only check up to the number of bits we can encode623 // in the first byte (8).624 uint64_t it = value >> 7;625 for (size_t numBytes = 2; numBytes < 9; ++numBytes) {626 if (LLVM_LIKELY(it >>= 7) == 0) {627 uint64_t encodedValue = (value << 1) | 0x1;628 encodedValue <<= (numBytes - 1);629 llvm::support::ulittle64_t encodedValueLE(encodedValue);630 emitBytes({reinterpret_cast<uint8_t *>(&encodedValueLE), numBytes}, desc);631 return;632 }633 }634 635 // If the value is too large to encode in a single byte, emit a special all636 // zero marker byte and splat the value directly.637 emitByte(0, desc);638 llvm::support::ulittle64_t valueLE(value);639 emitBytes({reinterpret_cast<uint8_t *>(&valueLE), sizeof(valueLE)}, desc);640}641 642//===----------------------------------------------------------------------===//643// Bytecode Writer644//===----------------------------------------------------------------------===//645 646namespace {647class BytecodeWriter {648public:649 BytecodeWriter(Operation *op, const BytecodeWriterConfig &config)650 : numberingState(op, config), config(config.getImpl()),651 propertiesSection(numberingState, stringSection, config.getImpl()) {}652 653 /// Write the bytecode for the given root operation.654 LogicalResult write(Operation *rootOp, raw_ostream &os);655 656private:657 //===--------------------------------------------------------------------===//658 // Dialects659 660 void writeDialectSection(EncodingEmitter &emitter);661 662 //===--------------------------------------------------------------------===//663 // Attributes and Types664 665 void writeAttrTypeSection(EncodingEmitter &emitter);666 667 //===--------------------------------------------------------------------===//668 // Operations669 670 LogicalResult writeBlock(EncodingEmitter &emitter, Block *block);671 LogicalResult writeOp(EncodingEmitter &emitter, Operation *op);672 LogicalResult writeRegion(EncodingEmitter &emitter, Region *region);673 LogicalResult writeIRSection(EncodingEmitter &emitter, Operation *op);674 675 LogicalResult writeRegions(EncodingEmitter &emitter,676 MutableArrayRef<Region> regions) {677 return success(llvm::all_of(regions, [&](Region ®ion) {678 return succeeded(writeRegion(emitter, ®ion));679 }));680 }681 682 //===--------------------------------------------------------------------===//683 // Resources684 685 void writeResourceSection(Operation *op, EncodingEmitter &emitter);686 687 //===--------------------------------------------------------------------===//688 // Strings689 690 void writeStringSection(EncodingEmitter &emitter);691 692 //===--------------------------------------------------------------------===//693 // Properties694 695 void writePropertiesSection(EncodingEmitter &emitter);696 697 //===--------------------------------------------------------------------===//698 // Helpers699 700 void writeUseListOrders(EncodingEmitter &emitter, uint8_t &opEncodingMask,701 ValueRange range);702 703 //===--------------------------------------------------------------------===//704 // Fields705 706 /// The builder used for the string section.707 StringSectionBuilder stringSection;708 709 /// The IR numbering state generated for the root operation.710 IRNumberingState numberingState;711 712 /// Configuration dictating bytecode emission.713 const BytecodeWriterConfig::Impl &config;714 715 /// Storage for the properties section716 PropertiesSectionBuilder propertiesSection;717};718} // namespace719 720LogicalResult BytecodeWriter::write(Operation *rootOp, raw_ostream &os) {721 EncodingEmitter emitter;722 723 // Emit the bytecode file header. This is how we identify the output as a724 // bytecode file.725 emitter.emitString("ML\xefR", "bytecode header");726 727 // Emit the bytecode version.728 if (config.bytecodeVersion < bytecode::kMinSupportedVersion ||729 config.bytecodeVersion > bytecode::kVersion)730 return rootOp->emitError()731 << "unsupported version requested " << config.bytecodeVersion732 << ", must be in range ["733 << static_cast<int64_t>(bytecode::kMinSupportedVersion) << ", "734 << static_cast<int64_t>(bytecode::kVersion) << ']';735 emitter.emitVarInt(config.bytecodeVersion, "bytecode version");736 737 // Emit the producer.738 emitter.emitNulTerminatedString(config.producer, "bytecode producer");739 740 // Emit the dialect section.741 writeDialectSection(emitter);742 743 // Emit the attributes and types section.744 writeAttrTypeSection(emitter);745 746 // Emit the IR section.747 if (failed(writeIRSection(emitter, rootOp)))748 return failure();749 750 // Emit the resources section.751 writeResourceSection(rootOp, emitter);752 753 // Emit the string section.754 writeStringSection(emitter);755 756 // Emit the properties section.757 if (config.bytecodeVersion >= bytecode::kNativePropertiesEncoding)758 writePropertiesSection(emitter);759 else if (!propertiesSection.empty())760 return rootOp->emitError(761 "unexpected properties emitted incompatible with bytecode <5");762 763 // Write the generated bytecode to the provided output stream.764 emitter.writeTo(os);765 766 return success();767}768 769//===----------------------------------------------------------------------===//770// Dialects771//===----------------------------------------------------------------------===//772 773/// Write the given entries in contiguous groups with the same parent dialect.774/// Each dialect sub-group is encoded with the parent dialect and number of775/// elements, followed by the encoding for the entries. The given callback is776/// invoked to encode each individual entry.777template <typename EntriesT, typename EntryCallbackT>778static void writeDialectGrouping(EncodingEmitter &emitter, EntriesT &&entries,779 EntryCallbackT &&callback) {780 for (auto it = entries.begin(), e = entries.end(); it != e;) {781 auto groupStart = it++;782 783 // Find the end of the group that shares the same parent dialect.784 DialectNumbering *currentDialect = groupStart->dialect;785 it = std::find_if(it, e, [&](const auto &entry) {786 return entry.dialect != currentDialect;787 });788 789 // Emit the dialect and number of elements.790 emitter.emitVarInt(currentDialect->number, "dialect number");791 emitter.emitVarInt(std::distance(groupStart, it), "dialect offset");792 793 // Emit the entries within the group.794 for (auto &entry : llvm::make_range(groupStart, it))795 callback(entry);796 }797}798 799void BytecodeWriter::writeDialectSection(EncodingEmitter &emitter) {800 EncodingEmitter dialectEmitter;801 802 // Emit the referenced dialects.803 auto dialects = numberingState.getDialects();804 dialectEmitter.emitVarInt(llvm::size(dialects), "dialects count");805 for (DialectNumbering &dialect : dialects) {806 // Write the string section and get the ID.807 size_t nameID = stringSection.insert(dialect.name);808 809 if (config.bytecodeVersion < bytecode::kDialectVersioning) {810 dialectEmitter.emitVarInt(nameID, "dialect name ID");811 continue;812 }813 814 // Try writing the version to the versionEmitter.815 EncodingEmitter versionEmitter;816 if (dialect.interface) {817 // The writer used when emitting using a custom bytecode encoding.818 DialectWriter versionWriter(config.bytecodeVersion, versionEmitter,819 numberingState, stringSection,820 config.dialectVersionMap);821 dialect.interface->writeVersion(versionWriter);822 }823 824 // If the version emitter is empty, version is not available. We can encode825 // this in the dialect ID, so if there is no version, we don't write the826 // section.827 size_t versionAvailable = versionEmitter.size() > 0;828 dialectEmitter.emitVarIntWithFlag(nameID, versionAvailable,829 "dialect version");830 if (versionAvailable)831 dialectEmitter.emitSection(bytecode::Section::kDialectVersions,832 std::move(versionEmitter));833 }834 835 if (config.bytecodeVersion >= bytecode::kElideUnknownBlockArgLocation)836 dialectEmitter.emitVarInt(size(numberingState.getOpNames()),837 "op names count");838 839 // Emit the referenced operation names grouped by dialect.840 auto emitOpName = [&](OpNameNumbering &name) {841 size_t stringId = stringSection.insert(name.name.stripDialect());842 if (config.bytecodeVersion < bytecode::kNativePropertiesEncoding)843 dialectEmitter.emitVarInt(stringId, "dialect op name");844 else845 dialectEmitter.emitVarIntWithFlag(stringId, name.name.isRegistered(),846 "dialect op name");847 };848 writeDialectGrouping(dialectEmitter, numberingState.getOpNames(), emitOpName);849 850 emitter.emitSection(bytecode::Section::kDialect, std::move(dialectEmitter));851}852 853//===----------------------------------------------------------------------===//854// Attributes and Types855//===----------------------------------------------------------------------===//856 857void BytecodeWriter::writeAttrTypeSection(EncodingEmitter &emitter) {858 EncodingEmitter attrTypeEmitter;859 EncodingEmitter offsetEmitter;860 offsetEmitter.emitVarInt(llvm::size(numberingState.getAttributes()),861 "attributes count");862 offsetEmitter.emitVarInt(llvm::size(numberingState.getTypes()),863 "types count");864 865 // A functor used to emit an attribute or type entry.866 uint64_t prevOffset = 0;867 auto emitAttrOrType = [&](auto &entry) {868 auto entryValue = entry.getValue();869 870 auto emitAttrOrTypeRawImpl = [&]() -> void {871 RawEmitterOstream(attrTypeEmitter) << entryValue;872 attrTypeEmitter.emitByte(0, "attr/type separator");873 };874 auto emitAttrOrTypeImpl = [&]() -> bool {875 // TODO: We don't currently support custom encoded mutable types and876 // attributes.877 if (entryValue.template hasTrait<TypeTrait::IsMutable>() ||878 entryValue.template hasTrait<AttributeTrait::IsMutable>()) {879 emitAttrOrTypeRawImpl();880 return false;881 }882 883 DialectWriter dialectWriter(config.bytecodeVersion, attrTypeEmitter,884 numberingState, stringSection,885 config.dialectVersionMap);886 if constexpr (std::is_same_v<std::decay_t<decltype(entryValue)>, Type>) {887 for (const auto &callback : config.typeWriterCallbacks) {888 if (succeeded(callback->write(entryValue, dialectWriter)))889 return true;890 }891 if (const BytecodeDialectInterface *interface =892 entry.dialect->interface) {893 if (succeeded(interface->writeType(entryValue, dialectWriter)))894 return true;895 }896 } else {897 for (const auto &callback : config.attributeWriterCallbacks) {898 if (succeeded(callback->write(entryValue, dialectWriter)))899 return true;900 }901 if (const BytecodeDialectInterface *interface =902 entry.dialect->interface) {903 if (succeeded(interface->writeAttribute(entryValue, dialectWriter)))904 return true;905 }906 }907 908 // If the entry was not emitted using a callback or a dialect interface,909 // emit it using the textual format.910 emitAttrOrTypeRawImpl();911 return false;912 };913 914 bool hasCustomEncoding = emitAttrOrTypeImpl();915 916 // Record the offset of this entry.917 uint64_t curOffset = attrTypeEmitter.size();918 offsetEmitter.emitVarIntWithFlag(curOffset - prevOffset, hasCustomEncoding,919 "attr/type offset");920 prevOffset = curOffset;921 };922 923 // Emit the attribute and type entries for each dialect.924 writeDialectGrouping(offsetEmitter, numberingState.getAttributes(),925 emitAttrOrType);926 writeDialectGrouping(offsetEmitter, numberingState.getTypes(),927 emitAttrOrType);928 929 // Emit the sections to the stream.930 emitter.emitSection(bytecode::Section::kAttrTypeOffset,931 std::move(offsetEmitter));932 emitter.emitSection(bytecode::Section::kAttrType, std::move(attrTypeEmitter));933}934 935//===----------------------------------------------------------------------===//936// Operations937//===----------------------------------------------------------------------===//938 939LogicalResult BytecodeWriter::writeBlock(EncodingEmitter &emitter,940 Block *block) {941 ArrayRef<BlockArgument> args = block->getArguments();942 bool hasArgs = !args.empty();943 944 // Emit the number of operations in this block, and if it has arguments. We945 // use the low bit of the operation count to indicate if the block has946 // arguments.947 unsigned numOps = numberingState.getOperationCount(block);948 emitter.emitVarIntWithFlag(numOps, hasArgs, "block num ops");949 950 // Emit the arguments of the block.951 if (hasArgs) {952 emitter.emitVarInt(args.size(), "block args count");953 for (BlockArgument arg : args) {954 Location argLoc = arg.getLoc();955 if (config.bytecodeVersion >= bytecode::kElideUnknownBlockArgLocation) {956 emitter.emitVarIntWithFlag(numberingState.getNumber(arg.getType()),957 !isa<UnknownLoc>(argLoc), "block arg type");958 if (!isa<UnknownLoc>(argLoc))959 emitter.emitVarInt(numberingState.getNumber(argLoc),960 "block arg location");961 } else {962 emitter.emitVarInt(numberingState.getNumber(arg.getType()),963 "block arg type");964 emitter.emitVarInt(numberingState.getNumber(argLoc),965 "block arg location");966 }967 }968 if (config.bytecodeVersion >= bytecode::kUseListOrdering) {969 uint64_t maskOffset = emitter.size();970 uint8_t encodingMask = 0;971 emitter.emitByte(0, "use-list separator");972 writeUseListOrders(emitter, encodingMask, args);973 if (encodingMask)974 emitter.patchByte(maskOffset, encodingMask, "block patch encoding");975 }976 }977 978 // Emit the operations within the block.979 for (Operation &op : *block)980 if (failed(writeOp(emitter, &op)))981 return failure();982 return success();983}984 985LogicalResult BytecodeWriter::writeOp(EncodingEmitter &emitter, Operation *op) {986 emitter.emitVarInt(numberingState.getNumber(op->getName()), "op name ID");987 988 // Emit a mask for the operation components. We need to fill this in later989 // (when we actually know what needs to be emitted), so emit a placeholder for990 // now.991 uint64_t maskOffset = emitter.size();992 uint8_t opEncodingMask = 0;993 emitter.emitByte(0, "op separator");994 995 // Emit the location for this operation.996 emitter.emitVarInt(numberingState.getNumber(op->getLoc()), "op location");997 998 // Emit the attributes of this operation.999 DictionaryAttr attrs = op->getDiscardableAttrDictionary();1000 // Allow deployment to version <kNativePropertiesEncoding by merging inherent1001 // attribute with the discardable ones. We should fail if there are any1002 // conflicts. When properties are not used by the op, also store everything as1003 // attributes.1004 if (config.bytecodeVersion < bytecode::kNativePropertiesEncoding ||1005 !op->getPropertiesStorage()) {1006 attrs = op->getAttrDictionary();1007 }1008 if (!attrs.empty()) {1009 opEncodingMask |= bytecode::OpEncodingMask::kHasAttrs;1010 emitter.emitVarInt(numberingState.getNumber(attrs), "op attrs count");1011 }1012 1013 // Emit the properties of this operation, for now we still support deployment1014 // to version <kNativePropertiesEncoding.1015 if (config.bytecodeVersion >= bytecode::kNativePropertiesEncoding) {1016 std::optional<ssize_t> propertiesId = propertiesSection.emit(op);1017 if (propertiesId.has_value()) {1018 opEncodingMask |= bytecode::OpEncodingMask::kHasProperties;1019 emitter.emitVarInt(*propertiesId, "op properties ID");1020 }1021 }1022 1023 // Emit the result types of the operation.1024 if (unsigned numResults = op->getNumResults()) {1025 opEncodingMask |= bytecode::OpEncodingMask::kHasResults;1026 emitter.emitVarInt(numResults, "op results count");1027 for (Type type : op->getResultTypes())1028 emitter.emitVarInt(numberingState.getNumber(type), "op result type");1029 }1030 1031 // Emit the operands of the operation.1032 if (unsigned numOperands = op->getNumOperands()) {1033 opEncodingMask |= bytecode::OpEncodingMask::kHasOperands;1034 emitter.emitVarInt(numOperands, "op operands count");1035 for (Value operand : op->getOperands())1036 emitter.emitVarInt(numberingState.getNumber(operand), "op operand types");1037 }1038 1039 // Emit the successors of the operation.1040 if (unsigned numSuccessors = op->getNumSuccessors()) {1041 opEncodingMask |= bytecode::OpEncodingMask::kHasSuccessors;1042 emitter.emitVarInt(numSuccessors, "op successors count");1043 for (Block *successor : op->getSuccessors())1044 emitter.emitVarInt(numberingState.getNumber(successor), "op successor");1045 }1046 1047 // Emit the use-list orders to bytecode, so we can reconstruct the same order1048 // at parsing.1049 if (config.bytecodeVersion >= bytecode::kUseListOrdering)1050 writeUseListOrders(emitter, opEncodingMask, ValueRange(op->getResults()));1051 1052 // Check for regions.1053 unsigned numRegions = op->getNumRegions();1054 if (numRegions)1055 opEncodingMask |= bytecode::OpEncodingMask::kHasInlineRegions;1056 1057 // Update the mask for the operation.1058 emitter.patchByte(maskOffset, opEncodingMask, "op encoding mask");1059 1060 // With the mask emitted, we can now emit the regions of the operation. We do1061 // this after mask emission to avoid offset complications that may arise by1062 // emitting the regions first (e.g. if the regions are huge, backpatching the1063 // op encoding mask is more annoying).1064 if (numRegions) {1065 bool isIsolatedFromAbove = numberingState.isIsolatedFromAbove(op);1066 emitter.emitVarIntWithFlag(numRegions, isIsolatedFromAbove,1067 "op regions count");1068 1069 // If the region is not isolated from above, or we are emitting bytecode1070 // targeting version <kLazyLoading, we don't use a section.1071 if (isIsolatedFromAbove &&1072 config.bytecodeVersion >= bytecode::kLazyLoading) {1073 EncodingEmitter regionEmitter;1074 if (failed(writeRegions(regionEmitter, op->getRegions())))1075 return failure();1076 emitter.emitSection(bytecode::Section::kIR, std::move(regionEmitter));1077 1078 } else if (failed(writeRegions(emitter, op->getRegions()))) {1079 return failure();1080 }1081 }1082 return success();1083}1084 1085void BytecodeWriter::writeUseListOrders(EncodingEmitter &emitter,1086 uint8_t &opEncodingMask,1087 ValueRange range) {1088 // Loop over the results and store the use-list order per result index.1089 DenseMap<unsigned, llvm::SmallVector<unsigned>> map;1090 for (auto item : llvm::enumerate(range)) {1091 auto value = item.value();1092 // No need to store a custom use-list order if the result does not have1093 // multiple uses.1094 if (value.use_empty() || value.hasOneUse())1095 continue;1096 1097 // For each result, assemble the list of pairs (use-list-index,1098 // global-value-index). While doing so, detect if the global-value-index is1099 // already ordered with respect to the use-list-index.1100 bool alreadyOrdered = true;1101 auto &firstUse = *value.use_begin();1102 uint64_t prevID = bytecode::getUseID(1103 firstUse, numberingState.getNumber(firstUse.getOwner()));1104 llvm::SmallVector<std::pair<unsigned, uint64_t>> useListPairs(1105 {{0, prevID}});1106 1107 for (auto use : llvm::drop_begin(llvm::enumerate(value.getUses()))) {1108 uint64_t currentID = bytecode::getUseID(1109 use.value(), numberingState.getNumber(use.value().getOwner()));1110 // The use-list order achieved when building the IR at parsing always1111 // pushes new uses on front. Hence, if the order by unique ID is1112 // monotonically decreasing, a roundtrip to bytecode preserves such order.1113 alreadyOrdered &= (prevID > currentID);1114 useListPairs.push_back({use.index(), currentID});1115 prevID = currentID;1116 }1117 1118 // Do not emit if the order is already sorted.1119 if (alreadyOrdered)1120 continue;1121 1122 // Sort the use indices by the unique ID indices in descending order.1123 std::sort(1124 useListPairs.begin(), useListPairs.end(),1125 [](auto elem1, auto elem2) { return elem1.second > elem2.second; });1126 1127 map.try_emplace(item.index(), llvm::map_range(useListPairs, [](auto elem) {1128 return elem.first;1129 }));1130 }1131 1132 if (map.empty())1133 return;1134 1135 opEncodingMask |= bytecode::OpEncodingMask::kHasUseListOrders;1136 // Emit the number of results that have a custom use-list order if the number1137 // of results is greater than one.1138 if (range.size() != 1) {1139 emitter.emitVarInt(map.size(), "custom use-list size");1140 }1141 1142 for (const auto &item : map) {1143 auto resultIdx = item.getFirst();1144 auto useListOrder = item.getSecond();1145 1146 // Compute the number of uses that are actually shuffled. If those are less1147 // than half of the total uses, encoding the index pair `(src, dst)` is more1148 // space efficient.1149 size_t shuffledElements =1150 llvm::count_if(llvm::enumerate(useListOrder),1151 [](auto item) { return item.index() != item.value(); });1152 bool indexPairEncoding = shuffledElements < (useListOrder.size() / 2);1153 1154 // For single result, we don't need to store the result index.1155 if (range.size() != 1)1156 emitter.emitVarInt(resultIdx, "use-list result index");1157 1158 if (indexPairEncoding) {1159 emitter.emitVarIntWithFlag(shuffledElements * 2, indexPairEncoding,1160 "use-list index pair size");1161 for (auto pair : llvm::enumerate(useListOrder)) {1162 if (pair.index() != pair.value()) {1163 emitter.emitVarInt(pair.value(), "use-list index pair first");1164 emitter.emitVarInt(pair.index(), "use-list index pair second");1165 }1166 }1167 } else {1168 emitter.emitVarIntWithFlag(useListOrder.size(), indexPairEncoding,1169 "use-list size");1170 for (const auto &index : useListOrder)1171 emitter.emitVarInt(index, "use-list order");1172 }1173 }1174}1175 1176LogicalResult BytecodeWriter::writeRegion(EncodingEmitter &emitter,1177 Region *region) {1178 // If the region is empty, we only need to emit the number of blocks (which is1179 // zero).1180 if (region->empty()) {1181 emitter.emitVarInt(/*numBlocks*/ 0, "region block count empty");1182 return success();1183 }1184 1185 // Emit the number of blocks and values within the region.1186 unsigned numBlocks, numValues;1187 std::tie(numBlocks, numValues) = numberingState.getBlockValueCount(region);1188 emitter.emitVarInt(numBlocks, "region block count");1189 emitter.emitVarInt(numValues, "region value count");1190 1191 // Emit the blocks within the region.1192 for (Block &block : *region)1193 if (failed(writeBlock(emitter, &block)))1194 return failure();1195 return success();1196}1197 1198LogicalResult BytecodeWriter::writeIRSection(EncodingEmitter &emitter,1199 Operation *op) {1200 EncodingEmitter irEmitter;1201 1202 // Write the IR section the same way as a block with no arguments. Note that1203 // the low-bit of the operation count for a block is used to indicate if the1204 // block has arguments, which in this case is always false.1205 irEmitter.emitVarIntWithFlag(/*numOps*/ 1, /*hasArgs*/ false, "ir section");1206 1207 // Emit the operations.1208 if (failed(writeOp(irEmitter, op)))1209 return failure();1210 1211 emitter.emitSection(bytecode::Section::kIR, std::move(irEmitter));1212 return success();1213}1214 1215//===----------------------------------------------------------------------===//1216// Resources1217//===----------------------------------------------------------------------===//1218 1219namespace {1220/// This class represents a resource builder implementation for the MLIR1221/// bytecode format.1222class ResourceBuilder : public AsmResourceBuilder {1223public:1224 using PostProcessFn = function_ref<void(StringRef, AsmResourceEntryKind)>;1225 1226 ResourceBuilder(EncodingEmitter &emitter, StringSectionBuilder &stringSection,1227 PostProcessFn postProcessFn, bool shouldElideData)1228 : emitter(emitter), stringSection(stringSection),1229 postProcessFn(postProcessFn), shouldElideData(shouldElideData) {}1230 ~ResourceBuilder() override = default;1231 1232 void buildBlob(StringRef key, ArrayRef<char> data,1233 uint32_t dataAlignment) final {1234 if (!shouldElideData)1235 emitter.emitOwnedBlobAndAlignment(data, dataAlignment, "resource blob");1236 postProcessFn(key, AsmResourceEntryKind::Blob);1237 }1238 void buildBool(StringRef key, bool data) final {1239 if (!shouldElideData)1240 emitter.emitByte(data, "resource bool");1241 postProcessFn(key, AsmResourceEntryKind::Bool);1242 }1243 void buildString(StringRef key, StringRef data) final {1244 if (!shouldElideData)1245 emitter.emitVarInt(stringSection.insert(data), "resource string");1246 postProcessFn(key, AsmResourceEntryKind::String);1247 }1248 1249private:1250 EncodingEmitter &emitter;1251 StringSectionBuilder &stringSection;1252 PostProcessFn postProcessFn;1253 bool shouldElideData = false;1254};1255} // namespace1256 1257void BytecodeWriter::writeResourceSection(Operation *op,1258 EncodingEmitter &emitter) {1259 EncodingEmitter resourceEmitter;1260 EncodingEmitter resourceOffsetEmitter;1261 uint64_t prevOffset = 0;1262 SmallVector<std::tuple<StringRef, AsmResourceEntryKind, uint64_t>>1263 curResourceEntries;1264 1265 // Functor used to process the offset for a resource of `kind` defined by1266 // 'key'.1267 auto appendResourceOffset = [&](StringRef key, AsmResourceEntryKind kind) {1268 uint64_t curOffset = resourceEmitter.size();1269 curResourceEntries.emplace_back(key, kind, curOffset - prevOffset);1270 prevOffset = curOffset;1271 };1272 1273 // Functor used to emit a resource group defined by 'key'.1274 auto emitResourceGroup = [&](uint64_t key) {1275 resourceOffsetEmitter.emitVarInt(key, "resource group key");1276 resourceOffsetEmitter.emitVarInt(curResourceEntries.size(),1277 "resource group size");1278 for (auto [key, kind, size] : curResourceEntries) {1279 resourceOffsetEmitter.emitVarInt(stringSection.insert(key),1280 "resource key");1281 resourceOffsetEmitter.emitVarInt(size, "resource size");1282 resourceOffsetEmitter.emitByte(kind, "resource kind");1283 }1284 };1285 1286 // Builder used to emit resources.1287 ResourceBuilder entryBuilder(resourceEmitter, stringSection,1288 appendResourceOffset,1289 config.shouldElideResourceData);1290 1291 // Emit the external resource entries.1292 resourceOffsetEmitter.emitVarInt(config.externalResourcePrinters.size(),1293 "external resource printer count");1294 for (const auto &printer : config.externalResourcePrinters) {1295 curResourceEntries.clear();1296 printer->buildResources(op, entryBuilder);1297 emitResourceGroup(stringSection.insert(printer->getName()));1298 }1299 1300 // Emit the dialect resource entries.1301 for (DialectNumbering &dialect : numberingState.getDialects()) {1302 if (!dialect.asmInterface)1303 continue;1304 curResourceEntries.clear();1305 dialect.asmInterface->buildResources(op, dialect.resources, entryBuilder);1306 1307 // Emit the declaration resources for this dialect, these didn't get emitted1308 // by the interface. These resources don't have data attached, so just use a1309 // "blob" kind as a placeholder.1310 for (const auto &resource : dialect.resourceMap)1311 if (resource.second->isDeclaration)1312 appendResourceOffset(resource.first, AsmResourceEntryKind::Blob);1313 1314 // Emit the resource group for this dialect.1315 if (!curResourceEntries.empty())1316 emitResourceGroup(dialect.number);1317 }1318 1319 // If we didn't emit any resource groups, elide the resource sections.1320 if (resourceOffsetEmitter.size() == 0)1321 return;1322 1323 emitter.emitSection(bytecode::Section::kResourceOffset,1324 std::move(resourceOffsetEmitter));1325 emitter.emitSection(bytecode::Section::kResource, std::move(resourceEmitter));1326}1327 1328//===----------------------------------------------------------------------===//1329// Strings1330//===----------------------------------------------------------------------===//1331 1332void BytecodeWriter::writeStringSection(EncodingEmitter &emitter) {1333 EncodingEmitter stringEmitter;1334 stringSection.write(stringEmitter);1335 emitter.emitSection(bytecode::Section::kString, std::move(stringEmitter));1336}1337 1338//===----------------------------------------------------------------------===//1339// Properties1340//===----------------------------------------------------------------------===//1341 1342void BytecodeWriter::writePropertiesSection(EncodingEmitter &emitter) {1343 EncodingEmitter propertiesEmitter;1344 propertiesSection.write(propertiesEmitter);1345 emitter.emitSection(bytecode::Section::kProperties,1346 std::move(propertiesEmitter));1347}1348 1349//===----------------------------------------------------------------------===//1350// Entry Points1351//===----------------------------------------------------------------------===//1352 1353LogicalResult mlir::writeBytecodeToFile(Operation *op, raw_ostream &os,1354 const BytecodeWriterConfig &config) {1355 BytecodeWriter writer(op, config);1356 return writer.write(op, os);1357}1358