LLVMAOTTarget compilation and linking
- Introduce FLAGS for target compilation.
- Compiles LLVMIR module into target object.
- Link target object using target toolchain binaries provided via IREE_LLVMAOT_LINKER_PATH environment variable to produce target dylib.
- If target IREE_LLVMAOT_LINKER_PATH isn't provided we call ELFLinker (Not implemented yet)

PiperOrigin-RevId: 319160133
diff --git a/bindings/python/build_defs.oss.bzl b/bindings/python/build_defs.oss.bzl
index d8cf789..7e51827 100644
--- a/bindings/python/build_defs.oss.bzl
+++ b/bindings/python/build_defs.oss.bzl
@@ -22,6 +22,7 @@
 NUMPY_DEPS = []
 PLATFORM_VULKAN_DEPS = _PLATFORM_VULKAN_DEPS
 PYTHON_HEADERS_DEPS = ["@iree_native_python//:python_headers"]
+PYTHON_CPP_EXTRA_DEPS = []
 
 PYBIND_COPTS = [
     "-fexceptions",
diff --git a/bindings/python/pyiree/compiler/BUILD b/bindings/python/pyiree/compiler/BUILD
index a6560f9..3f4cf09 100644
--- a/bindings/python/pyiree/compiler/BUILD
+++ b/bindings/python/pyiree/compiler/BUILD
@@ -19,6 +19,7 @@
     "PYBIND_EXTENSION_COPTS",
     "PYBIND_FEATURES",
     "PYBIND_REGISTER_MLIR_PASSES",
+    "PYTHON_CPP_EXTRA_DEPS",
     "iree_py_extension",
     "iree_py_library",
     "iree_py_test",
@@ -54,7 +55,7 @@
         "__init__.py",
     ],
     srcs_version = "PY3",
-    deps = [
+    deps = PYTHON_CPP_EXTRA_DEPS + [
         ":binding",
         "//bindings/python:pathsetup",  # build_cleaner: keep
     ],
@@ -89,6 +90,7 @@
         "//iree/tools:init_compiler_modules",
         "//iree/tools:init_iree_passes_and_dialects",
         "//iree/tools:init_mlir_passes_and_dialects",
+        "//iree/base:localfile",
         "//iree/tools:init_targets",
         "@llvm-project//llvm:Support",
         "@llvm-project//mlir:AllPassesAndDialectsNoRegistration",
diff --git a/bindings/python/pyiree/rt/BUILD b/bindings/python/pyiree/rt/BUILD
index fb00167..db48f43 100644
--- a/bindings/python/pyiree/rt/BUILD
+++ b/bindings/python/pyiree/rt/BUILD
@@ -82,6 +82,7 @@
     deps = [
         "//bindings/python/pyiree/common",
         "//iree/base:api",
+        "//iree/base:localfile",
         "//iree/base:signature_mangle",
         "//iree/hal:api",
         "//iree/modules/hal",
diff --git a/build_tools/bazel/build_bindings.sh b/build_tools/bazel/build_bindings.sh
index 56aef23..e79cade 100755
--- a/build_tools/bazel/build_bindings.sh
+++ b/build_tools/bazel/build_bindings.sh
@@ -39,9 +39,11 @@
 if ! [[ -v IREE_VULKAN_DISABLE ]]; then
   IREE_VULKAN_DISABLE=1
 fi
+
 declare -a test_env_args=(
   --test_env=IREE_LLVMJIT_DISABLE=$IREE_LLVMJIT_DISABLE
   --test_env=IREE_VULKAN_DISABLE=$IREE_VULKAN_DISABLE
+  --test_env=IREE_LLVMAOT_LINKER_PATH
 )
 
 declare -a default_build_tag_filters=("-nokokoro")
diff --git a/build_tools/cmake/iree_copts.cmake b/build_tools/cmake/iree_copts.cmake
index 542536b..63776ef 100644
--- a/build_tools/cmake/iree_copts.cmake
+++ b/build_tools/cmake/iree_copts.cmake
@@ -149,7 +149,8 @@
 set(LLVM_ENABLE_IDE ON CACHE BOOL "" FORCE)
 set(LLVM_ENABLE_RTTI ON CACHE BOOL "" FORCE)
 
-set(LLVM_TARGETS_TO_BUILD "WebAssembly;X86" CACHE STRING "" FORCE)
+# TODO(ataei): Use optional build time targets selection for LLVMAOT.
+set(LLVM_TARGETS_TO_BUILD "WebAssembly;X86;ARM;AArch64" CACHE STRING "" FORCE)
 
 set(LLVM_ENABLE_PROJECTS "mlir" CACHE STRING "" FORCE)
 set(LLVM_ENABLE_BINDINGS OFF CACHE BOOL "" FORCE)
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/BUILD b/iree/compiler/Dialect/HAL/Target/LLVM/BUILD
index ee7d12f..20d26ae 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/BUILD
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/BUILD
@@ -11,6 +11,8 @@
 # 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.
+#
+load("//iree:build_defs.oss.bzl", "platform_trampoline_deps")
 
 package(
     default_visibility = ["//visibility:public"],
@@ -48,13 +50,20 @@
         "LLVMAOTTarget.h",
     ],
     deps = [
+        ":LLVMAOTTargetLinker",
         ":LLVMIRPasses",
         ":LLVMTargetOptions",
         "//iree/compiler/Conversion/LinalgToLLVM",
         "//iree/compiler/Dialect/HAL/Target",
         "//iree/schemas:dylib_executable_def_cc_fbs",
+        "@llvm-project//llvm:AArch64AsmParser",
+        "@llvm-project//llvm:AArch64CodeGen",
+        "@llvm-project//llvm:ARMAsmParser",
+        "@llvm-project//llvm:ARMCodeGen",
         "@llvm-project//llvm:Core",
         "@llvm-project//llvm:Support",
+        "@llvm-project//llvm:X86AsmParser",
+        "@llvm-project//llvm:X86CodeGen",
         "@llvm-project//mlir:TargetLLVMIR",
     ],
 )
@@ -88,5 +97,23 @@
     deps = [
         "@llvm-project//llvm:Passes",
         "@llvm-project//llvm:Support",
+        "@llvm-project//llvm:Target",
+    ],
+)
+
+cc_library(
+    name = "LLVMAOTTargetLinker",
+    hdrs = ["LLVMAOTTargetLinker.h"],
+    deps = [
+        "//iree/base:file_io",
+    ] + platform_trampoline_deps("LLVMAOTTargetLinker", "compiler/Dialect/HAL/Target/LLVM"),
+)
+
+cc_library(
+    name = "LLVMAOTTargetLinker_hdrs",
+    hdrs = ["LLVMAOTTargetLinker.h"],
+    deps = [
+        ":LLVMTargetOptions",
+        "//iree/base:file_io",
     ],
 )
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt b/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt
index 3916453..4aa0ad3 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt
@@ -42,10 +42,17 @@
   SRCS
     "LLVMAOTTarget.cpp"
   DEPS
+    ::LLVMAOTTargetLinker
     ::LLVMIRPasses
     ::LLVMTargetOptions
+    LLVMAArch64AsmParser
+    LLVMAArch64CodeGen
+    LLVMARMAsmParser
+    LLVMARMCodeGen
     LLVMCore
     LLVMSupport
+    LLVMX86AsmParser
+    LLVMX86CodeGen
     MLIRTargetLLVMIR
     iree::compiler::Conversion::LinalgToLLVM
     iree::compiler::Dialect::HAL::Target
@@ -80,5 +87,28 @@
   DEPS
     LLVMPasses
     LLVMSupport
