xref: /llvm-project/bolt/utils/llvm-bolt-wrapper.py (revision 76a9ea1321b1365713bbf6afafbd18cc5d7a9381)
1c2a961e4SAmir Ayupov#!/usr/bin/env python3
2c2a961e4SAmir Ayupovimport argparse
3c2a961e4SAmir Ayupovimport subprocess
4c2a961e4SAmir Ayupovfrom typing import *
5c2a961e4SAmir Ayupovimport tempfile
6c2a961e4SAmir Ayupovimport copy
7c2a961e4SAmir Ayupovimport os
8c2a961e4SAmir Ayupovimport shutil
9c2a961e4SAmir Ayupovimport sys
10c2a961e4SAmir Ayupovimport re
11c2a961e4SAmir Ayupovimport configparser
12c2a961e4SAmir Ayupovfrom types import SimpleNamespace
13c2a961e4SAmir Ayupovfrom textwrap import dedent
14c2a961e4SAmir Ayupov
15c2a961e4SAmir Ayupov# USAGE:
16c2a961e4SAmir Ayupov# 0. Prepare two BOLT build versions: base and compare.
17c2a961e4SAmir Ayupov# 1. Create the config by invoking this script with required options.
18c2a961e4SAmir Ayupov#    Save the config as `llvm-bolt-wrapper.ini` next to the script or
19c2a961e4SAmir Ayupov#    in the testing directory.
20c2a961e4SAmir Ayupov# In the base BOLT build directory:
21c2a961e4SAmir Ayupov# 2. Rename `llvm-bolt` to `llvm-bolt.real`
22c2a961e4SAmir Ayupov# 3. Create a symlink from this script to `llvm-bolt`
23c2a961e4SAmir Ayupov# 4. Create `llvm-bolt-wrapper.ini` and fill it using the example below.
24c2a961e4SAmir Ayupov#
25c2a961e4SAmir Ayupov# This script will compare binaries produced by base and compare BOLT, and
26c2a961e4SAmir Ayupov# report elapsed processing time and max RSS.
27c2a961e4SAmir Ayupov
28c2a961e4SAmir Ayupov# read options from config file llvm-bolt-wrapper.ini in script CWD
29c2a961e4SAmir Ayupov#
30c2a961e4SAmir Ayupov# [config]
31c2a961e4SAmir Ayupov# # mandatory
32c2a961e4SAmir Ayupov# base_bolt = /full/path/to/llvm-bolt.real
33c2a961e4SAmir Ayupov# cmp_bolt = /full/path/to/other/llvm-bolt
34c2a961e4SAmir Ayupov# # optional, default to False
35c2a961e4SAmir Ayupov# verbose
36c2a961e4SAmir Ayupov# keep_tmp
37c2a961e4SAmir Ayupov# no_minimize
38c2a961e4SAmir Ayupov# run_sequentially
39c2a961e4SAmir Ayupov# compare_output
40c2a961e4SAmir Ayupov# skip_binary_cmp
41c2a961e4SAmir Ayupov# # optional, defaults to timing.log in CWD
42c2a961e4SAmir Ayupov# timing_file = timing1.log
43c2a961e4SAmir Ayupov
44f98ee40fSTobias Hieta
45c2a961e4SAmir Ayupovdef read_cfg():
46c2a961e4SAmir Ayupov    src_dir = os.path.dirname(os.path.abspath(__file__))
47c2a961e4SAmir Ayupov    cfg = configparser.ConfigParser(allow_no_value=True)
48c2a961e4SAmir Ayupov    cfgs = cfg.read("llvm-bolt-wrapper.ini")
49c2a961e4SAmir Ayupov    if not cfgs:
50c2a961e4SAmir Ayupov        cfgs = cfg.read(os.path.join(src_dir, "llvm-bolt-wrapper.ini"))
51c2a961e4SAmir Ayupov    assert cfgs, f"llvm-bolt-wrapper.ini is not found in {os.getcwd()}"
52c2a961e4SAmir Ayupov
53c2a961e4SAmir Ayupov    def get_cfg(key):
54c2a961e4SAmir Ayupov        # if key is not present in config, assume False
55f98ee40fSTobias Hieta        if key not in cfg["config"]:
56c2a961e4SAmir Ayupov            return False
57c2a961e4SAmir Ayupov        # if key is present, but has no value, assume True
58f98ee40fSTobias Hieta        if not cfg["config"][key]:
59c2a961e4SAmir Ayupov            return True
60c2a961e4SAmir Ayupov        # if key has associated value, interpret the value
61f98ee40fSTobias Hieta        return cfg["config"].getboolean(key)
62c2a961e4SAmir Ayupov
63c2a961e4SAmir Ayupov    d = {
64c2a961e4SAmir Ayupov        # BOLT binary locations
65f98ee40fSTobias Hieta        "BASE_BOLT": cfg["config"]["base_bolt"],
66f98ee40fSTobias Hieta        "CMP_BOLT": cfg["config"]["cmp_bolt"],
67c2a961e4SAmir Ayupov        # optional
68f98ee40fSTobias Hieta        "VERBOSE": get_cfg("verbose"),
69f98ee40fSTobias Hieta        "KEEP_TMP": get_cfg("keep_tmp"),
70f98ee40fSTobias Hieta        "NO_MINIMIZE": get_cfg("no_minimize"),
71f98ee40fSTobias Hieta        "RUN_SEQUENTIALLY": get_cfg("run_sequentially"),
72f98ee40fSTobias Hieta        "COMPARE_OUTPUT": get_cfg("compare_output"),
73f98ee40fSTobias Hieta        "SKIP_BINARY_CMP": get_cfg("skip_binary_cmp"),
74f98ee40fSTobias Hieta        "TIMING_FILE": cfg["config"].get("timing_file", "timing.log"),
75c2a961e4SAmir Ayupov    }
76f98ee40fSTobias Hieta    if d["VERBOSE"]:
77c2a961e4SAmir Ayupov        print(f"Using config {os.path.abspath(cfgs[0])}")
78c2a961e4SAmir Ayupov    return SimpleNamespace(**d)
79c2a961e4SAmir Ayupov
80f98ee40fSTobias Hieta
81c2a961e4SAmir Ayupov# perf2bolt mode
82f98ee40fSTobias HietaPERF2BOLT_MODE = ["-aggregate-only", "-ignore-build-id"]
83c2a961e4SAmir Ayupov
84c2a961e4SAmir Ayupov# boltdiff mode
85f98ee40fSTobias HietaBOLTDIFF_MODE = ["-diff-only", "-o", "/dev/null"]
86c2a961e4SAmir Ayupov
87c2a961e4SAmir Ayupov# options to suppress binary differences as much as possible
88f98ee40fSTobias HietaMINIMIZE_DIFFS = ["-bolt-info=0"]
89c2a961e4SAmir Ayupov
90c2a961e4SAmir Ayupov# bolt output options that need to be intercepted
91c2a961e4SAmir AyupovBOLT_OUTPUT_OPTS = {
92f98ee40fSTobias Hieta    "-o": "BOLT output binary",
93f98ee40fSTobias Hieta    "-w": "BOLT recorded profile",
94c2a961e4SAmir Ayupov}
95c2a961e4SAmir Ayupov
96c2a961e4SAmir Ayupov# regex patterns to exclude the line from log comparison
97c2a961e4SAmir AyupovSKIP_MATCH = [
98f98ee40fSTobias Hieta    "BOLT-INFO: BOLT version",
99f98ee40fSTobias Hieta    r"^Args: ",
100f98ee40fSTobias Hieta    r"^BOLT-DEBUG:",
101f98ee40fSTobias Hieta    r"BOLT-INFO:.*data.*output data",
102f98ee40fSTobias Hieta    "WARNING: reading perf data directly",
103c2a961e4SAmir Ayupov]
104c2a961e4SAmir Ayupov
105f98ee40fSTobias Hieta
106c2a961e4SAmir Ayupovdef run_cmd(cmd, out_f, cfg):
107c2a961e4SAmir Ayupov    if cfg.VERBOSE:
108f98ee40fSTobias Hieta        print(" ".join(cmd))
109c2a961e4SAmir Ayupov    return subprocess.Popen(cmd, stdout=out_f, stderr=subprocess.STDOUT)
110c2a961e4SAmir Ayupov
111f98ee40fSTobias Hieta
112c2a961e4SAmir Ayupovdef run_bolt(bolt_path, bolt_args, out_f, cfg):
113f98ee40fSTobias Hieta    p2b = os.path.basename(sys.argv[0]) == "perf2bolt"  # perf2bolt mode
114f98ee40fSTobias Hieta    bd = os.path.basename(sys.argv[0]) == "llvm-boltdiff"  # boltdiff mode
115f98ee40fSTobias Hieta    cmd = ["/usr/bin/time", "-f", "%e %M", bolt_path] + bolt_args
116c2a961e4SAmir Ayupov    if p2b:
117c2a961e4SAmir Ayupov        # -ignore-build-id can occur at most once, hence remove it from cmd
118f98ee40fSTobias Hieta        if "-ignore-build-id" in cmd:
119f98ee40fSTobias Hieta            cmd.remove("-ignore-build-id")
120c2a961e4SAmir Ayupov        cmd += PERF2BOLT_MODE
121c2a961e4SAmir Ayupov    elif bd:
122c2a961e4SAmir Ayupov        cmd += BOLTDIFF_MODE
123*76a9ea13SAmir Ayupov    elif not cfg.NO_MINIMIZE:
124c2a961e4SAmir Ayupov        cmd += MINIMIZE_DIFFS
125c2a961e4SAmir Ayupov    return run_cmd(cmd, out_f, cfg)
126c2a961e4SAmir Ayupov
127f98ee40fSTobias Hieta
128c2a961e4SAmir Ayupovdef prepend_dash(args: Mapping[AnyStr, AnyStr]) -> Sequence[AnyStr]:
129f98ee40fSTobias Hieta    """
130c2a961e4SAmir Ayupov    Accepts parsed arguments and returns flat list with dash prepended to
131c2a961e4SAmir Ayupov    the option.
132c2a961e4SAmir Ayupov    Example: Namespace(o='test.tmp') -> ['-o', 'test.tmp']
133f98ee40fSTobias Hieta    """
134f98ee40fSTobias Hieta    dashed = [("-" + key, value) for (key, value) in args.items()]
135c2a961e4SAmir Ayupov    flattened = list(sum(dashed, ()))
136c2a961e4SAmir Ayupov    return flattened
137c2a961e4SAmir Ayupov
138f98ee40fSTobias Hieta
139c2a961e4SAmir Ayupovdef replace_cmp_path(tmp: AnyStr, args: Mapping[AnyStr, AnyStr]) -> Sequence[AnyStr]:
140f98ee40fSTobias Hieta    """
141c2a961e4SAmir Ayupov    Keeps file names, but replaces the path to a temp folder.
142c2a961e4SAmir Ayupov    Example: Namespace(o='abc/test.tmp') -> Namespace(o='/tmp/tmpf9un/test.tmp')
143c2a961e4SAmir Ayupov    Except preserve /dev/null.
144f98ee40fSTobias Hieta    """
145f98ee40fSTobias Hieta    replace_path = (
146f98ee40fSTobias Hieta        lambda x: os.path.join(tmp, os.path.basename(x))
147f98ee40fSTobias Hieta        if x != "/dev/null"
148f98ee40fSTobias Hieta        else "/dev/null"
149f98ee40fSTobias Hieta    )
150c2a961e4SAmir Ayupov    new_args = {key: replace_path(value) for key, value in args.items()}
151c2a961e4SAmir Ayupov    return prepend_dash(new_args)
152c2a961e4SAmir Ayupov
153f98ee40fSTobias Hieta
154c2a961e4SAmir Ayupovdef preprocess_args(args: argparse.Namespace) -> Mapping[AnyStr, AnyStr]:
155f98ee40fSTobias Hieta    """
156c2a961e4SAmir Ayupov    Drop options that weren't parsed (e.g. -w), convert to a dict
157f98ee40fSTobias Hieta    """
158c2a961e4SAmir Ayupov    return {key: value for key, value in vars(args).items() if value}
159c2a961e4SAmir Ayupov
160f98ee40fSTobias Hieta
161f98ee40fSTobias Hietadef write_to(txt, filename, mode="w"):
162c2a961e4SAmir Ayupov    with open(filename, mode) as f:
163c2a961e4SAmir Ayupov        f.write(txt)
164c2a961e4SAmir Ayupov
165f98ee40fSTobias Hieta
166c2a961e4SAmir Ayupovdef wait(proc, fdesc):
167c2a961e4SAmir Ayupov    proc.wait()
168c2a961e4SAmir Ayupov    fdesc.close()
169c2a961e4SAmir Ayupov    return open(fdesc.name)
170c2a961e4SAmir Ayupov
171f98ee40fSTobias Hieta
172c2a961e4SAmir Ayupovdef compare_logs(main, cmp, skip_begin=0, skip_end=0, str_input=True):
173f98ee40fSTobias Hieta    """
174c2a961e4SAmir Ayupov    Compares logs but allows for certain lines to be excluded from comparison.
175c2a961e4SAmir Ayupov    If str_input is True (default), the input it assumed to be a string,
176c2a961e4SAmir Ayupov    which is split into lines. Otherwise the input is assumed to be a file.
177c2a961e4SAmir Ayupov    Returns None on success, mismatch otherwise.
178f98ee40fSTobias Hieta    """
179c2a961e4SAmir Ayupov    main_inp = main.splitlines() if str_input else main.readlines()
180c2a961e4SAmir Ayupov    cmp_inp = cmp.splitlines() if str_input else cmp.readlines()
181c2a961e4SAmir Ayupov    # rewind logs after consumption
182c2a961e4SAmir Ayupov    if not str_input:
183c2a961e4SAmir Ayupov        main.seek(0)
184c2a961e4SAmir Ayupov        cmp.seek(0)
185c2a961e4SAmir Ayupov    for lhs, rhs in list(zip(main_inp, cmp_inp))[skip_begin : -skip_end or None]:
186c2a961e4SAmir Ayupov        if lhs != rhs:
187c2a961e4SAmir Ayupov            # check skip patterns
188c2a961e4SAmir Ayupov            for skip in SKIP_MATCH:
189c2a961e4SAmir Ayupov                # both lines must contain the pattern
190c2a961e4SAmir Ayupov                if re.search(skip, lhs) and re.search(skip, rhs):
191c2a961e4SAmir Ayupov                    break
192c2a961e4SAmir Ayupov            # otherwise return mismatching lines
193c2a961e4SAmir Ayupov            else:
194c2a961e4SAmir Ayupov                return (lhs, rhs)
195c2a961e4SAmir Ayupov    return None
196c2a961e4SAmir Ayupov
197f98ee40fSTobias Hieta
198c2a961e4SAmir Ayupovdef fmt_cmp(cmp_tuple):
199c2a961e4SAmir Ayupov    if not cmp_tuple:
200f98ee40fSTobias Hieta        return ""
201f98ee40fSTobias Hieta    return f"main:\n{cmp_tuple[0]}\ncmp:\n{cmp_tuple[1]}\n"
202f98ee40fSTobias Hieta
203c2a961e4SAmir Ayupov
204c2a961e4SAmir Ayupovdef compare_with(lhs, rhs, cmd, skip_begin=0, skip_end=0):
205f98ee40fSTobias Hieta    """
206c2a961e4SAmir Ayupov    Runs cmd on both lhs and rhs and compares stdout.
207c2a961e4SAmir Ayupov    Returns tuple (mismatch, lhs_stdout):
208c2a961e4SAmir Ayupov        - if stdout matches between two files, mismatch is None,
209c2a961e4SAmir Ayupov        - otherwise mismatch is a tuple of mismatching lines.
210f98ee40fSTobias Hieta    """
211f98ee40fSTobias Hieta    run = lambda binary: subprocess.run(
212f98ee40fSTobias Hieta        cmd.split() + [binary], text=True, check=True, capture_output=True
213f98ee40fSTobias Hieta    ).stdout
214c2a961e4SAmir Ayupov    run_lhs = run(lhs)
215c2a961e4SAmir Ayupov    run_rhs = run(rhs)
216c2a961e4SAmir Ayupov    cmp = compare_logs(run_lhs, run_rhs, skip_begin, skip_end)
217c2a961e4SAmir Ayupov    return cmp, run_lhs
218c2a961e4SAmir Ayupov
219f98ee40fSTobias Hieta
220c2a961e4SAmir Ayupovdef parse_cmp_offset(cmp_out):
221f98ee40fSTobias Hieta    """
222c2a961e4SAmir Ayupov    Extracts byte number from cmp output:
223c2a961e4SAmir Ayupov    file1 file2 differ: byte X, line Y
224f98ee40fSTobias Hieta    """
2258421c7adSJob Noorman    # NOTE: cmp counts bytes starting from 1!
226f98ee40fSTobias Hieta    return int(re.search(r"byte (\d+),", cmp_out).groups()[0]) - 1
227f98ee40fSTobias Hieta
228c2a961e4SAmir Ayupov
229c2a961e4SAmir Ayupovdef report_real_time(binary, main_err, cmp_err, cfg):
230f98ee40fSTobias Hieta    """
231c2a961e4SAmir Ayupov    Extracts real time from stderr and appends it to TIMING FILE it as csv:
232c2a961e4SAmir Ayupov    "output binary; base bolt; cmp bolt"
233f98ee40fSTobias Hieta    """
234f98ee40fSTobias Hieta
235c2a961e4SAmir Ayupov    def get_real_from_stderr(logline):
236f98ee40fSTobias Hieta        return "; ".join(logline.split())
237f98ee40fSTobias Hieta
238c2a961e4SAmir Ayupov    for line in main_err:
239c2a961e4SAmir Ayupov        pass
240c2a961e4SAmir Ayupov    main = get_real_from_stderr(line)
241c2a961e4SAmir Ayupov    for line in cmp_err:
242c2a961e4SAmir Ayupov        pass
243c2a961e4SAmir Ayupov    cmp = get_real_from_stderr(line)
244f98ee40fSTobias Hieta    write_to(f"{binary}; {main}; {cmp}\n", cfg.TIMING_FILE, "a")
245c2a961e4SAmir Ayupov    # rewind logs after consumption
246c2a961e4SAmir Ayupov    main_err.seek(0)
247c2a961e4SAmir Ayupov    cmp_err.seek(0)
248c2a961e4SAmir Ayupov
249f98ee40fSTobias Hieta
250c2a961e4SAmir Ayupovdef clean_exit(tmp, out, exitcode, cfg):
251c2a961e4SAmir Ayupov    # temp files are only cleaned on success
252c2a961e4SAmir Ayupov    if not cfg.KEEP_TMP:
253c2a961e4SAmir Ayupov        shutil.rmtree(tmp)
254c2a961e4SAmir Ayupov
255c2a961e4SAmir Ayupov    # report stdout and stderr from the main process
256c2a961e4SAmir Ayupov    shutil.copyfileobj(out, sys.stdout)
257c2a961e4SAmir Ayupov    sys.exit(exitcode)
258c2a961e4SAmir Ayupov
259f98ee40fSTobias Hieta
260c2a961e4SAmir Ayupovdef find_section(offset, readelf_hdr):
261f98ee40fSTobias Hieta    hdr = readelf_hdr.split("\n")
262c2a961e4SAmir Ayupov    section = None
263c2a961e4SAmir Ayupov    # extract sections table (parse objdump -hw output)
264c2a961e4SAmir Ayupov    for line in hdr[5:-1]:
265c2a961e4SAmir Ayupov        cols = line.strip().split()
266c2a961e4SAmir Ayupov        # extract section offset
267c2a961e4SAmir Ayupov        file_offset = int(cols[5], 16)
268c2a961e4SAmir Ayupov        # section size
269c2a961e4SAmir Ayupov        size = int(cols[2], 16)
2708a5a1205SJob Noorman        if offset >= file_offset and offset < file_offset + size:
271c2a961e4SAmir Ayupov            if sys.stdout.isatty():  # terminal supports colors
272c2a961e4SAmir Ayupov                print(f"\033[1m{line}\033[0m")
273c2a961e4SAmir Ayupov            else:
274c2a961e4SAmir Ayupov                print(f">{line}")
275c2a961e4SAmir Ayupov            section = cols[1]
276c2a961e4SAmir Ayupov        else:
277c2a961e4SAmir Ayupov            print(line)
278c2a961e4SAmir Ayupov    return section
279c2a961e4SAmir Ayupov
280f98ee40fSTobias Hieta
281c2a961e4SAmir Ayupovdef main_config_generator():
282c2a961e4SAmir Ayupov    parser = argparse.ArgumentParser()
283f98ee40fSTobias Hieta    parser.add_argument("base_bolt", help="Full path to base llvm-bolt binary")
284f98ee40fSTobias Hieta    parser.add_argument("cmp_bolt", help="Full path to cmp llvm-bolt binary")
285f98ee40fSTobias Hieta    parser.add_argument(
286f98ee40fSTobias Hieta        "--verbose",
287f98ee40fSTobias Hieta        action="store_true",
288f98ee40fSTobias Hieta        help="Print subprocess invocation cmdline (default False)",
289f98ee40fSTobias Hieta    )
290f98ee40fSTobias Hieta    parser.add_argument(
291f98ee40fSTobias Hieta        "--keep_tmp",
292f98ee40fSTobias Hieta        action="store_true",
293f98ee40fSTobias Hieta        help="Preserve tmp folder on a clean exit "
294f98ee40fSTobias Hieta        "(tmp directory is preserved on crash by default)",
295f98ee40fSTobias Hieta    )
296f98ee40fSTobias Hieta    parser.add_argument(
297f98ee40fSTobias Hieta        "--no_minimize",
298f98ee40fSTobias Hieta        action="store_true",
299f98ee40fSTobias Hieta        help=f"Do not add `{MINIMIZE_DIFFS}` that is used "
300f98ee40fSTobias Hieta        "by default to reduce binary differences",
301f98ee40fSTobias Hieta    )
302f98ee40fSTobias Hieta    parser.add_argument(
303f98ee40fSTobias Hieta        "--run_sequentially",
304f98ee40fSTobias Hieta        action="store_true",
305f98ee40fSTobias Hieta        help="Run both binaries sequentially (default "
306f98ee40fSTobias Hieta        "in parallel). Use for timing comparison",
307f98ee40fSTobias Hieta    )
308f98ee40fSTobias Hieta    parser.add_argument(
309f98ee40fSTobias Hieta        "--compare_output",
310f98ee40fSTobias Hieta        action="store_true",
311f98ee40fSTobias Hieta        help="Compare bolt stdout/stderr (disabled by default)",
312f98ee40fSTobias Hieta    )
313f98ee40fSTobias Hieta    parser.add_argument(
314f98ee40fSTobias Hieta        "--skip_binary_cmp", action="store_true", help="Disable output comparison"
315f98ee40fSTobias Hieta    )
316f98ee40fSTobias Hieta    parser.add_argument(
317f98ee40fSTobias Hieta        "--timing_file",
318f98ee40fSTobias Hieta        help="Override path to timing log " "file (default `timing.log` in CWD)",
319f98ee40fSTobias Hieta    )
320c2a961e4SAmir Ayupov    args = parser.parse_args()
321c2a961e4SAmir Ayupov
322f98ee40fSTobias Hieta    print(
323f98ee40fSTobias Hieta        dedent(
324f98ee40fSTobias Hieta            f"""\
325c2a961e4SAmir Ayupov    [config]
326c2a961e4SAmir Ayupov    # mandatory
327c2a961e4SAmir Ayupov    base_bolt = {args.base_bolt}
328f98ee40fSTobias Hieta    cmp_bolt = {args.cmp_bolt}"""
329f98ee40fSTobias Hieta        )
330f98ee40fSTobias Hieta    )
331c2a961e4SAmir Ayupov    del args.base_bolt
332c2a961e4SAmir Ayupov    del args.cmp_bolt
333c2a961e4SAmir Ayupov    d = vars(args)
334c2a961e4SAmir Ayupov    if any(d.values()):
335c2a961e4SAmir Ayupov        print("# optional")
336c2a961e4SAmir Ayupov        for key, value in d.items():
337c2a961e4SAmir Ayupov            if value:
338c2a961e4SAmir Ayupov                print(key)
339c2a961e4SAmir Ayupov
340f98ee40fSTobias Hieta
341c2a961e4SAmir Ayupovdef main():
342c2a961e4SAmir Ayupov    cfg = read_cfg()
343c2a961e4SAmir Ayupov    # intercept output arguments
344c2a961e4SAmir Ayupov    parser = argparse.ArgumentParser(add_help=False)
345c2a961e4SAmir Ayupov    for option, help in BOLT_OUTPUT_OPTS.items():
346c2a961e4SAmir Ayupov        parser.add_argument(option, help=help)
347c2a961e4SAmir Ayupov    args, unknownargs = parser.parse_known_args()
348c2a961e4SAmir Ayupov    args = preprocess_args(args)
349c2a961e4SAmir Ayupov    cmp_args = copy.deepcopy(args)
350c2a961e4SAmir Ayupov    tmp = tempfile.mkdtemp()
351c2a961e4SAmir Ayupov    cmp_args = replace_cmp_path(tmp, cmp_args)
352c2a961e4SAmir Ayupov
353c2a961e4SAmir Ayupov    # reconstruct output arguments: prepend dash
354c2a961e4SAmir Ayupov    args = prepend_dash(args)
355c2a961e4SAmir Ayupov
356c2a961e4SAmir Ayupov    # run both BOLT binaries
357f98ee40fSTobias Hieta    main_f = open(os.path.join(tmp, "main_bolt.stdout"), "w")
358f98ee40fSTobias Hieta    cmp_f = open(os.path.join(tmp, "cmp_bolt.stdout"), "w")
359c2a961e4SAmir Ayupov    main_bolt = run_bolt(cfg.BASE_BOLT, unknownargs + args, main_f, cfg)
360c2a961e4SAmir Ayupov    if cfg.RUN_SEQUENTIALLY:
361c2a961e4SAmir Ayupov        main_out = wait(main_bolt, main_f)
362c2a961e4SAmir Ayupov        cmp_bolt = run_bolt(cfg.CMP_BOLT, unknownargs + cmp_args, cmp_f, cfg)
363c2a961e4SAmir Ayupov    else:
364c2a961e4SAmir Ayupov        cmp_bolt = run_bolt(cfg.CMP_BOLT, unknownargs + cmp_args, cmp_f, cfg)
365c2a961e4SAmir Ayupov        main_out = wait(main_bolt, main_f)
366c2a961e4SAmir Ayupov    cmp_out = wait(cmp_bolt, cmp_f)
367c2a961e4SAmir Ayupov
368c2a961e4SAmir Ayupov    # check exit code
369c2a961e4SAmir Ayupov    if main_bolt.returncode != cmp_bolt.returncode:
370c2a961e4SAmir Ayupov        print(tmp)
371c2a961e4SAmir Ayupov        exit("exitcode mismatch")
372c2a961e4SAmir Ayupov
373701109b9SAmir Ayupov    # don't compare output upon unsuccessful exit
374701109b9SAmir Ayupov    if main_bolt.returncode != 0:
375701109b9SAmir Ayupov        cfg.SKIP_BINARY_CMP = True
376701109b9SAmir Ayupov
377c2a961e4SAmir Ayupov    # compare logs, skip_end=1 skips the line with time
378f98ee40fSTobias Hieta    out = (
379f98ee40fSTobias Hieta        compare_logs(main_out, cmp_out, skip_end=1, str_input=False)
380f98ee40fSTobias Hieta        if cfg.COMPARE_OUTPUT
381f98ee40fSTobias Hieta        else None
382f98ee40fSTobias Hieta    )
383c2a961e4SAmir Ayupov    if out:
384c2a961e4SAmir Ayupov        print(tmp)
385c2a961e4SAmir Ayupov        print(fmt_cmp(out))
386f98ee40fSTobias Hieta        write_to(fmt_cmp(out), os.path.join(tmp, "summary.txt"))
387c2a961e4SAmir Ayupov        exit("logs mismatch")
388c2a961e4SAmir Ayupov
389f98ee40fSTobias Hieta    if os.path.basename(sys.argv[0]) == "llvm-boltdiff":  # boltdiff mode
390c2a961e4SAmir Ayupov        # no output binary to compare, so just exit
391c2a961e4SAmir Ayupov        clean_exit(tmp, main_out, main_bolt.returncode, cfg)
392c2a961e4SAmir Ayupov
393c2a961e4SAmir Ayupov    # compare binaries (using cmp)
394f98ee40fSTobias Hieta    main_binary = args[args.index("-o") + 1]
395f98ee40fSTobias Hieta    cmp_binary = cmp_args[cmp_args.index("-o") + 1]
396f98ee40fSTobias Hieta    if main_binary == "/dev/null":
397f98ee40fSTobias Hieta        assert cmp_binary == "/dev/null"
398c2a961e4SAmir Ayupov        cfg.SKIP_BINARY_CMP = True
399c2a961e4SAmir Ayupov
400c2a961e4SAmir Ayupov    # report binary timing as csv: output binary; base bolt real; cmp bolt real
401c2a961e4SAmir Ayupov    report_real_time(main_binary, main_out, cmp_out, cfg)
402c2a961e4SAmir Ayupov
403701109b9SAmir Ayupov    if not cfg.SKIP_BINARY_CMP:
404c2a961e4SAmir Ayupov        # check if files exist
405c2a961e4SAmir Ayupov        main_exists = os.path.exists(main_binary)
406c2a961e4SAmir Ayupov        cmp_exists = os.path.exists(cmp_binary)
407c2a961e4SAmir Ayupov        if main_exists and cmp_exists:
408c2a961e4SAmir Ayupov            # proceed to comparison
409c2a961e4SAmir Ayupov            pass
410c2a961e4SAmir Ayupov        elif not main_exists and not cmp_exists:
411c2a961e4SAmir Ayupov            # both don't exist, assume it's intended, skip comparison
412c2a961e4SAmir Ayupov            clean_exit(tmp, main_out, main_bolt.returncode, cfg)
413c2a961e4SAmir Ayupov        elif main_exists:
414c2a961e4SAmir Ayupov            assert not cmp_exists
415c2a961e4SAmir Ayupov            exit(f"{cmp_binary} doesn't exist")
416c2a961e4SAmir Ayupov        else:
417c2a961e4SAmir Ayupov            assert not main_exists
418c2a961e4SAmir Ayupov            exit(f"{main_binary} doesn't exist")
419c2a961e4SAmir Ayupov
420f98ee40fSTobias Hieta        cmp_proc = subprocess.run(
421f98ee40fSTobias Hieta            ["cmp", "-b", main_binary, cmp_binary], capture_output=True, text=True
422f98ee40fSTobias Hieta        )
423c2a961e4SAmir Ayupov        if cmp_proc.returncode:
424c2a961e4SAmir Ayupov            # check if output is an ELF file (magic bytes)
425f98ee40fSTobias Hieta            with open(main_binary, "rb") as f:
426c2a961e4SAmir Ayupov                magic = f.read(4)
427f98ee40fSTobias Hieta                if magic != b"\x7fELF":
428c2a961e4SAmir Ayupov                    exit("output mismatch")
429c2a961e4SAmir Ayupov            # check if ELF headers match
430f98ee40fSTobias Hieta            mismatch, _ = compare_with(main_binary, cmp_binary, "readelf -We")
431c2a961e4SAmir Ayupov            if mismatch:
432c2a961e4SAmir Ayupov                print(fmt_cmp(mismatch))
433f98ee40fSTobias Hieta                write_to(fmt_cmp(mismatch), os.path.join(tmp, "headers.txt"))
434c2a961e4SAmir Ayupov                exit("headers mismatch")
435c2a961e4SAmir Ayupov            # if headers match, compare sections (skip line with filename)
436f98ee40fSTobias Hieta            mismatch, hdr = compare_with(
437f98ee40fSTobias Hieta                main_binary, cmp_binary, "objdump -hw", skip_begin=2
438f98ee40fSTobias Hieta            )
439c2a961e4SAmir Ayupov            assert not mismatch
440c2a961e4SAmir Ayupov            # check which section has the first mismatch
441c2a961e4SAmir Ayupov            mismatch_offset = parse_cmp_offset(cmp_proc.stdout)
442c2a961e4SAmir Ayupov            section = find_section(mismatch_offset, hdr)
443c2a961e4SAmir Ayupov            exit(f"binary mismatch @{hex(mismatch_offset)} ({section})")
444c2a961e4SAmir Ayupov
445c2a961e4SAmir Ayupov    clean_exit(tmp, main_out, main_bolt.returncode, cfg)
446c2a961e4SAmir Ayupov
447f98ee40fSTobias Hieta
448c2a961e4SAmir Ayupovif __name__ == "__main__":
449c2a961e4SAmir Ayupov    # config generator mode if the script is launched as is
450c2a961e4SAmir Ayupov    if os.path.basename(__file__) == "llvm-bolt-wrapper.py":
451c2a961e4SAmir Ayupov        main_config_generator()
452c2a961e4SAmir Ayupov    else:
453c2a961e4SAmir Ayupov        # llvm-bolt interceptor mode otherwise
454c2a961e4SAmir Ayupov        main()
455