|
| 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 | + |
| 27 | +import argparse |
| 28 | +import pipeline |
| 29 | +import shutil |
| 30 | +import subprocess |
| 31 | +import tempfile |
| 32 | + |
| 33 | +parser = argparse.ArgumentParser( |
| 34 | + description= |
| 35 | + 'Automatic opt pipeline reducer. Unrecognized arguments are forwarded to opt.' |
| 36 | +) |
| 37 | +parser.add_argument('--opt-binary', |
| 38 | + action='store', |
| 39 | + dest='opt_binary', |
| 40 | + default='opt') |
| 41 | +parser.add_argument('--passes', action='store', dest='passes', required=True) |
| 42 | +parser.add_argument('--input', action='store', dest='input', required=True) |
| 43 | +parser.add_argument('--output', action='store', dest='output') |
| 44 | +parser.add_argument('--dont-expand-passes', |
| 45 | + action='store_true', |
| 46 | + dest='dont_expand_passes', |
| 47 | + help='Do not expand pipeline before starting reduction.') |
| 48 | +parser.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 | + |
| 56 | +print('The following extra args will be passed to opt: {}'.format( |
| 57 | + extra_opt_args)) |
| 58 | + |
| 59 | +lst = pipeline.fromStr(args.passes) |
| 60 | +passes = '-passes={}'.format(pipeline.toStr(lst)) |
| 61 | +ll_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>'). |
| 67 | +if 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. |
| 89 | +print('---Starting step #0---') |
| 90 | +run_args = [ |
| 91 | + args.opt_binary, '-disable-symbolication', '-disable-output', passes, |
| 92 | + ll_input |
| 93 | +] |
| 94 | +run_args.extend(extra_opt_args) |
| 95 | +opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| 96 | +if 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 | + |
| 103 | +expected_error_returncode = opt.returncode |
| 104 | +print('-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. |
| 114 | +print('---Starting step #1---') |
| 115 | +prevLstB = None |
| 116 | +prevIntermediate = None |
| 117 | +tmpd = tempfile.TemporaryDirectory() |
| 118 | + |
| 119 | +for 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 |
| 150 | +if prevLstB: |
| 151 | + lst = prevLstB |
| 152 | + ll_input = prevIntermediate |
| 153 | +print('-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. |
| 158 | +print('---Starting step #2---') |
| 159 | +prevLstA = None |
| 160 | +for 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 |
| 176 | +if prevLstA: |
| 177 | + lst = prevLstA |
| 178 | +print('-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. |
| 184 | +print('---Starting step #3---') |
| 185 | +while 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 |
| 205 | +print('-passes="{}"'.format(pipeline.toStr(lst))) |
| 206 | + |
| 207 | +print('---FINISHED---') |
| 208 | +if args.output: |
| 209 | + shutil.copy(ll_input, args.output) |
| 210 | + print('Wrote output to \'{}\'.'.format(args.output)) |
| 211 | +print('-passes="{}"'.format(pipeline.toStr(lst))) |
| 212 | +exit(0) |
0 commit comments