| #!/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) |