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