| // Copyright 2021 The IREE Authors |
| // |
| // Licensed under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| #include "iree/compiler/Codegen/Dialect/Codegen/IR/IREECodegenDialect.h" |
| #include "iree/compiler/Codegen/VMVX/Passes.h" |
| #include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h" |
| #include "iree/compiler/Dialect/LinalgExt/IR/LinalgExtDialect.h" |
| #include "iree/compiler/Dialect/VM/Conversion/ConversionTarget.h" |
| #include "iree/compiler/Dialect/VM/IR/VMDialect.h" |
| #include "iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.h" |
| #include "iree/compiler/Dialect/VM/Transforms/Passes.h" |
| #include "iree/compiler/Dialect/VMVX/IR/VMVXDialect.h" |
| #include "iree/compiler/Dialect/VMVX/Transforms/Passes.h" |
| #include "iree/compiler/PluginAPI/Client.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "mlir/IR/Builders.h" |
| #include "mlir/IR/BuiltinOps.h" |
| #include "mlir/IR/OperationSupport.h" |
| #include "mlir/Pass/PassManager.h" |
| #include "mlir/Support/LogicalResult.h" |
| |
| namespace mlir::iree_compiler::IREE::HAL { |
| |
| namespace { |
| struct VMVXOptions { |
| bool enableMicrokernels = false; |
| |
| void bindOptions(OptionsBinder &binder) { |
| static llvm::cl::OptionCategory category("VMVX HAL Target"); |
| binder.opt<bool>( |
| "iree-vmvx-enable-microkernels", enableMicrokernels, |
| llvm::cl::cat(category), |
| llvm::cl::desc("Enables microkernel lowering for vmvx (experimental)")); |
| } |
| }; |
| } // namespace |
| |
| static IREE::HAL::ExecutableTargetAttr |
| getVMVXExecutableTarget(bool enableMicrokernels, MLIRContext *context, |
| StringRef backend, StringRef format) { |
| Builder b(context); |
| SmallVector<NamedAttribute> configItems; |
| |
| configItems.emplace_back( |
| b.getStringAttr("ukernels"), |
| b.getStringAttr(enableMicrokernels ? "all" : "none")); |
| |
| return b.getAttr<IREE::HAL::ExecutableTargetAttr>( |
| b.getStringAttr(backend), b.getStringAttr(format), |
| b.getDictionaryAttr(configItems)); |
| } |
| |
| // TODO(benvanik): move to a CPU device registration outside of VMVX. |
| class VMVXTargetDevice final : public TargetDevice { |
| public: |
| VMVXTargetDevice() = default; |
| |
| IREE::HAL::DeviceTargetAttr |
| getDefaultDeviceTarget(MLIRContext *context, |
| const TargetRegistry &targetRegistry) const override { |
| Builder b(context); |
| SmallVector<NamedAttribute> configItems; |
| |
| auto configAttr = b.getDictionaryAttr(configItems); |
| |
| // If we had multiple target environments we would generate one target attr |
| // per environment, with each setting its own environment attribute. |
| // If we had multiple target environments we would generate one target attr |
| // per environment, with each setting its own environment attribute. |
| SmallVector<IREE::HAL::ExecutableTargetAttr> executableTargetAttrs; |
| targetRegistry.getTargetBackend("vmvx")->getDefaultExecutableTargets( |
| context, "vmvx", configAttr, executableTargetAttrs); |
| |
| return IREE::HAL::DeviceTargetAttr::get(context, b.getStringAttr("vmvx"), |
| configAttr, executableTargetAttrs); |
| } |
| |
| std::optional<IREE::HAL::DeviceTargetAttr> |
| getHostDeviceTarget(MLIRContext *context, |
| const TargetRegistry &targetRegistry) const override { |
| return getDefaultDeviceTarget(context, targetRegistry); |
| } |
| }; |
| |
| class VMVXTargetBackend final : public TargetBackend { |
| public: |
| VMVXTargetBackend(const VMVXOptions &options) : options(options) {} |
| |
| std::string getLegacyDefaultDeviceID() const override { return "vmvx"; } |
| |
| void getDefaultExecutableTargets( |
| MLIRContext *context, StringRef deviceID, DictionaryAttr deviceConfigAttr, |
| SmallVectorImpl<IREE::HAL::ExecutableTargetAttr> &executableTargetAttrs) |
| const override { |
| executableTargetAttrs.push_back(getVMVXExecutableTarget( |
| options.enableMicrokernels, context, "vmvx", "vmvx-bytecode-fb")); |
| } |
| |
| void getHostExecutableTargets(MLIRContext *context, StringRef deviceID, |
| DictionaryAttr deviceConfigAttr, |
| SmallVectorImpl<IREE::HAL::ExecutableTargetAttr> |
| &executableTargetAttrs) const override { |
| executableTargetAttrs.push_back(getVMVXExecutableTarget( |
| options.enableMicrokernels, context, "vmvx", "vmvx-bytecode-fb")); |
| } |
| |
| void getDependentDialects(DialectRegistry ®istry) const override { |
| registry.insert<IREE::Codegen::IREECodegenDialect, IREE::VM::VMDialect, |
| IREE::VMVX::VMVXDialect, |
| IREE::LinalgExt::IREELinalgExtDialect>(); |
| } |
| |
| IREE::VM::TargetOptions |
| getTargetOptions(IREE::HAL::ExecutableTargetAttr targetAttr) { |
| // TODO(benvanik): derive these from a vm target triple. |
| auto vmOptions = IREE::VM::TargetOptions::FromFlags::get(); |
| vmOptions.f32Extension = true; |
| vmOptions.optimizeForStackSize = false; |
| return vmOptions; |
| } |
| |
| void buildConfigurationPassPipeline(IREE::HAL::ExecutableVariantOp variantOp, |
| OpPassManager &passManager) override { |
| IREE::VMVX::buildVMVXConfigurationPassPipeline(passManager); |
| } |
| |
| void buildTranslationPassPipeline(IREE::HAL::ExecutableVariantOp variantOp, |
| OpPassManager &passManager) override { |
| IREE::VMVX::buildVMVXTransformPassPipeline(passManager); |
| |
| OpPassManager &nestedModulePM = passManager.nest<ModuleOp>(); |
| |
| auto vmOptions = getTargetOptions(variantOp.getTargetAttr()); |
| IREE::VM::buildVMTransformPassPipeline(nestedModulePM, vmOptions); |
| } |
| |
| void buildLinkingPassPipeline(OpPassManager &passManager) override { |
| buildVMVXLinkingPassPipeline(passManager); |
| } |
| |
| LogicalResult serializeExecutable(const SerializationOptions &serOptions, |
| IREE::HAL::ExecutableVariantOp variantOp, |
| OpBuilder &executableBuilder) override { |
| // Add reflection information used at runtime specific to the HAL interface. |
| SymbolTable symbolTable(variantOp.getInnerModule()); |
| for (auto exportOp : variantOp.getBlock().getOps<ExecutableExportOp>()) { |
| auto funcOp = symbolTable.lookup<IREE::VM::FuncOp>(exportOp.getName()); |
| |
| // Optionally entry points may specify that they require workgroup local |
| // memory. We fetch that value here and plumb it through so the runtime |
| // knows how much memory to reserve and pass in. |
| auto localMemorySizeAttr = exportOp.getWorkgroupLocalMemoryAttr(); |
| if (localMemorySizeAttr) { |
| funcOp.setReflectionAttr("local_memory", localMemorySizeAttr); |
| } |
| } |
| |
| // Serialize the VM module to bytes and embed it directly. |
| SmallVector<char> moduleData; |
| { |
| auto vmOptions = getTargetOptions(variantOp.getTargetAttr()); |
| // TODO(benvanik): plumb this through somewhere? these options are mostly |
| // about output format stuff such as debug information so it's probably |
| // fine to share. |
| auto bytecodeOptions = IREE::VM::BytecodeTargetOptions::FromFlags::get(); |
| llvm::raw_svector_ostream stream(moduleData); |
| if (failed(translateModuleToBytecode(variantOp.getInnerModule(), |
| vmOptions, bytecodeOptions, |
| stream))) { |
| return variantOp.emitOpError() |
| << "failed to serialize VM bytecode module"; |
| } |
| } |
| if (!serOptions.dumpBinariesPath.empty()) { |
| dumpDataToPath<char>(serOptions.dumpBinariesPath, serOptions.dumpBaseName, |
| variantOp.getName(), ".vmfb", moduleData); |
| } |
| |
| auto bufferAttr = DenseIntElementsAttr::get( |
| VectorType::get({static_cast<int64_t>(moduleData.size())}, |
| IntegerType::get(executableBuilder.getContext(), 8)), |
| std::move(moduleData)); |
| |
| // Add the binary data to the target executable. |
| // NOTE: this snapshots the FlatBuffer builder data at the time it is called |
| // and future changes to the target op will not be observed. |
| auto binaryOp = executableBuilder.create<IREE::HAL::ExecutableBinaryOp>( |
| variantOp.getLoc(), variantOp.getSymName(), |
| variantOp.getTarget().getFormat(), bufferAttr); |
| binaryOp.setMimeTypeAttr( |
| executableBuilder.getStringAttr("application/x-flatbuffers")); |
| |
| return success(); |
| } |
| |
| private: |
| const VMVXOptions &options; |
| }; |
| |
| class VMVXInlineTargetDevice final : public TargetDevice { |
| public: |
| VMVXInlineTargetDevice() = default; |
| |
| IREE::HAL::DeviceTargetAttr |
| getDefaultDeviceTarget(MLIRContext *context, |
| const TargetRegistry &targetRegistry) const override { |
| Builder b(context); |
| SmallVector<NamedAttribute> configItems; |
| |
| auto configAttr = b.getDictionaryAttr(configItems); |
| |
| // If we had multiple target environments we would generate one target attr |
| // per environment, with each setting its own environment attribute. |
| SmallVector<IREE::HAL::ExecutableTargetAttr> executableTargetAttrs; |
| targetRegistry.getTargetBackend("vmvx-inline") |
| ->getDefaultExecutableTargets(context, "vmvx-inline", configAttr, |
| executableTargetAttrs); |
| |
| return IREE::HAL::DeviceTargetAttr::get(context, |
| b.getStringAttr("vmvx-inline"), |
| configAttr, executableTargetAttrs); |
| } |
| }; |
| |
| class VMVXInlineTargetBackend final : public TargetBackend { |
| public: |
| VMVXInlineTargetBackend(const VMVXOptions &options) : options(options) {} |
| |
| std::string getLegacyDefaultDeviceID() const override { |
| return "vmvx-inline"; |
| } |
| |
| void getDefaultExecutableTargets( |
| MLIRContext *context, StringRef deviceID, DictionaryAttr deviceConfigAttr, |
| SmallVectorImpl<IREE::HAL::ExecutableTargetAttr> &executableTargetAttrs) |
| const override { |
| executableTargetAttrs.push_back(getVMVXExecutableTarget( |
| options.enableMicrokernels, context, "vmvx-inline", "vmvx-ir")); |
| } |
| |
| void getDependentDialects(DialectRegistry ®istry) const override { |
| registry |
| .insert<IREE::Codegen::IREECodegenDialect, IREE::VMVX::VMVXDialect>(); |
| } |
| |
| void buildConfigurationPassPipeline(IREE::HAL::ExecutableVariantOp variantOp, |
| OpPassManager &passManager) override { |
| IREE::VMVX::buildVMVXConfigurationPassPipeline(passManager); |
| } |
| |
| void buildTranslationPassPipeline(IREE::HAL::ExecutableVariantOp variantOp, |
| OpPassManager &passManager) override { |
| IREE::VMVX::buildVMVXTransformPassPipeline(passManager); |
| } |
| |
| private: |
| const VMVXOptions &options; |
| }; |
| |
| namespace { |
| struct VMVXSession |
| : public PluginSession<VMVXSession, VMVXOptions, |
| PluginActivationPolicy::DefaultActivated> { |
| void populateHALTargetDevices(IREE::HAL::TargetDeviceList &targets) { |
| // TODO(benvanik): move to a CPU device registration outside of VMVX. Note |
| // that the inline device does need to be special. |
| // #hal.device.target<"vmvx", ... |
| targets.add("vmvx", [&]() { return std::make_shared<VMVXTargetDevice>(); }); |
| // #hal.device.target<"vmvx-inline", ... |
| targets.add("vmvx-inline", |
| [&]() { return std::make_shared<VMVXInlineTargetDevice>(); }); |
| } |
| void populateHALTargetBackends(IREE::HAL::TargetBackendList &targets) { |
| // #hal.executable.target<"vmvx", ... |
| targets.add("vmvx", |
| [&]() { return std::make_shared<VMVXTargetBackend>(options); }); |
| // #hal.executable.target<"vmvx-inline", ... |
| targets.add("vmvx-inline", [&]() { |
| return std::make_shared<VMVXInlineTargetBackend>(options); |
| }); |
| } |
| }; |
| |
| } // namespace |
| |
| } // namespace mlir::iree_compiler::IREE::HAL |
| |
| extern "C" bool iree_register_compiler_plugin_hal_target_vmvx( |
| mlir::iree_compiler::PluginRegistrar *registrar) { |
| registrar->registerPlugin<mlir::iree_compiler::IREE::HAL::VMVXSession>( |
| "hal_target_vmvx"); |
| return true; |
| } |
| |
| IREE_DEFINE_COMPILER_OPTION_FLAGS(mlir::iree_compiler::IREE::HAL::VMVXOptions); |