+    LLVMTarget
+  PUBLIC
+)
+
+iree_cc_library(
+  NAME
+    LLVMAOTTargetLinker
+  HDRS
+    "LLVMAOTTargetLinker.h"
+  DEPS
+    iree::base::file_io
+    iree::compiler::Dialect::HAL::Target::LLVM::internal::LLVMAOTTargetLinker_internal
+  PUBLIC
+)
+
+iree_cc_library(
+  NAME
+    LLVMAOTTargetLinker_hdrs
+  HDRS
+    "LLVMAOTTargetLinker.h"
+  DEPS
+    ::LLVMTargetOptions
+    iree::base::file_io
   PUBLIC
 )
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.cpp
index c2b8d9f..7269089 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.cpp
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.cpp
@@ -14,7 +14,10 @@
 
 #include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.h"
 
+#include <cstdlib>
+
 #include "iree/compiler/Conversion/LinalgToLLVM/Passes.h"
+#include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTargetLinker.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"
@@ -54,6 +57,9 @@
     // At this moment we are leaving MLIR LLVM dialect land translating module
     // into target independent LLVMIR.
     auto llvmModule = mlir::translateModuleToLLVMIR(targetOp.getInnerModule());
+    if (!llvmModule) {
+      return failure();
+    }
 
     // Create invocation function an populate entry_points.
     auto executableOp = cast<ExecutableOp>(targetOp.getParentOp());
@@ -64,16 +70,55 @@
       std::string funcName =
           addCInterface ? "_mlir_ciface_" + std::string(entryPointOp.sym_name())
                         : std::string(entryPointOp.sym_name());
-      dyLibExecutableDef.entry_points.push_back(funcName);
+      dyLibExecutableDef.entry_points.push_back("invoke_" + funcName);
       createLLVMInvocationFunc(funcName, llvmModule.get());
     }
 
-    if (!llvmModule) {
+    // LLVMIR opt passes.
+    auto targetMachine = createTargetMachine(options_);
+    if (!targetMachine) {
+      targetOp.emitError("Can't create target machine for target triple: " +
+                         options_.targetTriple);
       return failure();
     }
 
-    // TODO(scotttodd): LLVM AOT compilation to
-    //   dyLibExecutableDef.library_embedded.assign(...)
+    llvmModule->setDataLayout(targetMachine->createDataLayout());
+    llvmModule->setTargetTriple(targetMachine->getTargetTriple().str());
+
+    if (failed(
+            runLLVMIRPasses(options_, targetMachine.get(), llvmModule.get()))) {
+      return targetOp.emitError(
+          "Can't build LLVMIR opt passes for ExecutableOp module");
+    }
+
+    std::string objData;
+    if (failed(runEmitObjFilePasses(targetMachine.get(), llvmModule.get(),
+                                    &objData))) {
+      return targetOp.emitError("Can't compile LLVMIR module to an obj");
+    }
+
+    std::string sharedLibData;
+    const char* linkerToolPath = std::getenv("IREE_LLVMAOT_LINKER_PATH");
+    if (linkerToolPath != nullptr) {
+      auto sharedLibDataStatus = linkLLVMAOTObjects(linkerToolPath, objData);
+      if (!sharedLibDataStatus.ok()) {
+        return targetOp.emitError(
+            "Can't link executable and generate target dylib, using linker "
+            "toolchain: '" +
+            std::string(linkerToolPath) + "'");
+      }
+      sharedLibData = sharedLibDataStatus.value();
+    } else {
+      auto sharedLibDataStatus = linkLLVMAOTObjectsWithLLDElf(objData);
+      if (!sharedLibDataStatus.ok()) {
+        return targetOp.emitError(
+            "Can't link executable and generate target dylib using "
+            "lld::elf::link");
+      }
+      sharedLibData = sharedLibDataStatus.value();
+    }
+    dyLibExecutableDef.library_embedded = {sharedLibData.begin(),
+                                           sharedLibData.end()};
 
     ::flatbuffers::FlatBufferBuilder fbb;
     auto executableOffset =
@@ -109,8 +154,15 @@
     std::function<LLVMTargetOptions()> queryOptions) {
   getLLVMTargetOptionsFromFlags();
   static TargetBackendRegistration registration("dylib-llvm-aot", [=]() {
-    // Initalize registered targets.
-    llvm::InitializeNativeTarget();
+#define INIT_LLVM_TARGET(TargetName)        \
+  LLVMInitialize##TargetName##Target();     \
+  LLVMInitialize##TargetName##TargetMC();   \
+  LLVMInitialize##TargetName##TargetInfo(); \
+  LLVMInitialize##TargetName##AsmPrinter(); \
+  LLVMInitialize##TargetName##AsmParser();
+    INIT_LLVM_TARGET(X86)
+    INIT_LLVM_TARGET(ARM)
+    INIT_LLVM_TARGET(AArch64)
     return std::make_unique<LLVMAOTTargetBackend>(queryOptions());
   });
 }
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTargetLinker.h b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTargetLinker.h
new file mode 100644
index 0000000..764ad02
--- /dev/null
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTargetLinker.h
@@ -0,0 +1,41 @@
+// 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.
+//
+
+#ifndef IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_AOT_TARGET_LINKER_H_
+#define IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_AOT_TARGET_LINKER_H_
+
+#include <string>
+
+#include "iree/base/file_io.h"
+#include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMTargetOptions.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace HAL {
+
+// Calls linker tool to link objData and returns shared library blob.
+iree::StatusOr<std::string> linkLLVMAOTObjects(
+    const std::string& linkerToolPath, const std::string& objData);
+// Use lld::elf::link for linking objData and returns shared library blob.
+iree::StatusOr<std::string> linkLLVMAOTObjectsWithLLDElf(
+    const std::string& objData);
+
+}  // namespace HAL
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir
+
+#endif  // IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_AOT_TARGET_LINKER_H_
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.cpp
index ede5f9d..cb2a526 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.cpp
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.cpp
@@ -15,6 +15,7 @@
 #include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h"
 
 #include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/LegacyPassManager.h"
 #include "llvm/IR/Module.h"
 #include "llvm/IR/PassManager.h"
 #include "llvm/IR/Verifier.h"
