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= 35 'Automatic opt pipeline reducer. Unrecognized arguments are forwarded to opt.' 36) 37parser.add_argument('--opt-binary', 38 action='store', 39 dest='opt_binary', 40 default='opt') 41parser.add_argument('--passes', action='store', dest='passes', required=True) 42parser.add_argument('--input', action='store', dest='input', required=True) 43parser.add_argument('--output', action='store', dest='output') 44parser.add_argument('--dont-expand-passes', 45 action='store_true', 46 dest='dont_expand_passes', 47 help='Do not expand pipeline before starting reduction.') 48parser.add_argument( 49 '--dont-remove-empty-pm', 50 action='store_true', 51 dest='dont_remove_empty_pm', 52 help='Do not remove empty pass-managers from the pipeline during reduction.' 53) 54[args, extra_opt_args] = parser.parse_known_args() 55 56print('The following extra args will be passed to opt: {}'.format( 57 extra_opt_args)) 58 59lst = pipeline.fromStr(args.passes) 60passes = '-passes={}'.format(pipeline.toStr(lst)) 61ll_input = args.input 62 63# Step #-1 64# Launch 'opt' once with '-print-pipeline-passes' to expand pipeline before 65# starting reduction. Allows specifying a default pipelines (e.g. 66# '-passes=default<O3>'). 67if not args.dont_expand_passes: 68 run_args = [ 69 args.opt_binary, '-disable-symbolication', '-disable-output', 70 '-print-pipeline-passes', passes, ll_input 71 ] 72 run_args.extend(extra_opt_args) 73 opt = subprocess.run(run_args, 74 stdout=subprocess.PIPE, 75 stderr=subprocess.PIPE) 76 if opt.returncode != 0: 77 print('Failed to expand passes. Aborting.') 78 print(run_args) 79 print('exitcode: {}'.format(opt.returncode)) 80 print(opt.stderr.decode()) 81 exit(1) 82 stdout = opt.stdout.decode() 83 stdout = stdout[:stdout.rfind('\n')] 84 print('Expanded pass sequence: {}'.format(stdout)) 85 passes = '-passes={}'.format(stdout) 86 87# Step #0 88# Confirm that the given input, passes and options result in failure. 89print('---Starting step #0---') 90run_args = [ 91 args.opt_binary, '-disable-symbolication', '-disable-output', passes, 92 ll_input 93] 94run_args.extend(extra_opt_args) 95opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 96if opt.returncode >= 0: 97 print('Input does not result in failure as expected. Aborting.') 98 print(run_args) 99 print('exitcode: {}'.format(opt.returncode)) 100 print(opt.stderr.decode()) 101 exit(1) 102 103expected_error_returncode = opt.returncode 104print('-passes="{}"'.format(pipeline.toStr(lst))) 105 106# Step #1 107# Try to narrow down the failing pass sequence by splitting the pipeline in two 108# opt invocations (A and B) starting with invocation A only running the first 109# pipeline pass and invocation B the remaining. Keep moving the split point 110# forward as long as invocation A exits normally and invocation B fails with 111# the expected error. This will accomplish two things first the input IR will be 112# further reduced and second, with that IR, the reduced pipeline for invocation 113# B will be sufficient to reproduce. 114print('---Starting step #1---') 115prevLstB = None 116prevIntermediate = None 117tmpd = tempfile.TemporaryDirectory() 118 119for idx in range(pipeline.count(lst)): 120 [lstA, lstB] = pipeline.split(lst, idx) 121 if not args.dont_remove_empty_pm: 122 lstA = pipeline.prune(lstA) 123 lstB = pipeline.prune(lstB) 124 passesA = '-passes=' + pipeline.toStr(lstA) 125 passesB = '-passes=' + pipeline.toStr(lstB) 126 127 intermediate = 'intermediate-0.ll' if idx % 2 else 'intermediate-1.ll' 128 intermediate = tmpd.name + '/' + intermediate 129 run_args = [ 130 args.opt_binary, '-disable-symbolication', '-S', '-o', intermediate, 131 passesA, ll_input 132 ] 133 run_args.extend(extra_opt_args) 134 optA = subprocess.run(run_args, 135 stdout=subprocess.PIPE, 136 stderr=subprocess.PIPE) 137 run_args = [ 138 args.opt_binary, '-disable-symbolication', '-disable-output', passesB, 139 intermediate 140 ] 141 run_args.extend(extra_opt_args) 142 optB = subprocess.run(run_args, 143 stdout=subprocess.PIPE, 144 stderr=subprocess.PIPE) 145 if not (optA.returncode == 0 146 and optB.returncode == expected_error_returncode): 147 break 148 prevLstB = lstB 149 prevIntermediate = intermediate 150if prevLstB: 151 lst = prevLstB 152 ll_input = prevIntermediate 153print('-passes="{}"'.format(pipeline.toStr(lst))) 154 155# Step #2 156# Try removing passes from the end of the remaining pipeline while still 157# reproducing the error. 158print('---Starting step #2---') 159prevLstA = None 160for idx in reversed(range(pipeline.count(lst))): 161 [lstA, lstB] = pipeline.split(lst, idx) 162 if not args.dont_remove_empty_pm: 163 lstA = pipeline.prune(lstA) 164 passesA = '-passes=' + pipeline.toStr(lstA) 165 run_args = [ 166 args.opt_binary, '-disable-symbolication', '-disable-output', passesA, 167 ll_input 168 ] 169 run_args.extend(extra_opt_args) 170 optA = subprocess.run(run_args, 171 stdout=subprocess.PIPE, 172 stderr=subprocess.PIPE) 173 if optA.returncode != expected_error_returncode: 174 break 175 prevLstA = lstA 176if prevLstA: 177 lst = prevLstA 178print('-passes="{}"'.format(pipeline.toStr(lst))) 179 180# Step #3 181# Now that we have a pipeline that is reduced both front and back we do 182# exhaustive sweeps over the remainder trying to remove one pass at a time. 183# Repeat as long as reduction is possible. 184print('---Starting step #3---') 185while True: 186 keepGoing = False 187 for idx in range(pipeline.count(lst)): 188 candLst = pipeline.remove(lst, idx) 189 if not args.dont_remove_empty_pm: 190 candLst = pipeline.prune(candLst) 191 passes = '-passes=' + pipeline.toStr(candLst) 192 run_args = [ 193 args.opt_binary, '-disable-symbolication', '-disable-output', 194 passes, ll_input 195 ] 196 run_args.extend(extra_opt_args) 197 opt = subprocess.run(run_args, 198 stdout=subprocess.PIPE, 199 stderr=subprocess.PIPE) 200 if opt.returncode == expected_error_returncode: 201 lst = candLst 202 keepGoing = True 203 if not keepGoing: 204 break 205print('-passes="{}"'.format(pipeline.toStr(lst))) 206 207print('---FINISHED---') 208if args.output: 209 shutil.copy(ll_input, args.output) 210 print('Wrote output to \'{}\'.'.format(args.output)) 211print('-passes="{}"'.format(pipeline.toStr(lst))) 212exit(0) 213