xref: /llvm-project/llvm/utils/reduce_pipeline.py (revision b71edfaa4ec3c998aadb35255ce2f60bba2940b0)
191d15aa0SMarkus Lavin#!/usr/bin/env python3
291d15aa0SMarkus Lavin
391d15aa0SMarkus Lavin# Automatically formatted with yapf (https://github.com/google/yapf)
491d15aa0SMarkus Lavin
591d15aa0SMarkus Lavin# Script for automatic 'opt' pipeline reduction for when using the new
691d15aa0SMarkus Lavin# pass-manager (NPM). Based around the '-print-pipeline-passes' option.
791d15aa0SMarkus Lavin#
891d15aa0SMarkus Lavin# The reduction algorithm consists of several phases (steps).
991d15aa0SMarkus Lavin#
1091d15aa0SMarkus Lavin# Step #0: Verify that input fails with the given pipeline and make note of the
1191d15aa0SMarkus Lavin# error code.
1291d15aa0SMarkus Lavin#
1391d15aa0SMarkus Lavin# Step #1: Split pipeline in two starting from front and move forward as long as
1491d15aa0SMarkus Lavin# first pipeline exits normally and the second pipeline fails with the expected
1591d15aa0SMarkus Lavin# error code. Move on to step #2 with the IR from the split point and the
1691d15aa0SMarkus Lavin# pipeline from the second invocation.
1791d15aa0SMarkus Lavin#
1891d15aa0SMarkus Lavin# Step #2: Remove passes from end of the pipeline as long as the pipeline fails
1991d15aa0SMarkus Lavin# with the expected error code.
2091d15aa0SMarkus Lavin#
2191d15aa0SMarkus Lavin# Step #3: Make several sweeps over the remaining pipeline trying to remove one
2291d15aa0SMarkus Lavin# pass at a time. Repeat sweeps until unable to remove any more passes.
2391d15aa0SMarkus Lavin#
2491d15aa0SMarkus Lavin# Usage example:
2591d15aa0SMarkus Lavin# reduce_pipeline.py --opt-binary=./build-all-Debug/bin/opt --input=input.ll --output=output.ll --passes=PIPELINE [EXTRA-OPT-ARGS ...]
2691d15aa0SMarkus Lavin
2791d15aa0SMarkus Lavinimport argparse
2891d15aa0SMarkus Lavinimport pipeline
2991d15aa0SMarkus Lavinimport shutil
3091d15aa0SMarkus Lavinimport subprocess
3191d15aa0SMarkus Lavinimport tempfile
3291d15aa0SMarkus Lavin
3391d15aa0SMarkus Lavinparser = argparse.ArgumentParser(
34*b71edfaaSTobias Hieta    description="Automatic opt pipeline reducer. Unrecognized arguments are forwarded to opt."
3591d15aa0SMarkus Lavin)
36*b71edfaaSTobias Hietaparser.add_argument("--opt-binary", action="store", dest="opt_binary", default="opt")
37*b71edfaaSTobias Hietaparser.add_argument("--passes", action="store", dest="passes", required=True)
38*b71edfaaSTobias Hietaparser.add_argument("--input", action="store", dest="input", required=True)
39*b71edfaaSTobias Hietaparser.add_argument("--output", action="store", dest="output")
4091d15aa0SMarkus Lavinparser.add_argument(
41*b71edfaaSTobias Hieta    "--dont-expand-passes",
42*b71edfaaSTobias Hieta    action="store_true",
43*b71edfaaSTobias Hieta    dest="dont_expand_passes",
44*b71edfaaSTobias Hieta    help="Do not expand pipeline before starting reduction.",
45*b71edfaaSTobias Hieta)
46*b71edfaaSTobias Hietaparser.add_argument(
47*b71edfaaSTobias Hieta    "--dont-remove-empty-pm",
48*b71edfaaSTobias Hieta    action="store_true",
49*b71edfaaSTobias Hieta    dest="dont_remove_empty_pm",
50*b71edfaaSTobias Hieta    help="Do not remove empty pass-managers from the pipeline during reduction.",
5191d15aa0SMarkus Lavin)
5291d15aa0SMarkus Lavin[args, extra_opt_args] = parser.parse_known_args()
5391d15aa0SMarkus Lavin
54*b71edfaaSTobias Hietaprint("The following extra args will be passed to opt: {}".format(extra_opt_args))
5591d15aa0SMarkus Lavin
5691d15aa0SMarkus Lavinlst = pipeline.fromStr(args.passes)
5791d15aa0SMarkus Lavinll_input = args.input
5891d15aa0SMarkus Lavin
5991d15aa0SMarkus Lavin# Step #-1
6091d15aa0SMarkus Lavin# Launch 'opt' once with '-print-pipeline-passes' to expand pipeline before
6191d15aa0SMarkus Lavin# starting reduction. Allows specifying a default pipelines (e.g.
6291d15aa0SMarkus Lavin# '-passes=default<O3>').
6391d15aa0SMarkus Lavinif not args.dont_expand_passes:
6491d15aa0SMarkus Lavin    run_args = [
65*b71edfaaSTobias Hieta        args.opt_binary,
66*b71edfaaSTobias Hieta        "-disable-symbolication",
67*b71edfaaSTobias Hieta        "-disable-output",
68*b71edfaaSTobias Hieta        "-print-pipeline-passes",
69*b71edfaaSTobias Hieta        "-passes={}".format(pipeline.toStr(lst)),
70*b71edfaaSTobias Hieta        ll_input,
7191d15aa0SMarkus Lavin    ]
7291d15aa0SMarkus Lavin    run_args.extend(extra_opt_args)
73*b71edfaaSTobias Hieta    opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
7491d15aa0SMarkus Lavin    if opt.returncode != 0:
75*b71edfaaSTobias Hieta        print("Failed to expand passes. Aborting.")
7691d15aa0SMarkus Lavin        print(run_args)
77*b71edfaaSTobias Hieta        print("exitcode: {}".format(opt.returncode))
7891d15aa0SMarkus Lavin        print(opt.stderr.decode())
7991d15aa0SMarkus Lavin        exit(1)
8091d15aa0SMarkus Lavin    stdout = opt.stdout.decode()
81*b71edfaaSTobias Hieta    stdout = stdout[: stdout.rfind("\n")]
827e34d5eaSMarkus Lavin    lst = pipeline.fromStr(stdout)
83*b71edfaaSTobias Hieta    print("Expanded pass sequence: {}".format(pipeline.toStr(lst)))
8491d15aa0SMarkus Lavin
8591d15aa0SMarkus Lavin# Step #0
8691d15aa0SMarkus Lavin# Confirm that the given input, passes and options result in failure.
87*b71edfaaSTobias Hietaprint("---Starting step #0---")
8891d15aa0SMarkus Lavinrun_args = [
89*b71edfaaSTobias Hieta    args.opt_binary,
90*b71edfaaSTobias Hieta    "-disable-symbolication",
91*b71edfaaSTobias Hieta    "-disable-output",
92*b71edfaaSTobias Hieta    "-passes={}".format(pipeline.toStr(lst)),
93*b71edfaaSTobias Hieta    ll_input,
9491d15aa0SMarkus Lavin]
9591d15aa0SMarkus Lavinrun_args.extend(extra_opt_args)
9691d15aa0SMarkus Lavinopt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
9791d15aa0SMarkus Lavinif opt.returncode >= 0:
98*b71edfaaSTobias Hieta    print("Input does not result in failure as expected. Aborting.")
9991d15aa0SMarkus Lavin    print(run_args)
100*b71edfaaSTobias Hieta    print("exitcode: {}".format(opt.returncode))
10191d15aa0SMarkus Lavin    print(opt.stderr.decode())
10291d15aa0SMarkus Lavin    exit(1)
10391d15aa0SMarkus Lavin
10491d15aa0SMarkus Lavinexpected_error_returncode = opt.returncode
10591d15aa0SMarkus Lavinprint('-passes="{}"'.format(pipeline.toStr(lst)))
10691d15aa0SMarkus Lavin
10791d15aa0SMarkus Lavin# Step #1
10891d15aa0SMarkus Lavin# Try to narrow down the failing pass sequence by splitting the pipeline in two
10991d15aa0SMarkus Lavin# opt invocations (A and B) starting with invocation A only running the first
11091d15aa0SMarkus Lavin# pipeline pass and invocation B the remaining. Keep moving the split point
11191d15aa0SMarkus Lavin# forward as long as invocation A exits normally and invocation B fails with
11291d15aa0SMarkus Lavin# the expected error. This will accomplish two things first the input IR will be
11391d15aa0SMarkus Lavin# further reduced and second, with that IR, the reduced pipeline for invocation
11491d15aa0SMarkus Lavin# B will be sufficient to reproduce.
115*b71edfaaSTobias Hietaprint("---Starting step #1---")
11691d15aa0SMarkus LavinprevLstB = None
11791d15aa0SMarkus LavinprevIntermediate = None
11891d15aa0SMarkus Lavintmpd = tempfile.TemporaryDirectory()
11991d15aa0SMarkus Lavin
12091d15aa0SMarkus Lavinfor idx in range(pipeline.count(lst)):
12191d15aa0SMarkus Lavin    [lstA, lstB] = pipeline.split(lst, idx)
12291d15aa0SMarkus Lavin    if not args.dont_remove_empty_pm:
12391d15aa0SMarkus Lavin        lstA = pipeline.prune(lstA)
12491d15aa0SMarkus Lavin        lstB = pipeline.prune(lstB)
12591d15aa0SMarkus Lavin
126*b71edfaaSTobias Hieta    intermediate = "intermediate-0.ll" if idx % 2 else "intermediate-1.ll"
127*b71edfaaSTobias Hieta    intermediate = tmpd.name + "/" + intermediate
12891d15aa0SMarkus Lavin    run_args = [
129*b71edfaaSTobias Hieta        args.opt_binary,
130*b71edfaaSTobias Hieta        "-disable-symbolication",
131*b71edfaaSTobias Hieta        "-S",
132*b71edfaaSTobias Hieta        "-o",
133*b71edfaaSTobias Hieta        intermediate,
134*b71edfaaSTobias Hieta        "-passes={}".format(pipeline.toStr(lstA)),
135*b71edfaaSTobias Hieta        ll_input,
13691d15aa0SMarkus Lavin    ]
13791d15aa0SMarkus Lavin    run_args.extend(extra_opt_args)
138*b71edfaaSTobias Hieta    optA = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
13991d15aa0SMarkus Lavin    run_args = [
140*b71edfaaSTobias Hieta        args.opt_binary,
141*b71edfaaSTobias Hieta        "-disable-symbolication",
142*b71edfaaSTobias Hieta        "-disable-output",
143*b71edfaaSTobias Hieta        "-passes={}".format(pipeline.toStr(lstB)),
144*b71edfaaSTobias Hieta        intermediate,
14591d15aa0SMarkus Lavin    ]
14691d15aa0SMarkus Lavin    run_args.extend(extra_opt_args)
147*b71edfaaSTobias Hieta    optB = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
148*b71edfaaSTobias Hieta    if not (optA.returncode == 0 and optB.returncode == expected_error_returncode):
14991d15aa0SMarkus Lavin        break
15091d15aa0SMarkus Lavin    prevLstB = lstB
15191d15aa0SMarkus Lavin    prevIntermediate = intermediate
15291d15aa0SMarkus Lavinif prevLstB:
15391d15aa0SMarkus Lavin    lst = prevLstB
15491d15aa0SMarkus Lavin    ll_input = prevIntermediate
15591d15aa0SMarkus Lavinprint('-passes="{}"'.format(pipeline.toStr(lst)))
15691d15aa0SMarkus Lavin
15791d15aa0SMarkus Lavin# Step #2
15891d15aa0SMarkus Lavin# Try removing passes from the end of the remaining pipeline while still
15991d15aa0SMarkus Lavin# reproducing the error.
160*b71edfaaSTobias Hietaprint("---Starting step #2---")
16191d15aa0SMarkus LavinprevLstA = None
16291d15aa0SMarkus Lavinfor idx in reversed(range(pipeline.count(lst))):
16391d15aa0SMarkus Lavin    [lstA, lstB] = pipeline.split(lst, idx)
16491d15aa0SMarkus Lavin    if not args.dont_remove_empty_pm:
16591d15aa0SMarkus Lavin        lstA = pipeline.prune(lstA)
16691d15aa0SMarkus Lavin    run_args = [
167*b71edfaaSTobias Hieta        args.opt_binary,
168*b71edfaaSTobias Hieta        "-disable-symbolication",
169*b71edfaaSTobias Hieta        "-disable-output",
170*b71edfaaSTobias Hieta        "-passes={}".format(pipeline.toStr(lstA)),
171*b71edfaaSTobias Hieta        ll_input,
17291d15aa0SMarkus Lavin    ]
17391d15aa0SMarkus Lavin    run_args.extend(extra_opt_args)
174*b71edfaaSTobias Hieta    optA = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
17591d15aa0SMarkus Lavin    if optA.returncode != expected_error_returncode:
17691d15aa0SMarkus Lavin        break
17791d15aa0SMarkus Lavin    prevLstA = lstA
17891d15aa0SMarkus Lavinif prevLstA:
17991d15aa0SMarkus Lavin    lst = prevLstA
18091d15aa0SMarkus Lavinprint('-passes="{}"'.format(pipeline.toStr(lst)))
18191d15aa0SMarkus Lavin
18291d15aa0SMarkus Lavin# Step #3
18391d15aa0SMarkus Lavin# Now that we have a pipeline that is reduced both front and back we do
18491d15aa0SMarkus Lavin# exhaustive sweeps over the remainder trying to remove one pass at a time.
18591d15aa0SMarkus Lavin# Repeat as long as reduction is possible.
186*b71edfaaSTobias Hietaprint("---Starting step #3---")
18791d15aa0SMarkus Lavinwhile True:
18891d15aa0SMarkus Lavin    keepGoing = False
18991d15aa0SMarkus Lavin    for idx in range(pipeline.count(lst)):
19091d15aa0SMarkus Lavin        candLst = pipeline.remove(lst, idx)
19191d15aa0SMarkus Lavin        if not args.dont_remove_empty_pm:
19291d15aa0SMarkus Lavin            candLst = pipeline.prune(candLst)
19391d15aa0SMarkus Lavin        run_args = [
194*b71edfaaSTobias Hieta            args.opt_binary,
195*b71edfaaSTobias Hieta            "-disable-symbolication",
196*b71edfaaSTobias Hieta            "-disable-output",
197*b71edfaaSTobias Hieta            "-passes={}".format(pipeline.toStr(candLst)),
198*b71edfaaSTobias Hieta            ll_input,
19991d15aa0SMarkus Lavin        ]
20091d15aa0SMarkus Lavin        run_args.extend(extra_opt_args)
201*b71edfaaSTobias Hieta        opt = subprocess.run(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
20291d15aa0SMarkus Lavin        if opt.returncode == expected_error_returncode:
20391d15aa0SMarkus Lavin            lst = candLst
20491d15aa0SMarkus Lavin            keepGoing = True
20591d15aa0SMarkus Lavin    if not keepGoing:
20691d15aa0SMarkus Lavin        break
20791d15aa0SMarkus Lavinprint('-passes="{}"'.format(pipeline.toStr(lst)))
20891d15aa0SMarkus Lavin
209*b71edfaaSTobias Hietaprint("---FINISHED---")
21091d15aa0SMarkus Lavinif args.output:
21191d15aa0SMarkus Lavin    shutil.copy(ll_input, args.output)
212*b71edfaaSTobias Hieta    print("Wrote output to '{}'.".format(args.output))
21391d15aa0SMarkus Lavinprint('-passes="{}"'.format(pipeline.toStr(lst)))
21491d15aa0SMarkus Lavinexit(0)
215