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;