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