@@ -31,15 +32,15 @@
 namespace HAL {
 
 std::unique_ptr<llvm::TargetMachine> createTargetMachine(
-    const LLVMTargetOptions& options) {
+    const LLVMTargetOptions& targetOptions) {
   std::string errorMessage;
-  auto target =
-      llvm::TargetRegistry::lookupTarget(options.targetTriple, errorMessage);
+  auto target = llvm::TargetRegistry::lookupTarget(targetOptions.targetTriple,
+                                                   errorMessage);
   if (!target) return nullptr;
   // TODO(ataei): Once we have an AOT backend pass cpu and cpu-features
   std::unique_ptr<llvm::TargetMachine> machine(target->createTargetMachine(
-      options.targetTriple, "generic" /* cpu e.g k8*/,
-      "" /* cpu features e.g avx512fma*/, {}, {}));
+      targetOptions.targetTriple, "generic" /* cpu e.g k8*/,
+      "" /* cpu features e.g avx512fma*/, targetOptions.options, {}));
   return machine;
 }
 
@@ -82,7 +83,7 @@
 }
 
 LogicalResult runLLVMIRPasses(const LLVMTargetOptions& options,
-                              std::unique_ptr<llvm::TargetMachine> machine,
+                              llvm::TargetMachine* machine,
                               llvm::Module* module) {
   llvm::LoopAnalysisManager loopAnalysisManager;
   llvm::FunctionAnalysisManager functionAnalysisManager;
@@ -93,8 +94,8 @@
   llvm::StandardInstrumentations standardInstrumentations;
   standardInstrumentations.registerCallbacks(passInstrumentationCallbacks);
 
-  llvm::PassBuilder passBuilder(machine.get(), options.pipelineTuningOptions,
-                                {}, &passInstrumentationCallbacks);
+  llvm::PassBuilder passBuilder(machine, options.pipelineTuningOptions, {},
+                                &passInstrumentationCallbacks);
   llvm::AAManager aa = passBuilder.buildDefaultAAPipeline();
   functionAnalysisManager.registerPass([&] { return std::move(aa); });
 
@@ -114,6 +115,28 @@
   return success();
 }
 
