1#!/usr/bin/env python3 2 3# Automatically formatted with yapf (https://github.com/google/yapf) 4 5# Script for automatic 'opt' pipeline reduction for when using the new 6# 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 the 11# error code. 12# 13# Step #1: Split pipeline in two starting from front and move forward as long as 14# first pipeline exits normally and the second pipeline fails with the expected 15# error code. Move on to step #2 with the IR from the split point and the 16# pipeline from the second invocation. 17# 18# Step #2: Remove passes from end of the pipeline as long as the pipeline fails 19# with the expected error code. 20# 21# Step #3: Make several sweeps over the remaining pipeline trying to remove one 22# 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 argparse 28import pipeline 29import shutil 30import subprocess 31import tempfile 32 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.input 58 59# Step #-1 60# Launch 'opt' once with '-print-pipeline-passes' to expand pipeline before 61# 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 #0 86# 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.returncode 105print('-passes="{}"'.format(pipeline.toStr(lst))) 106 107# Step #1 108# Try to narrow down the failing pass sequence by splitting the pipeline in two 109# opt invocations (A and B) starting with invocation A only running the first 110# pipeline pass and invocation B the remaining. Keep moving the split point 111# forward as long as invocation A exits normally and invocation B fails with 112# the expected error. This will accomplish two things first the input IR will be 113# further reduced and second, with that IR, the reduced pipeline for invocation 114# B will be sufficient to reproduce. 115print("---Starting step #1---") 116prevLstB = None 117prevIntermediate = None 118tmpd = 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 + "/" + intermediate 128 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 break 150 prevLstB = lstB 151 prevIntermediate = intermediate 152if prevLstB: 153 lst = prevLstB 154 ll_input = prevIntermediate 155print('-passes="{}"'.format(pipeline.toStr(lst))) 156 157# Step #2 158# Try removing passes from the end of the remaining pipeline while still 159# reproducing the error. 160print("---Starting step #2---") 161prevLstA = None 162for 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 break 177 prevLstA = lstA 178if prevLstA: 179 lst = prevLstA 180print('-passes="{}"'.format(pipeline.toStr(lst))) 181 182# Step #3 183# Now that we have a pipeline that is reduced both front and back we do 184# 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 = False 189 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 = candLst 204 keepGoing = True 205 if not keepGoing: 206 break 207print('-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