Split LLVMTarget into LLVMIRTarget and (WIP) LLVMAOTTarget.

Depends on https://github.com/google/iree/pull/2099.

This starts on a new compiler target for LLVM that mimics LLVMIR except instead of targeting the llvmjit (just-in-time) HAL, it targets dylib (Dynamic Library) with ahead-of-time compiled code. It doesn't yet compile code ahead of time, but it does plug into the dylib HAL after running the same set of passes as LLVMIR.

Closes https://github.com/google/iree/pull/2101

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/iree/pull/2101 from ScottTodd:llvm-aot 6dd719706d56606be24ef10b9a8a2b0a7b60ff08
PiperOrigin-RevId: 314943677
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bcb4876..1495026 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -95,6 +95,7 @@
 
 # List of all target backends to be built by default:
 set(IREE_ALL_TARGET_BACKENDS
+  # TODO(scotttodd): LLVMAOT
   LLVMIR
   Vulkan_SPIRV
   VMLA
diff --git a/bindings/python/pyiree/compiler/BUILD b/bindings/python/pyiree/compiler/BUILD
index f759e2b..bbe390b 100644
--- a/bindings/python/pyiree/compiler/BUILD
+++ b/bindings/python/pyiree/compiler/BUILD
@@ -41,7 +41,8 @@
 
     # Targets.
     "//iree/compiler/Dialect/HAL/Target/VMLA",
-    "//iree/compiler/Dialect/HAL/Target/LLVM",
+    "//iree/compiler/Dialect/HAL/Target/LLVM:LLVMAOT",
+    "//iree/compiler/Dialect/HAL/Target/LLVM:LLVMIR",
     "//iree/compiler/Dialect/HAL/Target/VulkanSPIRV",
     "//iree/compiler/Dialect/VM/Target:init_targets",
     "//iree/compiler/Dialect/VM/Target/Bytecode",
diff --git a/bindings/python/pyiree/compiler/CMakeLists.txt b/bindings/python/pyiree/compiler/CMakeLists.txt
index 22269ad..35dbc6f 100644
--- a/bindings/python/pyiree/compiler/CMakeLists.txt
+++ b/bindings/python/pyiree/compiler/CMakeLists.txt
@@ -53,7 +53,8 @@
     iree::compiler::Dialect::VM::Transforms
     # Targets. Adopted from the Bazel variable COMPILER_DEPS.
     iree::compiler::Dialect::HAL::Target::VMLA
-    iree::compiler::Dialect::HAL::Target::LLVM
+    iree::compiler::Dialect::HAL::Target::LLVM::LLVMAOT
+    iree::compiler::Dialect::HAL::Target::LLVM::LLVMIR
     iree::compiler::Dialect::HAL::Target::VulkanSPIRV
     iree::compiler::Dialect::VM::Target::Bytecode
     iree::compiler::Dialect::VM::Target::init_targets
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/BUILD b/iree/compiler/Dialect/HAL/Target/LLVM/BUILD
index 79ff644..f172340 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/BUILD
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/BUILD
@@ -18,12 +18,12 @@
 )
 
 cc_library(
-    name = "LLVM",
+    name = "LLVMIR",
     srcs = [
-        "LLVMTarget.cpp",
+        "LLVMIRTarget.cpp",
     ],
     hdrs = [
-        "LLVMTarget.h",
+        "LLVMIRTarget.h",
     ],
     deps = [
         ":LLVMIRPasses",
@@ -33,9 +33,29 @@
         "//iree/schemas:llvmir_executable_def_cc_fbs",
         "@llvm-project//llvm:core",
         "@llvm-project//llvm:support",
-        "@llvm-project//mlir:TargetLLVMIR",
         # TODO(ataei): Link with native target dep.
         "@llvm-project//llvm:x86_code_gen",
+        "@llvm-project//mlir:TargetLLVMIR",
+    ],
+)
+
+cc_library(
+    name = "LLVMAOT",
+    srcs = [
+        "LLVMAOTTarget.cpp",
+    ],
+    hdrs = [
+        "LLVMAOTTarget.h",
+    ],
+    deps = [
+        ":LLVMIRPasses",
+        ":LLVMTargetOptions",
+        "//iree/compiler/Conversion/LinalgToLLVM",
+        "//iree/compiler/Dialect/HAL/Target",
+        "//iree/schemas:dylib_executable_def_cc_fbs",
+        "@llvm-project//llvm:core",
+        "@llvm-project//llvm:support",
+        "@llvm-project//mlir:TargetLLVMIR",
     ],
 )
 
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt b/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt
index caeb686..3916453 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt
@@ -16,11 +16,11 @@
 
 iree_cc_library(
   NAME
-    LLVM
+    LLVMIR
   HDRS
-    "LLVMTarget.h"
+    "LLVMIRTarget.h"
   SRCS
-    "LLVMTarget.cpp"
+    "LLVMIRTarget.cpp"
   DEPS
     ::LLVMIRPasses
     ::LLVMTargetOptions
@@ -36,6 +36,25 @@
 
 iree_cc_library(
   NAME
+    LLVMAOT
+  HDRS
+    "LLVMAOTTarget.h"
+  SRCS
+    "LLVMAOTTarget.cpp"
+  DEPS
+    ::LLVMIRPasses
+    ::LLVMTargetOptions
+    LLVMCore
+    LLVMSupport
+    MLIRTargetLLVMIR
+    iree::compiler::Conversion::LinalgToLLVM
+    iree::compiler::Dialect::HAL::Target
+    iree::schemas::dylib_executable_def_cc_fbs
+  PUBLIC
+)
+
+iree_cc_library(
+  NAME
     LLVMIRPasses
   HDRS
     "LLVMIRPasses.h"
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.cpp
new file mode 100644
index 0000000..0680e57
--- /dev/null
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.cpp
@@ -0,0 +1,112 @@
+// Copyright 2020 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/HAL/Target/LLVM/LLVMAOTTarget.h"
+
+#include "iree/compiler/Conversion/LinalgToLLVM/Passes.h"
+#include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h"
+#include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h"
+#include "iree/schemas/dylib_executable_def_generated.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Support/Mutex.h"
+#include "llvm/Support/TargetSelect.h"
+#include "mlir/Target/LLVMIR.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace HAL {
+
+class LLVMAOTTargetBackend final : public TargetBackend {
+ public:
+  LLVMAOTTargetBackend(LLVMTargetOptions options)
+      : options_(std::move(options)) {}
+
+  // NOTE: we could vary this based on the options, such as by arch/etc.
+  std::string name() const override { return "dylib*"; }
+
+  void buildTranslationPassPipeline(ExecutableTargetOp targetOp,
+                                    OpPassManager& passManager) override {
+    buildLLVMTransformPassPipeline(passManager);
+  }
+
+  LogicalResult serializeExecutable(IREE::HAL::ExecutableTargetOp targetOp,
+                                    OpBuilder& executableBuilder) override {
+    // LLVM is not thread safe and currently translation shares an LLVMContext.
+    // Since we serialize executables from multiple threads we have to take a
+    // global lock here.
+    static llvm::sys::SmartMutex<true> mutex;
+    llvm::sys::SmartScopedLock<true> lock(mutex);
+
+    iree::DyLibExecutableDefT dyLibExecutableDef;
+
+    // At this moment we are leaving MLIR LLVM dialect land translating module
+    // into target independent LLVMIR.
+    auto llvmModule = mlir::translateModuleToLLVMIR(targetOp.getInnerModule());
+
+    // Create invocation function an populate entry_points.
+    auto executableOp = cast<ExecutableOp>(targetOp.getParentOp());
+    auto entryPointOps =
+        executableOp.getBlock().getOps<ExecutableEntryPointOp>();
+    const bool addCInterface = true;
+    for (auto entryPointOp : entryPointOps) {
+      std::string funcName =
+          addCInterface ? "_mlir_ciface_" + std::string(entryPointOp.sym_name())
+                        : std::string(entryPointOp.sym_name());
+      dyLibExecutableDef.entry_points.push_back(funcName);
+      createLLVMInvocationFunc(funcName, llvmModule.get());
+    }
+
+    if (!llvmModule) {
+      return failure();
+    }
+
+    // TODO(scotttodd): LLVM AOT compilation to
+    //   dyLibExecutableDef.library_embedded.assign(...)
+
+    ::flatbuffers::FlatBufferBuilder fbb;
+    auto executableOffset =
+        iree::DyLibExecutableDef::Pack(fbb, &dyLibExecutableDef);
+    iree::FinishDyLibExecutableDefBuffer(fbb, executableOffset);
+    std::vector<uint8_t> bytes;
+    bytes.resize(fbb.GetSize());
+    std::memcpy(bytes.data(), fbb.GetBufferPointer(), bytes.size());
+
+    // Add the binary data to the target executable.
+    executableBuilder.create<IREE::HAL::ExecutableBinaryOp>(
+        targetOp.getLoc(),
+        static_cast<uint32_t>(IREE::HAL::ExecutableFormat::DyLib),
+        std::move(bytes));
+
+    return success();
+  }
+
+ private:
+  LLVMTargetOptions options_;
+};
+
+void registerLLVMAOTTargetBackends(
+    std::function<LLVMTargetOptions()> queryOptions) {
+  getLLVMTargetOptionsFromFlags();
+  static TargetBackendRegistration registration("dylib-llvm-aot", [=]() {
+    // Initalize registered targets.
+    llvm::InitializeNativeTarget();
+    return std::make_unique<LLVMAOTTargetBackend>(queryOptions());
+  });
+}
+
+}  // namespace HAL
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTarget.h b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.h
similarity index 75%
rename from iree/compiler/Dialect/HAL/Target/LLVM/LLVMTarget.h
rename to iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.h
index def1117..305d543 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTarget.h
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.h
@@ -13,19 +13,18 @@
 // limitations under the License.
 //
 
-#ifndef IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_TARGET_H_
-#define IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_TARGET_H_
+#ifndef IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_AOT_TARGET_H_
+#define IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_AOT_TARGET_H_
 
 #include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMTargetOptions.h"
-#include "iree/compiler/Dialect/HAL/Target/TargetBackend.h"
 
 namespace mlir {
 namespace iree_compiler {
 namespace IREE {
 namespace HAL {
 
-// Registers the LLVM backends.
-void registerLLVMTargetBackends(
+// Registers the LLVM Ahead-Of-Time (AOT) target backends.
+void registerLLVMAOTTargetBackends(
     std::function<LLVMTargetOptions()> queryOptions);
 
 }  // namespace HAL
@@ -33,4 +32,4 @@
 }  // namespace iree_compiler
 }  // namespace mlir
 
-#endif  // IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_TARGET_H_
+#endif  // IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_AOT_TARGET_H_
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.cpp
index 336b750..ede5f9d 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.cpp
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.cpp
@@ -14,6 +14,7 @@
 
 #include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h"
 
+#include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/Module.h"
 #include "llvm/IR/PassManager.h"
 #include "llvm/IR/Verifier.h"
@@ -42,6 +43,44 @@
   return machine;
 }
 
+void createLLVMInvocationFunc(const std::string& name, llvm::Module* module) {
+  // TODO(ataei): This is written as a stub in LLVM IR. It would be easier to
+  // have this using MLIR and lower it to LLVM like the dispatch function
+  // implementation is.
+
+  auto& ctx = module->getContext();
+  llvm::IRBuilder<> builder(ctx);
+  auto var_func = module->getFunction(name);
+
+  auto new_type = llvm::FunctionType::get(
+      builder.getVoidTy(), builder.getInt8PtrTy()->getPointerTo(),
+      /*isVarArg=*/false);
+
+  auto new_name = "invoke_" + name;
+  auto func_cst = module->getOrInsertFunction(new_name, new_type);
+  llvm::Function* interface_func =
+      llvm::cast<llvm::Function>(func_cst.getCallee());
+
+  auto bb = llvm::BasicBlock::Create(ctx);
+  bb->insertInto(interface_func);
+  builder.SetInsertPoint(bb);
+  llvm::Value* argList = interface_func->arg_begin();
+  llvm::SmallVector<llvm::Value*, 8> args;
+  args.reserve(llvm::size(var_func->args()));
+  for (auto& indexedArg : llvm::enumerate(var_func->args())) {
+    llvm::Value* arg_index = llvm::Constant::getIntegerValue(
+        builder.getInt64Ty(), llvm::APInt(64, indexedArg.index()));
+    llvm::Value* arg_ptr_ptr = builder.CreateGEP(argList, arg_index);
+    llvm::Value* arg_ptr = builder.CreateLoad(arg_ptr_ptr);
+    arg_ptr = builder.CreateBitCast(
+        arg_ptr, indexedArg.value().getType()->getPointerTo());
+    llvm::Value* arg = builder.CreateLoad(arg_ptr);
+    args.push_back(arg);
+  }
+  builder.CreateCall(var_func, args);
+  builder.CreateRetVoid();
+}
+
 LogicalResult runLLVMIRPasses(const LLVMTargetOptions& options,
                               std::unique_ptr<llvm::TargetMachine> machine,
                               llvm::Module* module) {
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h
index 6a3e6dd..99fa937 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h
@@ -31,6 +31,9 @@
 std::unique_ptr<llvm::TargetMachine> createTargetMachine(
     const LLVMTargetOptions& options);
 
+// Creates an invocation function in a module for the given function name.
+void createLLVMInvocationFunc(const std::string& name, llvm::Module* module);
+
 // Creates and runs LLVMIR optimization passes defined in LLVMTargetOptions.
 LogicalResult runLLVMIRPasses(const LLVMTargetOptions& options,
                               std::unique_ptr<llvm::TargetMachine> machine,
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTarget.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRTarget.cpp
similarity index 69%
rename from iree/compiler/Dialect/HAL/Target/LLVM/LLVMTarget.cpp
rename to iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRTarget.cpp
index 4efe5b7..60c8647 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTarget.cpp
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRTarget.cpp
@@ -12,14 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMTarget.h"
+#include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRTarget.h"
 
 #include "iree/compiler/Conversion/LinalgToLLVM/Passes.h"
 #include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h"
 #include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h"
 #include "iree/schemas/llvmir_executable_def_generated.h"
-#include "llvm/ADT/DenseMap.h"
-#include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/Module.h"
 #include "llvm/Support/Mutex.h"
 #include "llvm/Support/TargetSelect.h"
@@ -30,55 +28,15 @@
 namespace IREE {
 namespace HAL {
 
-// TODO(ataei): This is written as a stub in LLVM IR. It would be easier to have
-// this using MLIR and lower it to LLVM like the dispatch function
-// implementation is.
-static void createInvocationFunc(const std::string& name,
-                                 llvm::Module* module) {
-  auto& ctx = module->getContext();
-  llvm::IRBuilder<> builder(ctx);
-  auto var_func = module->getFunction(name);
-
-  auto new_type = llvm::FunctionType::get(
-      builder.getVoidTy(), builder.getInt8PtrTy()->getPointerTo(),
-      /*isVarArg=*/false);
-
-  auto new_name = "invoke_" + name;
-  auto func_cst = module->getOrInsertFunction(new_name, new_type);
-  llvm::Function* interface_func =
-      llvm::cast<llvm::Function>(func_cst.getCallee());
-
-  auto bb = llvm::BasicBlock::Create(ctx);
-  bb->insertInto(interface_func);
-  builder.SetInsertPoint(bb);
-  llvm::Value* argList = interface_func->arg_begin();
-  llvm::SmallVector<llvm::Value*, 8> args;
-  args.reserve(llvm::size(var_func->args()));
-  for (auto& indexedArg : llvm::enumerate(var_func->args())) {
-    llvm::Value* arg_index = llvm::Constant::getIntegerValue(
-        builder.getInt64Ty(), llvm::APInt(64, indexedArg.index()));
-    llvm::Value* arg_ptr_ptr = builder.CreateGEP(argList, arg_index);
-    llvm::Value* arg_ptr = builder.CreateLoad(arg_ptr_ptr);
-    arg_ptr = builder.CreateBitCast(
-        arg_ptr, indexedArg.value().getType()->getPointerTo());
-    llvm::Value* arg = builder.CreateLoad(arg_ptr);
-    args.push_back(arg);
-  }
-  builder.CreateCall(var_func, args);
-  builder.CreateRetVoid();
-}
-
 class LLVMIRTargetBackend final : public TargetBackend {
  public:
   LLVMIRTargetBackend(LLVMTargetOptions options)
       : options_(std::move(options)) {}
 
   // NOTE: we could vary this based on the options, such as by arch/etc.
-  std::string name() const override { return "llvm*"; }
+  std::string name() const override { return "llvm-ir*"; }
 
-  // Adds a sequence of passess to a given pass manager that progressively lower
-  // from HLO to LLVM throught linalg dialect.
-  void buildTranslationPassPipeline(IREE::HAL::ExecutableTargetOp targetOp,
+  void buildTranslationPassPipeline(ExecutableTargetOp targetOp,
                                     OpPassManager& passManager) override {
     buildLLVMTransformPassPipeline(passManager);
   }
@@ -106,7 +64,7 @@
           addCInterface ? "_mlir_ciface_" + std::string(entryPointOp.sym_name())
                         : std::string(entryPointOp.sym_name());
       llvmIrExecutableDef.entry_points.push_back(funcName);
-      createInvocationFunc(funcName, llvmModule.get());
+      createLLVMInvocationFunc(funcName, llvmModule.get());
     }
 
     // LLVMIR opt passes.
@@ -153,7 +111,7 @@
   LLVMTargetOptions options_;
 };
 
-void registerLLVMTargetBackends(
+void registerLLVMIRTargetBackends(
     std::function<LLVMTargetOptions()> queryOptions) {
   getLLVMTargetOptionsFromFlags();
   static TargetBackendRegistration registration("llvm-ir", [=]() {
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTarget.h b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRTarget.h
similarity index 75%
copy from iree/compiler/Dialect/HAL/Target/LLVM/LLVMTarget.h
copy to iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRTarget.h
index def1117..6a561be 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTarget.h
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRTarget.h
@@ -13,19 +13,18 @@
 // limitations under the License.
 //
 
-#ifndef IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_TARGET_H_
-#define IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_TARGET_H_
+#ifndef IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_IR_TARGET_H_
+#define IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_IR_TARGET_H_
 
 #include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMTargetOptions.h"
-#include "iree/compiler/Dialect/HAL/Target/TargetBackend.h"
 
 namespace mlir {
 namespace iree_compiler {
 namespace IREE {
 namespace HAL {
 
-// Registers the LLVM backends.
-void registerLLVMTargetBackends(
+// Registers the LLVM IR target backends.
+void registerLLVMIRTargetBackends(
     std::function<LLVMTargetOptions()> queryOptions);
 
 }  // namespace HAL
@@ -33,4 +32,4 @@
 }  // namespace iree_compiler
 }  // namespace mlir
 
-#endif  // IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_TARGET_H_
+#endif  // IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_IR_TARGET_H_
diff --git a/iree/hal/llvmjit/llvmjit_driver.cc b/iree/hal/llvmjit/llvmjit_driver.cc
index da4c6b1..e0b0cae 100644
--- a/iree/hal/llvmjit/llvmjit_driver.cc
+++ b/iree/hal/llvmjit/llvmjit_driver.cc
@@ -30,7 +30,7 @@
   // supported_features |= DeviceFeature::kDebugging;
   // supported_features |= DeviceFeature::kCoverage;
   // supported_features |= DeviceFeature::kProfiling;
-  DeviceInfo device_info("llvmjit", "llvm", supported_features);
+  DeviceInfo device_info("llvm-ir-jit", "llvm", supported_features);
   // TODO(benvanik): device info.
   return device_info;
 }
diff --git a/iree/tools/BUILD b/iree/tools/BUILD
index 1ebd1ee..fcbf7c4 100644
--- a/iree/tools/BUILD
+++ b/iree/tools/BUILD
@@ -141,7 +141,8 @@
     name = "init_targets",
     hdrs = ["init_targets.h"],
     deps = [
-        "//iree/compiler/Dialect/HAL/Target/LLVM",
+        "//iree/compiler/Dialect/HAL/Target/LLVM:LLVMAOT",
+        "//iree/compiler/Dialect/HAL/Target/LLVM:LLVMIR",
         "//iree/compiler/Dialect/HAL/Target/VMLA",
         "//iree/compiler/Dialect/HAL/Target/VulkanSPIRV",
     ],
diff --git a/iree/tools/CMakeLists.txt b/iree/tools/CMakeLists.txt
index 0e5e43a..f06bba2 100644
--- a/iree/tools/CMakeLists.txt
+++ b/iree/tools/CMakeLists.txt
@@ -182,7 +182,8 @@
     HDRS
       "init_targets.h"
     DEPS
-      iree::compiler::Dialect::HAL::Target::LLVM
+      iree::compiler::Dialect::HAL::Target::LLVM::LLVMAOT
+      iree::compiler::Dialect::HAL::Target::LLVM::LLVMIR
       iree::compiler::Dialect::HAL::Target::VMLA
       iree::compiler::Dialect::HAL::Target::VulkanSPIRV
     PUBLIC
diff --git a/iree/tools/init_targets.h b/iree/tools/init_targets.h
index 4cda437..6f1b11f 100644
--- a/iree/tools/init_targets.h
+++ b/iree/tools/init_targets.h
@@ -15,7 +15,8 @@
 #ifndef IREE_TOOLS_INIT_TARGETS_H_
 #define IREE_TOOLS_INIT_TARGETS_H_
 
-#include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMTarget.h"
+#include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.h"
+#include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRTarget.h"
 #include "iree/compiler/Dialect/HAL/Target/VMLA/VMLATarget.h"
 #include "iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.h"
 
@@ -28,7 +29,9 @@
 // need.
 inline void registerHALTargetBackends() {
   static bool init_once = []() {
-    IREE::HAL::registerLLVMTargetBackends(
+    IREE::HAL::registerLLVMAOTTargetBackends(
+        []() { return IREE::HAL::getLLVMTargetOptionsFromFlags(); });
+    IREE::HAL::registerLLVMIRTargetBackends(
         []() { return IREE::HAL::getLLVMTargetOptionsFromFlags(); });
     IREE::HAL::registerVMLATargetBackends(
         []() { return IREE::HAL::getVMLATargetOptionsFromFlags(); });
diff --git a/scripts/update_op_coverage.py b/scripts/update_op_coverage.py
index da2e3f7..e9e83de 100755
--- a/scripts/update_op_coverage.py
+++ b/scripts/update_op_coverage.py
@@ -29,6 +29,7 @@
 
 E2E_XLA_OPS_PATH = 'iree/test/e2e/xla_ops'
 
+# TODO(scotttodd): LLVM AOT (dylib-llvm-aot) HAL target(s)
 OP_COVERAGE_DESCRIPTION = """# HLO Op Coverage
 There are three backend [targets](https://github.com/google/iree/tree/master/iree/compiler/Dialect/HAL/Target) in IREE: