| |
| import json |
| import md5 |
| import subprocess |
| |
| import bug_reducer_utils |
| |
| import list_reducer |
| from list_reducer import TESTRESULT_KEEPPREFIX |
| from list_reducer import TESTRESULT_KEEPSUFFIX |
| from list_reducer import TESTRESULT_NOFAILURE |
| |
| |
| class ReduceMiscompilingPasses(list_reducer.ListReducer): |
| |
| def __init__(self, l, invoker): |
| list_reducer.ListReducer.__init__(self, l) |
| self.invoker = invoker |
| |
| def run_test(self, prefix, suffix): |
| # First, run the program with just the Suffix passes. If it is still |
| # broken with JUST the kept passes, discard the prefix passes. |
| suffix_joined = ' '.join(suffix) |
| suffix_hash = md5.md5(suffix_joined).hexdigest() |
| print("Checking to see if '%s' compiles correctly" % suffix_joined) |
| |
| result = self.invoker.invoke_with_passlist( |
| self.invoker.get_suffixed_filename(suffix_hash), |
| suffix) |
| |
| # Found a miscompile! Keep the suffix |
| if result != 0: |
| print("Suffix maintains the predicate. Returning suffix") |
| return (TESTRESULT_KEEPSUFFIX, prefix, suffix) |
| |
| if len(prefix) == 0: |
| print("Suffix passes and no prefix, returning nofailure") |
| return (TESTRESULT_NOFAILURE, prefix, suffix) |
| |
| # Next see if the program is broken if we run the "prefix" passes |
| # first, then separately run the "kept" passes. |
| prefix_joined = ' '.join(prefix) |
| prefix_hash = md5.md5(prefix_joined).hexdigest() |
| print("Checking to see if '%s' compiles correctly" % prefix_joined) |
| |
| # If it is not broken with the kept passes, it's possible that the |
| # prefix passes must be run before the kept passes to break it. If |
| # the program WORKS after the prefix passes, but then fails if running |
| # the prefix AND kept passes, we can update our bitcode file to |
| # include the result of the prefix passes, then discard the prefix |
| # passes. |
| prefix_path = self.invoker.get_suffixed_filename(prefix_hash) |
| result = self.invoker.invoke_with_passlist( |
| prefix_path, |
| prefix) |
| if result != 0: |
| print("Prefix maintains the predicate by itself. Returning keep " |
| "prefix") |
| return (TESTRESULT_KEEPPREFIX, prefix, suffix) |
| |
| # Ok, so now we know that the prefix passes work, first check if we |
| # actually have any suffix passes. If we don't, just return. |
| if len(suffix) == 0: |
| print("No suffix, and prefix passes, returning no failure") |
| return (TESTRESULT_NOFAILURE, prefix, suffix) |
| |
| # Otherwise, treat the prefix as our new baseline and see if suffix on |
| # the new baseline finds the crash. |
| original_input_file = self.invoker.input_file |
| self.invoker.input_file = prefix_path |
| print("Checking to see if '%s' compiles correctly after the '%s' " |
| "passes" % (suffix_joined, prefix_joined)) |
| result = self.invoker.invoke_with_passlist( |
| self.invoker.get_suffixed_filename(suffix_hash), |
| suffix) |
| |
| # If we failed at this point, then the prefix is our new |
| # baseline. Return keep suffix. |
| if result != 0: |
| print("Suffix failed. Keeping prefix as new baseline") |
| return (TESTRESULT_KEEPSUFFIX, prefix, suffix) |
| |
| # Otherwise, we must not be running the bad pass anymore. Restore the |
| # original input_file and return NoFailure. |
| self.invoker.input_file = original_input_file |
| return (TESTRESULT_NOFAILURE, prefix, suffix) |
| |
| |
| def pass_bug_reducer(args): |
| """Given a path to a sib file with canonical sil, attempt to find a perturbed |
| list of passes that the perf pipeline""" |
| tools = bug_reducer_utils.SwiftTools(args.swift_build_dir) |
| |
| passes = [] |
| early_module_passes = [] |
| if args.pass_list is None: |
| json_data = json.loads(subprocess.check_output( |
| [tools.sil_passpipeline_dumper, '-Performance'])) |
| passes = sum((p[2:] for p in json_data if p[0] != 'EarlyModulePasses'), []) |
| passes = ['-' + x[1] for x in passes] |
| # We assume we have an early module passes that runs until fix point and |
| # that is strictly not what is causing the issue. |
| # |
| # Everything else runs one iteration. |
| early_module_passes = [p[2:] for p in json_data |
| if p[0] == 'EarlyModulePasses'][0] |
| early_module_passes = ['-' + x[1] for x in early_module_passes] |
| else: |
| passes = ['-' + x for x in args.pass_list] |
| |
| extra_args = [] |
| if args.extra_args is not None: |
| extra_args = args.extra_args |
| sil_opt_invoker = bug_reducer_utils.SILOptInvoker(args, tools, |
| early_module_passes, |
| extra_args) |
| |
| # Make sure that the base case /does/ crash. |
| filename = sil_opt_invoker.get_suffixed_filename('base_case') |
| result = sil_opt_invoker.invoke_with_passlist(filename, passes) |
| # If we succeed, there is no further work to do. |
| if result == 0: |
| print("Success with PassList: %s" % (' '.join(passes))) |
| return |
| |
| # Otherwise, reduce the list of pases that cause the optimzier to crash. |
| r = ReduceMiscompilingPasses(passes, sil_opt_invoker) |
| if not r.reduce_list(): |
| print("Failed to find miscompiling pass list!") |
| print("Found miscompiling passes: %s" % (' '.join(r.target_list))) |
| |
| |
| def add_parser_arguments(parser): |
| """Add parser arguments for opt_bug_reducer""" |
| parser.set_defaults(func=pass_bug_reducer) |
| parser.add_argument('input_file', help='The input file to optimize') |
| parser.add_argument('--module-cache', help='The module cache to use') |
| parser.add_argument('--sdk', help='The sdk to pass to sil-opt') |
| parser.add_argument('--target', help='The target to pass to sil-opt') |
| parser.add_argument('--resource-dir', |
| help='The resource-dir to pass to sil-opt') |
| parser.add_argument('--work-dir', |
| help='Working directory to use for temp files', |
| default='bug_reducer') |
| parser.add_argument('--module-name', |
| help='The name of the module we are optimizing') |
| parser.add_argument('--pass', help='pass to test', dest='pass_list', |
| action='append') |
| parser.add_argument('--extra-arg', help='extra argument to pass to sil-opt', |
| dest='extra_args', action='append') |