|  | #!/usr/bin/env python3 | 
|  |  | 
|  | # Automatically formatted with yapf (https://github.com/google/yapf) | 
|  |  | 
|  | # Script for automatic 'opt' pipeline reduction for when using the new | 
|  | # pass-manager (NPM). Based around the '-print-pipeline-passes' option. | 
|  | # | 
|  | # The reduction algorithm consists of several phases (steps). | 
|  | # | 
|  | # Step #0: Verify that input fails with the given pipeline and make note of the | 
|  | # error code. | 
|  | # | 
|  | # Step #1: Split pipeline in two starting from front and move forward as long as | 
|  | # first pipeline exits normally and the second pipeline fails with the expected | 
|  | # error code. Move on to step #2 with the IR from the split point and the | 
|  | # pipeline from the second invocation. | 
|  | # | 
|  | # Step #2: Remove passes from end of the pipeline as long as the pipeline fails | 
|  | # with the expected error code. | 
|  | # | 
|  | # Step #3: Make several sweeps over the remaining pipeline trying to remove one | 
|  | # pass at a time. Repeat sweeps until unable to remove any more passes. | 
|  | # | 
|  | # Usage example: | 
|  | # reduce_pipeline.py --opt-binary=./build-all-Debug/bin/opt --input=input.ll --output=output.ll --passes=PIPELINE [EXTRA-OPT-ARGS ...] | 
|  |  | 
|  | import argparse | 
|  | import pipeline | 
|  | import shutil | 
|  | import subprocess | 
|  | import tempfile | 
|  |  | 
|  | parser = argparse.ArgumentParser( | 
|  | description="Automatic opt pipeline reducer. Unrecognized arguments are forwarded to opt." | 
|  | ) | 
|  | parser.add_argument("--opt-binary", action="store", dest="opt_binary", default="opt") | 
|  | parser.add_argument("--passes", action="store", dest="passes", required=True) | 
|  | parser.add_argument("--input", action="store", dest="input", required=True) | 
|  | parser.add_argument("--output", action="store", dest="output") | 
|  | parser.add_argument( | 
|  | "--dont-expand-passes", | 
|  | action="store_true", | 
|  | dest="dont_expand_passes", | 
|  | help="Do not expand pipeline before starting reduction.", | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--dont-remove-empty-pm", | 
|  | action="store_true", | 
|  | dest="dont_remove_empty_pm", | 
|  | help="Do not remove empty pass-managers from the pipeline during reduction.", | 
|  | ) | 
|  | [args, extra_opt_args] = parser.parse_known_args() | 
|  |  | 
|  | print("The following extra args will be passed to opt: {}".format(extra_opt_args)) | 
|  |  | 
|  | lst = pipeline.fromStr(args.passes) | 
|  | ll_input = args.input | 
|  |  | 
|  | # Step #-1 | 
|  | # Launch 'opt' once with '-print-pipeline-passes' to expand pipeline before | 
|  | # starting reduction. Allows specifying a default pipelines (e.g. | 
|  | # '-passes=default<O3>'). | 
|  | if not args.dont_expand_passes: | 
|  | run_args = [ | 
|  | args.opt_binary, | 
|  | "-disable-symbolication", | 
|  | "-disable-output", | 
|  | "-print-pipeline-passes", | 
|  | "-passes={}".format(pipeline.toStr(lst)), | 
|  | ll_input, | 
|  | ] | 
|  | run_args.extend(extra_opt_args) | 
|  | opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 
|  | if opt.returncode != 0: | 
|  | print("Failed to expand passes. Aborting.") | 
|  | print(run_args) | 
|  | print("exitcode: {}".format(opt.returncode)) | 
|  | print(opt.stderr.decode()) | 
|  | exit(1) | 
|  | stdout = opt.stdout.decode() | 
|  | stdout = stdout[: stdout.rfind("\n")] | 
|  | lst = pipeline.fromStr(stdout) | 
|  | print("Expanded pass sequence: {}".format(pipeline.toStr(lst))) | 
|  |  | 
|  | # Step #0 | 
|  | # Confirm that the given input, passes and options result in failure. | 
|  | print("---Starting step #0---") | 
|  | run_args = [ | 
|  | args.opt_binary, | 
|  | "-disable-symbolication", | 
|  | "-disable-output", | 
|  | "-passes={}".format(pipeline.toStr(lst)), | 
|  | ll_input, | 
|  | ] | 
|  | run_args.extend(extra_opt_args) | 
|  | opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 
|  | if opt.returncode >= 0: | 
|  | print("Input does not result in failure as expected. Aborting.") | 
|  | print(run_args) | 
|  | print("exitcode: {}".format(opt.returncode)) | 
|  | print(opt.stderr.decode()) | 
|  | exit(1) | 
|  |  | 
|  | expected_error_returncode = opt.returncode | 
|  | print('-passes="{}"'.format(pipeline.toStr(lst))) | 
|  |  | 
|  | # Step #1 | 
|  | # Try to narrow down the failing pass sequence by splitting the pipeline in two | 
|  | # opt invocations (A and B) starting with invocation A only running the first | 
|  | # pipeline pass and invocation B the remaining. Keep moving the split point | 
|  | # forward as long as invocation A exits normally and invocation B fails with | 
|  | # the expected error. This will accomplish two things first the input IR will be | 
|  | # further reduced and second, with that IR, the reduced pipeline for invocation | 
|  | # B will be sufficient to reproduce. | 
|  | print("---Starting step #1---") | 
|  | prevLstB = None | 
|  | prevIntermediate = None | 
|  | tmpd = tempfile.TemporaryDirectory() | 
|  |  | 
|  | for idx in range(pipeline.count(lst)): | 
|  | [lstA, lstB] = pipeline.split(lst, idx) | 
|  | if not args.dont_remove_empty_pm: | 
|  | lstA = pipeline.prune(lstA) | 
|  | lstB = pipeline.prune(lstB) | 
|  |  | 
|  | intermediate = "intermediate-0.ll" if idx % 2 else "intermediate-1.ll" | 
|  | intermediate = tmpd.name + "/" + intermediate | 
|  | run_args = [ | 
|  | args.opt_binary, | 
|  | "-disable-symbolication", | 
|  | "-S", | 
|  | "-o", | 
|  | intermediate, | 
|  | "-passes={}".format(pipeline.toStr(lstA)), | 
|  | ll_input, | 
|  | ] | 
|  | run_args.extend(extra_opt_args) | 
|  | optA = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 
|  | run_args = [ | 
|  | args.opt_binary, | 
|  | "-disable-symbolication", | 
|  | "-disable-output", | 
|  | "-passes={}".format(pipeline.toStr(lstB)), | 
|  | intermediate, | 
|  | ] | 
|  | run_args.extend(extra_opt_args) | 
|  | optB = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 
|  | if not (optA.returncode == 0 and optB.returncode == expected_error_returncode): | 
|  | break | 
|  | prevLstB = lstB | 
|  | prevIntermediate = intermediate | 
|  | if prevLstB: | 
|  | lst = prevLstB | 
|  | ll_input = prevIntermediate | 
|  | print('-passes="{}"'.format(pipeline.toStr(lst))) | 
|  |  | 
|  | # Step #2 | 
|  | # Try removing passes from the end of the remaining pipeline while still | 
|  | # reproducing the error. | 
|  | print("---Starting step #2---") | 
|  | prevLstA = None | 
|  | for idx in reversed(range(pipeline.count(lst))): | 
|  | [lstA, lstB] = pipeline.split(lst, idx) | 
|  | if not args.dont_remove_empty_pm: | 
|  | lstA = pipeline.prune(lstA) | 
|  | run_args = [ | 
|  | args.opt_binary, | 
|  | "-disable-symbolication", | 
|  | "-disable-output", | 
|  | "-passes={}".format(pipeline.toStr(lstA)), | 
|  | ll_input, | 
|  | ] | 
|  | run_args.extend(extra_opt_args) | 
|  | optA = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 
|  | if optA.returncode != expected_error_returncode: | 
|  | break | 
|  | prevLstA = lstA | 
|  | if prevLstA: | 
|  | lst = prevLstA | 
|  | print('-passes="{}"'.format(pipeline.toStr(lst))) | 
|  |  | 
|  | # Step #3 | 
|  | # Now that we have a pipeline that is reduced both front and back we do | 
|  | # exhaustive sweeps over the remainder trying to remove one pass at a time. | 
|  | # Repeat as long as reduction is possible. | 
|  | print("---Starting step #3---") | 
|  | while True: | 
|  | keepGoing = False | 
|  | for idx in range(pipeline.count(lst)): | 
|  | candLst = pipeline.remove(lst, idx) | 
|  | if not args.dont_remove_empty_pm: | 
|  | candLst = pipeline.prune(candLst) | 
|  | run_args = [ | 
|  | args.opt_binary, | 
|  | "-disable-symbolication", | 
|  | "-disable-output", | 
|  | "-passes={}".format(pipeline.toStr(candLst)), | 
|  | ll_input, | 
|  | ] | 
|  | run_args.extend(extra_opt_args) | 
|  | opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 
|  | if opt.returncode == expected_error_returncode: | 
|  | lst = candLst | 
|  | keepGoing = True | 
|  | if not keepGoing: | 
|  | break | 
|  | print('-passes="{}"'.format(pipeline.toStr(lst))) | 
|  |  | 
|  | print("---FINISHED---") | 
|  | if args.output: | 
|  | shutil.copy(ll_input, args.output) | 
|  | print("Wrote output to '{}'.".format(args.output)) | 
|  | print('-passes="{}"'.format(pipeline.toStr(lst))) | 
|  | exit(0) |