+LogicalResult runEmitObjFilePasses(llvm::TargetMachine* machine,
+                                   llvm::Module* module, std::string* objData) {
+  llvm::SmallVector<char, 0> stream_buffer;
+  {
+    // TODO(ataei): Use non legacy pass mamanger for this.
+    llvm::legacy::PassManager passManager;
+    passManager.add(
+        new llvm::TargetLibraryInfoWrapperPass(machine->getTargetTriple()));
+    llvm::raw_svector_ostream ostream(stream_buffer);
+    if (machine->addPassesToEmitFile(passManager, ostream,
+                                     /*DwoOut=*/nullptr,
+                                     llvm::CGFT_ObjectFile)) {
+      return failure();
+    }
+    passManager.run(*module);
+  }
+  // TODO(ataei): This is a work around stream truncation when directly write to
+  // string.
+  *objData = std::string(stream_buffer.begin(), stream_buffer.end());
+  return success();
+}
+
 }  // namespace HAL
 }  // namespace IREE
 }  // namespace iree_compiler
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h
index 99fa937..199e36f 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h
@@ -36,9 +36,13 @@
 
 // Creates and runs LLVMIR optimization passes defined in LLVMTargetOptions.
 LogicalResult runLLVMIRPasses(const LLVMTargetOptions& options,
-                              std::unique_ptr<llvm::TargetMachine> machine,
+                              llvm::TargetMachine* machine,
                               llvm::Module* module);
 
+// Emits compiled module obj for the target machine.
+LogicalResult runEmitObjFilePasses(llvm::TargetMachine* machine,
+                                   llvm::Module* module, std::string* objData);
+
 }  // namespace HAL
 }  // namespace IREE
 }  // namespace iree_compiler
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRTarget.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRTarget.cpp
index af5f3b2..98c0bf4 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRTarget.cpp
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRTarget.cpp
@@ -74,8 +74,8 @@
                          options_.targetTriple);
       return failure();
     }
