215 lines · python
1#!/usr/bin/env python32 3# Automatically formatted with yapf (https://github.com/google/yapf)4 5# Script for automatic 'opt' pipeline reduction for when using the new6# pass-manager (NPM). Based around the '-print-pipeline-passes' option.7#8# The reduction algorithm consists of several phases (steps).9#10# Step #0: Verify that input fails with the given pipeline and make note of the11# error code.12#13# Step #1: Split pipeline in two starting from front and move forward as long as14# first pipeline exits normally and the second pipeline fails with the expected15# error code. Move on to step #2 with the IR from the split point and the16# pipeline from the second invocation.17#18# Step #2: Remove passes from end of the pipeline as long as the pipeline fails19# with the expected error code.20#21# Step #3: Make several sweeps over the remaining pipeline trying to remove one22# pass at a time. Repeat sweeps until unable to remove any more passes.23#24# Usage example:25# reduce_pipeline.py --opt-binary=./build-all-Debug/bin/opt --input=input.ll --output=output.ll --passes=PIPELINE [EXTRA-OPT-ARGS ...]26 27import argparse28import pipeline29import shutil30import subprocess31import tempfile32 33parser = argparse.ArgumentParser(34 description="Automatic opt pipeline reducer. Unrecognized arguments are forwarded to opt."35)36parser.add_argument("--opt-binary", action="store", dest="opt_binary", default="opt")37parser.add_argument("--passes", action="store", dest="passes", required=True)38parser.add_argument("--input", action="store", dest="input", required=True)39parser.add_argument("--output", action="store", dest="output")40parser.add_argument(41 "--dont-expand-passes",42 action="store_true",43 dest="dont_expand_passes",44 help="Do not expand pipeline before starting reduction.",45)46parser.add_argument(47 "--dont-remove-empty-pm",48 action="store_true",49 dest="dont_remove_empty_pm",50 help="Do not remove empty pass-managers from the pipeline during reduction.",51)52[args, extra_opt_args] = parser.parse_known_args()53 54print("The following extra args will be passed to opt: {}".format(extra_opt_args))55 56lst = pipeline.fromStr(args.passes)57ll_input = args.input58 59# Step #-160# Launch 'opt' once with '-print-pipeline-passes' to expand pipeline before61# starting reduction. Allows specifying a default pipelines (e.g.62# '-passes=default<O3>').63if not args.dont_expand_passes:64 run_args = [65 args.opt_binary,66 "-disable-symbolication",67 "-disable-output",68 "-print-pipeline-passes",69 "-passes={}".format(pipeline.toStr(lst)),70 ll_input,71 ]72 run_args.extend(extra_opt_args)73 opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)74 if opt.returncode != 0:75 print("Failed to expand passes. Aborting.")76 print(run_args)77 print("exitcode: {}".format(opt.returncode))78 print(opt.stderr.decode())79 exit(1)80 stdout = opt.stdout.decode()81 stdout = stdout[: stdout.rfind("\n")]82 lst = pipeline.fromStr(stdout)83 print("Expanded pass sequence: {}".format(pipeline.toStr(lst)))84 85# Step #086# Confirm that the given input, passes and options result in failure.87print("---Starting step #0---")88run_args = [89 args.opt_binary,90 "-disable-symbolication",91 "-disable-output",92 "-passes={}".format(pipeline.toStr(lst)),93 ll_input,94]95run_args.extend(extra_opt_args)96opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)97if opt.returncode >= 0:98 print("Input does not result in failure as expected. Aborting.")99 print(run_args)100 print("exitcode: {}".format(opt.returncode))101 print(opt.stderr.decode())102 exit(1)103 104expected_error_returncode = opt.returncode105print('-passes="{}"'.format(pipeline.toStr(lst)))106 107# Step #1108# Try to narrow down the failing pass sequence by splitting the pipeline in two109# opt invocations (A and B) starting with invocation A only running the first110# pipeline pass and invocation B the remaining. Keep moving the split point111# forward as long as invocation A exits normally and invocation B fails with112# the expected error. This will accomplish two things first the input IR will be113# further reduced and second, with that IR, the reduced pipeline for invocation114# B will be sufficient to reproduce.115print("---Starting step #1---")116prevLstB = None117prevIntermediate = None118tmpd = tempfile.TemporaryDirectory()119 120for idx in range(pipeline.count(lst)):121 [lstA, lstB] = pipeline.split(lst, idx)122 if not args.dont_remove_empty_pm:123 lstA = pipeline.prune(lstA)124 lstB = pipeline.prune(lstB)125 126 intermediate = "intermediate-0.ll" if idx % 2 else "intermediate-1.ll"127 intermediate = tmpd.name + "/" + intermediate128 run_args = [129 args.opt_binary,130 "-disable-symbolication",131 "-S",132 "-o",133 intermediate,134 "-passes={}".format(pipeline.toStr(lstA)),135 ll_input,136 ]137 run_args.extend(extra_opt_args)138 optA = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)139 run_args = [140 args.opt_binary,141 "-disable-symbolication",142 "-disable-output",143 "-passes={}".format(pipeline.toStr(lstB)),144 intermediate,145 ]146 run_args.extend(extra_opt_args)147 optB = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)148 if not (optA.returncode == 0 and optB.returncode == expected_error_returncode):149 break150 prevLstB = lstB151 prevIntermediate = intermediate152if prevLstB:153 lst = prevLstB154 ll_input = prevIntermediate155print('-passes="{}"'.format(pipeline.toStr(lst)))156 157# Step #2158# Try removing passes from the end of the remaining pipeline while still159# reproducing the error.160print("---Starting step #2---")161prevLstA = None162for idx in reversed(range(pipeline.count(lst))):163 [lstA, lstB] = pipeline.split(lst, idx)164 if not args.dont_remove_empty_pm:165 lstA = pipeline.prune(lstA)166 run_args = [167 args.opt_binary,168 "-disable-symbolication",169 "-disable-output",170 "-passes={}".format(pipeline.toStr(lstA)),171 ll_input,172 ]173 run_args.extend(extra_opt_args)174 optA = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)175 if optA.returncode != expected_error_returncode:176 break177 prevLstA = lstA178if prevLstA:179 lst = prevLstA180print('-passes="{}"'.format(pipeline.toStr(lst)))181 182# Step #3183# Now that we have a pipeline that is reduced both front and back we do184# exhaustive sweeps over the remainder trying to remove one pass at a time.185# Repeat as long as reduction is possible.186print("---Starting step #3---")187while True:188 keepGoing = False189 for idx in range(pipeline.count(lst)):190 candLst = pipeline.remove(lst, idx)191 if not args.dont_remove_empty_pm:192 candLst = pipeline.prune(candLst)193 run_args = [194 args.opt_binary,195 "-disable-symbolication",196 "-disable-output",197 "-passes={}".format(pipeline.toStr(candLst)),198 ll_input,199 ]200 run_args.extend(extra_opt_args)201 opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)202 if opt.returncode == expected_error_returncode:203 lst = candLst204 keepGoing = True205 if not keepGoing:206 break207print('-passes="{}"'.format(pipeline.toStr(lst)))208 209print("---FINISHED---")210if args.output:211 shutil.copy(ll_input, args.output)212 print("Wrote output to '{}'.".format(args.output))213print('-passes="{}"'.format(pipeline.toStr(lst)))214exit(0)215