| from __future__ import print_function |
| |
| import md5 |
| import os |
| import sys |
| |
| import list_reducer |
| from list_reducer import TESTRESULT_KEEPPREFIX |
| from list_reducer import TESTRESULT_KEEPSUFFIX |
| from list_reducer import TESTRESULT_NOFAILURE |
| |
| import swift_tools |
| |
| |
| class ReduceMiscompilingFunctions(list_reducer.ListReducer): |
| |
| def __init__(self, functions, silextract, tester): |
| list_reducer.ListReducer.__init__(self, functions) |
| self.silextract = silextract |
| self.tester = tester |
| |
| def run_test(self, prefix, suffix): |
| if len(suffix) > 0 and self._test_funclist(suffix): |
| return (TESTRESULT_KEEPSUFFIX, prefix, suffix) |
| if len(prefix) > 0 and self._test_funclist(prefix): |
| return (TESTRESULT_KEEPPREFIX, prefix, suffix) |
| return (TESTRESULT_NOFAILURE, prefix, suffix) |
| |
| # Split functions in a module into two groups: Those that are under |
| # consideration for miscompilation vs those that are not. Eventually, we |
| # will split the module into separate partial modules and then compile so |
| # we can find miscompiles. For now, we just look for crashsers in the |
| # optimizer. |
| def _test_funclist(self, funcs): |
| funclist_hash = self.get_funclist_hash(funcs) |
| funclist_path = os.path.join(self.silextract.config.work_dir, |
| 'func_list_' + funclist_hash) |
| with open(funclist_path, 'w') as funclist_file: |
| for f in funcs: |
| funclist_file.write(f + '\n') |
| |
| print("Checking to see if the program is misoptimized with func " |
| "list: %s" % funclist_path) |
| |
| # Split the module into the two halves of the program. |
| (outfile_stem, outfile_invert_stem) = \ |
| self.get_output_filename_stems(funclist_hash) |
| outfile = self.silextract.get_suffixed_filename(outfile_stem) |
| outfile_invert = \ |
| self.silextract.get_suffixed_filename(outfile_invert_stem) |
| self.silextract.invoke_with_functions(funclist_path, outfile) |
| self.silextract.invoke_with_functions(funclist_path, outfile_invert, |
| invert=True) |
| return self.tester(outfile, outfile_stem, outfile_invert, |
| outfile_invert_stem) |
| |
| def get_output_filename_stems(self, funclist_hash): |
| filename_stem = funclist_hash |
| return (filename_stem, filename_stem + '_invert') |
| |
| def get_funclist_hash(self, funcs): |
| m = md5.new() |
| for f in funcs: |
| m.update(f) |
| return m.hexdigest() |
| |
| |
| class OptimizerTester(object): |
| |
| def __init__(self, silopt, passes): |
| self.silopt = silopt |
| self.result = None |
| self.passes = ['-' + p for p in passes] |
| m = md5.new() |
| for p in passes: |
| m.update(p) |
| self.pass_hash = m.hexdigest() |
| |
| def get_filepaths(self, optstem, nooptstem): |
| f = self.silopt.get_suffixed_filename |
| return (f(optstem + '_' + self.pass_hash), |
| f(nooptstem + '_' + self.pass_hash)) |
| |
| def __call__(self, filepath_to_opt, filepath_to_opt_stem, |
| filepath_to_notopt, filepath_to_notopt_stem): |
| (out_optpath, out_nooptpath) = \ |
| self.get_filepaths(filepath_to_opt_stem, filepath_to_notopt_stem) |
| sys.stdout.write("Trying to optimize functions...") |
| self.silopt.input_file = filepath_to_opt |
| result = self.silopt.invoke_with_passlist(self.passes, out_optpath) |
| if result['exit_code'] == 0: |
| print(' NOCRASH!\n') |
| else: |
| self.result = self.silopt.input_file |
| print(' CRASH!\n') |
| return result['exit_code'] |
| |
| |
| def function_bug_reducer(input_file, nm, sil_opt_invoker, sil_extract_invoker, |
| pass_list): |
| functions = [s[1] for s in nm.get_symbols(input_file) if s[0] == 'F'] |
| |
| print("Base case crashes! Trying to reduce *.sib file") |
| |
| # Otherwise, reduce the list of pases that cause the optimzier to crash. |
| tester = OptimizerTester(sil_opt_invoker, pass_list) |
| r = ReduceMiscompilingFunctions(functions, sil_extract_invoker, |
| tester) |
| if not r.reduce_list(): |
| print("Failed to find miscompiling pass list!") |
| sil_opt_invoker.input_file = tester.result |
| cmdline = sil_opt_invoker.cmdline_with_passlist(pass_list) |
| print("*** Successfully Reduced file!") |
| print("*** Final File: %s" % sil_opt_invoker.input_file) |
| print("*** Final Functions: %s" % (' '.join(r.target_list))) |
| print("*** Repro command line: %s" % (' '.join(cmdline))) |
| |
| |
| def invoke_function_bug_reducer(args): |
| """Given a path to a sib file with canonical sil, attempt to find a perturbed |
| list of function given a specific pass that causes the perf pipeline to crash |
| """ |
| tools = swift_tools.SwiftTools(args.swift_build_dir) |
| config = swift_tools.SILToolInvokerConfig(args) |
| nm = swift_tools.SILNMInvoker(config, tools) |
| |
| input_file = args.input_file |
| extra_args = args.extra_args |
| sil_opt_invoker = swift_tools.SILOptInvoker(config, tools, |
| input_file, |
| 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(args.pass_list, filename) |
| # If we succeed, there is no further work to do. |
| if result['exit_code'] == 0: |
| print("Success with PassList: %s" % (' '.join(args.pass_list))) |
| return |
| |
| sil_extract_invoker = swift_tools.SILFuncExtractorInvoker(config, |
| tools, |
| input_file) |
| |
| function_bug_reducer(input_file, nm, sil_opt_invoker, sil_extract_invoker, |
| args.pass_list) |
| |
| |
| def add_parser_arguments(parser): |
| """Add parser arguments for func_bug_reducer""" |
| parser.set_defaults(func=invoke_function_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-func-extractor') |
| parser.add_argument('--target', help='The target to pass to ' |
| 'sil-func-extractor') |
| parser.add_argument('--resource-dir', |
| help='The resource-dir to pass to sil-func-extractor') |
| 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-silopt-arg', help='extra argument to pass to ' |
| 'sil-opt', |
| dest='extra_args', action='append') |