[docs] Add LICENSE.txt to the root of the mono-repo
[llvm-project.git] / llvm / utils / reduce_pipeline.py
blobbaf6b2f6930d2c98de773ae834fa2bfb85182543
1 #!/usr/bin/env python3
3 # Automatically formatted with yapf (https://github.com/google/yapf)
5 # Script for automatic 'opt' pipeline reduction for when using the new
6 # pass-manager (NPM). Based around the '-print-pipeline-passes' option.
8 # The reduction algorithm consists of several phases (steps).
10 # Step #0: Verify that input fails with the given pipeline and make note of the
11 # error code.
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.
18 # Step #2: Remove passes from end of the pipeline as long as the pipeline fails
19 # with the expected error code.
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.
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 ...]
27 import argparse
28 import pipeline
29 import shutil
30 import subprocess
31 import tempfile
33 parser = argparse.ArgumentParser(
34 description=
35 'Automatic opt pipeline reducer. Unrecognized arguments are forwarded to opt.'
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.'
54 [args, extra_opt_args] = parser.parse_known_args()
56 print('The following extra args will be passed to opt: {}'.format(
57 extra_opt_args))
59 lst = pipeline.fromStr(args.passes)
60 ll_input = args.input
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>').
66 if 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
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)))
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',
92 '-passes={}'.format(pipeline.toStr(lst)), ll_input
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)
103 expected_error_returncode = opt.returncode
104 print('-passes="{}"'.format(pipeline.toStr(lst)))
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()
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)
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
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
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
148 if prevLstB:
149 lst = prevLstB
150 ll_input = prevIntermediate
151 print('-passes="{}"'.format(pipeline.toStr(lst)))
153 # Step #2
154 # Try removing passes from the end of the remaining pipeline while still
155 # reproducing the error.
156 print('---Starting step #2---')
157 prevLstA = None
158 for 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
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
173 if prevLstA:
174 lst = prevLstA
175 print('-passes="{}"'.format(pipeline.toStr(lst)))
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.
181 print('---Starting step #3---')
182 while 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
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
201 print('-passes="{}"'.format(pipeline.toStr(lst)))
203 print('---FINISHED---')
204 if args.output:
205 shutil.copy(ll_input, args.output)
206 print('Wrote output to \'{}\'.'.format(args.output))
207 print('-passes="{}"'.format(pipeline.toStr(lst)))
208 exit(0)