diff --git a/include/revng/Model/Pass/PurgeUnnamedAndUnreachableTypes.h b/include/revng/Model/Pass/PurgeUnnamedAndUnreachableTypes.h index d25f10489..5f604fdf5 100644 --- a/include/revng/Model/Pass/PurgeUnnamedAndUnreachableTypes.h +++ b/include/revng/Model/Pass/PurgeUnnamedAndUnreachableTypes.h @@ -12,4 +12,10 @@ namespace model { /// "outside" the type system itself. void purgeUnnamedAndUnreachableTypes(TupleTree &Model); +/// Remove all the types that cannot be reached from any type from `Functions`. +void pruneUnusedTypes(TupleTree &Model); + +/// Implement the purge logic. +template +void purgeTypesImpl(TupleTree &Model); } // namespace model diff --git a/lib/Model/Importer/DebugInfo/DwarfImporter.cpp b/lib/Model/Importer/DebugInfo/DwarfImporter.cpp index a8599e4f4..dd81f195c 100644 --- a/lib/Model/Importer/DebugInfo/DwarfImporter.cpp +++ b/lib/Model/Importer/DebugInfo/DwarfImporter.cpp @@ -20,6 +20,7 @@ #include "llvm/Support/Error.h" #include "llvm/Support/raw_os_ostream.h" #include "llvm/Support/raw_ostream.h" + #include "revng/Model/Importer/DebugInfo/DwarfImporter.h" #include "revng/Model/Pass/AllPasses.h" #include "revng/Model/Processing.h" diff --git a/lib/Model/Pass/PurgeUnnamedAndUnreachableTypes.cpp b/lib/Model/Pass/PurgeUnnamedAndUnreachableTypes.cpp index b80ae7c48..6760d2c32 100644 --- a/lib/Model/Pass/PurgeUnnamedAndUnreachableTypes.cpp +++ b/lib/Model/Pass/PurgeUnnamedAndUnreachableTypes.cpp @@ -19,6 +19,12 @@ static RegisterModelPass R("purge-unnamed-and-unreachable-types", "system itself", model::purgeUnnamedAndUnreachableTypes); +static RegisterModelPass RPruneUnusedTypes("prune-unused-types", + "Remove all the types that cannot " + "be reached from " + "any type from a Function ", + model::pruneUnusedTypes); + template static void visitTuple(V &&Visitor, T &Tuple, const std::index_sequence &) { @@ -48,6 +54,15 @@ static auto visitTupleExcept(V &&Visitor, T &Tuple, E *Exclude) { } void model::purgeUnnamedAndUnreachableTypes(TupleTree &Model) { + purgeTypesImpl(Model); +} + +void model::pruneUnusedTypes(TupleTree &Model) { + purgeTypesImpl(Model); +} + +template +void model::purgeTypesImpl(TupleTree &Model) { struct NodeData { model::Type *T; }; @@ -59,13 +74,25 @@ void model::purgeUnnamedAndUnreachableTypes(TupleTree &Model) { llvm::SmallPtrSet ToKeep; - // Create nodes - for (UpcastablePointer &T : Model->Types) { + // Remember those types we want to preserve. + if constexpr (PruneAllUnusedTypes) { + for (const auto &Function : Model->Functions) { + if (Function.Prototype.isValid()) { + ToKeep.insert(const_cast(Function.Prototype.get())); + } + } - if (not T->CustomName.empty() or not T->OriginalName.empty()) - ToKeep.insert(T.get()); + for (UpcastablePointer &T : Model->Types) { + TypeToNode[T.get()] = TypeGraph.addNode(NodeData{ T.get() }); + } + } else { + for (UpcastablePointer &T : Model->Types) { - TypeToNode[T.get()] = TypeGraph.addNode(NodeData{ T.get() }); + if (not T->CustomName.empty() or not T->OriginalName.empty()) + ToKeep.insert(T.get()); + + TypeToNode[T.get()] = TypeGraph.addNode(NodeData{ T.get() }); + } } // Create type system edges @@ -77,16 +104,18 @@ void model::purgeUnnamedAndUnreachableTypes(TupleTree &Model) { } // Record references to types *outside* of Model->Types - auto VisitBinary = [&](auto &Field) { - auto Visitor = [&](auto &Element) { - using type = std::decay_t; - if constexpr (std::is_same_v) - if (Element.isValid()) - ToKeep.insert(Element.get()); + if constexpr (!PruneAllUnusedTypes) { + auto VisitBinary = [&](auto &Field) { + auto Visitor = [&](auto &Element) { + using type = std::decay_t; + if constexpr (std::is_same_v) + if (Element.isValid()) + ToKeep.insert(Element.get()); + }; + visitTupleTree(Field, Visitor, [](auto) {}); }; - visitTupleTree(Field, Visitor, [](auto) {}); - }; - visitTupleExcept(VisitBinary, *Model, &Model->Types); + visitTupleExcept(VisitBinary, *Model, &Model->Types); + } // Visit all the nodes reachable from ToKeep df_iterator_default_set Visited; diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 571d027de..e27ba9961 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -149,6 +149,12 @@ python_module(TARGET_NAME revng-python-cmd-daemon MODULE_FILES python_module(TARGET_NAME revng-python-cmd-download-pdb MODULE_FILES "revng/cli/download_pdb.py") +# +# Install revng.cli.hard_purge +# +python_module(TARGET_NAME revng-python-cmd-hard-purge MODULE_FILES + "revng/cli/hard_purge.py") + # # Install revng.api # diff --git a/python/revng/cli/commands_registry.py b/python/revng/cli/commands_registry.py index 8f9fe9d51..8ac8ad145 100644 --- a/python/revng/cli/commands_registry.py +++ b/python/revng/cli/commands_registry.py @@ -184,4 +184,6 @@ commands_registry.define_namespace(("model", "import")) commands_registry.define_namespace(("yield",)) commands_registry.define_namespace(("yield", "assembly")) + commands_registry.define_namespace(("model", "download-pdb")) +commands_registry.define_namespace(("model", "hard-purge")) diff --git a/python/revng/cli/hard_purge.py b/python/revng/cli/hard_purge.py new file mode 100644 index 000000000..59a953051 --- /dev/null +++ b/python/revng/cli/hard_purge.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +# +# This file is distributed under the MIT License. See LICENSE.md for details. +# + +import sys +from tempfile import NamedTemporaryFile + +import yaml + +from .commands_registry import Command, Options, commands_registry +from .revng import run_revng_command + + +def log(message): + sys.stderr.write(message + "\n") + + +def open_argument(path, mode): + if path == "-": + return sys.stdin + elif path == "/dev/stdout": + return sys.stdout + return open(path, mode) + + +class HardPurgeCommand(Command): + def __init__(self): + super().__init__( + ("model", "hard-purge"), + "Purge all the functions from original model that does not exist in " + "the reference model.", + ) + + def register_arguments(self, parser): + parser.add_argument( + "reference_model_path", default="", help="The reference model in form of YAML." + ) + parser.add_argument( + "original_model_path", + nargs="?", + default="-", + help="The original model in form of YAML.", + ) + parser.add_argument( + "-o", + dest="purged_model_path", + nargs="?", + default="/dev/stdout", + help="The pruned model in form of YAML.", + ) + + def log(self, message): + if self.verbose: + sys.stderr.write(message + "\n") + + def run(self, options: Options): + args = options.parsed_args + self.verbose = args.verbose + + functions_to_preserve = set() + + # Collect functions to be preserved. + with open_argument(args.reference_model_path, "rb") as reference_model_file: + self.log("Loading the reference model...") + reference_model = yaml.load(reference_model_file, Loader=yaml.SafeLoader) + + if "Functions" in reference_model: + for function in reference_model["Functions"]: + function_name = function["OriginalName"] + self.log(" Function to be preserved: " + function_name) + functions_to_preserve.add(function_name) + + if "ImportedDynamicFunctions" in reference_model: + for dynamic_function in reference_model["ImportedDynamicFunctions"]: + function_name = dynamic_function["OriginalName"] + self.log(" Dynamic function to be preserved: " + function_name) + functions_to_preserve.add(function_name) + + # Remove the functions. + self.log("Removing functions from original mode...") + patched_model = {} + with open_argument(args.original_model_path, "r") as patched_file: + patched_model = yaml.load(patched_file, Loader=yaml.SafeLoader) + + # Delete functions. + patched_model["Functions"] = [ + f for f in patched_model["Functions"] if f["OriginalName"] in functions_to_preserve + ] + + # Delete dynamic functions. + patched_model["ImportedDynamicFunctions"] = [ + f + for f in patched_model["ImportedDynamicFunctions"] + if f["OriginalName"] in functions_to_preserve + ] + + def temporary_file(suffix="", mode="w+"): + return NamedTemporaryFile(suffix=suffix, mode=mode, delete=not options.keep_temporaries) + + with temporary_file(suffix=".yml") as model_file: + model_file.write("---\n") + yaml.dump(patched_model, stream=model_file) + model_file.write("...\n") + model_file.flush() + + # Optimize the model by purging all unreachable types from any Function. + result = run_revng_command( + [ + "model", + "opt", + "-prune-unused-types", + model_file.name, + "-o", + args.purged_model_path, + ], + options, + ) + + return result + + return 0 + + +commands_registry.register_command(HardPurgeCommand()) diff --git a/python/revng/cli/revng.py b/python/revng/cli/revng.py index 02264b15c..75d859882 100755 --- a/python/revng/cli/revng.py +++ b/python/revng/cli/revng.py @@ -18,6 +18,7 @@ def run_revng_command(arguments, options: Options): # Import built-in commands from .daemon import DaemonCommand from .download_pdb import DownloadPDBCommand + from .hard_purge import HardPurgeCommand from .lift import LiftCommand from .llvm_pipeline import IRPipelineCommand from .opt import IROptCommand