xref: /llvm-project/llvm/utils/reduce_pipeline.py (revision b71edfaa4ec3c998aadb35255ce2f60bba2940b0)
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(
34    description="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",
42    action="store_true",
43    dest="dont_expand_passes",
44    help="Do not expand pipeline before starting reduction.",
45)
46parser.add_argument(
47    "--dont-remove-empty-pm",
48    action="store_true",
49    dest="dont_remove_empty_pm",
50    help="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:
64    run_args = [
65        args.opt_binary,
66        "-disable-symbolication",
67        "-disable-output",
68        "-print-pipeline-passes",
69        "-passes={}".format(pipeline.toStr(lst)),
70        ll_input,
71    ]
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.")
76        print(run_args)
77        print("exitcode: {}".format(opt.returncode))
78        print(opt.stderr.decode())
79        exit(1)
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)))
84
85# Step #0
86# Confirm that the given input, passes and options result in failure.
87print("---Starting step #0---")
88run_args = [
89    args.opt_binary,
90    "-disable-symbolication",
91    "-disable-output",
92    "-passes={}".format(pipeline.toStr(lst)),
93    ll_input,
94]
95run_args.extend(extra_opt_args)
96opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
97if opt.returncode >= 0:
98    print("Input does not result in failure as expected. Aborting.")
99    print(run_args)
100    print("exitcode: {}".format(opt.returncode))
101    print(opt.stderr.decode())
102    exit(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)
122    if not args.dont_remove_empty_pm:
123        lstA = pipeline.prune(lstA)
124        lstB = pipeline.prune(lstB)
125
126    intermediate = "intermediate-0.ll" if idx % 2 else "intermediate-1.ll"
127    intermediate = tmpd.name + "/" + intermediate
128    run_args = [
129        args.opt_binary,
130        "-disable-symbolication",
131        "-S",
132        "-o",
133        intermediate,
134        "-passes={}".format(pipeline.toStr(lstA)),
135        ll_input,
136    ]
137    run_args.extend(extra_opt_args)
138    optA = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
139    run_args = [
140        args.opt_binary,
141        "-disable-symbolication",
142        "-disable-output",
143        "-passes={}".format(pipeline.toStr(lstB)),
144        intermediate,
145    ]
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):
149        break
150    prevLstB = lstB
151    prevIntermediate = intermediate
152if prevLstB:
153    lst = prevLstB
154    ll_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)
164    if not args.dont_remove_empty_pm:
165        lstA = pipeline.prune(lstA)
166    run_args = [
167        args.opt_binary,
168        "-disable-symbolication",
169        "-disable-output",
170        "-passes={}".format(pipeline.toStr(lstA)),
171        ll_input,
172    ]
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:
176        break
177    prevLstA = lstA
178if prevLstA:
179    lst = 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:
188    keepGoing = False
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)
193        run_args = [
194            args.opt_binary,
195            "-disable-symbolication",
196            "-disable-output",
197            "-passes={}".format(pipeline.toStr(candLst)),
198            ll_input,
199        ]
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:
203            lst = candLst
204            keepGoing = True
205    if not keepGoing:
206        break
207print('-passes="{}"'.format(pipeline.toStr(lst)))
208
209print("---FINISHED---")
210if args.output:
211    shutil.copy(ll_input, args.output)
212    print("Wrote output to '{}'.".format(args.output))
213print('-passes="{}"'.format(pipeline.toStr(lst)))
214exit(0)
215