From 8a9809814e992687ab5405502370ddb3c2649337 Mon Sep 17 00:00:00 2001 From: disinvite Date: Mon, 29 Jan 2024 16:01:09 -0500 Subject: [PATCH] roadmap: Suggest order of modules --- tools/roadmap/roadmap.py | 116 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/tools/roadmap/roadmap.py b/tools/roadmap/roadmap.py index e8023c1d..6fa7f1ba 100644 --- a/tools/roadmap/roadmap.py +++ b/tools/roadmap/roadmap.py @@ -58,6 +58,9 @@ def print_sections(sections): print() +ALLOWED_TYPE_ABBREVIATIONS = ["fun", "dat", "poi", "str", "vta"] + + def match_type_abbreviation(mtype: Optional[SymbolType]) -> str: """Return abbreviation of the given SymbolType name""" if mtype is None: @@ -66,6 +69,20 @@ def match_type_abbreviation(mtype: Optional[SymbolType]) -> str: return mtype.name.lower()[:3] +def truncate_module_name(prefix: str, module: str) -> str: + """Remove the CMakeFiles prefix and the .obj suffix for the given module. + Input: CMakeFiles/lego1.dir/, CMakeFiles/lego1.dir/LEGO1/define.cpp.obj + Output: LEGO1/define.cpp""" + + if module.startswith(prefix): + module = module[len(prefix) :] + + if module.endswith(".obj"): + module = module[:-4] + + return module + + RoadmapRow = namedtuple( "RoadmapRow", [ @@ -82,6 +99,94 @@ def match_type_abbreviation(mtype: Optional[SymbolType]) -> str: ) +class DeltaCollector: + """Reads each row of the results and aggregates information about the + placement of each module.""" + + def __init__(self, match_type: str = "fun"): + self.disp_map = {} + self.earliest = {} + self.seen = set() + self.match_type = "fun" + + match_type = str(match_type).strip().lower()[:3] + if match_type in ALLOWED_TYPE_ABBREVIATIONS: + self.match_type = match_type + + def read_row(self, row: RoadmapRow): + if row.module is None: + return + + self.seen.add(row.module) + if row.sym_type != self.match_type: + return + + if row.orig_addr is not None: + if row.orig_addr < self.earliest.get(row.module, 0xFFFFFFFFF): + self.earliest[row.module] = row.orig_addr + + if row.displacement is not None: + if row.module not in self.disp_map: + self.disp_map[row.module] = [] + + self.disp_map[row.module].append(row.displacement) + + +def suggest_order(results: List[RoadmapRow], match_type: str): + """Suggest the order of modules for CMakeLists.txt""" + + dc = DeltaCollector(match_type) + for row in results: + dc.read_row(row) + + leftover_modules = set(mod for mod in dc.seen if mod.startswith("CMakeFiles")) + + # A little convoluted, but we want to take the first two tokens + # of the string with '/' as the delimiter. + # i.e. CMakeFiles/isle.dir/ + # The idea is to print exactly what appears in CMakeLists.txt. + cmake_prefixes = sorted( + set("/".join(mod.split("/", 2)[:2]) + "/" for mod in leftover_modules) + ) + + # These may already be sorted by earliest, but make sure + first_function = [(earliest, module) for (module, earliest) in dc.earliest.items()] + first_function.sort() + + for prefix in cmake_prefixes: + print(prefix) + # Show modules ordered by the first appearance of whichever symbol type. + for start, module in first_function: + if not module.startswith(prefix): + continue + + leftover_modules.remove(module) + + avg_displacement = None + displacements = dc.disp_map.get(module) + if displacements is not None and len(displacements) > 0: + avg_displacement = int(sum(displacements) / len(displacements)) + + code_file = truncate_module_name(prefix, module) + print(f"0x{start:08x} {or_blank(avg_displacement):10} {code_file}") + + # These modules are included in the final binary (in some form) but + # they are not represented by whichever type of symbol we were checking. + # n.b. There could still be other modules that are part of CMakeLists.txt + # but are not included in the pdb for whatever reason. + # In other words: don't take the list we provide as the final word on what + # should or should not be included. This is merely a suggestion of the order. + for module in leftover_modules: + if not module.startswith(prefix): + continue + + # aligned with previous print + code_file = truncate_module_name(prefix, module) + print(f" no suggestion {code_file}") + + print() + + def print_text_report(results: List[RoadmapRow]): """Print the result with original and recomp addresses.""" for row in results: @@ -150,6 +255,13 @@ def parse_args() -> argparse.Namespace: parser.add_argument( "--verbose", "-v", action="store_true", help="Show recomp addresses in output" ) + parser.add_argument( + "--order", + const="fun", + nargs="?", + type=str, + help="Show suggested order of modules (using the specified symbol type)", + ) (args, _) = parser.parse_known_args() @@ -245,6 +357,10 @@ def to_roadmap_row(match): results = list(map(to_roadmap_row, engine.get_all())) + if args.order is not None: + suggest_order(results, args.order) + return + if args.csv is None: if args.verbose: print("ORIG sections:")