[Mlir-commits] [mlir] c8e0364 - [mlir][Target][LLVM] Adds an utility class for serializing operations to binary strings.
Fabian Mora
llvmlistbot at llvm.org
Tue Aug 8 06:09:39 PDT 2023
Author: Fabian Mora
Date: 2023-08-08T13:09:31Z
New Revision: c8e0364a4336569f91fe74dbc231beefc8793518
URL: https://github.com/llvm/llvm-project/commit/c8e0364a4336569f91fe74dbc231beefc8793518
DIFF: https://github.com/llvm/llvm-project/commit/c8e0364a4336569f91fe74dbc231beefc8793518.diff
LOG: [mlir][Target][LLVM] Adds an utility class for serializing operations to binary strings.
**For an explanation of these patches see D154153.**
Commit message:
This patch adds the utility base class `ModuleToObject`. This class provides an
interface for compiling module operations into binary strings, by default this
class serialize modules to LLVM bitcode.
Reviewed By: mehdi_amini
Differential Revision: https://reviews.llvm.org/D154100
Added:
mlir/include/mlir/Target/LLVM/ModuleToObject.h
mlir/lib/Target/LLVM/CMakeLists.txt
mlir/lib/Target/LLVM/ModuleToObject.cpp
mlir/unittests/Target/CMakeLists.txt
mlir/unittests/Target/LLVM/CMakeLists.txt
mlir/unittests/Target/LLVM/SerializeToLLVMBitcode.cpp
Modified:
mlir/lib/Target/CMakeLists.txt
mlir/unittests/CMakeLists.txt
Removed:
################################################################################
diff --git a/mlir/include/mlir/Target/LLVM/ModuleToObject.h b/mlir/include/mlir/Target/LLVM/ModuleToObject.h
new file mode 100644
index 00000000000000..d17afc1077fb45
--- /dev/null
+++ b/mlir/include/mlir/Target/LLVM/ModuleToObject.h
@@ -0,0 +1,122 @@
+//===- ModuleToObject.h - Module to object base class -----------*- C++ -*-===//
+//
+// Part of the LLVM Project, 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file declares the base class for transforming operations into binary
+// objects.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_TARGET_LLVM_MODULETOOBJECT_H
+#define MLIR_TARGET_LLVM_MODULETOOBJECT_H
+
+#include "mlir/IR/Operation.h"
+#include "llvm/IR/Module.h"
+
+namespace llvm {
+class TargetMachine;
+} // namespace llvm
+
+namespace mlir {
+namespace LLVM {
+class ModuleTranslation;
+/// Utility base class for transforming operations into binary objects, by
+/// default it returns the serialized LLVM bitcode for the module. The
+/// operations being transformed must be translatable into LLVM IR.
+class ModuleToObject {
+public:
+ ModuleToObject(Operation &module, StringRef triple, StringRef chip,
+ StringRef features = {}, int optLevel = 3);
+ virtual ~ModuleToObject() = default;
+
+ /// Returns the operation being serialized.
+ Operation &getOperation();
+
+ /// Runs the serialization pipeline, returning `std::nullopt` on error.
+ virtual std::optional<SmallVector<char, 0>> run();
+
+protected:
+ // Hooks to be implemented by derived classes.
+
+ /// Hook for loading bitcode files, returns std::nullopt on failure.
+ virtual std::optional<SmallVector<std::unique_ptr<llvm::Module>>>
+ loadBitcodeFiles(llvm::Module &module, llvm::TargetMachine &targetMachine) {
+ return SmallVector<std::unique_ptr<llvm::Module>>();
+ }
+
+ /// Hook for performing additional actions on a loaded bitcode file.
+ virtual LogicalResult handleBitcodeFile(llvm::Module &module,
+ llvm::TargetMachine &targetMachine) {
+ return success();
+ }
+
+ /// Hook for performing additional actions on the llvmModule pre linking.
+ virtual void handleModulePreLink(llvm::Module &module,
+ llvm::TargetMachine &targetMachine) {}
+
+ /// Hook for performing additional actions on the llvmModule post linking.
+ virtual void handleModulePostLink(llvm::Module &module,
+ llvm::TargetMachine &targetMachine) {}
+
+ /// Serializes the LLVM IR bitcode to an object file, by default it serializes
+ /// to LLVM bitcode.
+ virtual std::optional<SmallVector<char, 0>>
+ moduleToObject(llvm::Module &llvmModule, llvm::TargetMachine &targetMachine);
+
+protected:
+ /// Create the target machine based on the target triple and chip.
+ std::unique_ptr<llvm::TargetMachine> createTargetMachine();
+
+ /// Loads a bitcode file from path.
+ std::unique_ptr<llvm::Module>
+ loadBitcodeFile(llvm::LLVMContext &context,
+ llvm::TargetMachine &targetMachine, StringRef path);
+
+ /// Loads multiple bitcode files.
+ LogicalResult loadBitcodeFilesFromList(
+ llvm::LLVMContext &context, llvm::TargetMachine &targetMachine,
+ ArrayRef<std::string> fileList,
+ SmallVector<std::unique_ptr<llvm::Module>> &llvmModules,
+ bool failureOnError = true);
+
+ /// Translates the operation to LLVM IR.
+ std::unique_ptr<llvm::Module>
+ translateToLLVMIR(llvm::LLVMContext &llvmContext);
+
+ /// Link the llvmModule to other bitcode file.
+ LogicalResult linkFiles(llvm::Module &module,
+ SmallVector<std::unique_ptr<llvm::Module>> &&libs);
+
+ /// Optimize the module.
+ LogicalResult optimizeModule(llvm::Module &module,
+ llvm::TargetMachine &targetMachine, int optL);
+
+ /// Utility function for translating to ISA, returns `std::nullopt` on
+ /// failure.
+ static std::optional<std::string>
+ translateToISA(llvm::Module &llvmModule, llvm::TargetMachine &targetMachine);
+
+protected:
+ /// Module to transform to a binary object.
+ Operation &module;
+
+ /// Target triple.
+ StringRef triple;
+
+ /// Target chip.
+ StringRef chip;
+
+ /// Target features.
+ StringRef features;
+
+ /// Optimization level.
+ int optLevel;
+};
+} // namespace LLVM
+} // namespace mlir
+
+#endif // MLIR_TARGET_LLVM_MODULETOOBJECT_H
diff --git a/mlir/lib/Target/CMakeLists.txt b/mlir/lib/Target/CMakeLists.txt
index acc3985cc80e66..c3ec1b4f1e3fed 100644
--- a/mlir/lib/Target/CMakeLists.txt
+++ b/mlir/lib/Target/CMakeLists.txt
@@ -1,3 +1,4 @@
add_subdirectory(Cpp)
add_subdirectory(SPIRV)
add_subdirectory(LLVMIR)
+add_subdirectory(LLVM)
diff --git a/mlir/lib/Target/LLVM/CMakeLists.txt b/mlir/lib/Target/LLVM/CMakeLists.txt
new file mode 100644
index 00000000000000..6fc5310e04fa0e
--- /dev/null
+++ b/mlir/lib/Target/LLVM/CMakeLists.txt
@@ -0,0 +1,20 @@
+add_mlir_library(MLIRTargetLLVM
+ ModuleToObject.cpp
+
+ ADDITIONAL_HEADER_DIRS
+ ${MLIR_MAIN_INCLUDE_DIR}/mlir/Target/LLVM
+
+ DEPENDS
+ intrinsics_gen
+
+ LINK_COMPONENTS
+ Core
+ IPO
+ Passes
+ Support
+ Target
+ TargetParser
+ LINK_LIBS PUBLIC
+ MLIRExecutionEngineUtils
+ MLIRTargetLLVMIRExport
+)
diff --git a/mlir/lib/Target/LLVM/ModuleToObject.cpp b/mlir/lib/Target/LLVM/ModuleToObject.cpp
new file mode 100644
index 00000000000000..1ce72e6bb16230
--- /dev/null
+++ b/mlir/lib/Target/LLVM/ModuleToObject.cpp
@@ -0,0 +1,228 @@
+//===- ModuleToObject.cpp - Module to object base class ---------*- C++ -*-===//
+//
+// Part of the LLVM Project, 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the base class for transforming Operations into binary
+// objects.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Target/LLVM/ModuleToObject.h"
+
+#include "mlir/ExecutionEngine/OptUtils.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h"
+#include "mlir/Target/LLVMIR/Export.h"
+#include "mlir/Target/LLVMIR/ModuleTranslation.h"
+
+#include "llvm/Bitcode/BitcodeWriter.h"
+#include "llvm/IR/LegacyPassManager.h"
+#include "llvm/IRReader/IRReader.h"
+#include "llvm/Linker/Linker.h"
+#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/SourceMgr.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Target/TargetMachine.h"
+#include "llvm/TargetParser/TargetParser.h"
+#include "llvm/Transforms/IPO/Internalize.h"
+
+using namespace mlir;
+using namespace mlir::LLVM;
+
+ModuleToObject::ModuleToObject(Operation &module, StringRef triple,
+ StringRef chip, StringRef features, int optLevel)
+ : module(module), triple(triple), chip(chip), features(features),
+ optLevel(optLevel) {}
+
+Operation &ModuleToObject::getOperation() { return module; }
+
+std::unique_ptr<llvm::TargetMachine> ModuleToObject::createTargetMachine() {
+ std::string error;
+ // Load the target.
+ const llvm::Target *target =
+ llvm::TargetRegistry::lookupTarget(triple, error);
+ if (!target) {
+ getOperation().emitError() << "Failed to lookup target: " << error;
+ return {};
+ }
+
+ // Create the target machine using the target.
+ llvm::TargetMachine *machine =
+ target->createTargetMachine(triple, chip, features, {}, {});
+ if (!machine) {
+ getOperation().emitError() << "Failed to create the target machine.";
+ return {};
+ }
+ return std::unique_ptr<llvm::TargetMachine>{machine};
+}
+
+std::unique_ptr<llvm::Module>
+ModuleToObject::loadBitcodeFile(llvm::LLVMContext &context,
+ llvm::TargetMachine &targetMachine,
+ StringRef path) {
+ llvm::SMDiagnostic error;
+ std::unique_ptr<llvm::Module> library =
+ llvm::getLazyIRFileModule(path, error, context);
+ if (!library) {
+ getOperation().emitError() << "Failed loading file from " << path
+ << ", error: " << error.getMessage();
+ return nullptr;
+ }
+ if (failed(handleBitcodeFile(*library, targetMachine))) {
+ return nullptr;
+ }
+ return library;
+}
+
+LogicalResult ModuleToObject::loadBitcodeFilesFromList(
+ llvm::LLVMContext &context, llvm::TargetMachine &targetMachine,
+ ArrayRef<std::string> fileList,
+ SmallVector<std::unique_ptr<llvm::Module>> &llvmModules,
+ bool failureOnError) {
+ for (const std::string &str : fileList) {
+ // Test if the path exists, if it doesn't abort.
+ StringRef pathRef = StringRef(str.data(), str.size());
+ if (!llvm::sys::fs::is_regular_file(pathRef)) {
+ getOperation().emitError()
+ << "File path: " << pathRef << " does not exist or is not a file.\n";
+ return failure();
+ }
+ // Load the file or abort on error.
+ if (auto bcFile = loadBitcodeFile(context, targetMachine, pathRef))
+ llvmModules.push_back(std::move(bcFile));
+ else if (failureOnError)
+ return failure();
+ }
+ return success();
+}
+
+std::unique_ptr<llvm::Module>
+ModuleToObject::translateToLLVMIR(llvm::LLVMContext &llvmContext) {
+ return translateModuleToLLVMIR(&getOperation(), llvmContext);
+}
+
+LogicalResult
+ModuleToObject::linkFiles(llvm::Module &module,
+ SmallVector<std::unique_ptr<llvm::Module>> &&libs) {
+ if (libs.empty())
+ return success();
+ llvm::Linker linker(module);
+ for (std::unique_ptr<llvm::Module> &libModule : libs) {
+ // This bitcode linking imports the library functions into the module,
+ // allowing LLVM optimization passes (which must run after linking) to
+ // optimize across the libraries and the module's code. We also only import
+ // symbols if they are referenced by the module or a previous library since
+ // there will be no other source of references to those symbols in this
+ // compilation and since we don't want to bloat the resulting code object.
+ bool err = linker.linkInModule(
+ std::move(libModule), llvm::Linker::Flags::LinkOnlyNeeded,
+ [](llvm::Module &m, const StringSet<> &gvs) {
+ llvm::internalizeModule(m, [&gvs](const llvm::GlobalValue &gv) {
+ return !gv.hasName() || (gvs.count(gv.getName()) == 0);
+ });
+ });
+ // True is linker failure
+ if (err) {
+ getOperation().emitError("Unrecoverable failure during bitcode linking.");
+ // We have no guaranties about the state of `ret`, so bail
+ return failure();
+ }
+ }
+ return success();
+}
+
+LogicalResult ModuleToObject::optimizeModule(llvm::Module &module,
+ llvm::TargetMachine &targetMachine,
+ int optLevel) {
+ if (optLevel < 0 || optLevel > 3)
+ return getOperation().emitError()
+ << "Invalid optimization level: " << optLevel << ".";
+
+ targetMachine.setOptLevel(static_cast<llvm::CodeGenOpt::Level>(optLevel));
+
+ auto transformer =
+ makeOptimizingTransformer(optLevel, /*sizeLevel=*/0, &targetMachine);
+ auto error = transformer(&module);
+ if (error) {
+ InFlightDiagnostic mlirError = getOperation().emitError();
+ llvm::handleAllErrors(
+ std::move(error), [&mlirError](const llvm::ErrorInfoBase &ei) {
+ mlirError << "Could not optimize LLVM IR: " << ei.message() << "\n";
+ });
+ return mlirError;
+ }
+ return success();
+}
+
+std::optional<std::string>
+ModuleToObject::translateToISA(llvm::Module &llvmModule,
+ llvm::TargetMachine &targetMachine) {
+ std::string targetISA;
+ llvm::raw_string_ostream stream(targetISA);
+
+ { // Drop pstream after this to prevent the ISA from being stuck buffering
+ llvm::buffer_ostream pstream(stream);
+ llvm::legacy::PassManager codegenPasses;
+
+ if (targetMachine.addPassesToEmitFile(codegenPasses, pstream, nullptr,
+ llvm::CGFT_AssemblyFile))
+ return std::nullopt;
+
+ codegenPasses.run(llvmModule);
+ }
+ return stream.str();
+}
+
+std::optional<SmallVector<char, 0>>
+ModuleToObject::moduleToObject(llvm::Module &llvmModule,
+ llvm::TargetMachine &targetMachine) {
+ SmallVector<char, 0> binaryData;
+ // Write the LLVM module bitcode to a buffer.
+ llvm::raw_svector_ostream outputStream(binaryData);
+ llvm::WriteBitcodeToFile(llvmModule, outputStream);
+ return binaryData;
+}
+
+std::optional<SmallVector<char, 0>> ModuleToObject::run() {
+ // Translate the module to LLVM IR.
+ llvm::LLVMContext llvmContext;
+ std::unique_ptr<llvm::Module> llvmModule = translateToLLVMIR(llvmContext);
+ if (!llvmModule) {
+ getOperation().emitError() << "Failed creating the llvm::Module.";
+ return std::nullopt;
+ }
+
+ // Create the target machine.
+ std::unique_ptr<llvm::TargetMachine> targetMachine = createTargetMachine();
+ if (!targetMachine)
+ return std::nullopt;
+
+ // Set the data layout and target triple of the module.
+ llvmModule->setDataLayout(targetMachine->createDataLayout());
+ llvmModule->setTargetTriple(targetMachine->getTargetTriple().getTriple());
+
+ // Link bitcode files.
+ handleModulePreLink(*llvmModule, *targetMachine);
+ {
+ auto libs = loadBitcodeFiles(*llvmModule, *targetMachine);
+ if (!libs)
+ return std::nullopt;
+ if (libs->size())
+ if (failed(linkFiles(*llvmModule, std::move(*libs))))
+ return std::nullopt;
+ handleModulePostLink(*llvmModule, *targetMachine);
+ }
+
+ // Optimize the module.
+ if (failed(optimizeModule(*llvmModule, *targetMachine, optLevel)))
+ return std::nullopt;
+
+ // Return the serialized object.
+ return moduleToObject(*llvmModule, *targetMachine);
+}
diff --git a/mlir/unittests/CMakeLists.txt b/mlir/unittests/CMakeLists.txt
index 5ca3826e529c07..d0e222091c9f89 100644
--- a/mlir/unittests/CMakeLists.txt
+++ b/mlir/unittests/CMakeLists.txt
@@ -16,6 +16,7 @@ add_subdirectory(Pass)
add_subdirectory(Support)
add_subdirectory(Rewrite)
add_subdirectory(TableGen)
+add_subdirectory(Target)
add_subdirectory(Transforms)
if(MLIR_ENABLE_EXECUTION_ENGINE)
diff --git a/mlir/unittests/Target/CMakeLists.txt b/mlir/unittests/Target/CMakeLists.txt
new file mode 100644
index 00000000000000..8bd3107356c056
--- /dev/null
+++ b/mlir/unittests/Target/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(LLVM)
diff --git a/mlir/unittests/Target/LLVM/CMakeLists.txt b/mlir/unittests/Target/LLVM/CMakeLists.txt
new file mode 100644
index 00000000000000..cae5b6ca221970
--- /dev/null
+++ b/mlir/unittests/Target/LLVM/CMakeLists.txt
@@ -0,0 +1,26 @@
+add_mlir_unittest(MLIRTargetLLVMTests
+ SerializeToLLVMBitcode.cpp
+)
+
+llvm_map_components_to_libnames(llvm_libs nativecodegen)
+
+target_link_libraries(MLIRTargetLLVMTests
+ PRIVATE
+ MLIRTargetLLVM
+ MLIRLLVMDialect
+ MLIRLLVMToLLVMIRTranslation
+ MLIRBuiltinToLLVMIRTranslation
+ ${llvm_libs}
+)
+
+if (DEFINED LLVM_NATIVE_TARGET)
+ target_compile_definitions(MLIRTargetLLVMTests
+ PRIVATE
+ -DLLVM_NATIVE_TARGET_TEST_ENABLED=1
+ )
+else()
+ target_compile_definitions(MLIRTargetLLVMTests
+ PRIVATE
+ -DLLVM_NATIVE_TARGET_TEST_ENABLED=0
+ )
+endif()
diff --git a/mlir/unittests/Target/LLVM/SerializeToLLVMBitcode.cpp b/mlir/unittests/Target/LLVM/SerializeToLLVMBitcode.cpp
new file mode 100644
index 00000000000000..c23ed7efd72fdf
--- /dev/null
+++ b/mlir/unittests/Target/LLVM/SerializeToLLVMBitcode.cpp
@@ -0,0 +1,76 @@
+//===- SerializeToLLVMBitcode.cpp -------------------------------*- C++ -*-===//
+//
+// This file is 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 "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/MLIRContext.h"
+#include "mlir/Parser/Parser.h"
+#include "mlir/Target/LLVM/ModuleToObject.h"
+#include "mlir/Target/LLVMIR/Dialect/Builtin/BuiltinToLLVMIRTranslation.h"
+#include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h"
+
+#include "llvm/IRReader/IRReader.h"
+#include "llvm/Support/MemoryBufferRef.h"
+#include "llvm/Support/TargetSelect.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/TargetParser/Host.h"
+
+#include "gmock/gmock.h"
+
+using namespace mlir;
+
+// Skip the test if the native target was not built.
+#if LLVM_NATIVE_TARGET_TEST_ENABLED == 0
+#define SKIP_WITHOUT_NATIVE(x) DISABLED_##x
+#else
+#define SKIP_WITHOUT_NATIVE(x) x
+#endif
+
+class MLIRTargetLLVM : public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ llvm::InitializeNativeTarget();
+ llvm::InitializeNativeTargetAsmPrinter();
+ }
+};
+
+TEST_F(MLIRTargetLLVM, SKIP_WITHOUT_NATIVE(SerializeToLLVMBitcode)) {
+ std::string moduleStr = R"mlir(
+ llvm.func @foo(%arg0 : i32) {
+ llvm.return
+ }
+ )mlir";
+
+ DialectRegistry registry;
+ registerBuiltinDialectTranslation(registry);
+ registerLLVMDialectTranslation(registry);
+ MLIRContext context(registry);
+
+ OwningOpRef<ModuleOp> module =
+ parseSourceString<ModuleOp>(moduleStr, &context);
+ ASSERT_TRUE(!!module);
+
+ // Serialize the module.
+ std::string targetTriple = llvm::sys::getDefaultTargetTriple();
+ LLVM::ModuleToObject serializer(*(module->getOperation()), targetTriple, "",
+ "");
+ std::optional<SmallVector<char, 0>> serializedModule = serializer.run();
+ ASSERT_TRUE(!!serializedModule);
+ ASSERT_TRUE(serializedModule->size() > 0);
+
+ // Read the serialized module.
+ llvm::MemoryBufferRef buffer(
+ StringRef(serializedModule->data(), serializedModule->size()), "module");
+ llvm::LLVMContext llvmContext;
+ llvm::Expected<std::unique_ptr<llvm::Module>> llvmModule =
+ llvm::getLazyBitcodeModule(buffer, llvmContext);
+ ASSERT_TRUE(!!llvmModule);
+ ASSERT_TRUE(!!*llvmModule);
+
+ // Check that it has a function named `foo`.
+ ASSERT_TRUE((*llvmModule)->getFunction("foo") != nullptr);
+}
More information about the Mlir-commits
mailing list