1*d415bd75Srobert#!/usr/bin/env python3 2*d415bd75Srobert 3*d415bd75Srobert# Automatically formatted with yapf (https://github.com/google/yapf) 4*d415bd75Srobert 5*d415bd75Srobert# Script for automatic 'opt' pipeline reduction for when using the new 6*d415bd75Srobert# pass-manager (NPM). Based around the '-print-pipeline-passes' option. 7*d415bd75Srobert# 8*d415bd75Srobert# The reduction algorithm consists of several phases (steps). 9*d415bd75Srobert# 10*d415bd75Srobert# Step #0: Verify that input fails with the given pipeline and make note of the 11*d415bd75Srobert# error code. 12*d415bd75Srobert# 13*d415bd75Srobert# Step #1: Split pipeline in two starting from front and move forward as long as 14*d415bd75Srobert# first pipeline exits normally and the second pipeline fails with the expected 15*d415bd75Srobert# error code. Move on to step #2 with the IR from the split point and the 16*d415bd75Srobert# pipeline from the second invocation. 17*d415bd75Srobert# 18*d415bd75Srobert# Step #2: Remove passes from end of the pipeline as long as the pipeline fails 19*d415bd75Srobert# with the expected error code. 20*d415bd75Srobert# 21*d415bd75Srobert# Step #3: Make several sweeps over the remaining pipeline trying to remove one 22*d415bd75Srobert# pass at a time. Repeat sweeps until unable to remove any more passes. 23*d415bd75Srobert# 24*d415bd75Srobert# Usage example: 25*d415bd75Srobert# reduce_pipeline.py --opt-binary=./build-all-Debug/bin/opt --input=input.ll --output=output.ll --passes=PIPELINE [EXTRA-OPT-ARGS ...] 26*d415bd75Srobert 27*d415bd75Srobertimport argparse 28*d415bd75Srobertimport pipeline 29*d415bd75Srobertimport shutil 30*d415bd75Srobertimport subprocess 31*d415bd75Srobertimport tempfile 32*d415bd75Srobert 33*d415bd75Srobertparser = argparse.ArgumentParser( 34*d415bd75Srobert description= 35*d415bd75Srobert 'Automatic opt pipeline reducer. Unrecognized arguments are forwarded to opt.' 36*d415bd75Srobert) 37*d415bd75Srobertparser.add_argument('--opt-binary', 38*d415bd75Srobert action='store', 39*d415bd75Srobert dest='opt_binary', 40*d415bd75Srobert default='opt') 41*d415bd75Srobertparser.add_argument('--passes', action='store', dest='passes', required=True) 42*d415bd75Srobertparser.add_argument('--input', action='store', dest='input', required=True) 43*d415bd75Srobertparser.add_argument('--output', action='store', dest='output') 44*d415bd75Srobertparser.add_argument('--dont-expand-passes', 45*d415bd75Srobert action='store_true', 46*d415bd75Srobert dest='dont_expand_passes', 47*d415bd75Srobert help='Do not expand pipeline before starting reduction.') 48*d415bd75Srobertparser.add_argument( 49*d415bd75Srobert '--dont-remove-empty-pm', 50*d415bd75Srobert action='store_true', 51*d415bd75Srobert dest='dont_remove_empty_pm', 52*d415bd75Srobert help='Do not remove empty pass-managers from the pipeline during reduction.' 53*d415bd75Srobert) 54*d415bd75Srobert[args, extra_opt_args] = parser.parse_known_args() 55*d415bd75Srobert 56*d415bd75Srobertprint('The following extra args will be passed to opt: {}'.format( 57*d415bd75Srobert extra_opt_args)) 58*d415bd75Srobert 59*d415bd75Srobertlst = pipeline.fromStr(args.passes) 60*d415bd75Srobertll_input = args.input 61*d415bd75Srobert 62*d415bd75Srobert# Step #-1 63*d415bd75Srobert# Launch 'opt' once with '-print-pipeline-passes' to expand pipeline before 64*d415bd75Srobert# starting reduction. Allows specifying a default pipelines (e.g. 65*d415bd75Srobert# '-passes=default<O3>'). 66*d415bd75Srobertif not args.dont_expand_passes: 67*d415bd75Srobert run_args = [ 68*d415bd75Srobert args.opt_binary, '-disable-symbolication', '-disable-output', 69*d415bd75Srobert '-print-pipeline-passes', '-passes={}'.format(pipeline.toStr(lst)), 70*d415bd75Srobert ll_input 71*d415bd75Srobert ] 72*d415bd75Srobert run_args.extend(extra_opt_args) 73*d415bd75Srobert opt = subprocess.run(run_args, 74*d415bd75Srobert stdout=subprocess.PIPE, 75*d415bd75Srobert stderr=subprocess.PIPE) 76*d415bd75Srobert if opt.returncode != 0: 77*d415bd75Srobert print('Failed to expand passes. Aborting.') 78*d415bd75Srobert print(run_args) 79*d415bd75Srobert print('exitcode: {}'.format(opt.returncode)) 80*d415bd75Srobert print(opt.stderr.decode()) 81*d415bd75Srobert exit(1) 82*d415bd75Srobert stdout = opt.stdout.decode() 83*d415bd75Srobert stdout = stdout[:stdout.rfind('\n')] 84*d415bd75Srobert lst = pipeline.fromStr(stdout) 85*d415bd75Srobert print('Expanded pass sequence: {}'.format(pipeline.toStr(lst))) 86*d415bd75Srobert 87*d415bd75Srobert# Step #0 88*d415bd75Srobert# Confirm that the given input, passes and options result in failure. 89*d415bd75Srobertprint('---Starting step #0---') 90*d415bd75Srobertrun_args = [ 91*d415bd75Srobert args.opt_binary, '-disable-symbolication', '-disable-output', 92*d415bd75Srobert '-passes={}'.format(pipeline.toStr(lst)), ll_input 93*d415bd75Srobert] 94*d415bd75Srobertrun_args.extend(extra_opt_args) 95*d415bd75Srobertopt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 96*d415bd75Srobertif opt.returncode >= 0: 97*d415bd75Srobert print('Input does not result in failure as expected. Aborting.') 98*d415bd75Srobert print(run_args) 99*d415bd75Srobert print('exitcode: {}'.format(opt.returncode)) 100*d415bd75Srobert print(opt.stderr.decode()) 101*d415bd75Srobert exit(1) 102*d415bd75Srobert 103*d415bd75Srobertexpected_error_returncode = opt.returncode 104*d415bd75Srobertprint('-passes="{}"'.format(pipeline.toStr(lst))) 105*d415bd75Srobert 106*d415bd75Srobert# Step #1 107*d415bd75Srobert# Try to narrow down the failing pass sequence by splitting the pipeline in two 108*d415bd75Srobert# opt invocations (A and B) starting with invocation A only running the first 109*d415bd75Srobert# pipeline pass and invocation B the remaining. Keep moving the split point 110*d415bd75Srobert# forward as long as invocation A exits normally and invocation B fails with 111*d415bd75Srobert# the expected error. This will accomplish two things first the input IR will be 112*d415bd75Srobert# further reduced and second, with that IR, the reduced pipeline for invocation 113*d415bd75Srobert# B will be sufficient to reproduce. 114*d415bd75Srobertprint('---Starting step #1---') 115*d415bd75SrobertprevLstB = None 116*d415bd75SrobertprevIntermediate = None 117*d415bd75Sroberttmpd = tempfile.TemporaryDirectory() 118*d415bd75Srobert 119*d415bd75Srobertfor idx in range(pipeline.count(lst)): 120*d415bd75Srobert [lstA, lstB] = pipeline.split(lst, idx) 121*d415bd75Srobert if not args.dont_remove_empty_pm: 122*d415bd75Srobert lstA = pipeline.prune(lstA) 123*d415bd75Srobert lstB = pipeline.prune(lstB) 124*d415bd75Srobert 125*d415bd75Srobert intermediate = 'intermediate-0.ll' if idx % 2 else 'intermediate-1.ll' 126*d415bd75Srobert intermediate = tmpd.name + '/' + intermediate 127*d415bd75Srobert run_args = [ 128*d415bd75Srobert args.opt_binary, '-disable-symbolication', '-S', '-o', intermediate, 129*d415bd75Srobert '-passes={}'.format(pipeline.toStr(lstA)), ll_input 130*d415bd75Srobert ] 131*d415bd75Srobert run_args.extend(extra_opt_args) 132*d415bd75Srobert optA = subprocess.run(run_args, 133*d415bd75Srobert stdout=subprocess.PIPE, 134*d415bd75Srobert stderr=subprocess.PIPE) 135*d415bd75Srobert run_args = [ 136*d415bd75Srobert args.opt_binary, '-disable-symbolication', '-disable-output', 137*d415bd75Srobert '-passes={}'.format(pipeline.toStr(lstB)), intermediate 138*d415bd75Srobert ] 139*d415bd75Srobert run_args.extend(extra_opt_args) 140*d415bd75Srobert optB = subprocess.run(run_args, 141*d415bd75Srobert stdout=subprocess.PIPE, 142*d415bd75Srobert stderr=subprocess.PIPE) 143*d415bd75Srobert if not (optA.returncode == 0 144*d415bd75Srobert and optB.returncode == expected_error_returncode): 145*d415bd75Srobert break 146*d415bd75Srobert prevLstB = lstB 147*d415bd75Srobert prevIntermediate = intermediate 148*d415bd75Srobertif prevLstB: 149*d415bd75Srobert lst = prevLstB 150*d415bd75Srobert ll_input = prevIntermediate 151*d415bd75Srobertprint('-passes="{}"'.format(pipeline.toStr(lst))) 152*d415bd75Srobert 153*d415bd75Srobert# Step #2 154*d415bd75Srobert# Try removing passes from the end of the remaining pipeline while still 155*d415bd75Srobert# reproducing the error. 156*d415bd75Srobertprint('---Starting step #2---') 157*d415bd75SrobertprevLstA = None 158*d415bd75Srobertfor idx in reversed(range(pipeline.count(lst))): 159*d415bd75Srobert [lstA, lstB] = pipeline.split(lst, idx) 160*d415bd75Srobert if not args.dont_remove_empty_pm: 161*d415bd75Srobert lstA = pipeline.prune(lstA) 162*d415bd75Srobert run_args = [ 163*d415bd75Srobert args.opt_binary, '-disable-symbolication', '-disable-output', 164*d415bd75Srobert '-passes={}'.format(pipeline.toStr(lstA)), ll_input 165*d415bd75Srobert ] 166*d415bd75Srobert run_args.extend(extra_opt_args) 167*d415bd75Srobert optA = subprocess.run(run_args, 168*d415bd75Srobert stdout=subprocess.PIPE, 169*d415bd75Srobert stderr=subprocess.PIPE) 170*d415bd75Srobert if optA.returncode != expected_error_returncode: 171*d415bd75Srobert break 172*d415bd75Srobert prevLstA = lstA 173*d415bd75Srobertif prevLstA: 174*d415bd75Srobert lst = prevLstA 175*d415bd75Srobertprint('-passes="{}"'.format(pipeline.toStr(lst))) 176*d415bd75Srobert 177*d415bd75Srobert# Step #3 178*d415bd75Srobert# Now that we have a pipeline that is reduced both front and back we do 179*d415bd75Srobert# exhaustive sweeps over the remainder trying to remove one pass at a time. 180*d415bd75Srobert# Repeat as long as reduction is possible. 181*d415bd75Srobertprint('---Starting step #3---') 182*d415bd75Srobertwhile True: 183*d415bd75Srobert keepGoing = False 184*d415bd75Srobert for idx in range(pipeline.count(lst)): 185*d415bd75Srobert candLst = pipeline.remove(lst, idx) 186*d415bd75Srobert if not args.dont_remove_empty_pm: 187*d415bd75Srobert candLst = pipeline.prune(candLst) 188*d415bd75Srobert run_args = [ 189*d415bd75Srobert args.opt_binary, '-disable-symbolication', '-disable-output', 190*d415bd75Srobert '-passes={}'.format(pipeline.toStr(candLst)), ll_input 191*d415bd75Srobert ] 192*d415bd75Srobert run_args.extend(extra_opt_args) 193*d415bd75Srobert opt = subprocess.run(run_args, 194*d415bd75Srobert stdout=subprocess.PIPE, 195*d415bd75Srobert stderr=subprocess.PIPE) 196*d415bd75Srobert if opt.returncode == expected_error_returncode: 197*d415bd75Srobert lst = candLst 198*d415bd75Srobert keepGoing = True 199*d415bd75Srobert if not keepGoing: 200*d415bd75Srobert break 201*d415bd75Srobertprint('-passes="{}"'.format(pipeline.toStr(lst))) 202*d415bd75Srobert 203*d415bd75Srobertprint('---FINISHED---') 204*d415bd75Srobertif args.output: 205*d415bd75Srobert shutil.copy(ll_input, args.output) 206*d415bd75Srobert print('Wrote output to \'{}\'.'.format(args.output)) 207*d415bd75Srobertprint('-passes="{}"'.format(pipeline.toStr(lst))) 208*d415bd75Srobertexit(0) 209