-    if (failed(runLLVMIRPasses(options_, std::move(targetMachine),
-                               llvmModule.get()))) {
+    if (failed(
+            runLLVMIRPasses(options_, targetMachine.get(), llvmModule.get()))) {
       return targetOp.emitError(
           "Can't build LLVMIR opt passes for ExecutableOp module");
     }
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTargetOptions.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTargetOptions.cpp
index c82e7d0..e71beac 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTargetOptions.cpp
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTargetOptions.cpp
@@ -14,7 +14,10 @@
 
 #include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMTargetOptions.h"
 
+#include "llvm/ADT/APFloat.h"
+#include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Host.h"
+#include "llvm/Target/TargetOptions.h"
 
 namespace mlir {
 namespace iree_compiler {
@@ -33,12 +36,27 @@
   targetOptions.pipelineTuningOptions.SLPVectorization = true;
   // LLVM -O3.
   targetOptions.optLevel = llvm::PassBuilder::OptimizationLevel::O3;
+  targetOptions.options.FloatABIType = llvm::FloatABI::Hard;
   return targetOptions;
 }
 
 LLVMTargetOptions getLLVMTargetOptionsFromFlags() {
-  // TODO(ataei): Add flags and construct options.
-  return getDefaultLLVMTargetOptions();
+  auto llvmTargetOptions = getDefaultLLVMTargetOptions();
+
+  static llvm::cl::opt<std::string> clTargetTriple(
+      "iree-llvm-target-triple", llvm::cl::desc("LLVM target machine triple"),
+      llvm::cl::init(llvmTargetOptions.targetTriple));
+  static llvm::cl::opt<bool> clSoftFloat(
+      "iree-llvm-enable-msoft-float-abi",
+      llvm::cl::desc("LLVM target codegen enables soft float abi e.g "
+                     "-mfloat-abi=softfp"),
+      llvm::cl::init(false));
+
+  llvmTargetOptions.targetTriple = clTargetTriple;
+  if (clSoftFloat) {
+    llvmTargetOptions.options.FloatABIType = llvm::FloatABI::Soft;
+  }
+  return llvmTargetOptions;
 }
 
 }  // namespace HAL
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTargetOptions.h b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTargetOptions.h
index d07dbe0..4893566 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTargetOptions.h
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMTargetOptions.h
@@ -16,6 +16,7 @@
 #define IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_LLVMTARGETOPTIONS_H_
 
 #include "llvm/Passes/PassBuilder.h"
+#include "llvm/Target/TargetOptions.h"
 
 namespace mlir {
 namespace iree_compiler {
@@ -25,6 +26,7 @@
 struct LLVMTargetOptions {
   llvm::PipelineTuningOptions pipelineTuningOptions;
   llvm::PassBuilder::OptimizationLevel optLevel;
+  llvm::TargetOptions options;
   std::string targetTriple;
 };
 
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/internal/BUILD b/iree/compiler/Dialect/HAL/Target/LLVM/internal/BUILD
new file mode 100644
index 0000000..97cc95a
--- /dev/null
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/internal/BUILD
@@ -0,0 +1,27 @@
+# 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.
+
+package(
+    default_visibility = ["//visibility:public"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+cc_library(
+    name = "LLVMAOTTargetLinker_internal",
+    srcs = ["LLVMAOTTargetLinker.cpp"],
+    deps = [
+        "//iree/base:file_io",
+        "//iree/compiler/Dialect/HAL/Target/LLVM:LLVMAOTTargetLinker_hdrs",
+    ],
+)
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/internal/CMakeLists.txt b/iree/compiler/Dialect/HAL/Target/LLVM/internal/CMakeLists.txt
new file mode 100644
index 0000000..3bb63dd
--- /dev/null
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/internal/CMakeLists.txt
@@ -0,0 +1,26 @@
+# 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.
+
+iree_add_all_subdirs()
+
+iree_cc_library(
+  NAME
+    LLVMAOTTargetLinker_internal
+  SRCS
+    "LLVMAOTTargetLinker.cpp"
+  DEPS
+    iree::base::file_io
+    iree::compiler::Dialect::HAL::Target::LLVM::LLVMAOTTargetLinker_hdrs
+  PUBLIC
+)
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/internal/LLVMAOTTargetLinker.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/internal/LLVMAOTTargetLinker.cpp
new file mode 100644
index 0000000..b883ef7
--- /dev/null
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/internal/LLVMAOTTargetLinker.cpp
@@ -0,0 +1,43 @@
+// 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/LLVMAOTTargetLinker.h"
+
+namespace mlir {
+namespace iree_compiler {
+namespace IREE {
+namespace HAL {
+
+iree::StatusOr<std::string> linkLLVMAOTObjects(
+    const std::string& linkerToolPath, const std::string& objData) {
+  std::string archiveFile, sharedLibFile;
+  ASSIGN_OR_RETURN(archiveFile, iree::file_io::GetTempFile("objfile"));
+  RETURN_IF_ERROR(iree::file_io::SetFileContents(archiveFile, objData));
+  ASSIGN_OR_RETURN(sharedLibFile, iree::file_io::GetTempFile("dylibfile"));
+  std::string linkingCmd =
+      linkerToolPath + " -shared " + archiveFile + " -o " + sharedLibFile;
+  system(linkingCmd.c_str());
+  return iree::file_io::GetFileContents(sharedLibFile);
+}
+
+iree::StatusOr<std::string> linkLLVMAOTObjectsWithLLDElf(
+    const std::string& objData) {
+  return iree::UnimplementedErrorBuilder(IREE_LOC)
+         << "linkLLVMAOTObjectsWithLLD not implemented yet!";
+}
+
+}  // namespace HAL
+}  // namespace IREE
+}  // namespace iree_compiler
+}  // namespace mlir