xref: /aosp_15_r20/external/toolchain-utils/afdo_redaction/remove_indirect_calls.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2019 The ChromiumOS Authors
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Script to remove all indirect call targets from textual AFDO profiles.
8
9Indirect call samples can cause code to appear 'live' when it otherwise
10wouldn't be. This resurrection can happen either by the way of profile-based
11speculative devirtualization, or because of imprecision in LLVM's liveness
12calculations when performing LTO.
13
14This generally isn't a problem when an AFDO profile is applied to the binary it
15was collected on. However, because we e.g., build NaCl from the same set of
16objects as Chrome, this can become problematic, and lead to NaCl doubling in
17size (or worse). See crbug.com/1005023 and crbug.com/916130.
18"""
19
20
21import argparse
22import re
23
24
25def _remove_indirect_call_targets(lines):
26    # Lines with indirect call targets look like:
27    #   1.1: 1234 foo:111 bar:122
28    #
29    # Where 1.1 is the line info/discriminator, 1234 is the total number of
30    # samples seen for that line/discriminator, foo:111 is "111 of the calls here
31    # went to foo," and bar:122 is "122 of the calls here went to bar."
32    call_target_re = re.compile(
33        r"""
34      ^\s+                    # Top-level lines are function records.
35      \d+(?:\.\d+)?:          # Line info/discriminator
36      \s+
37      \d+                     # Total sample count
38      \s+
39      ((?:[^\s:]+:\d+\s*)+)   # Indirect call target(s)
40      $
41  """,
42        re.VERBOSE,
43    )
44    for line in lines:
45        line = line.rstrip()
46
47        match = call_target_re.match(line)
48        if not match:
49            yield line + "\n"
50            continue
51
52        group_start, group_end = match.span(1)
53        assert group_end == len(line)
54        yield line[:group_start].rstrip() + "\n"
55
56
57def run(input_stream, output_stream):
58    for line in _remove_indirect_call_targets(input_stream):
59        output_stream.write(line)
60
61
62def main():
63    parser = argparse.ArgumentParser(
64        description=__doc__,
65        formatter_class=argparse.RawDescriptionHelpFormatter,
66    )
67    parser.add_argument(
68        "--input",
69        default="/dev/stdin",
70        help="File to read from. Defaults to stdin.",
71    )
72    parser.add_argument(
73        "--output",
74        default="/dev/stdout",
75        help="File to write to. Defaults to stdout.",
76    )
77    args = parser.parse_args()
78
79    with open(args.input) as stdin:
80        with open(args.output, "w") as stdout:
81            run(stdin, stdout)
82
83
84if __name__ == "__main__":
85    main()
86