blob: 0d4205de77d0bec1d4f70a91f3c50ef19e6854be [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.h"
#include <algorithm>
#include "iree/compiler/Dialect/IREE/IR/IREEOps.h"
#include "iree/compiler/Dialect/IREE/IR/IREETypes.h"
#include "iree/compiler/Dialect/IREE/Transforms/Passes.h"
#include "iree/compiler/Dialect/VM/Analysis/RegisterAllocation.h"
#include "iree/compiler/Dialect/VM/Analysis/ValueLiveness.h"
#include "iree/compiler/Dialect/VM/IR/VMDialect.h"
#include "iree/compiler/Dialect/VM/IR/VMOps.h"
#include "iree/compiler/Dialect/VM/Target/Bytecode/BytecodeEncoder.h"
#include "iree/compiler/Dialect/VM/Target/Bytecode/ConstantEncoder.h"
#include "iree/compiler/Dialect/VM/Target/CallingConventionUtils.h"
#include "iree/compiler/Dialect/VM/Transforms/Passes.h"
#include "iree/compiler/Utils/FlatbufferUtils.h"
#include "iree/schemas/bytecode_module_def_builder.h"
#include "iree/schemas/bytecode_module_def_json_printer.h"
#include "llvm/Support/ErrorHandling.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/Visitors.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassManager.h"
#include "mlir/Transforms/DialectConversion.h"
#include "mlir/Transforms/Passes.h"
#include "mlir/Translation.h"
namespace mlir {
namespace iree_compiler {
namespace IREE {
namespace VM {
namespace {
struct TypeDef {
Type type;
std::string full_name;
};
} // namespace
// Finds all types in the module and builds a type table mapping the index in
// the vector to the type represented by the type ordinal.
static std::vector<TypeDef> buildTypeTable(IREE::VM::ModuleOp moduleOp) {
llvm::DenseMap<Type, std::string> typeMap;
std::function<void(Type)> tryInsertType;
tryInsertType = [&](Type type) {
if (auto refPtrType = type.dyn_cast<IREE::VM::RefType>()) {
type = refPtrType.getObjectType();
}
if (typeMap.count(type)) return;
std::string str;
llvm::raw_string_ostream sstream(str);
type.print(sstream);
sstream.flush();
typeMap.try_emplace(type, str);
if (auto listType = type.dyn_cast<IREE::VM::ListType>()) {
if (listType.getElementType()) {
tryInsertType(listType.getElementType());
}
}
};
for (auto funcOp : moduleOp.getBlock().getOps<IREE::VM::FuncOp>()) {
funcOp.walk([&](Operation *op) {
for (auto type : op->getOperandTypes()) tryInsertType(type);
for (auto type : op->getResultTypes()) tryInsertType(type);
});
}
std::vector<TypeDef> table;
table.reserve(typeMap.size());
for (const auto &typeString : typeMap) {
table.push_back(TypeDef{typeString.first, typeString.second});
}
llvm::sort(
table, +[](const TypeDef &lhs, const TypeDef &rhs) {
// Always sort builtins above custom types.
if (lhs.full_name[0] != '!' && rhs.full_name[0] == '!') {
return true;
} else if (lhs.full_name[0] == '!' && rhs.full_name[0] != '!') {
return false;
}
return lhs.full_name.compare(rhs.full_name) < 0;
});
return table;
}
// Canonicalizes the module to its final form prior to emission.
// This verifies that we only have ops we can serialize and performs any of the
// required transformations (such as debug op stripping).
static LogicalResult canonicalizeModule(BytecodeTargetOptions targetOptions,
IREE::VM::ModuleOp moduleOp) {
OwningRewritePatternList patterns;
ConversionTarget target(*moduleOp.getContext());
target.addLegalDialect<IREE::VM::VMDialect>();
target.addLegalOp<IREE::DoNotOptimizeOp>();
// Add all VM canonicalization patterns and mark pseudo-ops illegal.
auto *context = moduleOp.getContext();
for (auto *op : context->getRegisteredOperations()) {
// Non-serializable ops must be removed prior to serialization.
if (op->hasTrait<OpTrait::IREE::VM::PseudoOp>()) {
op->getCanonicalizationPatterns(patterns, context);
target.setOpAction(OperationName(op->name, context),
ConversionTarget::LegalizationAction::Illegal);
}
// Debug ops must not be present when stripping.
// TODO(benvanik): add RemoveDisabledDebugOp pattern.
if (op->hasTrait<OpTrait::IREE::VM::DebugOnly>() &&
targetOptions.stripDebugOps) {
target.setOpAction(OperationName(op->name, context),
ConversionTarget::LegalizationAction::Illegal);
}
}
if (failed(applyFullConversion(moduleOp, target, std::move(patterns)))) {
return moduleOp.emitError() << "unable to fully apply conversion to module";
}
PassManager passManager(context);
mlir::applyPassManagerCLOptions(passManager);
auto &modulePasses = passManager.nest<IREE::VM::ModuleOp>();
if (targetOptions.optimize) {
// TODO(benvanik): does this run until it quiesces?
modulePasses.addPass(mlir::createInlinerPass());
modulePasses.addPass(mlir::createCSEPass());
modulePasses.addPass(mlir::createCanonicalizerPass());
}
modulePasses.addPass(createDropCompilerHintsPass());
// Mark up the module with ordinals for each top-level op (func, etc).
// This will make it easier to correlate the MLIR textual output to the
// binary output.
// We don't want any more modifications after this point as they could
// invalidate the ordinals.
modulePasses.addPass(IREE::VM::createOrdinalAllocationPass());
if (failed(passManager.run(moduleOp->getParentOfType<mlir::ModuleOp>()))) {
return moduleOp.emitError() << "failed during transform passes";
}
return success();
}
// Creates a FunctionSignatureDef based on the given function metadata.
// Some fields are not used on all signature defs and added only when present on
// the argument objects/attrs.
static iree_vm_FunctionSignatureDef_ref_t createFunctionSignatureDef(
FunctionType functionType, llvm::DenseMap<Type, int> &typeTable,
StringRef callingConvention,
iree_vm_ReflectionAttrDef_vec_ref_t reflectionAttrsRef,
FlatbufferBuilder &fbb) {
auto resultTypesRef = fbb.createInt32Vec(
llvm::map_range(functionType.getResults(), [&](Type type) {
if (auto refPtrType = type.dyn_cast<IREE::VM::RefType>()) {
type = refPtrType.getObjectType();
}
return typeTable.lookup(type);
}));
auto argumentTypesRef = fbb.createInt32Vec(
llvm::map_range(functionType.getInputs(), [&](Type type) {
if (auto refPtrType = type.dyn_cast<IREE::VM::RefType>()) {
type = refPtrType.getObjectType();
}
return typeTable.lookup(type);
}));
auto callingConventionRef = fbb.createString(callingConvention);
// If the signature would be empty then let's avoid writing the empty table.
if (!argumentTypesRef && !resultTypesRef && !callingConventionRef &&
!reflectionAttrsRef) {
return 0;
}
iree_vm_FunctionSignatureDef_start(fbb);
iree_vm_FunctionSignatureDef_argument_types_add(fbb, argumentTypesRef);
iree_vm_FunctionSignatureDef_result_types_add(fbb, resultTypesRef);
iree_vm_FunctionSignatureDef_calling_convention_add(fbb,
callingConventionRef);
iree_vm_FunctionSignatureDef_reflection_attrs_add(fbb, reflectionAttrsRef);
return iree_vm_FunctionSignatureDef_end(fbb);
}
// Returns a serialized function signature.
static iree_vm_FunctionSignatureDef_ref_t makeImportFunctionSignatureDef(
IREE::VM::ImportOp importOp, llvm::DenseMap<Type, int> &typeTable,
FlatbufferBuilder &fbb) {
// Generate the signature calling convention string based on types.
auto cconv = makeImportCallingConventionString(importOp);
if (!cconv.hasValue()) return {};
return createFunctionSignatureDef(importOp.getType(), typeTable,
cconv.getValue(), /*reflectionAttrsRef=*/0,
fbb);
}
// Returns a serialized function signature.
static iree_vm_FunctionSignatureDef_ref_t makeExportFunctionSignatureDef(
IREE::VM::ExportOp exportOp, IREE::VM::FuncOp funcOp,
llvm::DenseMap<Type, int> &typeTable, FlatbufferBuilder &fbb) {
// Generate the signature calling convention string based on types.
auto cconv = makeCallingConventionString(funcOp);
if (!cconv.hasValue()) return {};
return createFunctionSignatureDef(funcOp.getType(), typeTable,
cconv.getValue(), /*reflectionAttrsRef=*/0,
fbb);
}
// Returns a serialized function signature.
static iree_vm_FunctionSignatureDef_ref_t makeInternalFunctionSignatureDef(
IREE::VM::FuncOp funcOp, llvm::DenseMap<Type, int> &typeTable,
FlatbufferBuilder &fbb) {
// Generate the signature calling convention string based on types.
// TODO(benvanik): only do this on exports. The runtime currently looks on
// internal functions, though, so we have to have it here.
auto cconv = makeCallingConventionString(funcOp);
if (!cconv.hasValue()) return {};
// Reflection attributes.
// TODO(benvanik): move these to exports (or remove entirely).
iree_vm_ReflectionAttrDef_vec_ref_t reflectionAttrsRef = 0;
if (auto reflectionAttrs =
funcOp->getAttrOfType<DictionaryAttr>("iree.reflection")) {
SmallVector<iree_vm_ReflectionAttrDef_ref_t, 4> reflectionAttrRefs;
for (auto reflectionAttr : reflectionAttrs) {
auto key = reflectionAttr.first.strref();
auto value = reflectionAttr.second.dyn_cast<StringAttr>();
if (!value || key.empty()) continue;
// NOTE: if we actually want to keep these we should dedupe them (as the
// keys and likely several of the values are shared across all functions).
auto valueRef = fbb.createString(value.getValue());
auto keyRef = fbb.createString(key);
reflectionAttrRefs.push_back(
iree_vm_ReflectionAttrDef_create(fbb, keyRef, valueRef));
}
reflectionAttrsRef = iree_vm_ReflectionAttrDef_vec_create(
fbb, reflectionAttrRefs.data(), reflectionAttrRefs.size());
}
return createFunctionSignatureDef(funcOp.getType(), typeTable,
cconv.getValue(), reflectionAttrsRef, fbb);
}
// Builds a complete BytecodeModuleDef FlatBuffer object in |fbb|.
// The order of the encoding is ordered to ensure that all metadata is at the
// front of the resulting buffer. Large read-only data and bytecode blobs always
// fill the end of the file meaning that when memory-mapping the file most will
// not need to be paged in to do the initial module preparation.
//
// To keep the actual BytecodeModuleDef and resulting parsing code simple a lot
// has been packed into the top-level table. This results in a messier function
// here during serialization but a much more trivial (and cache-friendly)
// representation at runtime.
static LogicalResult buildFlatBufferModule(BytecodeTargetOptions targetOptions,
IREE::VM::ModuleOp moduleOp,
FlatbufferBuilder &fbb) {
SymbolTable symbolTable(moduleOp);
if (!moduleOp.ordinal_counts().hasValue()) {
return moduleOp.emitError() << "ordinal_counts attribute not found. The "
"OrdinalAllocationPass must be run before.";
}
OrdinalCountsAttr ordinalCounts = moduleOp.ordinal_counts().getValue();
// Find all structural ops in the module.
std::vector<IREE::VM::ImportOp> importFuncOps;
std::vector<IREE::VM::ExportOp> exportFuncOps;
std::vector<IREE::VM::FuncOp> internalFuncOps;
std::vector<IREE::VM::RodataOp> rodataOps;
importFuncOps.resize(ordinalCounts.import_funcs());
exportFuncOps.resize(ordinalCounts.export_funcs());
internalFuncOps.resize(ordinalCounts.internal_funcs());
rodataOps.resize(ordinalCounts.rodatas());
for (auto &op : moduleOp.getBlock().getOperations()) {
if (auto funcOp = dyn_cast<IREE::VM::FuncOp>(op)) {
internalFuncOps[funcOp.ordinal().getValue().getLimitedValue()] = funcOp;
} else if (auto exportOp = dyn_cast<IREE::VM::ExportOp>(op)) {
exportFuncOps[exportOp.ordinal().getValue().getLimitedValue()] = exportOp;
} else if (auto importOp = dyn_cast<IREE::VM::ImportOp>(op)) {
importFuncOps[importOp.ordinal().getValue().getLimitedValue()] = importOp;
} else if (auto rodataOp = dyn_cast<IREE::VM::RodataOp>(op)) {
rodataOps[rodataOp.ordinal().getValue().getLimitedValue()] = rodataOp;
}
}
// Serialize read-only data first so that it ends up at the end of the file.
// This is where large things like parameters live and we don't want that to
// get paged in until it is needed.
//
// NOTE: flatbuffers are built bottom-up; after each rodata we serialize we
// move *backward* in the file and prepend the next, meaning that if we
// were to serialize all rodata we'd have it in the opposite order as we do
// in the IR. Though this it isn't required for correctness, enabling file
// layout planning by preserving the order in the IR is useful.
SmallVector<flatbuffers_uint8_vec_ref_t, 8> rodataContentRefs;
rodataContentRefs.reserve(rodataOps.size());
for (auto rodataOp : llvm::reverse(rodataOps)) {
auto rodataRef =
serializeConstant(rodataOp.getLoc(), rodataOp.value(), fbb);
if (!rodataRef) {
return rodataOp.emitOpError() << "failed to encode";
}
rodataContentRefs.push_back(rodataRef);
}
// List of references needs to be swapped forward (we wrote backward).
std::reverse(rodataContentRefs.begin(), rodataContentRefs.end());
// Find all types in the module to build the type table.
// Note that we don't emit it yet as we want to keep it near the top of the
// file (which, in FlatBuffers, is written last).
auto typeTable = buildTypeTable(moduleOp);
llvm::DenseMap<Type, int> typeOrdinalMap;
for (auto typeDef : llvm::enumerate(typeTable)) {
typeOrdinalMap[typeDef.value().type] = typeDef.index();
}
// Serialize function bytecode one at a time and then merge at the end.
SmallVector<std::vector<uint8_t>, 8> bytecodeDataParts;
SmallVector<iree_vm_FunctionDescriptor_t, 8> functionDescriptors;
bytecodeDataParts.resize(internalFuncOps.size());
functionDescriptors.resize(internalFuncOps.size());
size_t totalBytecodeLength = 0;
for (auto funcOp : llvm::enumerate(internalFuncOps)) {
auto encodedFunction = BytecodeEncoder::encodeFunction(
funcOp.value(), typeOrdinalMap, symbolTable);
if (!encodedFunction) {
return funcOp.value().emitError() << "failed to encode function bytecode";
}
iree_vm_FunctionDescriptor_assign(
&functionDescriptors[funcOp.index()], totalBytecodeLength,
encodedFunction->bytecodeData.size(), encodedFunction->i32RegisterCount,
encodedFunction->refRegisterCount);
totalBytecodeLength += encodedFunction->bytecodeData.size();
bytecodeDataParts[funcOp.index()] =
std::move(encodedFunction->bytecodeData);
}
flatbuffers_uint8_vec_start(fbb);
uint8_t *bytecodeDataPtr =
flatbuffers_uint8_vec_extend(fbb, totalBytecodeLength);
// NOTE: we need to ensure we clear the output data in case we have gaps for
// alignment (where otherwise uninitialized memory might sneak in and be bad
// for both security and determinism).
memset(bytecodeDataPtr, 0, totalBytecodeLength);
size_t currentBytecodeOffset = 0;
for (const auto &it : llvm::enumerate(internalFuncOps)) {
int ordinal = it.index();
auto data = std::move(bytecodeDataParts[ordinal]);
std::memcpy(bytecodeDataPtr + currentBytecodeOffset, data.data(),
data.size());
currentBytecodeOffset += data.size();
}
auto bytecodeDataRef = flatbuffers_uint8_vec_end(fbb);
// Encode the function descriptors adjacent to the bytcode data; they are
// always accessed together. Descriptor 0 is likely within a few hundred bytes
// of the referenced bytecode data offset 0, and from there we are at least
// able to hope sequential readahead caching helps; if not, at least we
// hopefully don't fault on the first function call every time.
auto functionDescriptorsRef = iree_vm_FunctionDescriptor_vec_create(
fbb, functionDescriptors.data(), functionDescriptors.size());
// Serialize metadata that should be near the front of the file.
auto rodataSegmentRefs = llvm::to_vector<8>(
llvm::map_range(rodataContentRefs, [&](auto rodataContentRef) {
iree_vm_RodataSegmentDef_start(fbb);
iree_vm_RodataSegmentDef_data_add(fbb, rodataContentRef);
return iree_vm_RodataSegmentDef_end(fbb);
}));
SmallVector<iree_vm_RwdataSegmentDef_ref_t, 8> rwdataSegmentRefs;
// NOTE: rwdata current unused.
auto typeRefs =
llvm::to_vector<8>(llvm::map_range(typeTable, [&](auto typeDef) {
auto fullNameRef = fbb.createString(typeDef.full_name);
iree_vm_TypeDef_start(fbb);
iree_vm_TypeDef_full_name_add(fbb, fullNameRef);
return iree_vm_TypeDef_end(fbb);
}));
auto importFuncRefs =
llvm::to_vector<8>(llvm::map_range(importFuncOps, [&](auto importOp) {
auto fullNameRef = fbb.createString(importOp.getName());
auto signatureRef =
makeImportFunctionSignatureDef(importOp, typeOrdinalMap, fbb);
iree_vm_ImportFunctionDef_start(fbb);
iree_vm_ImportFunctionDef_full_name_add(fbb, fullNameRef);
iree_vm_ImportFunctionDef_signature_add(fbb, signatureRef);
return iree_vm_ImportFunctionDef_end(fbb);
}));
auto exportFuncRefs =
llvm::to_vector<8>(llvm::map_range(exportFuncOps, [&](auto exportOp) {
auto localNameRef = fbb.createString(exportOp.export_name());
auto funcOp =
symbolTable.lookup<IREE::VM::FuncOp>(exportOp.function_ref());
auto signatureRef = makeExportFunctionSignatureDef(exportOp, funcOp,
typeOrdinalMap, fbb);
iree_vm_ExportFunctionDef_start(fbb);
iree_vm_ExportFunctionDef_local_name_add(fbb, localNameRef);
iree_vm_ExportFunctionDef_signature_add(fbb, signatureRef);
iree_vm_ExportFunctionDef_internal_ordinal_add(
fbb, funcOp.ordinal().getValue().getLimitedValue());
return iree_vm_ExportFunctionDef_end(fbb);
}));
SmallVector<iree_vm_InternalFunctionDef_ref_t, 8> internalFuncRefs;
if (!targetOptions.stripSymbols) {
internalFuncRefs.reserve(internalFuncOps.size());
for (auto funcOp : internalFuncOps) {
auto localNameRef = fbb.createString(funcOp.getName());
auto signatureRef =
makeInternalFunctionSignatureDef(funcOp, typeOrdinalMap, fbb);
iree_vm_InternalFunctionDef_start(fbb);
iree_vm_InternalFunctionDef_local_name_add(fbb, localNameRef);
iree_vm_InternalFunctionDef_signature_add(fbb, signatureRef);
internalFuncRefs.push_back(iree_vm_InternalFunctionDef_end(fbb));
}
}
// NOTE: we keep the vectors clustered here so that we can hopefully keep the
// pages mapped at runtime; vector dereferences in flatbuffers require
// touching these structs to get length/etc and as such we don't want to be
// gathering from all over the file (with giant rodata chunks and such
// inbetween) just to perform a bounds check and deference into another part
// of the file.
auto rodataSegmentsRef = fbb.createOffsetVecDestructive(rodataSegmentRefs);
auto rwdataSegmentsRef = fbb.createOffsetVecDestructive(rwdataSegmentRefs);
auto internalFuncsRef = fbb.createOffsetVecDestructive(internalFuncRefs);
auto exportFuncsOffset = fbb.createOffsetVecDestructive(exportFuncRefs);
auto importFuncsRef = fbb.createOffsetVecDestructive(importFuncRefs);
auto typesRef = fbb.createOffsetVecDestructive(typeRefs);
int32_t globalRefs = ordinalCounts.global_refs();
int32_t globalBytes = ordinalCounts.global_bytes();
iree_vm_ModuleStateDef_ref_t moduleStateDef = 0;
if (globalBytes || globalRefs) {
iree_vm_ModuleStateDef_start(fbb);
iree_vm_ModuleStateDef_global_bytes_capacity_add(fbb, globalBytes);
iree_vm_ModuleStateDef_global_ref_count_add(fbb, globalRefs);
moduleStateDef = iree_vm_ModuleStateDef_end(fbb);
}
auto moduleNameRef = fbb.createString(
moduleOp.sym_name().empty() ? "module" : moduleOp.sym_name());
iree_vm_BytecodeModuleDef_start_as_root(fbb);
iree_vm_BytecodeModuleDef_name_add(fbb, moduleNameRef);
iree_vm_BytecodeModuleDef_types_add(fbb, typesRef);
iree_vm_BytecodeModuleDef_imported_functions_add(fbb, importFuncsRef);
iree_vm_BytecodeModuleDef_exported_functions_add(fbb, exportFuncsOffset);
iree_vm_BytecodeModuleDef_internal_functions_add(fbb, internalFuncsRef);
iree_vm_BytecodeModuleDef_module_state_add(fbb, moduleStateDef);
iree_vm_BytecodeModuleDef_rodata_segments_add(fbb, rodataSegmentsRef);
iree_vm_BytecodeModuleDef_rwdata_segments_add(fbb, rwdataSegmentsRef);
iree_vm_BytecodeModuleDef_function_descriptors_add(fbb,
functionDescriptorsRef);
iree_vm_BytecodeModuleDef_bytecode_data_add(fbb, bytecodeDataRef);
iree_vm_BytecodeModuleDef_end_as_root(fbb);
return success();
}
LogicalResult translateModuleToBytecode(IREE::VM::ModuleOp moduleOp,
BytecodeTargetOptions targetOptions,
llvm::raw_ostream &output) {
if (failed(canonicalizeModule(targetOptions, moduleOp))) {
return moduleOp.emitError()
<< "failed to canonicalize vm.module to a serializable form";
}
if (targetOptions.outputFormat == BytecodeOutputFormat::kAnnotatedMlirText) {
// Run register allocation now and put the info in the IR so it's printed.
for (auto funcOp : moduleOp.getBlock().getOps<IREE::VM::FuncOp>()) {
if (!funcOp.empty()) {
if (failed(ValueLiveness::annotateIR(funcOp))) {
return funcOp.emitError() << "liveness analysis failed";
} else if (failed(RegisterAllocation::annotateIR(funcOp))) {
return funcOp.emitError() << "register allocation failed";
}
}
}
}
if (targetOptions.outputFormat == BytecodeOutputFormat::kMlirText ||
targetOptions.outputFormat == BytecodeOutputFormat::kAnnotatedMlirText) {
// Use the standard MLIR text printer.
moduleOp.getOperation()->print(output);
output << "\n";
return success();
}
// NOTE: we order things so that all of the metadata is close to the start of
// the module header in memory. This ensures that when we map the file only
// the first few pages need to be accessed to get the metadata and the rest
// can be large bulk data.
FlatbufferBuilder fbb;
if (failed(buildFlatBufferModule(targetOptions, moduleOp, fbb))) {
return moduleOp.emitError()
<< "failed to build FlatBuffer BytecodeModuleDef";
}
switch (targetOptions.outputFormat) {
case BytecodeOutputFormat::kFlatBufferBinary:
if (failed(fbb.copyToStream(output))) {
return moduleOp.emitError()
<< "failed to copy flatbuffer emitter contents to output stream "
"- possibly out of memory";
}
break;
case BytecodeOutputFormat::kFlatBufferText: {
if (failed(fbb.printJsonToStream(/*pretty=*/true,
/*includeDefaults=*/false,
bytecode_module_def_print_json,
output))) {
return moduleOp.emitError()
<< "failed to print flatbuffer emitter contents to output "
"stream - possibly out of memory, possibly unprintable "
"structure";
}
break;
}
default:
llvm_unreachable("unimplemented output format");
}
output.flush();
return success();
}
LogicalResult translateModuleToBytecode(mlir::ModuleOp outerModuleOp,
BytecodeTargetOptions targetOptions,
llvm::raw_ostream &output) {
auto moduleOps = outerModuleOp.getOps<IREE::VM::ModuleOp>();
if (moduleOps.empty()) {
return outerModuleOp.emitError()
<< "outer module does not contain a vm.module op";
}
return translateModuleToBytecode(*moduleOps.begin(), targetOptions, output);
}
} // namespace VM
} // namespace IREE
} // namespace iree_compiler
} // namespace mlir