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(
34 description
="Automatic opt pipeline reducer. Unrecognized arguments are forwarded to opt."
36 parser
.add_argument("--opt-binary", action
="store", dest
="opt_binary", default
="opt")
37 parser
.add_argument("--passes", action
="store", dest
="passes", required
=True)
38 parser
.add_argument("--input", action
="store", dest
="input", required
=True)
39 parser
.add_argument("--output", action
="store", dest
="output")
41 "--dont-expand-passes",
43 dest
="dont_expand_passes",
44 help="Do not expand pipeline before starting reduction.",
47 "--dont-remove-empty-pm",
49 dest
="dont_remove_empty_pm",
50 help="Do not remove empty pass-managers from the pipeline during reduction.",
52 [args
, extra_opt_args
] = parser
.parse_known_args()
54 print("The following extra args will be passed to opt: {}".format(extra_opt_args
))
56 lst
= pipeline
.fromStr(args
.passes
)
60 # Launch 'opt' once with '-print-pipeline-passes' to expand pipeline before
61 # starting reduction. Allows specifying a default pipelines (e.g.
62 # '-passes=default<O3>').
63 if not args
.dont_expand_passes
:
66 "-disable-symbolication",
68 "-print-pipeline-passes",
69 "-passes={}".format(pipeline
.toStr(lst
)),
72 run_args
.extend(extra_opt_args
)
73 opt
= subprocess
.run(run_args
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
74 if opt
.returncode
!= 0:
75 print("Failed to expand passes. Aborting.")
77 print("exitcode: {}".format(opt
.returncode
))
78 print(opt
.stderr
.decode())
80 stdout
= opt
.stdout
.decode()
81 stdout
= stdout
[: stdout
.rfind("\n")]
82 lst
= pipeline
.fromStr(stdout
)
83 print("Expanded pass sequence: {}".format(pipeline
.toStr(lst
)))
86 # Confirm that the given input, passes and options result in failure.
87 print("---Starting step #0---")
90 "-disable-symbolication",
92 "-passes={}".format(pipeline
.toStr(lst
)),
95 run_args
.extend(extra_opt_args
)
96 opt
= subprocess
.run(run_args
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
97 if opt
.returncode
>= 0:
98 print("Input does not result in failure as expected. Aborting.")
100 print("exitcode: {}".format(opt
.returncode
))
101 print(opt
.stderr
.decode())
104 expected_error_returncode
= opt
.returncode
105 print('-passes="{}"'.format(pipeline
.toStr(lst
)))
108 # Try to narrow down the failing pass sequence by splitting the pipeline in two
109 # opt invocations (A and B) starting with invocation A only running the first
110 # pipeline pass and invocation B the remaining. Keep moving the split point
111 # forward as long as invocation A exits normally and invocation B fails with
112 # the expected error. This will accomplish two things first the input IR will be
113 # further reduced and second, with that IR, the reduced pipeline for invocation
114 # B will be sufficient to reproduce.
115 print("---Starting step #1---")
117 prevIntermediate
= None
118 tmpd
= tempfile
.TemporaryDirectory()
120 for idx
in range(pipeline
.count(lst
)):
121 [lstA
, lstB
] = pipeline
.split(lst
, idx
)
122 if not args
.dont_remove_empty_pm
:
123 lstA
= pipeline
.prune(lstA
)
124 lstB
= pipeline
.prune(lstB
)
126 intermediate
= "intermediate-0.ll" if idx
% 2 else "intermediate-1.ll"
127 intermediate
= tmpd
.name
+ "/" + intermediate
130 "-disable-symbolication",
134 "-passes={}".format(pipeline
.toStr(lstA
)),
137 run_args
.extend(extra_opt_args
)
138 optA
= subprocess
.run(run_args
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
141 "-disable-symbolication",
143 "-passes={}".format(pipeline
.toStr(lstB
)),
146 run_args
.extend(extra_opt_args
)
147 optB
= subprocess
.run(run_args
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
148 if not (optA
.returncode
== 0 and optB
.returncode
== expected_error_returncode
):
151 prevIntermediate
= intermediate
154 ll_input
= prevIntermediate
155 print('-passes="{}"'.format(pipeline
.toStr(lst
)))
158 # Try removing passes from the end of the remaining pipeline while still
159 # reproducing the error.
160 print("---Starting step #2---")
162 for idx
in reversed(range(pipeline
.count(lst
))):
163 [lstA
, lstB
] = pipeline
.split(lst
, idx
)
164 if not args
.dont_remove_empty_pm
:
165 lstA
= pipeline
.prune(lstA
)
168 "-disable-symbolication",
170 "-passes={}".format(pipeline
.toStr(lstA
)),
173 run_args
.extend(extra_opt_args
)
174 optA
= subprocess
.run(run_args
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
175 if optA
.returncode
!= expected_error_returncode
:
180 print('-passes="{}"'.format(pipeline
.toStr(lst
)))
183 # Now that we have a pipeline that is reduced both front and back we do
184 # exhaustive sweeps over the remainder trying to remove one pass at a time.
185 # Repeat as long as reduction is possible.
186 print("---Starting step #3---")
189 for idx
in range(pipeline
.count(lst
)):
190 candLst
= pipeline
.remove(lst
, idx
)
191 if not args
.dont_remove_empty_pm
:
192 candLst
= pipeline
.prune(candLst
)
195 "-disable-symbolication",
197 "-passes={}".format(pipeline
.toStr(candLst
)),
200 run_args
.extend(extra_opt_args
)
201 opt
= subprocess
.run(run_args
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
202 if opt
.returncode
== expected_error_returncode
:
207 print('-passes="{}"'.format(pipeline
.toStr(lst
)))
209 print("---FINISHED---")
211 shutil
.copy(ll_input
, args
.output
)
212 print("Wrote output to '{}'.".format(args
.output
))
213 print('-passes="{}"'.format(pipeline
.toStr(lst
)))