aboutsummaryrefslogtreecommitdiff
path: root/llvm/utils/reduce_pipeline.py
blob: 515fdd22b161726eeb4441a8b0359bee110a1ffb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/env python3

# Automatically formatted with yapf (https://github.com/google/yapf)

# Script for automatic 'opt' pipeline reduction for when using the new
# pass-manager (NPM). Based around the '-print-pipeline-passes' option.
#
# The reduction algorithm consists of several phases (steps).
#
# Step #0: Verify that input fails with the given pipeline and make note of the
# error code.
#
# Step #1: Split pipeline in two starting from front and move forward as long as
# first pipeline exits normally and the second pipeline fails with the expected
# error code. Move on to step #2 with the IR from the split point and the
# pipeline from the second invocation.
#
# Step #2: Remove passes from end of the pipeline as long as the pipeline fails
# with the expected error code.
#
# Step #3: Make several sweeps over the remaining pipeline trying to remove one
# pass at a time. Repeat sweeps until unable to remove any more passes.
#
# Usage example:
# reduce_pipeline.py --opt-binary=./build-all-Debug/bin/opt --input=input.ll --output=output.ll --passes=PIPELINE [EXTRA-OPT-ARGS ...]

import argparse
import pipeline
import shutil
import subprocess
import tempfile

parser = argparse.ArgumentParser(
    description="Automatic opt pipeline reducer. Unrecognized arguments are forwarded to opt."
)
parser.add_argument("--opt-binary", action="store", dest="opt_binary", default="opt")
parser.add_argument("--passes", action="store", dest="passes", required=True)
parser.add_argument("--input", action="store", dest="input", required=True)
parser.add_argument("--output", action="store", dest="output")
parser.add_argument(
    "--dont-expand-passes",
    action="store_true",
    dest="dont_expand_passes",
    help="Do not expand pipeline before starting reduction.",
)
parser.add_argument(
    "--dont-remove-empty-pm",
    action="store_true",
    dest="dont_remove_empty_pm",
    help="Do not remove empty pass-managers from the pipeline during reduction.",
)
[args, extra_opt_args] = parser.parse_known_args()

print("The following extra args will be passed to opt: {}".format(extra_opt_args))

lst = pipeline.fromStr(args.passes)
ll_input = args.input

# Step #-1
# Launch 'opt' once with '-print-pipeline-passes' to expand pipeline before
# starting reduction. Allows specifying a default pipelines (e.g.
# '-passes=default<O3>').
if not args.dont_expand_passes:
    run_args = [
        args.opt_binary,
        "-disable-symbolication",
        "-disable-output",
        "-print-pipeline-passes",
        "-passes={}".format(pipeline.toStr(lst)),
        ll_input,
    ]
    run_args.extend(extra_opt_args)
    opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if opt.returncode != 0:
        print("Failed to expand passes. Aborting.")
        print(run_args)
        print("exitcode: {}".format(opt.returncode))
        print(opt.stderr.decode())
        exit(1)
    stdout = opt.stdout.decode()
    stdout = stdout[: stdout.rfind("\n")]
    lst = pipeline.fromStr(stdout)
    print("Expanded pass sequence: {}".format(pipeline.toStr(lst)))

# Step #0
# Confirm that the given input, passes and options result in failure.
print("---Starting step #0---")
run_args = [
    args.opt_binary,
    "-disable-symbolication",
    "-disable-output",
    "-passes={}".format(pipeline.toStr(lst)),
    ll_input,
]
run_args.extend(extra_opt_args)
opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if opt.returncode >= 0:
    print("Input does not result in failure as expected. Aborting.")
    print(run_args)
    print("exitcode: {}".format(opt.returncode))
    print(opt.stderr.decode())
    exit(1)

expected_error_returncode = opt.returncode
print('-passes="{}"'.format(pipeline.toStr(lst)))

# Step #1
# Try to narrow down the failing pass sequence by splitting the pipeline in two
# opt invocations (A and B) starting with invocation A only running the first
# pipeline pass and invocation B the remaining. Keep moving the split point
# forward as long as invocation A exits normally and invocation B fails with
# the expected error. This will accomplish two things first the input IR will be
# further reduced and second, with that IR, the reduced pipeline for invocation
# B will be sufficient to reproduce.
print("---Starting step #1---")
prevLstB = None
prevIntermediate = None
tmpd = tempfile.TemporaryDirectory()

for idx in range(pipeline.count(lst)):
    [lstA, lstB] = pipeline.split(lst, idx)
    if not args.dont_remove_empty_pm:
        lstA = pipeline.prune(lstA)
        lstB = pipeline.prune(lstB)

    intermediate = "intermediate-0.ll" if idx % 2 else "intermediate-1.ll"
    intermediate = tmpd.name + "/" + intermediate
    run_args = [
        args.opt_binary,
        "-disable-symbolication",
        "-S",
        "-o",
        intermediate,
        "-passes={}".format(pipeline.toStr(lstA)),
        ll_input,
    ]
    run_args.extend(extra_opt_args)
    optA = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    run_args = [
        args.opt_binary,
        "-disable-symbolication",
        "-disable-output",
        "-passes={}".format(pipeline.toStr(lstB)),
        intermediate,
    ]
    run_args.extend(extra_opt_args)
    optB = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if not (optA.returncode == 0 and optB.returncode == expected_error_returncode):
        break
    prevLstB = lstB
    prevIntermediate = intermediate
if prevLstB:
    lst = prevLstB
    ll_input = prevIntermediate
print('-passes="{}"'.format(pipeline.toStr(lst)))

# Step #2
# Try removing passes from the end of the remaining pipeline while still
# reproducing the error.
print("---Starting step #2---")
prevLstA = None
for idx in reversed(range(pipeline.count(lst))):
    [lstA, lstB] = pipeline.split(lst, idx)
    if not args.dont_remove_empty_pm:
        lstA = pipeline.prune(lstA)
    run_args = [
        args.opt_binary,
        "-disable-symbolication",
        "-disable-output",
        "-passes={}".format(pipeline.toStr(lstA)),
        ll_input,
    ]
    run_args.extend(extra_opt_args)
    optA = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if optA.returncode != expected_error_returncode:
        break
    prevLstA = lstA
if prevLstA:
    lst = prevLstA
print('-passes="{}"'.format(pipeline.toStr(lst)))

# Step #3
# Now that we have a pipeline that is reduced both front and back we do
# exhaustive sweeps over the remainder trying to remove one pass at a time.
# Repeat as long as reduction is possible.
print("---Starting step #3---")
while True:
    keepGoing = False
    for idx in range(pipeline.count(lst)):
        candLst = pipeline.remove(lst, idx)
        if not args.dont_remove_empty_pm:
            candLst = pipeline.prune(candLst)
        run_args = [
            args.opt_binary,
            "-disable-symbolication",
            "-disable-output",
            "-passes={}".format(pipeline.toStr(candLst)),
            ll_input,
        ]
        run_args.extend(extra_opt_args)
        opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        if opt.returncode == expected_error_returncode:
            lst = candLst
            keepGoing = True
    if not keepGoing:
        break
print('-passes="{}"'.format(pipeline.toStr(lst)))

print("---FINISHED---")
if args.output:
    shutil.copy(ll_input, args.output)
    print("Wrote output to '{}'.".format(args.output))
print('-passes="{}"'.format(pipeline.toStr(lst)))
exit(0)