llvm-project
214 строк · 7.4 Кб
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(
34description="Automatic opt pipeline reducer. Unrecognized arguments are forwarded to opt."
35)
36parser.add_argument("--opt-binary", action="store", dest="opt_binary", default="opt")
37parser.add_argument("--passes", action="store", dest="passes", required=True)
38parser.add_argument("--input", action="store", dest="input", required=True)
39parser.add_argument("--output", action="store", dest="output")
40parser.add_argument(
41"--dont-expand-passes",
42action="store_true",
43dest="dont_expand_passes",
44help="Do not expand pipeline before starting reduction.",
45)
46parser.add_argument(
47"--dont-remove-empty-pm",
48action="store_true",
49dest="dont_remove_empty_pm",
50help="Do not remove empty pass-managers from the pipeline during reduction.",
51)
52[args, extra_opt_args] = parser.parse_known_args()
53
54print("The following extra args will be passed to opt: {}".format(extra_opt_args))
55
56lst = pipeline.fromStr(args.passes)
57ll_input = args.input
58
59# Step #-1
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>').
63if not args.dont_expand_passes:
64run_args = [
65args.opt_binary,
66"-disable-symbolication",
67"-disable-output",
68"-print-pipeline-passes",
69"-passes={}".format(pipeline.toStr(lst)),
70ll_input,
71]
72run_args.extend(extra_opt_args)
73opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
74if opt.returncode != 0:
75print("Failed to expand passes. Aborting.")
76print(run_args)
77print("exitcode: {}".format(opt.returncode))
78print(opt.stderr.decode())
79exit(1)
80stdout = opt.stdout.decode()
81stdout = stdout[: stdout.rfind("\n")]
82lst = pipeline.fromStr(stdout)
83print("Expanded pass sequence: {}".format(pipeline.toStr(lst)))
84
85# Step #0
86# Confirm that the given input, passes and options result in failure.
87print("---Starting step #0---")
88run_args = [
89args.opt_binary,
90"-disable-symbolication",
91"-disable-output",
92"-passes={}".format(pipeline.toStr(lst)),
93ll_input,
94]
95run_args.extend(extra_opt_args)
96opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
97if opt.returncode >= 0:
98print("Input does not result in failure as expected. Aborting.")
99print(run_args)
100print("exitcode: {}".format(opt.returncode))
101print(opt.stderr.decode())
102exit(1)
103
104expected_error_returncode = opt.returncode
105print('-passes="{}"'.format(pipeline.toStr(lst)))
106
107# Step #1
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.
115print("---Starting step #1---")
116prevLstB = None
117prevIntermediate = None
118tmpd = tempfile.TemporaryDirectory()
119
120for idx in range(pipeline.count(lst)):
121[lstA, lstB] = pipeline.split(lst, idx)
122if not args.dont_remove_empty_pm:
123lstA = pipeline.prune(lstA)
124lstB = pipeline.prune(lstB)
125
126intermediate = "intermediate-0.ll" if idx % 2 else "intermediate-1.ll"
127intermediate = tmpd.name + "/" + intermediate
128run_args = [
129args.opt_binary,
130"-disable-symbolication",
131"-S",
132"-o",
133intermediate,
134"-passes={}".format(pipeline.toStr(lstA)),
135ll_input,
136]
137run_args.extend(extra_opt_args)
138optA = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
139run_args = [
140args.opt_binary,
141"-disable-symbolication",
142"-disable-output",
143"-passes={}".format(pipeline.toStr(lstB)),
144intermediate,
145]
146run_args.extend(extra_opt_args)
147optB = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
148if not (optA.returncode == 0 and optB.returncode == expected_error_returncode):
149break
150prevLstB = lstB
151prevIntermediate = intermediate
152if prevLstB:
153lst = prevLstB
154ll_input = prevIntermediate
155print('-passes="{}"'.format(pipeline.toStr(lst)))
156
157# Step #2
158# Try removing passes from the end of the remaining pipeline while still
159# reproducing the error.
160print("---Starting step #2---")
161prevLstA = None
162for idx in reversed(range(pipeline.count(lst))):
163[lstA, lstB] = pipeline.split(lst, idx)
164if not args.dont_remove_empty_pm:
165lstA = pipeline.prune(lstA)
166run_args = [
167args.opt_binary,
168"-disable-symbolication",
169"-disable-output",
170"-passes={}".format(pipeline.toStr(lstA)),
171ll_input,
172]
173run_args.extend(extra_opt_args)
174optA = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
175if optA.returncode != expected_error_returncode:
176break
177prevLstA = lstA
178if prevLstA:
179lst = prevLstA
180print('-passes="{}"'.format(pipeline.toStr(lst)))
181
182# Step #3
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.
186print("---Starting step #3---")
187while True:
188keepGoing = False
189for idx in range(pipeline.count(lst)):
190candLst = pipeline.remove(lst, idx)
191if not args.dont_remove_empty_pm:
192candLst = pipeline.prune(candLst)
193run_args = [
194args.opt_binary,
195"-disable-symbolication",
196"-disable-output",
197"-passes={}".format(pipeline.toStr(candLst)),
198ll_input,
199]
200run_args.extend(extra_opt_args)
201opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
202if opt.returncode == expected_error_returncode:
203lst = candLst
204keepGoing = True
205if not keepGoing:
206break
207print('-passes="{}"'.format(pipeline.toStr(lst)))
208
209print("---FINISHED---")
210if args.output:
211shutil.copy(ll_input, args.output)
212print("Wrote output to '{}'.".format(args.output))
213print('-passes="{}"'.format(pipeline.toStr(lst)))
214exit(0)
215