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
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.
25 # reduce_pipeline.py --opt-binary=./build-all-Debug/bin/opt --input=input.ll --output=output.ll --passes=PIPELINE [EXTRA-OPT-ARGS ...]
33 parser
= argparse
.ArgumentParser(
35 'Automatic opt pipeline reducer. Unrecognized arguments are forwarded to opt.'
37 parser
.add_argument('--opt-binary',
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',
46 dest
='dont_expand_passes',
47 help='Do not expand pipeline before starting reduction.')
49 '--dont-remove-empty-pm',
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(
59 lst
= pipeline
.fromStr(args
.passes
)
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
:
68 args
.opt_binary
, '-disable-symbolication', '-disable-output',
69 '-print-pipeline-passes', '-passes={}'.format(pipeline
.toStr(lst
)),
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.')
79 print('exitcode: {}'.format(opt
.returncode
))
80 print(opt
.stderr
.decode())
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
)))
88 # Confirm that the given input, passes and options result in failure.
89 print('---Starting step #0---')
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.')
99 print('exitcode: {}'.format(opt
.returncode
))
100 print(opt
.stderr
.decode())
103 expected_error_returncode
= opt
.returncode
104 print('-passes="{}"'.format(pipeline
.toStr(lst
)))
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---')
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
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
)
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
):
147 prevIntermediate
= intermediate
150 ll_input
= prevIntermediate
151 print('-passes="{}"'.format(pipeline
.toStr(lst
)))
154 # Try removing passes from the end of the remaining pipeline while still
155 # reproducing the error.
156 print('---Starting step #2---')
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
)
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
:
175 print('-passes="{}"'.format(pipeline
.toStr(lst
)))
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---')
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
)
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
:
201 print('-passes="{}"'.format(pipeline
.toStr(lst
)))
203 print('---FINISHED---')
205 shutil
.copy(ll_input
, args
.output
)
206 print('Wrote output to \'{}\'.'.format(args
.output
))
207 print('-passes="{}"'.format(pipeline
.toStr(lst
)))