209 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			209 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
| #!/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)
 |