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) 60ll_input = args.input 61 62# Step #-1 63# Launch 'opt' once with '-print-pipeline-passes' to expand pipeline before 64# starting reduction. Allows specifying a default pipelines (e.g. 65# '-passes=default<O3>'). 66if not args.dont_expand_passes: 67 run_args = [ 68 args.opt_binary, '-disable-symbolication', '-disable-output', 69 '-print-pipeline-passes', '-passes={}'.format(pipeline.toStr(lst)), 70 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 lst = pipeline.fromStr(stdout) 85 print('Expanded pass sequence: {}'.format(pipeline.toStr(lst))) 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', 92 '-passes={}'.format(pipeline.toStr(lst)), 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 125 intermediate = 'intermediate-0.ll' if idx % 2 else 'intermediate-1.ll' 126 intermediate = tmpd.name + '/' + intermediate 127 run_args = [ 128 args.opt_binary, '-disable-symbolication', '-S', '-o', intermediate, 129 '-passes={}'.format(pipeline.toStr(lstA)), ll_input 130 ] 131 run_args.extend(extra_opt_args) 132 optA = subprocess.run(run_args, 133 stdout=subprocess.PIPE, 134 stderr=subprocess.PIPE) 135 run_args = [ 136 args.opt_binary, '-disable-symbolication', '-disable-output', 137 '-passes={}'.format(pipeline.toStr(lstB)), intermediate 138 ] 139 run_args.extend(extra_opt_args) 140 optB = subprocess.run(run_args, 141 stdout=subprocess.PIPE, 142 stderr=subprocess.PIPE) 143 if not (optA.returncode == 0 144 and optB.returncode == expected_error_returncode): 145 break 146 prevLstB = lstB 147 prevIntermediate = intermediate 148if prevLstB: 149 lst = prevLstB 150 ll_input = prevIntermediate 151print('-passes="{}"'.format(pipeline.toStr(lst))) 152 153# Step #2 154# Try removing passes from the end of the remaining pipeline while still 155# reproducing the error. 156print('---Starting step #2---') 157prevLstA = None 158for idx in reversed(range(pipeline.count(lst))): 159 [lstA, lstB] = pipeline.split(lst, idx) 160 if not args.dont_remove_empty_pm: 161 lstA = pipeline.prune(lstA) 162 run_args = [ 163 args.opt_binary, '-disable-symbolication', '-disable-output', 164 '-passes={}'.format(pipeline.toStr(lstA)), ll_input 165 ] 166 run_args.extend(extra_opt_args) 167 optA = subprocess.run(run_args, 168 stdout=subprocess.PIPE, 169 stderr=subprocess.PIPE) 170 if optA.returncode != expected_error_returncode: 171 break 172 prevLstA = lstA 173if prevLstA: 174 lst = prevLstA 175print('-passes="{}"'.format(pipeline.toStr(lst))) 176 177# Step #3 178# Now that we have a pipeline that is reduced both front and back we do 179# exhaustive sweeps over the remainder trying to remove one pass at a time. 180# Repeat as long as reduction is possible. 181print('---Starting step #3---') 182while True: 183 keepGoing = False 184 for idx in range(pipeline.count(lst)): 185 candLst = pipeline.remove(lst, idx) 186 if not args.dont_remove_empty_pm: 187 candLst = pipeline.prune(candLst) 188 run_args = [ 189 args.opt_binary, '-disable-symbolication', '-disable-output', 190 '-passes={}'.format(pipeline.toStr(candLst)), ll_input 191 ] 192 run_args.extend(extra_opt_args) 193 opt = subprocess.run(run_args, 194 stdout=subprocess.PIPE, 195 stderr=subprocess.PIPE) 196 if opt.returncode == expected_error_returncode: 197 lst = candLst 198 keepGoing = True 199 if not keepGoing: 200 break 201print('-passes="{}"'.format(pipeline.toStr(lst))) 202 203print('---FINISHED---') 204if args.output: 205 shutil.copy(ll_input, args.output) 206 print('Wrote output to \'{}\'.'.format(args.output)) 207print('-passes="{}"'.format(pipeline.toStr(lst))) 208exit(0) 209