mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
Add revng merge llvm command
Add the `revng merge llvm` command which allows linking multiple LLVM IR modules together into a single module.
This commit is contained in:
committed by
Alessandro Di Federico
parent
5287e69be0
commit
49b2c523ab
@@ -351,6 +351,7 @@ set(REVNG_CLI_COMMANDS_MODULE_FILES
|
||||
revng/internal/cli/_commands/test_docs/__init__.py
|
||||
revng/internal/cli/_commands/test_docs/doctest_runner.py
|
||||
revng/internal/cli/_commands/trace_run.py
|
||||
revng/internal/cli/_commands/merge-irs.py
|
||||
revng/internal/cli/_commands/model_compare.py
|
||||
revng/internal/cli/_commands/model_migrate.py
|
||||
revng/internal/cli/_commands/model_export_sqlite.py
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#
|
||||
# This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
#
|
||||
|
||||
import tarfile
|
||||
from argparse import ArgumentParser, RawDescriptionHelpFormatter
|
||||
from base64 import b64decode
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
from shutil import copyfileobj
|
||||
from tempfile import TemporaryDirectory, TemporaryFile
|
||||
from typing import IO
|
||||
|
||||
import yaml
|
||||
|
||||
from revng.internal.cli.commands_registry import Command, CommandsRegistry, Options
|
||||
from revng.internal.cli.support import build_command_with_loads, file_wrapper, try_run
|
||||
from revng.support import TarDictionary
|
||||
|
||||
|
||||
def read_single_file(input_: IO[bytes]) -> list[bytes]:
|
||||
# If we encounter a non-seekable file (e.g. stdin) first copy it to a
|
||||
# temporary file and then re-run this function
|
||||
if not input_.seekable():
|
||||
with TemporaryFile() as temp_file:
|
||||
copyfileobj(input_, temp_file)
|
||||
temp_file.seek(0)
|
||||
return read_single_file(temp_file)
|
||||
|
||||
# If here, the file is seekable, test the various formats by trying to read
|
||||
# them and moving onto the next format if the previous throws an error
|
||||
|
||||
# Test for YAML
|
||||
with suppress(yaml.YAMLError):
|
||||
data = yaml.load(input_, Loader=yaml.CSafeLoader)
|
||||
return [b64decode(v) for v in data.values()]
|
||||
|
||||
# Test for tar
|
||||
input_.seek(0)
|
||||
with suppress(tarfile.ReadError):
|
||||
return list(TarDictionary(input_).values())
|
||||
|
||||
# No good format found, assume it's a single plain file
|
||||
input_.seek(0)
|
||||
return [input_.read()]
|
||||
|
||||
|
||||
class MergeLLVMIRCommand(Command):
|
||||
def __init__(self):
|
||||
super().__init__(("merge", "llvm"), "Merge multiple LLVM IR files into a single one")
|
||||
|
||||
def register_arguments(self, parser: ArgumentParser):
|
||||
parser.formatter_class = RawDescriptionHelpFormatter
|
||||
parser.description = """Links multiple LLVM IR files into a single one.
|
||||
This program works differently depending on the number of inputs:
|
||||
* If one input is passed it is interpreted as either:
|
||||
* a YAML dict with base64-encoded values
|
||||
* a tar where each file is a bitcode file
|
||||
* a single LLVM module
|
||||
* If no input is passed, treat stdin as a single input
|
||||
* If multiple input files are passed, treat all of them as bitcode files
|
||||
"""
|
||||
parser.add_argument("-o", "--output", help="Where to output the file, stdout if omitted")
|
||||
parser.add_argument("inputs", nargs="*", help="Input file(s), omit to use stdin")
|
||||
|
||||
def run(self, options: Options):
|
||||
args = options.parsed_args
|
||||
if len(args.inputs) >= 2:
|
||||
return self.run_merge(args.inputs, options)
|
||||
|
||||
input_file = args.inputs[0] if len(args.inputs) == 1 else "-"
|
||||
with file_wrapper(input_file, "rb") as input_:
|
||||
data = read_single_file(input_)
|
||||
|
||||
with TemporaryDirectory(prefix="tmp.revng-merge-llvm.") as temp_dir:
|
||||
temp_dir_path = Path(temp_dir)
|
||||
input_paths = []
|
||||
for index, value in enumerate(data):
|
||||
element_path = temp_dir_path / str(index)
|
||||
element_path.write_bytes(value)
|
||||
input_paths.append(str(element_path.resolve()))
|
||||
|
||||
return self.run_merge(input_paths, options)
|
||||
|
||||
def run_merge(self, inputs: list[str], options: Options):
|
||||
with file_wrapper(options.parsed_args.output, "wb") as output_file:
|
||||
return try_run(
|
||||
build_command_with_loads("merge-llvm", inputs, options),
|
||||
options,
|
||||
stdout=output_file,
|
||||
)
|
||||
|
||||
|
||||
def setup(commands_registry: CommandsRegistry):
|
||||
commands_registry.define_namespace(("merge",), "Tools to merge function-wise object together")
|
||||
commands_registry.register_command(MergeLLVMIRCommand())
|
||||
@@ -5,6 +5,7 @@
|
||||
add_subdirectory(clift-opt)
|
||||
add_subdirectory(internal)
|
||||
add_subdirectory(lddtree)
|
||||
add_subdirectory(merge-llvm)
|
||||
add_subdirectory(model)
|
||||
add_subdirectory(pipeline)
|
||||
add_subdirectory(pypeline)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
#
|
||||
|
||||
revng_add_executable(merge-llvm Main.cpp)
|
||||
|
||||
target_link_libraries(merge-llvm revngSupport ${LLVM_LIBRARIES})
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include "llvm/IR/Module.h"
|
||||
#include "llvm/IRReader/IRReader.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include "llvm/Support/PluginLoader.h"
|
||||
#include "llvm/Support/SourceMgr.h"
|
||||
|
||||
#include "revng/Support/CommandLine.h"
|
||||
#include "revng/Support/Error.h"
|
||||
#include "revng/Support/IRHelpers.h"
|
||||
#include "revng/Support/InitRevng.h"
|
||||
#include "revng/Support/Tar.h"
|
||||
|
||||
static llvm::cl::list<std::string> Arguments(llvm::cl::Positional,
|
||||
llvm::cl::ZeroOrMore,
|
||||
llvm::cl::desc("FILES"),
|
||||
llvm::cl::cat(MainCategory));
|
||||
|
||||
static llvm::ExitOnError AbortOnError;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
revng::InitRevng X(argc, argv, "", { &MainCategory });
|
||||
|
||||
llvm::LLVMContext Context;
|
||||
std::unique_ptr<llvm::Module> OutputModule;
|
||||
|
||||
for (llvm::StringRef Filename : Arguments) {
|
||||
llvm::SMDiagnostic Err;
|
||||
auto Module = llvm::parseIRFile(Filename, Err, Context);
|
||||
revng_assert(Module != nullptr);
|
||||
|
||||
if (OutputModule != nullptr) {
|
||||
// Make the existing module be linked to the new module, this allows
|
||||
// preserving the order of definitions as the order of the supplied files
|
||||
std::swap(Module, OutputModule);
|
||||
linkFunctionModules(std::move(Module), OutputModule);
|
||||
} else {
|
||||
OutputModule = std::move(Module);
|
||||
}
|
||||
}
|
||||
|
||||
revng_assert(OutputModule != nullptr);
|
||||
revng::verify(OutputModule.get());
|
||||
{
|
||||
std::error_code EC;
|
||||
llvm::raw_fd_ostream OS("-", EC);
|
||||
revng_assert(not EC);
|
||||
|
||||
OutputModule->print(OS, nullptr);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user