xref: /openbsd/gnu/llvm/llvm/utils/reduce_pipeline.py (revision d415bd75)
1*d415bd75Srobert#!/usr/bin/env python3
2*d415bd75Srobert
3*d415bd75Srobert# Automatically formatted with yapf (https://github.com/google/yapf)
4*d415bd75Srobert
5*d415bd75Srobert# Script for automatic 'opt' pipeline reduction for when using the new
6*d415bd75Srobert# pass-manager (NPM). Based around the '-print-pipeline-passes' option.
7*d415bd75Srobert#
8*d415bd75Srobert# The reduction algorithm consists of several phases (steps).
9*d415bd75Srobert#
10*d415bd75Srobert# Step #0: Verify that input fails with the given pipeline and make note of the
11*d415bd75Srobert# error code.
12*d415bd75Srobert#
13*d415bd75Srobert# Step #1: Split pipeline in two starting from front and move forward as long as
14*d415bd75Srobert# first pipeline exits normally and the second pipeline fails with the expected
15*d415bd75Srobert# error code. Move on to step #2 with the IR from the split point and the
16*d415bd75Srobert# pipeline from the second invocation.
17*d415bd75Srobert#
18*d415bd75Srobert# Step #2: Remove passes from end of the pipeline as long as the pipeline fails
19*d415bd75Srobert# with the expected error code.
20*d415bd75Srobert#
21*d415bd75Srobert# Step #3: Make several sweeps over the remaining pipeline trying to remove one
22*d415bd75Srobert# pass at a time. Repeat sweeps until unable to remove any more passes.
23*d415bd75Srobert#
24*d415bd75Srobert# Usage example:
25*d415bd75Srobert# reduce_pipeline.py --opt-binary=./build-all-Debug/bin/opt --input=input.ll --output=output.ll --passes=PIPELINE [EXTRA-OPT-ARGS ...]
26*d415bd75Srobert
27*d415bd75Srobertimport argparse
28*d415bd75Srobertimport pipeline
29*d415bd75Srobertimport shutil
30*d415bd75Srobertimport subprocess
31*d415bd75Srobertimport tempfile
32*d415bd75Srobert
33*d415bd75Srobertparser = argparse.ArgumentParser(
34*d415bd75Srobert    description=
35*d415bd75Srobert    'Automatic opt pipeline reducer. Unrecognized arguments are forwarded to opt.'
36*d415bd75Srobert)
37*d415bd75Srobertparser.add_argument('--opt-binary',
38*d415bd75Srobert                    action='store',
39*d415bd75Srobert                    dest='opt_binary',
40*d415bd75Srobert                    default='opt')
41*d415bd75Srobertparser.add_argument('--passes', action='store', dest='passes', required=True)
42*d415bd75Srobertparser.add_argument('--input', action='store', dest='input', required=True)
43*d415bd75Srobertparser.add_argument('--output', action='store', dest='output')
44*d415bd75Srobertparser.add_argument('--dont-expand-passes',
45*d415bd75Srobert                    action='store_true',
46*d415bd75Srobert                    dest='dont_expand_passes',
47*d415bd75Srobert                    help='Do not expand pipeline before starting reduction.')
48*d415bd75Srobertparser.add_argument(
49*d415bd75Srobert    '--dont-remove-empty-pm',
50*d415bd75Srobert    action='store_true',
51*d415bd75Srobert    dest='dont_remove_empty_pm',
52*d415bd75Srobert    help='Do not remove empty pass-managers from the pipeline during reduction.'
53*d415bd75Srobert)
54*d415bd75Srobert[args, extra_opt_args] = parser.parse_known_args()
55*d415bd75Srobert
56*d415bd75Srobertprint('The following extra args will be passed to opt: {}'.format(
57*d415bd75Srobert    extra_opt_args))
58*d415bd75Srobert
59*d415bd75Srobertlst = pipeline.fromStr(args.passes)
60*d415bd75Srobertll_input = args.input
61*d415bd75Srobert
62*d415bd75Srobert# Step #-1
63*d415bd75Srobert# Launch 'opt' once with '-print-pipeline-passes' to expand pipeline before
64*d415bd75Srobert# starting reduction. Allows specifying a default pipelines (e.g.
65*d415bd75Srobert# '-passes=default<O3>').
66*d415bd75Srobertif not args.dont_expand_passes:
67*d415bd75Srobert    run_args = [
68*d415bd75Srobert        args.opt_binary, '-disable-symbolication', '-disable-output',
69*d415bd75Srobert        '-print-pipeline-passes', '-passes={}'.format(pipeline.toStr(lst)),
70*d415bd75Srobert        ll_input
71*d415bd75Srobert    ]
72*d415bd75Srobert    run_args.extend(extra_opt_args)
73*d415bd75Srobert    opt = subprocess.run(run_args,
74*d415bd75Srobert                         stdout=subprocess.PIPE,
75*d415bd75Srobert                         stderr=subprocess.PIPE)
76*d415bd75Srobert    if opt.returncode != 0:
77*d415bd75Srobert        print('Failed to expand passes. Aborting.')
78*d415bd75Srobert        print(run_args)
79*d415bd75Srobert        print('exitcode: {}'.format(opt.returncode))
80*d415bd75Srobert        print(opt.stderr.decode())
81*d415bd75Srobert        exit(1)
82*d415bd75Srobert    stdout = opt.stdout.decode()
83*d415bd75Srobert    stdout = stdout[:stdout.rfind('\n')]
84*d415bd75Srobert    lst = pipeline.fromStr(stdout)
85*d415bd75Srobert    print('Expanded pass sequence: {}'.format(pipeline.toStr(lst)))
86*d415bd75Srobert
87*d415bd75Srobert# Step #0
88*d415bd75Srobert# Confirm that the given input, passes and options result in failure.
89*d415bd75Srobertprint('---Starting step #0---')
90*d415bd75Srobertrun_args = [
91*d415bd75Srobert    args.opt_binary, '-disable-symbolication', '-disable-output',
92*d415bd75Srobert    '-passes={}'.format(pipeline.toStr(lst)), ll_input
93*d415bd75Srobert]
94*d415bd75Srobertrun_args.extend(extra_opt_args)
95*d415bd75Srobertopt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
96*d415bd75Srobertif opt.returncode >= 0:
97*d415bd75Srobert    print('Input does not result in failure as expected. Aborting.')
98*d415bd75Srobert    print(run_args)
99*d415bd75Srobert    print('exitcode: {}'.format(opt.returncode))
100*d415bd75Srobert    print(opt.stderr.decode())
101*d415bd75Srobert    exit(1)
102*d415bd75Srobert
103*d415bd75Srobertexpected_error_returncode = opt.returncode
104*d415bd75Srobertprint('-passes="{}"'.format(pipeline.toStr(lst)))
105*d415bd75Srobert
106*d415bd75Srobert# Step #1
107*d415bd75Srobert# Try to narrow down the failing pass sequence by splitting the pipeline in two
108*d415bd75Srobert# opt invocations (A and B) starting with invocation A only running the first
109*d415bd75Srobert# pipeline pass and invocation B the remaining. Keep moving the split point
110*d415bd75Srobert# forward as long as invocation A exits normally and invocation B fails with
111*d415bd75Srobert# the expected error. This will accomplish two things first the input IR will be
112*d415bd75Srobert# further reduced and second, with that IR, the reduced pipeline for invocation
113*d415bd75Srobert# B will be sufficient to reproduce.
114*d415bd75Srobertprint('---Starting step #1---')
115*d415bd75SrobertprevLstB = None
116*d415bd75SrobertprevIntermediate = None
117*d415bd75Sroberttmpd = tempfile.TemporaryDirectory()
118*d415bd75Srobert
119*d415bd75Srobertfor idx in range(pipeline.count(lst)):
120*d415bd75Srobert    [lstA, lstB] = pipeline.split(lst, idx)
121*d415bd75Srobert    if not args.dont_remove_empty_pm:
122*d415bd75Srobert        lstA = pipeline.prune(lstA)
123*d415bd75Srobert        lstB = pipeline.prune(lstB)
124*d415bd75Srobert
125*d415bd75Srobert    intermediate = 'intermediate-0.ll' if idx % 2 else 'intermediate-1.ll'
126*d415bd75Srobert    intermediate = tmpd.name + '/' + intermediate
127*d415bd75Srobert    run_args = [
128*d415bd75Srobert        args.opt_binary, '-disable-symbolication', '-S', '-o', intermediate,
129*d415bd75Srobert        '-passes={}'.format(pipeline.toStr(lstA)), ll_input
130*d415bd75Srobert    ]
131*d415bd75Srobert    run_args.extend(extra_opt_args)
132*d415bd75Srobert    optA = subprocess.run(run_args,
133*d415bd75Srobert                          stdout=subprocess.PIPE,
134*d415bd75Srobert                          stderr=subprocess.PIPE)
135*d415bd75Srobert    run_args = [
136*d415bd75Srobert        args.opt_binary, '-disable-symbolication', '-disable-output',
137*d415bd75Srobert        '-passes={}'.format(pipeline.toStr(lstB)), intermediate
138*d415bd75Srobert    ]
139*d415bd75Srobert    run_args.extend(extra_opt_args)
140*d415bd75Srobert    optB = subprocess.run(run_args,
141*d415bd75Srobert                          stdout=subprocess.PIPE,
142*d415bd75Srobert                          stderr=subprocess.PIPE)
143*d415bd75Srobert    if not (optA.returncode == 0
144*d415bd75Srobert            and optB.returncode == expected_error_returncode):
145*d415bd75Srobert        break
146*d415bd75Srobert    prevLstB = lstB
147*d415bd75Srobert    prevIntermediate = intermediate
148*d415bd75Srobertif prevLstB:
149*d415bd75Srobert    lst = prevLstB
150*d415bd75Srobert    ll_input = prevIntermediate
151*d415bd75Srobertprint('-passes="{}"'.format(pipeline.toStr(lst)))
152*d415bd75Srobert
153*d415bd75Srobert# Step #2
154*d415bd75Srobert# Try removing passes from the end of the remaining pipeline while still
155*d415bd75Srobert# reproducing the error.
156*d415bd75Srobertprint('---Starting step #2---')
157*d415bd75SrobertprevLstA = None
158*d415bd75Srobertfor idx in reversed(range(pipeline.count(lst))):
159*d415bd75Srobert    [lstA, lstB] = pipeline.split(lst, idx)
160*d415bd75Srobert    if not args.dont_remove_empty_pm:
161*d415bd75Srobert        lstA = pipeline.prune(lstA)
162*d415bd75Srobert    run_args = [
163*d415bd75Srobert        args.opt_binary, '-disable-symbolication', '-disable-output',
164*d415bd75Srobert        '-passes={}'.format(pipeline.toStr(lstA)), ll_input
165*d415bd75Srobert    ]
166*d415bd75Srobert    run_args.extend(extra_opt_args)
167*d415bd75Srobert    optA = subprocess.run(run_args,
168*d415bd75Srobert                          stdout=subprocess.PIPE,
169*d415bd75Srobert                          stderr=subprocess.PIPE)
170*d415bd75Srobert    if optA.returncode != expected_error_returncode:
171*d415bd75Srobert        break
172*d415bd75Srobert    prevLstA = lstA
173*d415bd75Srobertif prevLstA:
174*d415bd75Srobert    lst = prevLstA
175*d415bd75Srobertprint('-passes="{}"'.format(pipeline.toStr(lst)))
176*d415bd75Srobert
177*d415bd75Srobert# Step #3
178*d415bd75Srobert# Now that we have a pipeline that is reduced both front and back we do
179*d415bd75Srobert# exhaustive sweeps over the remainder trying to remove one pass at a time.
180*d415bd75Srobert# Repeat as long as reduction is possible.
181*d415bd75Srobertprint('---Starting step #3---')
182*d415bd75Srobertwhile True:
183*d415bd75Srobert    keepGoing = False
184*d415bd75Srobert    for idx in range(pipeline.count(lst)):
185*d415bd75Srobert        candLst = pipeline.remove(lst, idx)
186*d415bd75Srobert        if not args.dont_remove_empty_pm:
187*d415bd75Srobert            candLst = pipeline.prune(candLst)
188*d415bd75Srobert        run_args = [
189*d415bd75Srobert            args.opt_binary, '-disable-symbolication', '-disable-output',
190*d415bd75Srobert            '-passes={}'.format(pipeline.toStr(candLst)), ll_input
191*d415bd75Srobert        ]
192*d415bd75Srobert        run_args.extend(extra_opt_args)
193*d415bd75Srobert        opt = subprocess.run(run_args,
194*d415bd75Srobert                             stdout=subprocess.PIPE,
195*d415bd75Srobert                             stderr=subprocess.PIPE)
196*d415bd75Srobert        if opt.returncode == expected_error_returncode:
197*d415bd75Srobert            lst = candLst
198*d415bd75Srobert            keepGoing = True
199*d415bd75Srobert    if not keepGoing:
200*d415bd75Srobert        break
201*d415bd75Srobertprint('-passes="{}"'.format(pipeline.toStr(lst)))
202*d415bd75Srobert
203*d415bd75Srobertprint('---FINISHED---')
204*d415bd75Srobertif args.output:
205*d415bd75Srobert    shutil.copy(ll_input, args.output)
206*d415bd75Srobert    print('Wrote output to \'{}\'.'.format(args.output))
207*d415bd75Srobertprint('-passes="{}"'.format(pipeline.toStr(lst)))
208*d415bd75Srobertexit(0)
209