Adding a skeleton librt to provide libc/libm/etc during embedded linking. (#6560)

The code should be factored out as we start to add things like placeholder
functions for querying runtime state/etc but starting simple here to get
us bootstrapped.

See librt/src/librt.h for some notes.
Run librt/build.sh to update the .bc file and then rebuild iree-* so the
new version is pulled in.
diff --git a/.gitignore b/.gitignore
index 4f52c93..6fe9bf2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,3 +58,6 @@
 # Generated documentation files
 mkdocs/site/
 docs/website/site/
+
+# Temporary files
+iree/compiler/Dialect/HAL/Target/LLVM/librt/bin/librt.ll
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/BUILD b/iree/compiler/Dialect/HAL/Target/LLVM/BUILD
index 01c1c13..3359b98 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/BUILD
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/BUILD
@@ -41,12 +41,14 @@
         "//iree/compiler/Codegen/LLVMCPU",
         "//iree/compiler/Codegen/Utils",
         "//iree/compiler/Dialect/HAL/Target",
+        "//iree/compiler/Dialect/HAL/Target/LLVM/librt",
         "//iree/compiler/Utils",
         "//iree/schemas:dylib_executable_def_c_fbs",
         "@llvm-project//llvm:AArch64AsmParser",
         "@llvm-project//llvm:AArch64CodeGen",
         "@llvm-project//llvm:ARMAsmParser",
         "@llvm-project//llvm:ARMCodeGen",
+        "@llvm-project//llvm:BitReader",
         "@llvm-project//llvm:BitWriter",
         "@llvm-project//llvm:Core",
         "@llvm-project//llvm:RISCVAsmParser",
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt b/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt
index f12301b..29c9514 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/CMakeLists.txt
@@ -32,6 +32,7 @@
     LLVMAArch64CodeGen
     LLVMARMAsmParser
     LLVMARMCodeGen
+    LLVMBitReader
     LLVMBitWriter
     LLVMCore
     LLVMRISCVAsmParser
@@ -50,6 +51,7 @@
     iree::compiler::Codegen::PassHeaders
     iree::compiler::Codegen::Utils
     iree::compiler::Dialect::HAL::Target
+    iree::compiler::Dialect::HAL::Target::LLVM::librt
     iree::compiler::Utils
     iree::schemas::dylib_executable_def_c_fbs
   PUBLIC
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.cpp
index f80e47c..fdefab2 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.cpp
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.cpp
@@ -13,9 +13,11 @@
 #include "iree/compiler/Dialect/HAL/Target/LLVM/LibraryBuilder.h"
 #include "iree/compiler/Dialect/HAL/Target/LLVM/LinkerTool.h"
 #include "iree/compiler/Dialect/HAL/Target/LLVM/StaticLibraryGenerator.h"
+#include "iree/compiler/Dialect/HAL/Target/LLVM/librt/librt.h"
 #include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h"
 #include "iree/compiler/Utils/FlatbufferUtils.h"
 #include "iree/schemas/dylib_executable_def_builder.h"
+#include "llvm/Bitcode/BitcodeReader.h"
 #include "llvm/Bitcode/BitcodeWriter.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Module.h"
@@ -30,11 +32,10 @@
 namespace IREE {
 namespace HAL {
 
-namespace {
+static constexpr char kQueryFunctionName[] =
+    "iree_hal_executable_library_query";
 
-constexpr char kQueryFunctionName[] = "iree_hal_executable_library_query";
-
-llvm::Optional<FileLineColLoc> findFirstFileLoc(Location baseLoc) {
+static llvm::Optional<FileLineColLoc> findFirstFileLoc(Location baseLoc) {
   if (auto loc = baseLoc.dyn_cast<FusedLoc>()) {
     for (auto &childLoc : loc.getLocations()) {
       auto childResult = findFirstFileLoc(childLoc);
@@ -46,7 +47,7 @@
   return llvm::None;
 }
 
-std::string guessModuleName(mlir::ModuleOp moduleOp) {
+static std::string guessModuleName(mlir::ModuleOp moduleOp) {
   std::string moduleName =
       moduleOp.getName().hasValue() ? moduleOp.getName().getValue().str() : "";
   if (!moduleName.empty()) return moduleName;
@@ -58,8 +59,6 @@
   }
 }
 
-}  // namespace
-
 class LLVMAOTTargetBackend final : public TargetBackend {
  public:
   explicit LLVMAOTTargetBackend(LLVMTargetOptions options)
@@ -284,8 +283,10 @@
              << options_.targetTriple << "'";
     }
 
-    // Emit object files.
-    SmallVector<Artifact, 4> objectFiles;
+    SmallVector<Artifact> objectFiles;
+
+    // Emit the base object file containing the bulk of our code.
+    // This must come first such that we have the proper library linking order.
     {
       // NOTE: today we just use a single object file, however if we wanted to
       // scale code generation and linking we'd want to generate one per
@@ -306,6 +307,16 @@
       objectFiles.push_back(std::move(objectFile));
     }
 
+    // Optionally append additional object files that provide functionality that
+    // may otherwise have been runtime-dynamic (like libc/libm calls).
+    // For now we only do this for embedded uses.
+    if (options_.linkEmbedded) {
+      if (failed(buildLibraryObjects(variantOp.getLoc(), targetMachine.get(),
+                                     objectFiles, context))) {
+        return variantOp.emitError() << "failed generating library objects";
+      }
+    }
+
     // If we are keeping artifacts then let's also add the bitcode for easier
     // debugging (vs just the binary object file).
     if (options_.keepLinkerArtifacts) {
@@ -329,7 +340,6 @@
       // Copy the static object file to the specified output along with
       // generated header file.
       const std::string &libraryPath = options_.staticLibraryOutput;
-      const auto library_name = objectFiles[0].path;
       if (!outputStaticLibrary(libraryName, queryFunctionName, libraryPath,
                                objectFiles[0].path)) {
         return variantOp.emitError() << "static library generation failed";
@@ -506,6 +516,76 @@
     return IREE::HAL::ExecutableTargetAttr::get(context, "llvm", format);
   }
 
+  static void overridePlatformGlobal(llvm::Module &module, StringRef globalName,
+                                     uint32_t newValue) {
+    // NOTE: the global will not be defined if it is not used in the module.
+    auto *globalValue = module.getNamedGlobal(globalName);
+    if (!globalValue) return;
+    globalValue->setLinkage(llvm::GlobalValue::PrivateLinkage);
+    globalValue->setDSOLocal(true);
+    globalValue->setConstant(true);
+    globalValue->setInitializer(llvm::ConstantInt::get(
+        globalValue->getValueType(), APInt(32, newValue)));
+  }
+
+  // Builds an object file for the librt embedded runtime library.
+  // This is done per link operation so that we can match the precise target
+  // configuration. Since we (mostly) link once per user-level compilation
+  // this is fine today. If in the future we invoke the compiler for thousands
+  // of modules we'd want to (carefully) cache this.
+  LogicalResult buildLibraryObjects(Location loc,
+                                    llvm::TargetMachine *targetMachine,
+                                    SmallVector<Artifact> &objectFiles,
+                                    llvm::LLVMContext &context) {
+    assert(!objectFiles.empty() && "libraries must come after the base object");
+
+    // Load the generic bitcode file contents.
+    llvm::MemoryBufferRef bitcodeBufferRef(
+        llvm::StringRef(iree_compiler_librt_create()->data,
+                        iree_compiler_librt_create()->size),
+        "librt.bc");
+    auto bitcodeModuleValue = llvm::parseBitcodeFile(bitcodeBufferRef, context);
+    if (!bitcodeModuleValue) {
+      return mlir::emitError(loc)
+             << "failed to parse librt bitcode: "
+             << llvm::toString(bitcodeModuleValue.takeError());
+    }
+    auto bitcodeModule = std::move(bitcodeModuleValue.get());
+    bitcodeModule->setDataLayout(targetMachine->createDataLayout());
+    bitcodeModule->setTargetTriple(targetMachine->getTargetTriple().str());
+
+    // Inject target-specific flags.
+    // TODO(benvanik): move this entire function to another file that can do
+    // more complex logic cleanly. This is just an example.
+    overridePlatformGlobal(*bitcodeModule, "librt_platform_example_flag", 0u);
+
+    // Run the LLVM passes to optimize it for the current target.
+    if (failed(runLLVMIRPasses(options_, targetMachine, bitcodeModule.get()))) {
+      return mlir::emitError(loc)
+             << "failed to run librt LLVM-IR opt passes targeting '"
+             << options_.targetTriple << "'";
+    }
+
+    // Emit an object file we can pass to the linker.
+    std::string objectData;
+    if (failed(runEmitObjFilePasses(targetMachine, bitcodeModule.get(),
+                                    &objectData))) {
+      return mlir::emitError(loc)
+             << "failed to compile librt LLVM-IR module to an object file";
+    }
+
+    // Write the object file to disk with a similar name to the base file.
+    auto objectFile =
+        Artifact::createVariant(objectFiles.front().path, ".librt.o");
+    auto &os = objectFile.outputFile->os();
+    os << objectData;
+    os.flush();
+    os.close();
+    objectFiles.push_back(std::move(objectFile));
+
+    return success();
+  }
+
   LLVMTargetOptions options_;
 };
 
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/internal/EmbeddedLinkerTool.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/internal/EmbeddedLinkerTool.cpp
index f2847c2..cda219e 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/internal/EmbeddedLinkerTool.cpp
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/internal/EmbeddedLinkerTool.cpp
@@ -80,7 +80,7 @@
 
     // Create the shared object name; if we only have a single input object we
     // can just reuse that.
-    if (objectFiles.size() == 1) {
+    if (!objectFiles.empty()) {
       artifacts.libraryFile =
           Artifact::createVariant(objectFiles.front().path, "so");
     } else {
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/librt/BUILD b/iree/compiler/Dialect/HAL/Target/LLVM/librt/BUILD
new file mode 100644
index 0000000..f9f6feb
--- /dev/null
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/librt/BUILD
@@ -0,0 +1,31 @@
+# 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
+
+load("//build_tools/embed_data:build_defs.bzl", "c_embed_data")
+load("//iree:build_defs.oss.bzl", "iree_cmake_extra_content")
+
+package(
+    default_visibility = ["//visibility:public"],
+    features = ["layering_check"],
+    licenses = ["notice"],  # Apache 2.0
+)
+
+iree_cmake_extra_content(
+    content = """
+if(NOT "${IREE_TARGET_BACKEND_DYLIB-LLVM-AOT}" AND NOT "${IREE_TARGET_BACKEND_WASM-LLVM-AOT}")
+  return()
+endif()
+""",
+)
+
+c_embed_data(
+    name = "librt",
+    srcs = ["bin/librt.bc"],
+    c_file_output = "librt.c",
+    flatten = True,
+    h_file_output = "librt.h",
+    identifier = "iree_compiler_librt",
+)
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/librt/CMakeLists.txt b/iree/compiler/Dialect/HAL/Target/LLVM/librt/CMakeLists.txt
new file mode 100644
index 0000000..c6dabe0
--- /dev/null
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/librt/CMakeLists.txt
@@ -0,0 +1,32 @@
+################################################################################
+# Autogenerated by build_tools/bazel_to_cmake/bazel_to_cmake.py from           #
+# iree/compiler/Dialect/HAL/Target/LLVM/librt/BUILD                            #
+#                                                                              #
+# Use iree_cmake_extra_content from iree/build_defs.oss.bzl to add arbitrary   #
+# CMake-only content.                                                          #
+#                                                                              #
+# To disable autogeneration for this file entirely, delete this header.        #
+################################################################################
+
+if(NOT "${IREE_TARGET_BACKEND_DYLIB-LLVM-AOT}" AND NOT "${IREE_TARGET_BACKEND_WASM-LLVM-AOT}")
+  return()
+endif()
+
+iree_add_all_subdirs()
+
+iree_c_embed_data(
+  NAME
+    librt
+  SRCS
+    "bin/librt.bc"
+  C_FILE_OUTPUT
+    "librt.c"
+  H_FILE_OUTPUT
+    "librt.h"
+  IDENTIFIER
+    "iree_compiler_librt"
+  FLATTEN
+  PUBLIC
+)
+
+### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/librt/bin/librt.bc b/iree/compiler/Dialect/HAL/Target/LLVM/librt/bin/librt.bc
new file mode 100644
index 0000000..a484f31
--- /dev/null
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/librt/bin/librt.bc
Binary files differ
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/librt/build.sh b/iree/compiler/Dialect/HAL/Target/LLVM/librt/build.sh
new file mode 100644
index 0000000..c671e4d
--- /dev/null
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/librt/build.sh
@@ -0,0 +1,45 @@
+# 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
+
+set -e
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
+OUT="${SCRIPT_DIR}/bin"
+SRC="${SCRIPT_DIR}/src"
+LL_FILE="${OUT}/librt.ll"
+BC_FILE="${OUT}/librt.bc"
+
+# Generate an LLVM IR assembly listing so we can easily read the file.
+# This is not checked in or used by the compiler.
+clang \
+    -target wasm32 \
+    -std=c17 \
+    -O2 \
+    -Xclang -disable-llvm-passes \
+    -fno-ident \
+    -fvisibility=hidden \
+    -nostdinc \
+    -g0 \
+    -S \
+    -emit-llvm \
+    -fno-verbose-asm \
+    -fdiscard-value-names \
+    -o "${LL_FILE}" \
+    -c \
+    "${SRC}/libm.c"
+
+# Clang adds a bunch of bad attributes and host-specific information that we
+# don't want (so we get at least somewhat deterministic builds).
+sed -i 's/^;.*$//' ${LL_FILE}
+sed -i 's/^source_filename.*$//' ${LL_FILE}
+sed -i 's/^target datalayout.*$//' ${LL_FILE}
+sed -i 's/^target triple.*$//' ${LL_FILE}
+sed -i 's/^\(attributes #[0-9]* = {\).*$/\1 inlinehint }/' ${LL_FILE}
+
+# Generate a binary bitcode file embedded into the compiler binary.
+# NOTE: we do this from stdin so that the filename on the user's system is not
+# embedded in the bitcode file (making it non-deterministic).
+cat ${LL_FILE} | llvm-as -o=${BC_FILE}
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/librt/src/libm.c b/iree/compiler/Dialect/HAL/Target/LLVM/librt/src/libm.c
new file mode 100644
index 0000000..bdbe0ae
--- /dev/null
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/librt/src/libm.c
@@ -0,0 +1,13 @@
+// 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 "libm.h"
+
+// https://en.cppreference.com/w/c/numeric/math/fma
+LIBRT_EXPORT float fmaf(float x, float y, float z) {
+  // TODO(*): a real implementation :)
+  return (x * y) + z;
+}
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/librt/src/libm.h b/iree/compiler/Dialect/HAL/Target/LLVM/librt/src/libm.h
new file mode 100644
index 0000000..10dfe76
--- /dev/null
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/librt/src/libm.h
@@ -0,0 +1,15 @@
+// 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
+
+#ifndef IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_LIBRT_SRC_LIBM_H_
+#define IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_LIBRT_SRC_LIBM_H_
+
+#include "librt.h"
+
+// https://en.cppreference.com/w/c/numeric/math/fma
+LIBRT_EXPORT float fmaf(float x, float y, float z);
+
+#endif  // IREE_COMPILER_DIALECT_HAL_TARGET_LLVM_LIBRT_SRC_LIBM_H_
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/librt/src/librt.h b/iree/compiler/Dialect/HAL/Target/LLVM/librt/src/librt.h
new file mode 100644
index 0000000..0af8b21
--- /dev/null
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/librt/src/librt.h
@@ -0,0 +1,75 @@
+// 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
+
+//===----------------------------------------------------------------------===//
+// A simplified libc/libm-alike that is designed to compile to portable LLVM IR.
+//===----------------------------------------------------------------------===//
+// This library is focused on supporting the subset of LLVM's RuntimeLibcalls
+// that we need in our embedded executable binaries. This means that things like
+// printf, malloc, etc are excluded.
+//
+// See the full list of possible functions here:
+// third_party/llvm-project/llvm/include/llvm/IR/RuntimeLibcalls.def
+//
+// Code here must not use any system headers - as almost all pull in bits/ and
+// various other target-dependent definitions that make the resulting IR
+// non-portable. This means there is no size_t, etc. Any definitions that may
+// come from an std* file must be redefined here with care.
+//
+// Code must also not use any mutable global or thread-local state ala
+// errno/rounding modes/etc. Each of the functions in the library will be called
+// concurrently from multiple threads and from multiple source modules. There
+// must be no mutable static values anywhere.
+//
+// Avoid #ifdef entirely: they indicate a leakage of host build configuration
+// into what is supposed to be a portable module. Anything that requires
+// target-specific conditional logic must be implemented via an extern that
+// can be substituted by the IREE compiler when producing the final
+// target-specific module.
+
+//===----------------------------------------------------------------------===//
+// Attributes and metadata
+//===----------------------------------------------------------------------===//
+
+// Tagged on functions that are part of the public API.
+#define LIBRT_EXPORT __attribute__((visibility("hidden")))
+
+//===----------------------------------------------------------------------===//
+// stdint.h
+//===----------------------------------------------------------------------===//
+// https://pubs.opengroup.org/onlinepubs/009604599/basedefs/stdint.h.html
+// NOTE: no size_t/ptrdiff_t/etc (as they are target dependent).
+
+typedef signed char int8_t;
+typedef short int16_t;
+typedef int int32_t;
+typedef long long int64_t;
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+typedef unsigned long long uint64_t;
+
+#define INT8_MIN (-127i8 - 1)
+#define INT16_MIN (-32767i16 - 1)
+#define INT32_MIN (-2147483647i32 - 1)
+#define INT64_MIN (-9223372036854775807i64 - 1)
+#define INT8_MAX 127i8
+#define INT16_MAX 32767i16
+#define INT32_MAX 2147483647i32
+#define INT64_MAX 9223372036854775807i64
+#define UINT8_MAX 0xffui8
+#define UINT16_MAX 0xffffui16
+#define UINT32_MAX 0xffffffffui32
+#define UINT64_MAX 0xffffffffffffffffui64
+
+//===----------------------------------------------------------------------===//
+// Target-specific queries
+//===----------------------------------------------------------------------===//
+// These are substituted with values from the compiler and must not be specified
+// here in C before we generate the IR.
+
+// Do not use: here as an example. Remove once we have any other flag.
+extern int librt_platform_example_flag;